Coverage Report

Created: 2024-02-11 06:23

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