Coverage Report

Created: 2025-10-28 06:09

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
0
{
148
0
  return (cupsLangGet(NULL));
149
0
}
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
0
{
231
0
  int     i;    /* Looping var */
232
0
  char      locale[255];  /* Copy of locale name */
233
0
  char      langname[16], /* Requested language name */
234
0
      country[16],  /* Country code */
235
0
      charset[16],  /* Character set */
236
0
      *csptr,   /* Pointer to CODESET string */
237
0
      *ptr,   /* Pointer into language/charset */
238
0
      real[48]; /* Real language name */
239
0
  cups_encoding_t encoding; /* Encoding to use */
240
0
  cups_lang_t   *lang;    /* Current language... */
241
0
  static const char * const locale_encodings[] =
242
0
    {     /* Locale charset names */
243
0
      "ASCII",  "ISO88591", "ISO88592", "ISO88593",
244
0
      "ISO88594", "ISO88595", "ISO88596", "ISO88597",
245
0
      "ISO88598", "ISO88599", "ISO885910",  "UTF8",
246
0
      "ISO885913",  "ISO885914",  "ISO885915",  "CP874",
247
0
      "CP1250", "CP1251", "CP1252", "CP1253",
248
0
      "CP1254", "CP1255", "CP1256", "CP1257",
249
0
      "CP1258", "KOI8R",  "KOI8U",  "ISO885911",
250
0
      "ISO885916",  "MACROMAN", "",   "",
251
252
0
      "",   "",   "",   "",
253
0
      "",   "",   "",   "",
254
0
      "",   "",   "",   "",
255
0
      "",   "",   "",   "",
256
0
      "",   "",   "",   "",
257
0
      "",   "",   "",   "",
258
0
      "",   "",   "",   "",
259
0
      "",   "",   "",   "",
260
261
0
      "CP932",  "CP936",  "CP949",  "CP950",
262
0
      "CP1361", "GB18030",  "",   "",
263
0
      "",   "",   "",   "",
264
0
      "",   "",   "",   "",
265
0
      "",   "",   "",   "",
266
0
      "",   "",   "",   "",
267
0
      "",   "",   "",   "",
268
0
      "",   "",   "",   "",
269
270
0
      "",   "",   "",   "",
271
0
      "",   "",   "",   "",
272
0
      "",   "",   "",   "",
273
0
      "",   "",   "",   "",
274
0
      "",   "",   "",   "",
275
0
      "",   "",   "",   "",
276
0
      "",   "",   "",   "",
277
0
      "",   "",   "",   "",
278
279
0
      "EUCCN",  "EUCJP",  "EUCKR",  "EUCTW",
280
0
      "SHIFT_JISX0213"
281
0
    };
282
283
284
0
  DEBUG_printf("2cupsLangGet(language=\"%s\")", language);
285
286
 /*
287
  * Set the charset to "unknown"...
288
  */
289
290
0
  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
0
  if (!language)
299
0
  {
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
0
#ifdef LC_MESSAGES
306
0
    ptr = setlocale(LC_MESSAGES, NULL);
307
#else
308
    ptr = setlocale(LC_ALL, NULL);
309
#endif /* LC_MESSAGES */
310
311
0
    DEBUG_printf("4cupsLangGet: current locale is \"%s\"", ptr);
312
313
0
    if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
314
0
    {
315
     /*
316
      * Get the character set from the LC_CTYPE locale setting...
317
      */
318
319
0
      if ((ptr = getenv("LC_CTYPE")) == NULL)
320
0
        if ((ptr = getenv("LC_ALL")) == NULL)
321
0
    if ((ptr = getenv("LANG")) == NULL)
322
0
      ptr = "en_US";
323
324
0
      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
0
      if ((ptr = getenv("LC_MESSAGES")) == NULL)
342
0
        if ((ptr = getenv("LC_ALL")) == NULL)
343
0
    if ((ptr = getenv("LANG")) == NULL)
344
0
      ptr = "en_US";
345
0
    }
346
347
0
    if (ptr)
348
0
    {
349
0
      cupsCopyString(locale, ptr, sizeof(locale));
350
0
      language = locale;
351
352
     /*
353
      * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
354
      */
355
356
0
      if (!strncmp(locale, "nb", 2))
357
0
        locale[1] = 'o';
358
359
0
      DEBUG_printf("4cupsLangGet: new language value is \"%s\"", language);
360
0
    }
361
0
  }
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
0
  if (!language)
369
0
  {
370
   /*
371
    * Switch to the POSIX ("C") locale...
372
    */
373
374
0
    language = "C";
375
0
  }
376
377
0
#ifdef CODESET
378
 /*
379
  * On systems that support the nl_langinfo(CODESET) call, use
380
  * this value as the character set...
381
  */
382
383
0
  if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
384
0
  {
385
   /*
386
    * Copy all of the letters and numbers in the CODESET string...
387
    */
388
389
0
    for (ptr = charset; *csptr; csptr ++)
390
0
      if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
391
0
        *ptr++ = *csptr;
392
393
0
    *ptr = '\0';
394
395
0
    DEBUG_printf("4cupsLangGet: charset set to \"%s\" via nl_langinfo(CODESET)...", charset);
396
0
  }
397
0
#endif /* CODESET */
398
399
 /*
400
  * If we don't have a character set by now, default to UTF-8...
401
  */
402
403
0
  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
0
  country[0] = '\0';
416
417
0
  if (language == NULL || !language[0] || !strcmp(language, "POSIX"))
418
0
  {
419
0
    cupsCopyString(langname, "C", sizeof(langname));
420
0
  }
421
0
  else
422
0
  {
423
   /*
424
    * Copy the parts of the locale string over safely...
425
    */
426
427
0
    for (ptr = langname; *language; language ++)
428
0
    {
429
0
      if (*language == '_' || *language == '-' || *language == '.')
430
0
  break;
431
0
      else if (ptr < (langname + sizeof(langname) - 1))
432
0
        *ptr++ = (char)tolower(*language & 255);
433
0
    }
434
435
0
    *ptr = '\0';
436
437
0
    if (*language == '_' || *language == '-')
438
0
    {
439
     /*
440
      * Copy the country code...
441
      */
442
443
0
      for (language ++, ptr = country; *language; language ++)
444
0
      {
445
0
  if (*language == '.')
446
0
    break;
447
0
  else if (ptr < (country + sizeof(country) - 1))
448
0
          *ptr++ = (char)toupper(*language & 255);
449
0
      }
450
451
0
      *ptr = '\0';
452
453
     /*
454
      * Map Chinese region codes to legacy country codes.
455
      */
456
457
0
      if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
458
0
        cupsCopyString(country, "CN", sizeof(country));
459
0
      if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
460
0
        cupsCopyString(country, "TW", sizeof(country));
461
0
    }
462
463
0
    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
0
    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
0
  }
489
490
0
  DEBUG_printf("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"", langname, country, charset);
491
492
 /*
493
  * Figure out the desired encoding...
494
  */
495
496
0
  encoding = CUPS_AUTO_ENCODING;
497
498
0
  if (charset[0])
499
0
  {
500
0
    for (i = 0; i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0])); i ++)
