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