Coverage Report

Created: 2026-03-19 06:49

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