501
0
    {
502
0
      if (!_cups_strcasecmp(charset, locale_encodings[i]))
503
0
      {
504
0
  encoding = (cups_encoding_t)i;
505
0
  break;
506
0
      }
507
0
    }
508
509
0
    if (encoding == CUPS_AUTO_ENCODING)
510
0
    {
511
     /*
512
      * Map alternate names for various character sets...
513
      */
514
515
0
      if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
516
0
          !_cups_strcasecmp(charset, "sjis"))
517
0
  encoding = CUPS_WINDOWS_932;
518
0
      else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
519
0
  encoding = CUPS_WINDOWS_936;
520
0
      else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
521
0
  encoding = CUPS_WINDOWS_949;
522
0
      else if (!_cups_strcasecmp(charset, "big5"))
523
0
  encoding = CUPS_WINDOWS_950;
524
0
    }
525
0
  }
526
527
0
  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
0
  if (country[0])
534
0
    snprintf(real, sizeof(real), "%s_%s", langname, country);
535
0
  else
536
0
    cupsCopyString(real, langname, sizeof(real));
537
538
0
  cupsMutexLock(&lang_mutex);
539
540
0
  if ((lang = cups_cache_lookup(real, encoding)) != NULL)
541
0
  {
542
0
    cupsMutexUnlock(&lang_mutex);
543
544
0
    DEBUG_printf("3cupsLangGet: Using cached copy of \"%s\"...", real);
545
546
0
    return (lang);
547
0
  }
548
549
 /*
550
  * See if there is a free language available; if so, use that
551
  * record...
552
  */
553
554
0
  for (lang = lang_cache; lang != NULL; lang = lang->next)
555
0
  {
556
0
    if (lang->used == 0)
557
0
      break;
558
0
  }
559
560
0
  if (lang == NULL)
561
0
  {
562
   /*
563
    * Allocate memory for the language and add it to the cache.
564
    */
565
566
0
    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
0
    lang->next = lang_cache;
574
0
    lang_cache = lang;
575
0
  }
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
0
  lang->used ++;
591
0
  cupsCopyString(lang->language, real, sizeof(lang->language));
592
593
0
  if (encoding != CUPS_AUTO_ENCODING)
594
0
    lang->encoding = encoding;
595
0
  else
596
0
    lang->encoding = CUPS_UTF8;
597
598
 /*
599
  * Return...
600
  */
601
602
0
  cupsMutexUnlock(&lang_mutex);
603
604
0
  return (lang);
605
0
}
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
0
{
937
0
  cups_lang_t *lang;      /* Current language */
938
939
940
0
  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
0
  for (lang = lang_cache; lang != NULL; lang = lang->next)
947
0
  {
948
0
    DEBUG_printf("9cups_cache_lookup: lang=%p, language=\"%s\", encoding=%d(%s)", (void *)lang, lang->language, lang->encoding, lang_encodings[lang->encoding]);
949
950
0
    if (!strcmp(lang->language, name) &&
951
0
        (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
952
0
    {
953
0
      lang->used ++;
954
955
0
      DEBUG_puts("8cups_cache_lookup: returning match!");
956
957
0
      return (lang);
958
0
    }
959
0
  }
960
961
0
  DEBUG_puts("8cups_cache_lookup: returning NULL!");
962
963
0
  return (NULL);
964
0
}
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
}