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