Home | History | Annotate | Download | only in cups
      1 /*
      2  * I18N/language support for CUPS.
      3  *
      4  * Copyright 2007-2017 by Apple Inc.
      5  * Copyright 1997-2007 by Easy Software Products.
      6  *
      7  * These coded instructions, statements, and computer programs are the
      8  * property of Apple Inc. and are protected by Federal copyright
      9  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
     10  * which should have been included with this file.  If this file is
     11  * missing or damaged, see the license at "http://www.cups.org/".
     12  *
     13  * This file is subject to the Apple OS-Developed Software exception.
     14  */
     15 
     16 /*
     17  * Include necessary headers...
     18  */
     19 
     20 #include "cups-private.h"
     21 #ifdef HAVE_LANGINFO_H
     22 #  include <langinfo.h>
     23 #endif /* HAVE_LANGINFO_H */
     24 #ifdef WIN32
     25 #  include <io.h>
     26 #else
     27 #  include <unistd.h>
     28 #endif /* WIN32 */
     29 #ifdef HAVE_COREFOUNDATION_H
     30 #  include <CoreFoundation/CoreFoundation.h>
     31 #endif /* HAVE_COREFOUNDATION_H */
     32 
     33 
     34 /*
     35  * Local globals...
     36  */
     37 
     38 static _cups_mutex_t	lang_mutex = _CUPS_MUTEX_INITIALIZER;
     39 					/* Mutex to control access to cache */
     40 static cups_lang_t	*lang_cache = NULL;
     41 					/* Language string cache */
     42 static const char * const lang_encodings[] =
     43 			{		/* Encoding strings */
     44 			  "us-ascii",		"iso-8859-1",
     45 			  "iso-8859-2",		"iso-8859-3",
     46 			  "iso-8859-4",		"iso-8859-5",
     47 			  "iso-8859-6",		"iso-8859-7",
     48 			  "iso-8859-8",		"iso-8859-9",
     49 			  "iso-8859-10",	"utf-8",
     50 			  "iso-8859-13",	"iso-8859-14",
     51 			  "iso-8859-15",	"cp874",
     52 			  "cp1250",		"cp1251",
     53 			  "cp1252",		"cp1253",
     54 			  "cp1254",		"cp1255",
     55 			  "cp1256",		"cp1257",
     56 			  "cp1258",		"koi8-r",
     57 			  "koi8-u",		"iso-8859-11",
     58 			  "iso-8859-16",	"mac",
     59 			  "unknown",		"unknown",
     60 			  "unknown",		"unknown",
     61 			  "unknown",		"unknown",
     62 			  "unknown",		"unknown",
     63 			  "unknown",		"unknown",
     64 			  "unknown",		"unknown",
     65 			  "unknown",		"unknown",
     66 			  "unknown",		"unknown",
     67 			  "unknown",		"unknown",
     68 			  "unknown",		"unknown",
     69 			  "unknown",		"unknown",
     70 			  "unknown",		"unknown",
     71 			  "unknown",		"unknown",
     72 			  "unknown",		"unknown",
     73 			  "unknown",		"unknown",
     74 			  "unknown",		"unknown",
     75 			  "unknown",		"unknown",
     76 			  "cp932",		"cp936",
     77 			  "cp949",		"cp950",
     78 			  "cp1361",		"unknown",
     79 			  "unknown",		"unknown",
     80 			  "unknown",		"unknown",
     81 			  "unknown",		"unknown",
     82 			  "unknown",		"unknown",
     83 			  "unknown",		"unknown",
     84 			  "unknown",		"unknown",
     85 			  "unknown",		"unknown",
     86 			  "unknown",		"unknown",
     87 			  "unknown",		"unknown",
     88 			  "unknown",		"unknown",
     89 			  "unknown",		"unknown",
     90 			  "unknown",		"unknown",
     91 			  "unknown",		"unknown",
     92 			  "unknown",		"unknown",
     93 			  "unknown",		"unknown",
     94 			  "unknown",		"unknown",
     95 			  "unknown",		"unknown",
     96 			  "unknown",		"unknown",
     97 			  "unknown",		"unknown",
     98 			  "unknown",		"unknown",
     99 			  "unknown",		"unknown",
    100 			  "unknown",		"unknown",
    101 			  "unknown",		"unknown",
    102 			  "unknown",		"unknown",
    103 			  "unknown",		"unknown",
    104 			  "unknown",		"unknown",
    105 			  "unknown",		"unknown",
    106 			  "unknown",		"unknown",
    107 			  "unknown",		"unknown",
    108 			  "euc-cn",		"euc-jp",
    109 			  "euc-kr",		"euc-tw",
    110 			  "shift_jisx0213"
    111 			};
    112 
    113 #ifdef __APPLE__
    114 typedef struct
    115 {
    116   const char * const language;		/* Language ID */
    117   const char * const locale;		/* Locale ID */
    118 } _apple_language_locale_t;
    119 
    120 static const _apple_language_locale_t apple_language_locale[] =
    121 {					/* Language to locale ID LUT */
    122   { "en",         "en_US" },
    123   { "nb",         "no" },
    124   { "nb_NO",      "no" },
    125   { "zh-Hans",    "zh_CN" },
    126   { "zh-Hant",    "zh_TW" },
    127   { "zh-Hant_CN", "zh_TW" }
    128 };
    129 #endif /* __APPLE__ */
    130 
    131 
    132 /*
    133  * Local functions...
    134  */
    135 
    136 
    137 #ifdef __APPLE__
    138 static const char	*appleLangDefault(void);
    139 #  ifdef CUPS_BUNDLEDIR
    140 #    ifndef CF_RETURNS_RETAINED
    141 #      if __has_feature(attribute_cf_returns_retained)
    142 #        define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
    143 #      else
    144 #        define CF_RETURNS_RETAINED
    145 #      endif /* __has_feature(attribute_cf_returns_retained) */
    146 #    endif /* !CF_RETURNED_RETAINED */
    147 static cups_array_t	*appleMessageLoad(const char *locale)
    148 			CF_RETURNS_RETAINED;
    149 #  endif /* CUPS_BUNDLEDIR */
    150 #endif /* __APPLE__ */
    151 static cups_lang_t	*cups_cache_lookup(const char *name,
    152 			                   cups_encoding_t encoding);
    153 static int		cups_message_compare(_cups_message_t *m1,
    154 			                     _cups_message_t *m2);
    155 static void		cups_message_free(_cups_message_t *m);
    156 static void		cups_message_load(cups_lang_t *lang);
    157 static void		cups_unquote(char *d, const char *s);
    158 
    159 
    160 #ifdef __APPLE__
    161 /*
    162  * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
    163  *                          locale ID.
    164  */
    165 
    166 const char *				/* O - Language ID */
    167 _cupsAppleLanguage(const char *locale,	/* I - Locale ID */
    168                    char       *language,/* I - Language ID buffer */
    169                    size_t     langsize)	/* I - Size of language ID buffer */
    170 {
    171   int		i;			/* Looping var */
    172   CFStringRef	localeid,		/* CF locale identifier */
    173 		langid;			/* CF language identifier */
    174 
    175 
    176  /*
    177   * Copy the locale name and convert, as needed, to the Apple-specific
    178   * locale identifier...
    179   */
    180 
    181   switch (strlen(locale))
    182   {
    183     default :
    184         /*
    185 	 * Invalid locale...
    186 	 */
    187 
    188 	 strlcpy(language, "en", langsize);
    189 	 break;
    190 
    191     case 2 :
    192         strlcpy(language, locale, langsize);
    193         break;
    194 
    195     case 5 :
    196         strlcpy(language, locale, langsize);
    197 
    198 	if (language[2] == '-')
    199 	{
    200 	 /*
    201 	  * Convert ll-cc to ll_CC...
    202 	  */
    203 
    204 	  language[2] = '_';
    205 	  language[3] = (char)toupper(language[3] & 255);
    206 	  language[4] = (char)toupper(language[4] & 255);
    207 	}
    208 	break;
    209   }
    210 
    211   for (i = 0;
    212        i < (int)(sizeof(apple_language_locale) /
    213 		 sizeof(apple_language_locale[0]));
    214        i ++)
    215     if (!strcmp(locale, apple_language_locale[i].locale))
    216     {
    217       strlcpy(language, apple_language_locale[i].language, sizeof(language));
    218       break;
    219     }
    220 
    221  /*
    222   * Attempt to map the locale ID to a language ID...
    223   */
    224 
    225   if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
    226                                             kCFStringEncodingASCII)) != NULL)
    227   {
    228     if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
    229                       kCFAllocatorDefault, localeid)) != NULL)
    230     {
    231       CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
    232       CFRelease(langid);
    233     }
    234 
    235     CFRelease(localeid);
    236   }
    237 
    238  /*
    239   * Return what we got...
    240   */
    241 
    242   return (language);
    243 }
    244 
    245 
    246 /*
    247  * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
    248  */
    249 
    250 const char *					/* O - Locale */
    251 _cupsAppleLocale(CFStringRef languageName,	/* I - Apple language ID */
    252                  char        *locale,		/* I - Buffer for locale */
    253 		 size_t      localesize)	/* I - Size of buffer */
    254 {
    255   int		i;			/* Looping var */
    256   CFStringRef	localeName;		/* Locale as a CF string */
    257 
    258 
    259   localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName);
    260 
    261   if (localeName)
    262   {
    263    /*
    264     * Copy the locale name and tweak as needed...
    265     */
    266 
    267     if (!CFStringGetCString(localeName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
    268       *locale = '\0';
    269 
    270     CFRelease(localeName);
    271 
    272    /*
    273     * Map new language identifiers to locales...
    274     */
    275 
    276     for (i = 0;
    277 	 i < (int)(sizeof(apple_language_locale) /
    278 		   sizeof(apple_language_locale[0]));
    279 	 i ++)
    280     {
    281       if (!strcmp(locale, apple_language_locale[i].language))
    282       {
    283 	strlcpy(locale, apple_language_locale[i].locale, localesize);
    284 	break;
    285       }
    286     }
    287   }
    288   else
    289   {
    290    /*
    291     * Just try the Apple language name...
    292     */
    293 
    294     if (!CFStringGetCString(languageName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
    295       *locale = '\0';
    296   }
    297 
    298   if (!*locale)
    299     return (NULL);
    300 
    301  /*
    302   * Convert language subtag into region subtag...
    303   */
    304 
    305   if (locale[2] == '-')
    306     locale[2] = '_';
    307 
    308   if (!strchr(locale, '.'))
    309     strlcat(locale, ".UTF-8", localesize);
    310 
    311   return (locale);
    312 }
    313 #endif /* __APPLE__ */
    314 
    315 
    316 /*
    317  * '_cupsEncodingName()' - Return the character encoding name string
    318  *                         for the given encoding enumeration.
    319  */
    320 
    321 const char *				/* O - Character encoding */
    322 _cupsEncodingName(
    323     cups_encoding_t encoding)		/* I - Encoding value */
    324 {
    325   if (encoding < CUPS_US_ASCII ||
    326       encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
    327   {
    328     DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
    329                   encoding, lang_encodings[0]));
    330     return (lang_encodings[0]);
    331   }
    332   else
    333   {
    334     DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
    335                   encoding, lang_encodings[encoding]));
    336     return (lang_encodings[encoding]);
    337   }
    338 }
    339 
    340 
    341 /*
    342  * 'cupsLangDefault()' - Return the default language.
    343  */
    344 
    345 cups_lang_t *				/* O - Language data */
    346 cupsLangDefault(void)
    347 {
    348   return (cupsLangGet(NULL));
    349 }
    350 
    351 
    352 /*
    353  * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
    354  *                        for the given language.
    355  */
    356 
    357 const char *				/* O - Character encoding */
    358 cupsLangEncoding(cups_lang_t *lang)	/* I - Language data */
    359 {
    360   if (lang == NULL)
    361     return ((char*)lang_encodings[0]);
    362   else
    363     return ((char*)lang_encodings[lang->encoding]);
    364 }
    365 
    366 
    367 /*
    368  * 'cupsLangFlush()' - Flush all language data out of the cache.
    369  */
    370 
    371 void
    372 cupsLangFlush(void)
    373 {
    374   cups_lang_t	*lang,			/* Current language */
    375 		*next;			/* Next language */
    376 
    377 
    378  /*
    379   * Free all languages in the cache...
    380   */
    381 
    382   _cupsMutexLock(&lang_mutex);
    383 
    384   for (lang = lang_cache; lang != NULL; lang = next)
    385   {
    386    /*
    387     * Free all messages...
    388     */
    389 
    390     _cupsMessageFree(lang->strings);
    391 
    392    /*
    393     * Then free the language structure itself...
    394     */
    395 
    396     next = lang->next;
    397     free(lang);
    398   }
    399 
    400   lang_cache = NULL;
    401 
    402   _cupsMutexUnlock(&lang_mutex);
    403 }
    404 
    405 
    406 /*
    407  * 'cupsLangFree()' - Free language data.
    408  *
    409  * This does not actually free anything; use @link cupsLangFlush@ for that.
    410  */
    411 
    412 void
    413 cupsLangFree(cups_lang_t *lang)		/* I - Language to free */
    414 {
    415   _cupsMutexLock(&lang_mutex);
    416 
    417   if (lang != NULL && lang->used > 0)
    418     lang->used --;
    419 
    420   _cupsMutexUnlock(&lang_mutex);
    421 }
    422 
    423 
    424 /*
    425  * 'cupsLangGet()' - Get a language.
    426  */
    427 
    428 cups_lang_t *				/* O - Language data */
    429 cupsLangGet(const char *language)	/* I - Language or locale */
    430 {
    431   int			i;		/* Looping var */
    432 #ifndef __APPLE__
    433   char			locale[255];	/* Copy of locale name */
    434 #endif /* !__APPLE__ */
    435   char			langname[16],	/* Requested language name */
    436 			country[16],	/* Country code */
    437 			charset[16],	/* Character set */
    438 			*csptr,		/* Pointer to CODESET string */
    439 			*ptr,		/* Pointer into language/charset */
    440 			real[48];	/* Real language name */
    441   cups_encoding_t	encoding;	/* Encoding to use */
    442   cups_lang_t		*lang;		/* Current language... */
    443   static const char * const locale_encodings[] =
    444 		{			/* Locale charset names */
    445 		  "ASCII",	"ISO88591",	"ISO88592",	"ISO88593",
    446 		  "ISO88594",	"ISO88595",	"ISO88596",	"ISO88597",
    447 		  "ISO88598",	"ISO88599",	"ISO885910",	"UTF8",
    448 		  "ISO885913",	"ISO885914",	"ISO885915",	"CP874",
    449 		  "CP1250",	"CP1251",	"CP1252",	"CP1253",
    450 		  "CP1254",	"CP1255",	"CP1256",	"CP1257",
    451 		  "CP1258",	"KOI8R",	"KOI8U",	"ISO885911",
    452 		  "ISO885916",	"MACROMAN",	"",		"",
    453 
    454 		  "",		"",		"",		"",
    455 		  "",		"",		"",		"",
    456 		  "",		"",		"",		"",
    457 		  "",		"",		"",		"",
    458 		  "",		"",		"",		"",
    459 		  "",		"",		"",		"",
    460 		  "",		"",		"",		"",
    461 		  "",		"",		"",		"",
    462 
    463 		  "CP932",	"CP936",	"CP949",	"CP950",
    464 		  "CP1361",	"",		"",		"",
    465 		  "",		"",		"",		"",
    466 		  "",		"",		"",		"",
    467 		  "",		"",		"",		"",
    468 		  "",		"",		"",		"",
    469 		  "",		"",		"",		"",
    470 		  "",		"",		"",		"",
    471 
    472 		  "",		"",		"",		"",
    473 		  "",		"",		"",		"",
    474 		  "",		"",		"",		"",
    475 		  "",		"",		"",		"",
    476 		  "",		"",		"",		"",
    477 		  "",		"",		"",		"",
    478 		  "",		"",		"",		"",
    479 		  "",		"",		"",		"",
    480 
    481 		  "EUCCN",	"EUCJP",	"EUCKR",	"EUCTW",
    482 		  "SHIFT_JISX0213"
    483 		};
    484 
    485 
    486   DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
    487 
    488 #ifdef __APPLE__
    489  /*
    490   * Set the character set to UTF-8...
    491   */
    492 
    493   strlcpy(charset, "UTF8", sizeof(charset));
    494 
    495  /*
    496   * Apple's setlocale doesn't give us the user's localization
    497   * preference so we have to look it up this way...
    498   */
    499 
    500   if (!language)
    501   {
    502     if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
    503       language = appleLangDefault();
    504 
    505     DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
    506   }
    507 
    508 #else
    509  /*
    510   * Set the charset to "unknown"...
    511   */
    512 
    513   charset[0] = '\0';
    514 
    515  /*
    516   * Use setlocale() to determine the currently set locale, and then
    517   * fallback to environment variables to avoid setting the locale,
    518   * since setlocale() is not thread-safe!
    519   */
    520 
    521   if (!language)
    522   {
    523    /*
    524     * First see if the locale has been set; if it is still "C" or
    525     * "POSIX", use the environment to get the default...
    526     */
    527 
    528 #  ifdef LC_MESSAGES
    529     ptr = setlocale(LC_MESSAGES, NULL);
    530 #  else
    531     ptr = setlocale(LC_ALL, NULL);
    532 #  endif /* LC_MESSAGES */
    533 
    534     DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
    535 
    536     if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
    537     {
    538      /*
    539       * Get the character set from the LC_CTYPE locale setting...
    540       */
    541 
    542       if ((ptr = getenv("LC_CTYPE")) == NULL)
    543         if ((ptr = getenv("LC_ALL")) == NULL)
    544 	  if ((ptr = getenv("LANG")) == NULL)
    545 	    ptr = "en_US";
    546 
    547       if ((csptr = strchr(ptr, '.')) != NULL)
    548       {
    549        /*
    550         * Extract the character set from the environment...
    551 	*/
    552 
    553 	for (ptr = charset, csptr ++; *csptr; csptr ++)
    554 	  if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
    555 	    *ptr++ = *csptr;
    556 
    557         *ptr = '\0';
    558       }
    559 
    560      /*
    561       * Get the locale for messages from the LC_MESSAGES locale setting...
    562       */
    563 
    564       if ((ptr = getenv("LC_MESSAGES")) == NULL)
    565         if ((ptr = getenv("LC_ALL")) == NULL)
    566 	  if ((ptr = getenv("LANG")) == NULL)
    567 	    ptr = "en_US";
    568     }
    569 
    570     if (ptr)
    571     {
    572       strlcpy(locale, ptr, sizeof(locale));
    573       language = locale;
    574 
    575      /*
    576       * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
    577       */
    578 
    579       if (!strncmp(locale, "nb", 2))
    580         locale[1] = 'o';
    581 
    582       DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
    583     }
    584   }
    585 #endif /* __APPLE__ */
    586 
    587  /*
    588   * If "language" is NULL at this point, then chances are we are using
    589   * a language that is not installed for the base OS.
    590   */
    591 
    592   if (!language)
    593   {
    594    /*
    595     * Switch to the POSIX ("C") locale...
    596     */
    597 
    598     language = "C";
    599   }
    600 
    601 #ifdef CODESET
    602  /*
    603   * On systems that support the nl_langinfo(CODESET) call, use
    604   * this value as the character set...
    605   */
    606 
    607   if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
    608   {
    609    /*
    610     * Copy all of the letters and numbers in the CODESET string...
    611     */
    612 
    613     for (ptr = charset; *csptr; csptr ++)
    614       if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
    615         *ptr++ = *csptr;
    616 
    617     *ptr = '\0';
    618 
    619     DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
    620                   "nl_langinfo(CODESET)...", charset));
    621   }
    622 #endif /* CODESET */
    623 
    624  /*
    625   * If we don't have a character set by now, default to UTF-8...
    626   */
    627 
    628   if (!charset[0])
    629     strlcpy(charset, "UTF8", sizeof(charset));
    630 
    631  /*
    632   * Parse the language string passed in to a locale string. "C" is the
    633   * standard POSIX locale and is copied unchanged.  Otherwise the
    634   * language string is converted from ll-cc[.charset] (language-country)
    635   * to ll_CC[.CHARSET] to match the file naming convention used by all
    636   * POSIX-compliant operating systems.  Invalid language names are mapped
    637   * to the POSIX locale.
    638   */
    639 
    640   country[0] = '\0';
    641 
    642   if (language == NULL || !language[0] ||
    643       !strcmp(language, "POSIX"))
    644     strlcpy(langname, "C", sizeof(langname));
    645   else
    646   {
    647    /*
    648     * Copy the parts of the locale string over safely...
    649     */
    650 
    651     for (ptr = langname; *language; language ++)
    652       if (*language == '_' || *language == '-' || *language == '.')
    653 	break;
    654       else if (ptr < (langname + sizeof(langname) - 1))
    655         *ptr++ = (char)tolower(*language & 255);
    656 
    657     *ptr = '\0';
    658 
    659     if (*language == '_' || *language == '-')
    660     {
    661      /*
    662       * Copy the country code...
    663       */
    664 
    665       for (language ++, ptr = country; *language; language ++)
    666 	if (*language == '.')
    667 	  break;
    668 	else if (ptr < (country + sizeof(country) - 1))
    669           *ptr++ = (char)toupper(*language & 255);
    670 
    671       *ptr = '\0';
    672     }
    673 
    674     if (*language == '.' && !charset[0])
    675     {
    676      /*
    677       * Copy the encoding...
    678       */
    679 
    680       for (language ++, ptr = charset; *language; language ++)
    681         if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
    682           *ptr++ = (char)toupper(*language & 255);
    683 
    684       *ptr = '\0';
    685     }
    686 
    687    /*
    688     * Force a POSIX locale for an invalid language name...
    689     */
    690 
    691     if (strlen(langname) != 2)
    692     {
    693       strlcpy(langname, "C", sizeof(langname));
    694       country[0] = '\0';
    695       charset[0] = '\0';
    696     }
    697   }
    698 
    699   DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
    700                 langname, country, charset));
    701 
    702  /*
    703   * Figure out the desired encoding...
    704   */
    705 
    706   encoding = CUPS_AUTO_ENCODING;
    707 
    708   if (charset[0])
    709   {
    710     for (i = 0;
    711          i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
    712 	 i ++)
    713       if (!_cups_strcasecmp(charset, locale_encodings[i]))
    714       {
    715 	encoding = (cups_encoding_t)i;
    716 	break;
    717       }
    718 
    719     if (encoding == CUPS_AUTO_ENCODING)
    720     {
    721      /*
    722       * Map alternate names for various character sets...
    723       */
    724 
    725       if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
    726           !_cups_strcasecmp(charset, "sjis"))
    727 	encoding = CUPS_WINDOWS_932;
    728       else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
    729 	encoding = CUPS_WINDOWS_936;
    730       else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
    731 	encoding = CUPS_WINDOWS_949;
    732       else if (!_cups_strcasecmp(charset, "big5"))
    733 	encoding = CUPS_WINDOWS_950;
    734     }
    735   }
    736 
    737   DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
    738                 encoding == CUPS_AUTO_ENCODING ? "auto" :
    739 		    lang_encodings[encoding]));
    740 
    741  /*
    742   * See if we already have this language/country loaded...
    743   */
    744 
    745   if (country[0])
    746     snprintf(real, sizeof(real), "%s_%s", langname, country);
    747   else
    748     strlcpy(real, langname, sizeof(real));
    749 
    750   _cupsMutexLock(&lang_mutex);
    751 
    752   if ((lang = cups_cache_lookup(real, encoding)) != NULL)
    753   {
    754     _cupsMutexUnlock(&lang_mutex);
    755 
    756     DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
    757 
    758     return (lang);
    759   }
    760 
    761  /*
    762   * See if there is a free language available; if so, use that
    763   * record...
    764   */
    765 
    766   for (lang = lang_cache; lang != NULL; lang = lang->next)
    767     if (lang->used == 0)
    768       break;
    769 
    770   if (lang == NULL)
    771   {
    772    /*
    773     * Allocate memory for the language and add it to the cache.
    774     */
    775 
    776     if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
    777     {
    778       _cupsMutexUnlock(&lang_mutex);
    779 
    780       return (NULL);
    781     }
    782 
    783     lang->next = lang_cache;
    784     lang_cache = lang;
    785   }
    786   else
    787   {
    788    /*
    789     * Free all old strings as needed...
    790     */
    791 
    792     _cupsMessageFree(lang->strings);
    793     lang->strings = NULL;
    794   }
    795 
    796  /*
    797   * Then assign the language and encoding fields...
    798   */
    799 
    800   lang->used ++;
    801   strlcpy(lang->language, real, sizeof(lang->language));
    802 
    803   if (encoding != CUPS_AUTO_ENCODING)
    804     lang->encoding = encoding;
    805   else
    806     lang->encoding = CUPS_UTF8;
    807 
    808  /*
    809   * Return...
    810   */
    811 
    812   _cupsMutexUnlock(&lang_mutex);
    813 
    814   return (lang);
    815 }
    816 
    817 
    818 /*
    819  * '_cupsLangString()' - Get a message string.
    820  *
    821  * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
    822  * convert the string to the language encoding.
    823  */
    824 
    825 const char *				/* O - Localized message */
    826 _cupsLangString(cups_lang_t *lang,	/* I - Language */
    827                 const char  *message)	/* I - Message */
    828 {
    829   const char *s;			/* Localized message */
    830 
    831  /*
    832   * Range check input...
    833   */
    834 
    835   if (!lang || !message || !*message)
    836     return (message);
    837 
    838   _cupsMutexLock(&lang_mutex);
    839 
    840  /*
    841   * Load the message catalog if needed...
    842   */
    843 
    844   if (!lang->strings)
    845     cups_message_load(lang);
    846 
    847   s = _cupsMessageLookup(lang->strings, message);
    848 
    849   _cupsMutexUnlock(&lang_mutex);
    850 
    851   return (s);
    852 }
    853 
    854 
    855 /*
    856  * '_cupsMessageFree()' - Free a messages array.
    857  */
    858 
    859 void
    860 _cupsMessageFree(cups_array_t *a)	/* I - Message array */
    861 {
    862 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
    863  /*
    864   * Release the cups.strings dictionary as needed...
    865   */
    866 
    867   if (cupsArrayUserData(a))
    868     CFRelease((CFDictionaryRef)cupsArrayUserData(a));
    869 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
    870 
    871  /*
    872   * Free the array...
    873   */
    874 
    875   cupsArrayDelete(a);
    876 }
    877 
    878 
    879 /*
    880  * '_cupsMessageLoad()' - Load a .po file into a messages array.
    881  */
    882 
    883 cups_array_t *				/* O - New message array */
    884 _cupsMessageLoad(const char *filename,	/* I - Message catalog to load */
    885                  int        unquote)	/* I - Unescape \foo in strings? */
    886 {
    887   cups_file_t		*fp;		/* Message file */
    888   cups_array_t		*a;		/* Message array */
    889   _cups_message_t	*m;		/* Current message */
    890   char			s[4096],	/* String buffer */
    891 			*ptr,		/* Pointer into buffer */
    892 			*temp;		/* New string */
    893   size_t		length,		/* Length of combined strings */
    894 			ptrlen;		/* Length of string */
    895 
    896 
    897   DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
    898 
    899  /*
    900   * Create an array to hold the messages...
    901   */
    902 
    903   if ((a = _cupsMessageNew(NULL)) == NULL)
    904   {
    905     DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
    906     return (NULL);
    907   }
    908 
    909  /*
    910   * Open the message catalog file...
    911   */
    912 
    913   if ((fp = cupsFileOpen(filename, "r")) == NULL)
    914   {
    915     DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
    916                   strerror(errno)));
    917     return (a);
    918   }
    919 
    920  /*
    921   * Read messages from the catalog file until EOF...
    922   *
    923   * The format is the GNU gettext .po format, which is fairly simple:
    924   *
    925   *     msgid "some text"
    926   *     msgstr "localized text"
    927   *
    928   * The ID and localized text can span multiple lines using the form:
    929   *
    930   *     msgid ""
    931   *     "some long text"
    932   *     msgstr ""
    933   *     "localized text spanning "
    934   *     "multiple lines"
    935   */
    936 
    937   m = NULL;
    938 
    939   while (cupsFileGets(fp, s, sizeof(s)) != NULL)
    940   {
    941    /*
    942     * Skip blank and comment lines...
    943     */
    944 
    945     if (s[0] == '#' || !s[0])
    946       continue;
    947 
    948    /*
    949     * Strip the trailing quote...
    950     */
    951 
    952     if ((ptr = strrchr(s, '\"')) == NULL)
    953       continue;
    954 
    955     *ptr = '\0';
    956 
    957    /*
    958     * Find start of value...
    959     */
    960 
    961     if ((ptr = strchr(s, '\"')) == NULL)
    962       continue;
    963 
    964     ptr ++;
    965 
    966    /*
    967     * Unquote the text...
    968     */
    969 
    970     if (unquote)
    971       cups_unquote(ptr, ptr);
    972 
    973    /*
    974     * Create or add to a message...
    975     */
    976 
    977     if (!strncmp(s, "msgid", 5))
    978     {
    979      /*
    980       * Add previous message as needed...
    981       */
    982 
    983       if (m)
    984       {
    985         if (m->str && m->str[0])
    986         {
    987           cupsArrayAdd(a, m);
    988         }
    989         else
    990         {
    991          /*
    992           * Translation is empty, don't add it... (STR #4033)
    993           */
    994 
    995           free(m->id);
    996           if (m->str)
    997             free(m->str);
    998           free(m);
    999         }
   1000       }
   1001 
   1002      /*
   1003       * Create a new message with the given msgid string...
   1004       */
   1005 
   1006       if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
   1007       {
   1008         cupsFileClose(fp);
   1009 	return (a);
   1010       }
   1011 
   1012       if ((m->id = strdup(ptr)) == NULL)
   1013       {
   1014         free(m);
   1015         cupsFileClose(fp);
   1016 	return (a);
   1017       }
   1018     }
   1019     else if (s[0] == '\"' && m)
   1020     {
   1021      /*
   1022       * Append to current string...
   1023       */
   1024 
   1025       length = strlen(m->str ? m->str : m->id);
   1026       ptrlen = strlen(ptr);
   1027 
   1028       if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL)
   1029       {
   1030         if (m->str)
   1031 	  free(m->str);
   1032 	free(m->id);
   1033         free(m);
   1034 
   1035 	cupsFileClose(fp);
   1036 	return (a);
   1037       }
   1038 
   1039       if (m->str)
   1040       {
   1041        /*
   1042         * Copy the new portion to the end of the msgstr string - safe
   1043 	* to use memcpy because the buffer is allocated to the correct
   1044 	* size...
   1045 	*/
   1046 
   1047         m->str = temp;
   1048 
   1049 	memcpy(m->str + length, ptr, ptrlen + 1);
   1050       }
   1051       else
   1052       {
   1053        /*
   1054         * Copy the new portion to the end of the msgid string - safe
   1055 	* to use memcpy because the buffer is allocated to the correct
   1056 	* size...
   1057 	*/
   1058 
   1059         m->id = temp;
   1060 
   1061 	memcpy(m->id + length, ptr, ptrlen + 1);
   1062       }
   1063     }
   1064     else if (!strncmp(s, "msgstr", 6) && m)
   1065     {
   1066      /*
   1067       * Set the string...
   1068       */
   1069 
   1070       if ((m->str = strdup(ptr)) == NULL)
   1071       {
   1072 	free(m->id);
   1073         free(m);
   1074 
   1075         cupsFileClose(fp);
   1076 	return (a);
   1077       }
   1078     }
   1079   }
   1080 
   1081  /*
   1082   * Add the last message string to the array as needed...
   1083   */
   1084 
   1085   if (m)
   1086   {
   1087     if (m->str && m->str[0])
   1088     {
   1089       cupsArrayAdd(a, m);
   1090     }
   1091     else
   1092     {
   1093      /*
   1094       * Translation is empty, don't add it... (STR #4033)
   1095       */
   1096 
   1097       free(m->id);
   1098       if (m->str)
   1099 	free(m->str);
   1100       free(m);
   1101     }
   1102   }
   1103 
   1104  /*
   1105   * Close the message catalog file and return the new array...
   1106   */
   1107 
   1108   cupsFileClose(fp);
   1109 
   1110   DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
   1111                 cupsArrayCount(a)));
   1112 
   1113   return (a);
   1114 }
   1115 
   1116 
   1117 /*
   1118  * '_cupsMessageLookup()' - Lookup a message string.
   1119  */
   1120 
   1121 const char *				/* O - Localized message */
   1122 _cupsMessageLookup(cups_array_t *a,	/* I - Message array */
   1123                    const char   *m)	/* I - Message */
   1124 {
   1125   _cups_message_t	key,		/* Search key */
   1126 			*match;		/* Matching message */
   1127 
   1128 
   1129  /*
   1130   * Lookup the message string; if it doesn't exist in the catalog,
   1131   * then return the message that was passed to us...
   1132   */
   1133 
   1134   key.id = (char *)m;
   1135   match  = (_cups_message_t *)cupsArrayFind(a, &key);
   1136 
   1137 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
   1138   if (!match && cupsArrayUserData(a))
   1139   {
   1140    /*
   1141     * Try looking the string up in the cups.strings dictionary...
   1142     */
   1143 
   1144     CFDictionaryRef	dict;		/* cups.strings dictionary */
   1145     CFStringRef		cfm,		/* Message as a CF string */
   1146 			cfstr;		/* Localized text as a CF string */
   1147 
   1148     dict      = (CFDictionaryRef)cupsArrayUserData(a);
   1149     cfm       = CFStringCreateWithCString(kCFAllocatorDefault, m,
   1150                                           kCFStringEncodingUTF8);
   1151     match     = calloc(1, sizeof(_cups_message_t));
   1152     match->id = strdup(m);
   1153     cfstr     = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
   1154 
   1155     if (cfstr)
   1156     {
   1157       char	buffer[1024];		/* Message buffer */
   1158 
   1159       CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
   1160       match->str = strdup(buffer);
   1161 
   1162       DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
   1163                     m, buffer));
   1164     }
   1165     else
   1166     {
   1167       match->str = strdup(m);
   1168 
   1169       DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
   1170     }
   1171 
   1172     cupsArrayAdd(a, match);
   1173 
   1174     if (cfm)
   1175       CFRelease(cfm);
   1176   }
   1177 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
   1178 
   1179   if (match && match->str)
   1180     return (match->str);
   1181   else
   1182     return (m);
   1183 }
   1184 
   1185 
   1186 /*
   1187  * '_cupsMessageNew()' - Make a new message catalog array.
   1188  */
   1189 
   1190 cups_array_t *				/* O - Array */
   1191 _cupsMessageNew(void *context)		/* I - User data */
   1192 {
   1193   return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context,
   1194                         (cups_ahash_func_t)NULL, 0,
   1195 			(cups_acopy_func_t)NULL,
   1196 			(cups_afree_func_t)cups_message_free));
   1197 }
   1198 
   1199 
   1200 #ifdef __APPLE__
   1201 /*
   1202  * 'appleLangDefault()' - Get the default locale string.
   1203  */
   1204 
   1205 static const char *			/* O - Locale string */
   1206 appleLangDefault(void)
   1207 {
   1208   CFBundleRef		bundle;		/* Main bundle (if any) */
   1209   CFArrayRef		bundleList;	/* List of localizations in bundle */
   1210   CFPropertyListRef 	localizationList = NULL;
   1211 					/* List of localization data */
   1212   CFStringRef		languageName;	/* Current name */
   1213   char			*lang;		/* LANG environment variable */
   1214   _cups_globals_t	*cg = _cupsGlobals();
   1215   					/* Pointer to library globals */
   1216 
   1217 
   1218   DEBUG_puts("2appleLangDefault()");
   1219 
   1220  /*
   1221   * Only do the lookup and translation the first time.
   1222   */
   1223 
   1224   if (!cg->language[0])
   1225   {
   1226     if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
   1227     {
   1228       DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
   1229       strlcpy(cg->language, lang, sizeof(cg->language));
   1230       return (cg->language);
   1231     }
   1232     else if ((bundle = CFBundleGetMainBundle()) != NULL &&
   1233              (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
   1234     {
   1235       CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
   1236 
   1237       DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
   1238 
   1239       if (resources)
   1240       {
   1241         CFStringRef	cfpath = CFURLCopyPath(resources);
   1242 	char		path[1024];
   1243 
   1244         if (cfpath)
   1245 	{
   1246 	 /*
   1247 	  * See if we have an Info.plist file in the bundle...
   1248 	  */
   1249 
   1250 	  CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
   1251 	  DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path));
   1252 	  strlcat(path, "Contents/Info.plist", sizeof(path));
   1253 
   1254           if (!access(path, R_OK))
   1255 	    localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList);
   1256 	  else
   1257 	    DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
   1258 
   1259 	  CFRelease(cfpath);
   1260 	}
   1261 
   1262 	CFRelease(resources);
   1263       }
   1264       else
   1265         DEBUG_puts("3appleLangDefault: No resource URL.");
   1266 
   1267       CFRelease(bundleList);
   1268     }
   1269 
   1270     if (!localizationList)
   1271     {
   1272       DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
   1273 
   1274       localizationList =
   1275 	  CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
   1276 				    kCFPreferencesCurrentApplication);
   1277     }
   1278 
   1279     if (localizationList)
   1280     {
   1281 #ifdef DEBUG
   1282       if (CFGetTypeID(localizationList) == CFArrayGetTypeID())
   1283         DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
   1284                       (int)CFArrayGetCount(localizationList)));
   1285       else
   1286         DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
   1287 #endif /* DEBUG */
   1288 
   1289       if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
   1290 	  CFArrayGetCount(localizationList) > 0)
   1291       {
   1292 	languageName = CFArrayGetValueAtIndex(localizationList, 0);
   1293 
   1294 	if (languageName &&
   1295 	    CFGetTypeID(languageName) == CFStringGetTypeID())
   1296 	{
   1297 	  if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
   1298 	    DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
   1299 			  cg->language));
   1300 	  else
   1301 	    DEBUG_puts("3appleLangDefault: Unable to get locale.");
   1302 	}
   1303       }
   1304 
   1305       CFRelease(localizationList);
   1306     }
   1307 
   1308    /*
   1309     * If we didn't find the language, default to en_US...
   1310     */
   1311 
   1312     if (!cg->language[0])
   1313     {
   1314       DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
   1315       strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
   1316     }
   1317   }
   1318   else
   1319     DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
   1320 
   1321  /*
   1322   * Return the cached locale...
   1323   */
   1324 
   1325   return (cg->language);
   1326 }
   1327 
   1328 
   1329 #  ifdef CUPS_BUNDLEDIR
   1330 /*
   1331  * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
   1332  */
   1333 
   1334 static cups_array_t *			/* O - Message catalog */
   1335 appleMessageLoad(const char *locale)	/* I - Locale ID */
   1336 {
   1337   char			filename[1024],	/* Path to cups.strings file */
   1338 			applelang[256],	/* Apple language ID */
   1339 			baselang[3];	/* Base language */
   1340   CFURLRef		url;		/* URL to cups.strings file */
   1341   CFReadStreamRef	stream = NULL;	/* File stream */
   1342   CFPropertyListRef	plist = NULL;	/* Localization file */
   1343 #ifdef DEBUG
   1344   CFErrorRef		error = NULL;	/* Error when opening file */
   1345 #endif /* DEBUG */
   1346 
   1347 
   1348   DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
   1349 
   1350  /*
   1351   * Load the cups.strings file...
   1352   */
   1353 
   1354   snprintf(filename, sizeof(filename),
   1355            CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
   1356 	   _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
   1357 
   1358   if (access(filename, 0))
   1359   {
   1360    /*
   1361     * <rdar://problem/22086642>
   1362     *
   1363     * Try with original locale string...
   1364     */
   1365 
   1366     snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
   1367   }
   1368 
   1369   if (access(filename, 0))
   1370   {
   1371    /*
   1372     * <rdar://problem/25292403>
   1373     *
   1374     * Try with just the language code...
   1375     */
   1376 
   1377     strlcpy(baselang, locale, sizeof(baselang));
   1378     snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
   1379   }
   1380 
   1381   DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
   1382 
   1383   if (access(filename, 0))
   1384   {
   1385    /*
   1386     * Try alternate lproj directory names...
   1387     */
   1388 
   1389     if (!strncmp(locale, "en", 2))
   1390       locale = "English";
   1391     else if (!strncmp(locale, "nb", 2))
   1392       locale = "no";
   1393     else if (!strncmp(locale, "nl", 2))
   1394       locale = "Dutch";
   1395     else if (!strncmp(locale, "fr", 2))
   1396       locale = "French";
   1397     else if (!strncmp(locale, "de", 2))
   1398       locale = "German";
   1399     else if (!strncmp(locale, "it", 2))
   1400       locale = "Italian";
   1401     else if (!strncmp(locale, "ja", 2))
   1402       locale = "Japanese";
   1403     else if (!strncmp(locale, "es", 2))
   1404       locale = "Spanish";
   1405     else if (!strcmp(locale, "zh_HK") || !strncmp(locale, "zh-Hant", 7))
   1406     {
   1407      /*
   1408       * <rdar://problem/22130168>
   1409       * <rdar://problem/27245567>
   1410       *
   1411       * Try zh_TW first, then zh...  Sigh...
   1412       */
   1413 
   1414       if (!access(CUPS_BUNDLEDIR "/Resources/zh_TW.lproj/cups.strings", 0))
   1415         locale = "zh_TW";
   1416       else
   1417         locale = "zh";
   1418     }
   1419     else if (strstr(locale, "_") != NULL || strstr(locale, "-") != NULL)
   1420     {
   1421      /*
   1422       * Drop country code, just try language...
   1423       */
   1424 
   1425       strlcpy(baselang, locale, sizeof(baselang));
   1426       locale = baselang;
   1427     }
   1428 
   1429     snprintf(filename, sizeof(filename),
   1430 	     CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
   1431     DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename));
   1432   }
   1433 
   1434   url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
   1435                                                 (UInt8 *)filename,
   1436 						(CFIndex)strlen(filename), false);
   1437   if (url)
   1438   {
   1439     stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
   1440     if (stream)
   1441     {
   1442      /*
   1443       * Read the property list containing the localization data.
   1444       *
   1445       * NOTE: This code currently generates a clang "potential leak"
   1446       * warning, but the object is released in _cupsMessageFree().
   1447       */
   1448 
   1449       CFReadStreamOpen(stream);
   1450 
   1451 #ifdef DEBUG
   1452       plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
   1453                                              kCFPropertyListImmutable, NULL,
   1454                                              &error);
   1455       if (error)
   1456       {
   1457         CFStringRef	msg = CFErrorCopyDescription(error);
   1458     					/* Error message */
   1459 
   1460         CFStringGetCString(msg, filename, sizeof(filename),
   1461                            kCFStringEncodingUTF8);
   1462         DEBUG_printf(("1appleMessageLoad: %s", filename));
   1463 
   1464 	CFRelease(msg);
   1465         CFRelease(error);
   1466       }
   1467 
   1468 #else
   1469       plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
   1470                                              kCFPropertyListImmutable, NULL,
   1471                                              NULL);
   1472 #endif /* DEBUG */
   1473 
   1474       if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID())
   1475       {
   1476          CFRelease(plist);
   1477          plist = NULL;
   1478       }
   1479 
   1480       CFRelease(stream);
   1481     }
   1482 
   1483     CFRelease(url);
   1484   }
   1485 
   1486   DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
   1487                 plist));
   1488 
   1489  /*
   1490   * Create and return an empty array to act as a cache for messages, passing the
   1491   * plist as the user data.
   1492   */
   1493 
   1494   return (_cupsMessageNew((void *)plist));
   1495 }
   1496 #  endif /* CUPS_BUNDLEDIR */
   1497 #endif /* __APPLE__ */
   1498 
   1499 
   1500 /*
   1501  * 'cups_cache_lookup()' - Lookup a language in the cache...
   1502  */
   1503 
   1504 static cups_lang_t *			/* O - Language data or NULL */
   1505 cups_cache_lookup(
   1506     const char      *name,		/* I - Name of locale */
   1507     cups_encoding_t encoding)		/* I - Encoding of locale */
   1508 {
   1509   cups_lang_t	*lang;			/* Current language */
   1510 
   1511 
   1512   DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
   1513                 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
   1514 		              lang_encodings[encoding]));
   1515 
   1516  /*
   1517   * Loop through the cache and return a match if found...
   1518   */
   1519 
   1520   for (lang = lang_cache; lang != NULL; lang = lang->next)
   1521   {
   1522     DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
   1523 		  "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
   1524 		  lang_encodings[lang->encoding]));
   1525 
   1526     if (!strcmp(lang->language, name) &&
   1527         (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
   1528     {
   1529       lang->used ++;
   1530 
   1531       DEBUG_puts("8cups_cache_lookup: returning match!");
   1532 
   1533       return (lang);
   1534     }
   1535   }
   1536 
   1537   DEBUG_puts("8cups_cache_lookup: returning NULL!");
   1538 
   1539   return (NULL);
   1540 }
   1541 
   1542 
   1543 /*
   1544  * 'cups_message_compare()' - Compare two messages.
   1545  */
   1546 
   1547 static int				/* O - Result of comparison */
   1548 cups_message_compare(
   1549     _cups_message_t *m1,		/* I - First message */
   1550     _cups_message_t *m2)		/* I - Second message */
   1551 {
   1552   return (strcmp(m1->id, m2->id));
   1553 }
   1554 
   1555 
   1556 /*
   1557  * 'cups_message_free()' - Free a message.
   1558  */
   1559 
   1560 static void
   1561 cups_message_free(_cups_message_t *m)	/* I - Message */
   1562 {
   1563   if (m->id)
   1564     free(m->id);
   1565 
   1566   if (m->str)
   1567     free(m->str);
   1568 
   1569   free(m);
   1570 }
   1571 
   1572 
   1573 /*
   1574  * 'cups_message_load()' - Load the message catalog for a language.
   1575  */
   1576 
   1577 static void
   1578 cups_message_load(cups_lang_t *lang)	/* I - Language */
   1579 {
   1580 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
   1581   lang->strings = appleMessageLoad(lang->language);
   1582 
   1583 #else
   1584   char			filename[1024];	/* Filename for language locale file */
   1585   _cups_globals_t	*cg = _cupsGlobals();
   1586   					/* Pointer to library globals */
   1587 
   1588 
   1589   snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
   1590 	   lang->language, lang->language);
   1591 
   1592   if (strchr(lang->language, '_') && access(filename, 0))
   1593   {
   1594    /*
   1595     * Country localization not available, look for generic localization...
   1596     */
   1597 
   1598     snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
   1599              lang->language, lang->language);
   1600 
   1601     if (access(filename, 0))
   1602     {
   1603      /*
   1604       * No generic localization, so use POSIX...
   1605       */
   1606 
   1607       DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename,
   1608                     strerror(errno)));
   1609 
   1610       snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
   1611     }
   1612   }
   1613 
   1614  /*
   1615   * Read the strings from the file...
   1616   */
   1617 
   1618   lang->strings = _cupsMessageLoad(filename, 1);
   1619 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
   1620 }
   1621 
   1622 
   1623 /*
   1624  * 'cups_unquote()' - Unquote characters in strings...
   1625  */
   1626 
   1627 static void
   1628 cups_unquote(char       *d,		/* O - Unquoted string */
   1629              const char *s)		/* I - Original string */
   1630 {
   1631   while (*s)
   1632   {
   1633     if (*s == '\\')
   1634     {
   1635       s ++;
   1636       if (isdigit(*s))
   1637       {
   1638 	*d = 0;
   1639 
   1640 	while (isdigit(*s))
   1641 	{
   1642 	  *d = *d * 8 + *s - '0';
   1643 	  s ++;
   1644 	}
   1645 
   1646 	d ++;
   1647       }
   1648       else
   1649       {
   1650 	if (*s == 'n')
   1651 	  *d ++ = '\n';
   1652 	else if (*s == 'r')
   1653 	  *d ++ = '\r';
   1654 	else if (*s == 't')
   1655 	  *d ++ = '\t';
   1656 	else
   1657 	  *d++ = *s;
   1658 
   1659 	s ++;
   1660       }
   1661     }
   1662     else
   1663       *d++ = *s++;
   1664   }
   1665 
   1666   *d = '\0';
   1667 }
   1668