Coverage Report

Created: 2023-11-27 06:38

/src/cups/cups/language.c
Line
Count
Source (jump to first uncovered line)
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",   "bg18030",
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
109
/*
110
 * Local functions...
111
 */
112
113
114
static cups_lang_t  *cups_cache_lookup(const char *name, cups_encoding_t encoding);
115
static int    cups_message_compare(_cups_message_t *m1, _cups_message_t *m2);
116
static void   cups_message_free(_cups_message_t *m);
117
static void   cups_message_load(cups_lang_t *lang);
118
static void   cups_message_puts(cups_file_t *fp, const char *s);
119
static int    cups_read_strings(cups_file_t *fp, int flags, cups_array_t *a);
120
static void   cups_unquote(char *d, const char *s);
121
122
123
/*
124
 * '_cupsEncodingName()' - Return the character encoding name string
125
 *                         for the given encoding enumeration.
126
 */
127
128
const char *        /* O - Character encoding */
129
_cupsEncodingName(
130
    cups_encoding_t encoding)   /* I - Encoding value */
131
0
{
132
0
  if (encoding < CUPS_US_ASCII ||
133
0
      encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
134
0
  {
135
0
    DEBUG_printf("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")", encoding, lang_encodings[0]);
136
0
    return (lang_encodings[0]);
137
0
  }
138
0
  else
139
0
  {
140
0
    DEBUG_printf("1_cupsEncodingName(encoding=%d) = \"%s\"", encoding, lang_encodings[encoding]);
141
0
    return (lang_encodings[encoding]);
142
0
  }
143
0
}
144
145
146
/*
147
 * 'cupsLangDefault()' - Return the default language.
148
 */
149
150
cups_lang_t *       /* O - Language data */
151
cupsLangDefault(void)
152
4.57k
{
153
4.57k
  return (cupsLangGet(NULL));
154
4.57k
}
155
156
157
/*
158
 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
159
 *                        for the given language.
160
 */
161
162
const char *        /* O - Character encoding */
163
cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
164
0
{
165
0
  if (lang == NULL)
166
0
    return ((char*)lang_encodings[0]);
167
0
  else
168
0
    return ((char*)lang_encodings[lang->encoding]);
169
0
}
170
171
172
/*
173
 * 'cupsLangFlush()' - Flush all language data out of the cache.
174
 */
175
176
void
177
cupsLangFlush(void)
178
0
{
179
0
  cups_lang_t *lang,      /* Current language */
180
0
    *next;      /* Next language */
181
182
183
 /*
184
  * Free all languages in the cache...
185
  */
186
187
0
  cupsMutexLock(&lang_mutex);
188
189
0
  for (lang = lang_cache; lang != NULL; lang = next)
190
0
  {
191
   /*
192
    * Free all messages...
193
    */
194
195
0
    _cupsMessageFree(lang->strings);
196
197
   /*
198
    * Then free the language structure itself...
199
    */
200
201
0
    next = lang->next;
202
0
    free(lang);
203
0
  }
204
205
0
  lang_cache = NULL;
206
207
0
  cupsMutexUnlock(&lang_mutex);
208
0
}
209
210
211
/*
212
 * 'cupsLangFree()' - Free language data.
213
 *
214
 * This does not actually free anything; use @link cupsLangFlush@ for that.
215
 */
216
217
void
218
cupsLangFree(cups_lang_t *lang)   /* I - Language to free */
219
0
{
220
0
  cupsMutexLock(&lang_mutex);
221
222
0
  if (lang != NULL && lang->used > 0)
223
0
    lang->used --;
224
225
0
  cupsMutexUnlock(&lang_mutex);
226
0
}
227
228
229
/*
230
 * 'cupsLangGet()' - Get a language.
231
 */
232
233
cups_lang_t *       /* O - Language data */
234
cupsLangGet(const char *language) /* I - Language or locale */
235
4.57k
{
236
4.57k
  int     i;    /* Looping var */
237
4.57k
  char      locale[255];  /* Copy of locale name */
238
4.57k
  char      langname[16], /* Requested language name */
239
4.57k
      country[16],  /* Country code */
240
4.57k
      charset[16],  /* Character set */
241
4.57k
      *csptr,   /* Pointer to CODESET string */
242
4.57k
      *ptr,   /* Pointer into language/charset */
243
4.57k
      real[48]; /* Real language name */
244
4.57k
  cups_encoding_t encoding; /* Encoding to use */
245
4.57k
  cups_lang_t   *lang;    /* Current language... */
246
4.57k
  static const char * const locale_encodings[] =
247
4.57k
    {     /* Locale charset names */
248
4.57k
      "ASCII",  "ISO88591", "ISO88592", "ISO88593",
249
4.57k
      "ISO88594", "ISO88595", "ISO88596", "ISO88597",
250
4.57k
      "ISO88598", "ISO88599", "ISO885910",  "UTF8",
251
4.57k
      "ISO885913",  "ISO885914",  "ISO885915",  "CP874",
252
4.57k
      "CP1250", "CP1251", "CP1252", "CP1253",
253
4.57k
      "CP1254", "CP1255", "CP1256", "CP1257",
254
4.57k
      "CP1258", "KOI8R",  "KOI8U",  "ISO885911",
255
4.57k
      "ISO885916",  "MACROMAN", "",   "",
256
257
4.57k
      "",   "",   "",   "",
258
4.57k
      "",   "",   "",   "",
259
4.57k
      "",   "",   "",   "",
260
4.57k
      "",   "",   "",   "",
261
4.57k
      "",   "",   "",   "",
262
4.57k
      "",   "",   "",   "",
263
4.57k
      "",   "",   "",   "",
264
4.57k
      "",   "",   "",   "",
265
266
4.57k
      "CP932",  "CP936",  "CP949",  "CP950",
267
4.57k
      "CP1361", "GB18030",  "",   "",
268
4.57k
      "",   "",   "",   "",
269
4.57k
      "",   "",   "",   "",
270
4.57k
      "",   "",   "",   "",
271
4.57k
      "",   "",   "",   "",
272
4.57k
      "",   "",   "",   "",
273
4.57k
      "",   "",   "",   "",
274
275
4.57k
      "",   "",   "",   "",
276
4.57k
      "",   "",   "",   "",
277
4.57k
      "",   "",   "",   "",
278
4.57k
      "",   "",   "",   "",
279
4.57k
      "",   "",   "",   "",
280
4.57k
      "",   "",   "",   "",
281
4.57k
      "",   "",   "",   "",
282
4.57k
      "",   "",   "",   "",
283
284
4.57k
      "EUCCN",  "EUCJP",  "EUCKR",  "EUCTW",
285
4.57k
      "SHIFT_JISX0213"
286
4.57k
    };
287
288
289
4.57k
  DEBUG_printf("2cupsLangGet(language=\"%s\")", language);
290
291
 /*
292
  * Set the charset to "unknown"...
293
  */
294
295
4.57k
  charset[0] = '\0';
296
297
 /*
298
  * Use setlocale() to determine the currently set locale, and then
299
  * fallback to environment variables to avoid setting the locale,
300
  * since setlocale() is not thread-safe!
301
  */
302
303
4.57k
  if (!language)
304
4.57k
  {
305
   /*
306
    * First see if the locale has been set; if it is still "C" or
307
    * "POSIX", use the environment to get the default...
308
    */
309
310
4.57k
#ifdef LC_MESSAGES
311
4.57k
    ptr = setlocale(LC_MESSAGES, NULL);
312
#else
313
    ptr = setlocale(LC_ALL, NULL);
314
#endif /* LC_MESSAGES */
315
316
4.57k
    DEBUG_printf("4cupsLangGet: current locale is \"%s\"", ptr);
317
318
4.57k
    if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
319
4.57k
    {
320
     /*
321
      * Get the character set from the LC_CTYPE locale setting...
322
      */
323
324
4.57k
      if ((ptr = getenv("LC_CTYPE")) == NULL)
325
4.57k
        if ((ptr = getenv("LC_ALL")) == NULL)
326
4.57k
    if ((ptr = getenv("LANG")) == NULL)
327
4.57k
      ptr = "en_US";
328
329
4.57k
      if ((csptr = strchr(ptr, '.')) != NULL)
330
0
      {
331
       /*
332
        * Extract the character set from the environment...
333
  */
334
335
0
  for (ptr = charset, csptr ++; *csptr; csptr ++)
336
0
    if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
337
0
      *ptr++ = *csptr;
338
339
0
        *ptr = '\0';
340
0
      }
341
342
     /*
343
      * Get the locale for messages from the LC_MESSAGES locale setting...
344
      */
345
346
4.57k
      if ((ptr = getenv("LC_MESSAGES")) == NULL)
347
4.57k
        if ((ptr = getenv("LC_ALL")) == NULL)
348
4.57k
    if ((ptr = getenv("LANG")) == NULL)
349
4.57k
      ptr = "en_US";
350
4.57k
    }
351
352
4.57k
    if (ptr)
353
4.57k
    {
354
4.57k
      cupsCopyString(locale, ptr, sizeof(locale));
355
4.57k
      language = locale;
356
357
     /*
358
      * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
359
      */
360
361
4.57k
      if (!strncmp(locale, "nb", 2))
362
0
        locale[1] = 'o';
363
364
4.57k
      DEBUG_printf("4cupsLangGet: new language value is \"%s\"", language);
365
4.57k
    }
366
4.57k
  }
367
368
 /*
369
  * If "language" is NULL at this point, then chances are we are using
370
  * a language that is not installed for the base OS.
371
  */
372
373
4.57k
  if (!language)
374
0
  {
375
   /*
376
    * Switch to the POSIX ("C") locale...
377
    */
378
379
0
    language = "C";
380
0
  }
381
382
4.57k
#ifdef CODESET
383
 /*
384
  * On systems that support the nl_langinfo(CODESET) call, use
385
  * this value as the character set...
386
  */
387
388
4.57k
  if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
389
4.57k
  {
390
   /*
391
    * Copy all of the letters and numbers in the CODESET string...
392
    */
393
394
68.5k
    for (ptr = charset; *csptr; csptr ++)
395
64.0k
      if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
396
50.2k
        *ptr++ = *csptr;
397
398
4.57k
    *ptr = '\0';
399
400
4.57k
    DEBUG_printf("4cupsLangGet: charset set to \"%s\" via nl_langinfo(CODESET)...", charset);
401
4.57k
  }
402
4.57k
#endif /* CODESET */
403
404
 /*
405
  * If we don't have a character set by now, default to UTF-8...
406
  */
407
408
4.57k
  if (!charset[0])
409
0
    cupsCopyString(charset, "UTF8", sizeof(charset));
410
411
 /*
412
  * Parse the language string passed in to a locale string. "C" is the
413
  * standard POSIX locale and is copied unchanged.  Otherwise the
414
  * language string is converted from ll-cc[.charset] (language-country)
415
  * to ll_CC[.CHARSET] to match the file naming convention used by all
416
  * POSIX-compliant operating systems.  Invalid language names are mapped
417
  * to the POSIX locale.
418
  */
419
420
4.57k
  country[0] = '\0';
421
422
4.57k
  if (language == NULL || !language[0] ||
423
4.57k
      !strcmp(language, "POSIX"))
424
0
    cupsCopyString(langname, "C", sizeof(langname));
425
4.57k
  else
426
4.57k
  {
427
   /*
428
    * Copy the parts of the locale string over safely...
429
    */
430
431
13.7k
    for (ptr = langname; *language; language ++)
432
13.7k
      if (*language == '_' || *language == '-' || *language == '.')
433
4.57k
  break;
434
9.14k
      else if (ptr < (langname + sizeof(langname) - 1))
435
9.14k
        *ptr++ = (char)tolower(*language & 255);
436
437
4.57k
    *ptr = '\0';
438
439
4.57k
    if (*language == '_' || *language == '-')
440
4.57k
    {
441
     /*
442
      * Copy the country code...
443
      */
444
445
13.7k
      for (language ++, ptr = country; *language; language ++)
446
9.14k
  if (*language == '.')
447
0
    break;
448
9.14k
  else if (ptr < (country + sizeof(country) - 1))
449
9.14k
          *ptr++ = (char)toupper(*language & 255);
450
451
4.57k
      *ptr = '\0';
452
453
     /*
454
      * Map Chinese region codes to legacy country codes.
455
      */
456
457
4.57k
      if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
458
0
        cupsCopyString(country, "CN", sizeof(country));
459
4.57k
      if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
460
0
        cupsCopyString(country, "TW", sizeof(country));
461
4.57k
    }
462
463
4.57k
    if (*language == '.' && !charset[0])
464
0
    {
465
     /*
466
      * Copy the encoding...
467
      */
468
469
0
      for (language ++, ptr = charset; *language; language ++)
470
0
        if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
471
0
          *ptr++ = (char)toupper(*language & 255);
472
473
0
      *ptr = '\0';
474
0
    }
475
476
   /*
477
    * Force a POSIX locale for an invalid language name...
478
    */
479
480
4.57k
    if (strlen(langname) != 2 && strlen(langname) != 3)
481
0
    {
482
0
      cupsCopyString(langname, "C", sizeof(langname));
483
0
      country[0] = '\0';
484
0
      charset[0] = '\0';
485
0
    }
486
4.57k
  }
487
488
4.57k
  DEBUG_printf("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"", langname, country, charset);
489
490
 /*
491
  * Figure out the desired encoding...
492
  */
493
494
4.57k
  encoding = CUPS_AUTO_ENCODING;
495
496
4.57k
  if (charset[0])
497
4.57k
  {
498
4.57k
    for (i = 0;
499
612k
         i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
500
608k
   i ++)
501
608k
      if (!_cups_strcasecmp(charset, locale_encodings[i]))
502
0
      {
503
0
  encoding = (cups_encoding_t)i;
504
0
  break;
505
0
      }
506
507
4.57k
    if (encoding == CUPS_AUTO_ENCODING)
508
4.57k
    {
509
     /*
510
      * Map alternate names for various character sets...
511
      */
512
513
4.57k
      if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
514
4.57k
          !_cups_strcasecmp(charset, "sjis"))
515
0
  encoding = CUPS_WINDOWS_932;
516
4.57k
      else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
517
0
  encoding = CUPS_WINDOWS_936;
518
4.57k
      else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
519
0
  encoding = CUPS_WINDOWS_949;
520
4.57k
      else if (!_cups_strcasecmp(charset, "big5"))
521
0
  encoding = CUPS_WINDOWS_950;
522
4.57k
    }
523
4.57k
  }
524
525
4.57k
  DEBUG_printf("4cupsLangGet: encoding=%d(%s)", encoding, encoding == CUPS_AUTO_ENCODING ? "auto" : lang_encodings[encoding]);
526
527
 /*
528
  * See if we already have this language/country loaded...
529
  */
530
531
4.57k
  if (country[0])
532
4.57k
    snprintf(real, sizeof(real), "%s_%s", langname, country);
533
0
  else
534
0
    cupsCopyString(real, langname, sizeof(real));
535
536
4.57k
  cupsMutexLock(&lang_mutex);
537
538
4.57k
  if ((lang = cups_cache_lookup(real, encoding)) != NULL)
539
4.57k
  {
540
4.57k
    cupsMutexUnlock(&lang_mutex);
541
542
4.57k
    DEBUG_printf("3cupsLangGet: Using cached copy of \"%s\"...", real);
543
544
4.57k
    return (lang);
545
4.57k
  }
546
547
 /*
548
  * See if there is a free language available; if so, use that
549
  * record...
550
  */
551
552
1
  for (lang = lang_cache; lang != NULL; lang = lang->next)
553
0
    if (lang->used == 0)
554
0
      break;
555
556
1
  if (lang == NULL)
557
1
  {
558
   /*
559
    * Allocate memory for the language and add it to the cache.
560
    */
561
562
1
    if ((lang = calloc(1, sizeof(cups_lang_t))) == NULL)
563
0
    {
564
0
      cupsMutexUnlock(&lang_mutex);
565
566
0
      return (NULL);
567
0
    }
568
569
1
    lang->next = lang_cache;
570
1
    lang_cache = lang;
571
1
  }
572
0
  else
573
0
  {
574
   /*
575
    * Free all old strings as needed...
576
    */
577
578
0
    _cupsMessageFree(lang->strings);
579
0
    lang->strings = NULL;
580
0
  }
581
582
 /*
583
  * Then assign the language and encoding fields...
584
  */
585
586
1
  lang->used ++;
587
1
  cupsCopyString(lang->language, real, sizeof(lang->language));
588
589
1
  if (encoding != CUPS_AUTO_ENCODING)
590
0
    lang->encoding = encoding;
591
1
  else
592
1
    lang->encoding = CUPS_UTF8;
593
594
 /*
595
  * Return...
596
  */
597
598
1
  cupsMutexUnlock(&lang_mutex);
599
600
1
  return (lang);
601
1
}
602
603
604
/*
605
 * '_cupsLangString()' - Get a message string.
606
 *
607
 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
608
 * convert the string to the language encoding.
609
 */
610
611
const char *        /* O - Localized message */
612
_cupsLangString(cups_lang_t *lang,  /* I - Language */
613
                const char  *message) /* I - Message */
614
622
{
615
622
  const char *s;      /* Localized message */
616
617
618
622
  DEBUG_printf("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message);
619
620
 /*
621
  * Range check input...
622
  */
623
624
622
  if (!lang || !message || !*message)
625
0
    return (message);
626
627
622
  cupsMutexLock(&lang_mutex);
628
629
 /*
630
  * Load the message catalog if needed...
631
  */
632
633
622
  if (!lang->strings)
634
1
    cups_message_load(lang);
635
636
622
  s = _cupsMessageLookup(lang->strings, message);
637
638
622
  cupsMutexUnlock(&lang_mutex);
639
640
622
  return (s);
641
622
}
642
643
644
/*
645
 * '_cupsMessageFree()' - Free a messages array.
646
 */
647
648
void
649
_cupsMessageFree(cups_array_t *a) /* I - Message array */
650
0
{
651
 /*
652
  * Free the array...
653
  */
654
655
0
  cupsArrayDelete(a);
656
0
}
657
658
659
/*
660
 * '_cupsMessageLoad()' - Load a .po or .strings file into a messages array.
661
 */
662
663
cups_array_t *        /* O - New message array */
664
_cupsMessageLoad(const char *filename,  /* I - Message catalog to load */
665
                 int        flags)  /* I - Load flags */
666
1
{
667
1
  cups_file_t   *fp;    /* Message file */
668
1
  cups_array_t    *a;   /* Message array */
669
1
  _cups_message_t *m;   /* Current message */
670
1
  char      s[4096],  /* String buffer */
671
1
      *ptr,   /* Pointer into buffer */
672
1
      *temp;    /* New string */
673
1
  size_t    length,   /* Length of combined strings */
674
1
      ptrlen;   /* Length of string */
675
676
677
1
  DEBUG_printf("4_cupsMessageLoad(filename=\"%s\")", filename);
678
679
 /*
680
  * Create an array to hold the messages...
681
  */
682
683
1
  if ((a = _cupsMessageNew(NULL)) == NULL)
684
0
  {
685
0
    DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
686
0
    return (NULL);
687
0
  }
688
689
 /*
690
  * Open the message catalog file...
691
  */
692
693
1
  if ((fp = cupsFileOpen(filename, "r")) == NULL)
694
1
  {
695
1
    DEBUG_printf("5_cupsMessageLoad: Unable to open file: %s", strerror(errno));
696
1
    return (a);
697
1
  }
698
699
0
  if (flags & _CUPS_MESSAGE_STRINGS)
700
0
  {
701
0
    while (cups_read_strings(fp, flags, a));
702
0
  }
703
0
  else
704
0
  {
705
   /*
706
    * Read messages from the catalog file until EOF...
707
    *
708
    * The format is the GNU gettext .po format, which is fairly simple:
709
    *
710
    *     msgid "some text"
711
    *     msgstr "localized text"
712
    *
713
    * The ID and localized text can span multiple lines using the form:
714
    *
715
    *     msgid ""
716
    *     "some long text"
717
    *     msgstr ""
718
    *     "localized text spanning "
719
    *     "multiple lines"
720
    */
721
722
0
    m = NULL;
723
724
0
    while (cupsFileGets(fp, s, sizeof(s)) != NULL)
725
0
    {
726
     /*
727
      * Skip blank and comment lines...
728
      */
729
730
0
      if (s[0] == '#' || !s[0])
731
0
  continue;
732
733
     /*
734
      * Strip the trailing quote...
735
      */
736
737
0
      if ((ptr = strrchr(s, '\"')) == NULL)
738
0
  continue;
739
740
0
      *ptr = '\0';
741
742
     /*
743
      * Find start of value...
744
      */
745
746
0
      if ((ptr = strchr(s, '\"')) == NULL)
747
0
  continue;
748
749
0
      ptr ++;
750
751
     /*
752
      * Unquote the text...
753
      */
754
755
0
      if (flags & _CUPS_MESSAGE_UNQUOTE)
756
0
  cups_unquote(ptr, ptr);
757
758
     /*
759
      * Create or add to a message...
760
      */
761
762
0
      if (!strncmp(s, "msgid", 5))
763
0
      {
764
       /*
765
  * Add previous message as needed...
766
  */
767
768
0
  if (m)
769
0
  {
770
0
    if (m->str && (m->str[0] || (flags & _CUPS_MESSAGE_EMPTY)))
771
0
    {
772
0
      cupsArrayAdd(a, m);
773
0
    }
774
0
    else
775
0
    {
776
     /*
777
      * Translation is empty, don't add it... (STR #4033)
778
      */
779
780
0
      free(m->msg);
781
0
      if (m->str)
782
0
        free(m->str);
783
0
      free(m);
784
0
    }
785
0
  }
786
787
       /*
788
  * Create a new message with the given msgid string...
789
  */
790
791
0
  if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
792
0
    break;
793
794
0
  if ((m->msg = strdup(ptr)) == NULL)
795
0
  {
796
0
    free(m);
797
0
    m = NULL;
798
0
    break;
799
0
  }
800
0
      }
801
0
      else if (s[0] == '\"' && m)
802
0
      {
803
       /*
804
  * Append to current string...
805
  */
806
807
0
  length = strlen(m->str ? m->str : m->msg);
808
0
  ptrlen = strlen(ptr);
809
810
0
  if ((temp = realloc(m->str ? m->str : m->msg, length + ptrlen + 1)) == NULL)
811
0
  {
812
0
    if (m->str)
813
0
      free(m->str);
814
0
    free(m->msg);
815
0
    free(m);
816
0
    m = NULL;
817
0
    break;
818
0
  }
819
820
0
  if (m->str)
821
0
  {
822
   /*
823
    * Copy the new portion to the end of the msgstr string - safe
824
    * to use memcpy because the buffer is allocated to the correct
825
    * size...
826
    */
827
828
0
    m->str = temp;
829
830
0
    memcpy(m->str + length, ptr, ptrlen + 1);
831
0
  }
832
0
  else
833
0
  {
834
   /*
835
    * Copy the new portion to the end of the msgid string - safe
836
    * to use memcpy because the buffer is allocated to the correct
837
    * size...
838
    */
839
840
0
    m->msg = temp;
841
842
0
    memcpy(m->msg + length, ptr, ptrlen + 1);
843
0
  }
844
0
      }
845
0
      else if (!strncmp(s, "msgstr", 6) && m)
846
0
      {
847
       /*
848
  * Set the string...
849
  */
850
851
0
  if ((m->str = strdup(ptr)) == NULL)
852
0
  {
853
0
    free(m->msg);
854
0
    free(m);
855
0
    m = NULL;
856
0
          break;
857
0
  }
858
0
      }
859
0
    }
860
861
   /*
862
    * Add the last message string to the array as needed...
863
    */
864
865
0
    if (m)
866
0
    {
867
0
      if (m->str && (m->str[0] || (flags & _CUPS_MESSAGE_EMPTY)))
868
0
      {
869
0
  cupsArrayAdd(a, m);
870
0
      }
871
0
      else
872
0
      {
873
       /*
874
  * Translation is empty, don't add it... (STR #4033)
875
  */
876
877
0
  free(m->msg);
878
0
  if (m->str)
879
0
    free(m->str);
880
0
  free(m);
881
0
      }
882
0
    }
883
0
  }
884
885
 /*
886
  * Close the message catalog file and return the new array...
887
  */
888
889
0
  cupsFileClose(fp);
890
891
0
  DEBUG_printf("5_cupsMessageLoad: Returning %d messages...", cupsArrayCount(a));
892
893
0
  return (a);
894
1
}
895
896
897
/*
898
 * '_cupsMessageLookup()' - Lookup a message string.
899
 */
900
901
const char *        /* O - Localized message */
902
_cupsMessageLookup(cups_array_t *a, /* I - Message array */
903
                   const char   *m) /* I - Message */
904
622
{
905
622
  _cups_message_t key,    /* Search key */
906
622
      *match;   /* Matching message */
907
908
909
622
  DEBUG_printf("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m);
910
911
 /*
912
  * Lookup the message string; if it doesn't exist in the catalog,
913
  * then return the message that was passed to us...
914
  */
915
916
622
  key.msg = (char *)m;
917
622
  match   = (_cups_message_t *)cupsArrayFind(a, &key);
918
919
622
  if (match && match->str)
920
0
    return (match->str);
921
622
  else
922
622
    return (m);
923
622
}
924
925
926
/*
927
 * '_cupsMessageNew()' - Make a new message catalog array.
928
 */
929
930
cups_array_t *        /* O - Array */
931
_cupsMessageNew(void *context)    /* I - User data */
932
1
{
933
1
  return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context,
934
1
                        (cups_ahash_func_t)NULL, 0,
935
1
      (cups_acopy_func_t)NULL,
936
1
      (cups_afree_func_t)cups_message_free));
937
1
}
938
939
940
/*
941
 * '_cupsMessageSave()' - Save a message catalog array.
942
 */
943
944
int         /* O - 0 on success, -1 on failure */
945
_cupsMessageSave(const char   *filename,/* I - Output filename */
946
                 int          flags,  /* I - Format flags */
947
                 cups_array_t *a) /* I - Message array */
948
0
{
949
0
  cups_file_t   *fp;    /* Output file */
950
0
  _cups_message_t *m;   /* Current message */
951
952
953
 /*
954
  * Output message catalog file...
955
  */
956
957
0
  if ((fp = cupsFileOpen(filename, "w")) == NULL)
958
0
    return (-1);
959
960
 /*
961
  * Write each message...
962
  */
963
964
0
  if (flags & _CUPS_MESSAGE_STRINGS)
965
0
  {
966
0
    for (m = (_cups_message_t *)cupsArrayFirst(a); m; m = (_cups_message_t *)cupsArrayNext(a))
967
0
    {
968
0
      cupsFilePuts(fp, "\"");
969
0
      cups_message_puts(fp, m->msg);
970
0
      cupsFilePuts(fp, "\" = \"");
971
0
      cups_message_puts(fp, m->str);
972
0
      cupsFilePuts(fp, "\";\n");
973
0
    }
974
0
  }
975
0
  else
976
0
  {
977
0
    for (m = (_cups_message_t *)cupsArrayFirst(a); m; m = (_cups_message_t *)cupsArrayNext(a))
978
0
    {
979
0
      cupsFilePuts(fp, "msgid \"");
980
0
      cups_message_puts(fp, m->msg);
981
0
      cupsFilePuts(fp, "\"\nmsgstr \"");
982
0
      cups_message_puts(fp, m->str);
983
0
      cupsFilePuts(fp, "\"\n");
984
0
    }
985
0
  }
986
987
0
  return (cupsFileClose(fp));
988
0
}
989
990
991
/*
992
 * 'cups_cache_lookup()' - Lookup a language in the cache...
993
 */
994
995
static cups_lang_t *      /* O - Language data or NULL */
996
cups_cache_lookup(
997
    const char      *name,    /* I - Name of locale */
998
    cups_encoding_t encoding)   /* I - Encoding of locale */
999
4.57k
{
1000
4.57k
  cups_lang_t *lang;      /* Current language */
1001
1002
1003
4.57k
  DEBUG_printf("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name, encoding, encoding == CUPS_AUTO_ENCODING ? "auto" : lang_encodings[encoding]);
1004
1005
 /*
1006
  * Loop through the cache and return a match if found...
1007
  */
1008
1009
4.57k
  for (lang = lang_cache; lang != NULL; lang = lang->next)
1010
4.57k
  {
1011
4.57k
    DEBUG_printf("9cups_cache_lookup: lang=%p, language=\"%s\", encoding=%d(%s)", (void *)lang, lang->language, lang->encoding, lang_encodings[lang->encoding]);
1012
1013
4.57k
    if (!strcmp(lang->language, name) &&
1014
4.57k
        (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1015
4.57k
    {
1016
4.57k
      lang->used ++;
1017
1018
4.57k
      DEBUG_puts("8cups_cache_lookup: returning match!");
1019
1020
4.57k
      return (lang);
1021
4.57k
    }
1022
4.57k
  }
1023
1024
1
  DEBUG_puts("8cups_cache_lookup: returning NULL!");
1025
1026
1
  return (NULL);
1027
4.57k
}
1028
1029
1030
/*
1031
 * 'cups_message_compare()' - Compare two messages.
1032
 */
1033
1034
static int        /* O - Result of comparison */
1035
cups_message_compare(
1036
    _cups_message_t *m1,    /* I - First message */
1037
    _cups_message_t *m2)    /* I - Second message */
1038
0
{
1039
0
  return (strcmp(m1->msg, m2->msg));
1040
0
}
1041
1042
1043
/*
1044
 * 'cups_message_free()' - Free a message.
1045
 */
1046
1047
static void
1048
cups_message_free(_cups_message_t *m) /* I - Message */
1049
0
{
1050
0
  if (m->msg)
1051
0
    free(m->msg);
1052
1053
0
  if (m->str)
1054
0
    free(m->str);
1055
1056
0
  free(m);
1057
0
}
1058
1059
1060
/*
1061
 * 'cups_message_load()' - Load the message catalog for a language.
1062
 */
1063
1064
static void
1065
cups_message_load(cups_lang_t *lang)  /* I - Language */
1066
1
{
1067
1
  char      filename[1024]; /* Filename for language locale file */
1068
1
  _cups_globals_t *cg = _cupsGlobals();
1069
            /* Pointer to library globals */
1070
1071
1072
1
  snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
1073
1
     lang->language, lang->language);
1074
1075
1
  if (strchr(lang->language, '_') && access(filename, 0))
1076
1
  {
1077
   /*
1078
    * Country localization not available, look for generic localization...
1079
    */
1080
1081
1
    snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
1082
1
             lang->language, lang->language);
1083
1084
1
    if (access(filename, 0))
1085
1
    {
1086
     /*
1087
      * No generic localization, so use POSIX...
1088
      */
1089
1090
1
      DEBUG_printf("4cups_message_load: access(\"%s\", 0): %s", filename, strerror(errno));
1091
1092
1
      snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
1093
1
    }
1094
1
  }
1095
1096
 /*
1097
  * Read the strings from the file...
1098
  */
1099
1100
1
  lang->strings = _cupsMessageLoad(filename, _CUPS_MESSAGE_UNQUOTE);
1101
1
}
1102
1103
1104
/*
1105
 * 'cups_message_puts()' - Write a message string with quoting.
1106
 */
1107
1108
static void
1109
cups_message_puts(cups_file_t *fp,  /* I - File to write to */
1110
                  const char  *s) /* I - String to write */
1111
0
{
1112
0
  const char  *start,     /* Start of substring */
1113
0
    *ptr;     /* Pointer into string */
1114
1115
1116
0
  for (start = s, ptr = s; *ptr; ptr ++)
1117
0
  {
1118
0
    if (strchr("\\\"\n\t", *ptr))
1119
0
    {
1120
0
      if (ptr > start)
1121
0
      {
1122
0
  cupsFileWrite(fp, start, (size_t)(ptr - start));
1123
0
  start = ptr + 1;
1124
0
      }
1125
1126
0
      if (*ptr == '\\')
1127
0
        cupsFileWrite(fp, "\\\\", 2);
1128
0
      else if (*ptr == '\"')
1129
0
        cupsFileWrite(fp, "\\\"", 2);
1130
0
      else if (*ptr == '\n')
1131
0
        cupsFileWrite(fp, "\\n", 2);
1132
0
      else /* if (*ptr == '\t') */
1133
0
        cupsFileWrite(fp, "\\t", 2);
1134
0
    }
1135
0
  }
1136
1137
0
  if (ptr > start)
1138
0
    cupsFileWrite(fp, start, (size_t)(ptr - start));
1139
0
}
1140
1141
1142
/*
1143
 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
1144
 */
1145
1146
static int        /* O - 1 on success, 0 on failure */
1147
cups_read_strings(cups_file_t  *fp, /* I - .strings file */
1148
                  int          flags, /* I - CUPS_MESSAGE_xxx flags */
1149
      cups_array_t *a)  /* I - Message catalog array */
1150
0
{
1151
0
  char      buffer[8192], /* Line buffer */
1152
0
      *bufptr,  /* Pointer into buffer */
1153
0
      *msg,   /* Pointer to start of message */
1154
0
      *str;   /* Pointer to start of translation string */
1155
0
  _cups_message_t *m;   /* New message */
1156
1157
1158
0
  while (cupsFileGets(fp, buffer, sizeof(buffer)))
1159
0
  {
1160
   /*
1161
    * Skip any line (comments, blanks, etc.) that isn't:
1162
    *
1163
    *   "message" = "translation";
1164
    */
1165
1166
0
    for (bufptr = buffer; *bufptr && isspace(*bufptr & 255); bufptr ++);
1167
1168
0
    if (*bufptr != '\"')
1169
0
      continue;
1170
1171
   /*
1172
    * Find the end of the message...
1173
    */
1174
1175
0
    bufptr ++;
1176
0
    for (msg = bufptr; *bufptr && *bufptr != '\"'; bufptr ++)
1177
0
      if (*bufptr == '\\' && bufptr[1])
1178
0
        bufptr ++;
1179
1180
0
    if (!*bufptr)
1181
0
      continue;
1182
1183
0
    *bufptr++ = '\0';
1184
1185
0
    if (flags & _CUPS_MESSAGE_UNQUOTE)
1186
0
      cups_unquote(msg, msg);
1187
1188
   /*
1189
    * Find the start of the translation...
1190
    */
1191
1192
0
    while (*bufptr && isspace(*bufptr & 255))
1193
0
      bufptr ++;
1194
1195
0
    if (*bufptr != '=')
1196
0
      continue;
1197
1198
0
    bufptr ++;
1199
0
    while (*bufptr && isspace(*bufptr & 255))
1200
0
      bufptr ++;
1201
1202
0
    if (*bufptr != '\"')
1203
0
      continue;
1204
1205
   /*
1206
    * Find the end of the translation...
1207
    */
1208
1209
0
    bufptr ++;
1210
0
    for (str = bufptr; *bufptr && *bufptr != '\"'; bufptr ++)
1211
0
      if (*bufptr == '\\' && bufptr[1])
1212
0
        bufptr ++;
1213
1214
0
    if (!*bufptr)
1215
0
      continue;
1216
1217
0
    *bufptr++ = '\0';
1218
1219
0
    if (flags & _CUPS_MESSAGE_UNQUOTE)
1220
0
      cups_unquote(str, str);
1221
1222
   /*
1223
    * If we get this far we have a valid pair of strings, add them...
1224
    */
1225
1226
0
    if ((m = malloc(sizeof(_cups_message_t))) == NULL)
1227
0
      break;
1228
1229
0
    m->msg = strdup(msg);
1230
0
    m->str = strdup(str);
1231
1232
0
    if (m->msg && m->str)
1233
0
    {
1234
0
      cupsArrayAdd(a, m);
1235
0
    }
1236
0
    else
1237
0
    {
1238
0
      if (m->msg)
1239
0
  free(m->msg);
1240
1241
0
      if (m->str)
1242
0
  free(m->str);
1243
1244
0
      free(m);
1245
0
      break;
1246
0
    }
1247
1248
0
    return (1);
1249
0
  }
1250
1251
 /*
1252
  * No more strings...
1253
  */
1254
1255
0
  return (0);
1256
0
}
1257
1258
1259
/*
1260
 * 'cups_unquote()' - Unquote characters in strings...
1261
 */
1262
1263
static void
1264
cups_unquote(char       *d,   /* O - Unquoted string */
1265
             const char *s)   /* I - Original string */
1266
0
{
1267
0
  while (*s)
1268
0
  {
1269
0
    if (*s == '\\')
1270
0
    {
1271
0
      s ++;
1272
0
      if (isdigit(*s))
1273
0
      {
1274
0
  *d = 0;
1275
1276
0
  while (isdigit(*s))
1277
0
  {
1278
0
    *d = *d * 8 + *s - '0';
1279
0
    s ++;
1280
0
  }
1281
1282
0
  d ++;
1283
0
      }
1284
0
      else
1285
0
      {
1286
0
  if (*s == 'n')
1287
0
    *d ++ = '\n';
1288
0
  else if (*s == 'r')
1289
0
    *d ++ = '\r';
1290
0
  else if (*s == 't')
1291
0
    *d ++ = '\t';
1292
0
  else
1293
0
    *d++ = *s;
1294
1295
0
  s ++;
1296
0
      }
1297
0
    }
1298
0
    else
1299
0
      *d++ = *s++;
1300
0
  }
1301
1302
0
  *d = '\0';
1303
0
}