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