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