Coverage Report

Created: 2025-05-12 06:19

/src/libcups/cups/language.c
Line
Count
Source (jump to first uncovered line)
1
//
2
// I18N/language support for CUPS.
3
//
4
// Copyright © 2022 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 <sys/stat.h>
14
#if _WIN32
15
#  include <io.h>
16
#else
17
#  include <unistd.h>
18
#endif // _WIN32
19
20
#include "strings/ca_strings.h"
21
#include "strings/cs_strings.h"
22
#include "strings/da_strings.h"
23
#include "strings/de_strings.h"
24
#include "strings/en_strings.h"
25
#include "strings/es_strings.h"
26
#include "strings/fr_strings.h"
27
#include "strings/it_strings.h"
28
#include "strings/ja_strings.h"
29
#include "strings/pt_BR_strings.h"
30
#include "strings/ru_strings.h"
31
#include "strings/zh_CN_strings.h"
32
33
34
//
35
// Types...
36
//
37
38
typedef struct _cups_message_s    // Message catalog entry
39
{
40
  char      *key,   // Key string
41
      *text;    // Localized text string
42
} _cups_message_t;
43
44
struct _cups_lang_s     // Language Cache
45
{
46
  cups_lang_t   *next;    // Next language in cache
47
  cups_rwlock_t   rwlock;   // Reader/writer lock
48
  char      language[16]; // Language/locale name
49
  size_t    num_messages, // Number of messages
50
      alloc_messages; // Allocated messages
51
  _cups_message_t *messages;  // Messages
52
};
53
54
55
//
56
// Local globals...
57
//
58
59
static cups_mutex_t lang_mutex = CUPS_MUTEX_INITIALIZER;
60
          // Mutex to control access to cache
61
static cups_lang_t  *lang_cache = NULL;
62
          // Language string cache
63
static char   *lang_directory = NULL;
64
          // Directory for strings files...
65
66
67
//
68
// Local functions...
69
//
70
71
static cups_lang_t  *cups_lang_new(const char *language);
72
static int    cups_message_compare(_cups_message_t *m1, _cups_message_t *m2);
73
74
75
//
76
// 'cupsLangAddStrings()' - Add strings for the specified language.
77
//
78
79
bool          // O - `true` on success, `false` on failure
80
cupsLangAddStrings(
81
    const char *language,   // I - Language name
82
    const char *strings)    // I - Contents of ".strings" file
83
0
{
84
0
  cups_lang_t *lang;      // Language data
85
86
87
0
  if ((lang = cupsLangFind(language)) != NULL)
88
0
    return (cupsLangLoadStrings(lang, NULL, strings));
89
0
  else
90
0
    return (false);
91
0
}
92
93
94
//
95
// 'cupsLangFind()' - Find a language localization.
96
//
97
98
cups_lang_t *       // O - Language data
99
cupsLangFind(const char *language)  // I - Language or locale name
100
1
{
101
1
  char    langname[16];   // Requested language name
102
1
  cups_lang_t *lang;      // Current language...
103
104
105
1
  DEBUG_printf("2cupsLangFind(language=\"%s\")", language);
106
107
1
  if (!language)
108
0
    return (cupsLangDefault());
109
110
1
  cupsMutexLock(&lang_mutex);
111
112
1
  cupsCopyString(langname, language, sizeof(langname));
113
1
  if (langname[2] == '-')
114
0
    langname[2] = '_';
115
116
1
  for (lang = lang_cache; lang; lang = lang->next)
117
0
  {
118
0
    if (!_cups_strcasecmp(lang->language, langname))
119
0
      break;
120
0
  }
121
122
1
  if (!lang)
123
1
  {
124
    // Create the language if it doesn't exist...
125
1
    lang = cups_lang_new(langname);
126
1
  }
127
128
1
  cupsMutexUnlock(&lang_mutex);
129
130
1
  return (lang);
131
1
}
132
133
134
//
135
// 'cupsLangFormatString()' - Create a localized formatted string.
136
//
137
138
const char *        // O - Formatted string
139
cupsLangFormatString(
140
    cups_lang_t *lang,      // I - Language data
141
    char        *buffer,    // I - Output buffer
142
    size_t      bufsize,    // I - Size of output buffer
143
    const char  *format,    // I - Printf-style format string
144
    ...)        // I - Additional arguments
145
0
{
146
0
  va_list ap;     // Pointer to additional arguments
147
148
149
0
  va_start(ap, format);
150
0
  vsnprintf(buffer, bufsize, cupsLangGetString(lang, format), ap);
151
0
  va_end(ap);
152
153
0
  return (buffer);
154
0
}
155
156
157
//
158
// 'cupsLangGetName()' - Get the language name.
159
//
160
161
const char *        // O - Language name
162
cupsLangGetName(cups_lang_t *lang)  // I - Language data
163
5.36k
{
164
5.36k
  return (lang ? lang->language : NULL);
165
5.36k
}
166
167
168
//
169
// 'cupsLangGetString()' - Get a localized message string.
170
//
171
// This function gets a localized UTF-8 message string for the specified
172
// language.  If the message is not localized, the original message pointer is
173
// returned.
174
//
175
176
const char *        // O - Localized message
177
cupsLangGetString(cups_lang_t *lang,  // I - Language
178
                  const char  *message) // I - Message
179
528
{
180
528
  _cups_message_t key,    // Search key
181
528
      *match;   // Matching message
182
528
  const char    *text;    // Localized message text
183
184
185
528
  DEBUG_printf("cupsLangGetString(lang=%p(%s), message=\"%s\")", (void *)lang, lang ? lang->language : "null", message);
186
187
  // Range check input...
188
528
  if (!lang || !lang->num_messages || !message || !*message)
189
0
    return (message);
190
191
528
  cupsRWLockRead(&lang->rwlock);
192
193
528
  key.key = (char *)message;
194
528
  match   = bsearch(&key, lang->messages, lang->num_messages, sizeof(_cups_message_t), (int (*)(const void *, const void *))cups_message_compare);
195
528
  text    = match ? match->text : message;
196
197
528
  cupsRWUnlock(&lang->rwlock);
198
199
528
  return (text);
200
528
}
201
202
203
//
204
// 'cupsLangLoadStrings()' - Load a message catalog for a language.
205
//
206
207
bool        // O - `true` on success, `false` on failure
208
cupsLangLoadStrings(
209
    cups_lang_t *lang,      // I - Language data
210
    const char  *filename,    // I - Filename of `NULL` for none
211
    const char  *strings)   // I - Strings or `NULL` for none
212
1
{
213
1
  bool    ret = true;   // Return value
214
1
  int   linenum;    // Current line number in data
215
1
  const char  *data,      // Pointer to strings data
216
1
    *dataptr;   // Pointer into string data
217
1
  char    key[1024],    // Key string
218
1
    text[1024],   // Localized text string
219
1
    *ptr;     // Pointer into strings
220
1
  _cups_message_t *m,     // Pointer to message
221
1
    mkey;     // Search key
222
1
  size_t  num_messages;   // New number of messages
223
224
225
1
  if (filename)
226
0
  {
227
    // Load the strings file...
228
0
    int   fd;     // File descriptor
229
0
    struct stat fileinfo;   // File information
230
0
    ssize_t bytes;      // Bytes read
231
232
0
    if ((fd = open(filename, O_RDONLY)) < 0)
233
0
    {
234
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
235
0
      return (false);
236
0
    }
237
238
0
    if (fstat(fd, &fileinfo))
239
0
    {
240
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
241
0
      close(fd);
242
0
      return (false);
243
0
    }
244
245
0
    if ((ptr = malloc((size_t)(fileinfo.st_size + 1))) == NULL)
246
0
    {
247
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
248
0
      close(fd);
249
0
      return (false);
250
0
    }
251
252
0
    if ((bytes = read(fd, ptr, (size_t)fileinfo.st_size)) < 0)
253
0
    {
254
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
255
0
      close(fd);
256
0
      free(ptr);
257
0
      return (false);
258
0
    }
259
260
0
    close(fd);
261
262
0
    ptr[bytes] = '\0';
263
0
    data       = ptr;
264
0
  }
265
1
  else
266
1
  {
267
    // Use in-memory strings data...
268
1
    data = (const char *)strings;
269
1
  }
270
271
1
  if (!data)
272
0
  {
273
0
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
274
0
    return (false);
275
0
  }
276
277
  // Scan the in-memory strings data and add key/text pairs...
278
  //
279
  // Format of strings files is:
280
  //
281
  // "key" = "text";
282
1
  cupsRWLockWrite(&lang->rwlock);
283
284
1
  num_messages = lang->num_messages;
285
1
  mkey.key     = key;
286
287
2.46k
  for (dataptr = data, linenum = 1; *dataptr; dataptr ++)
288
2.46k
  {
289
    // Skip leading whitespace...
290
2.46k
    while (*dataptr && isspace(*dataptr & 255))
291
0
    {
292
0
      if (*dataptr == '\n')
293
0
        linenum ++;
294
295
0
      dataptr ++;
296
0
    }
297
298
2.46k
    if (!*dataptr)
299
0
    {
300
      // End of string...
301
0
      break;
302
0
    }
303
2.46k
    else if (*dataptr == '/' && dataptr[1] == '*')
304
0
    {
305
      // Start of C-style comment...
306
0
      for (dataptr += 2; *dataptr; dataptr ++)
307
0
      {
308
0
        if (*dataptr == '*' && dataptr[1] == '/')
309
0
  {
310
0
    dataptr += 2;
311
0
    break;
312
0
  }
313
0
  else if (*dataptr == '\n')
314
0
    linenum ++;
315
0
      }
316
317
0
      if (!*dataptr)
318
0
        break;
319
0
    }
320
2.46k
    else if (*dataptr != '\"')
321
0
    {
322
      // Something else we don't recognize...
323
0
      snprintf(text, sizeof(text), "Syntax error on line %d of '%s'.", linenum, filename ? filename : "in-memory");
324
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
325
0
      ret = false;
326
0
      break;
327
0
    }
328
329
    // Parse key string...
330
2.46k
    dataptr ++;
331
88.7k
    for (ptr = key; *dataptr && *dataptr != '\"'; dataptr ++)
332
86.3k
    {
333
86.3k
      if (*dataptr == '\\' && dataptr[1])
334
143
      {
335
        // Escaped character...
336
143
        int ch;     // Character
337
338
143
        dataptr ++;
339
143
        if (*dataptr == '\\' || *dataptr == '\'' || *dataptr == '\"')
340
133
        {
341
133
          ch = *dataptr;
342
133
  }
343
10
  else if (*dataptr == 'n')
344
10
  {
345
10
    ch = '\n';
346
10
  }
347
0
  else if (*dataptr == 'r')
348
0
  {
349
0
    ch = '\r';
350
0
  }
351
0
  else if (*dataptr == 't')
352
0
  {
353
0
    ch = '\t';
354
0
  }
355
0
  else if (*dataptr >= '0' && *dataptr <= '3' && dataptr[1] >= '0' && dataptr[1] <= '7' && dataptr[2] >= '0' && dataptr[2] <= '7')
356
0
  {
357
    // Octal escape
358
0
    ch = ((*dataptr - '0') << 6) | ((dataptr[1] - '0') << 3) | (dataptr[2] - '0');
359
0
    dataptr += 2;
360
0
  }
361
0
  else
362
0
  {
363
0
    snprintf(text, sizeof(text), "Invalid escape in key string on line %d of '%s'.", linenum, filename ? filename : "in-memory");
364
0
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
365
0
    ret = false;
366
0
    break;
367
0
  }
368
369
143
        if (ptr < (key + sizeof(key) - 1))
370
143
          *ptr++ = (char)ch;
371
143
      }
372
86.1k
      else if (ptr < (key + sizeof(key) - 1))
373
86.1k
      {
374
86.1k
        *ptr++ = *dataptr;
375
86.1k
      }
376
86.3k
    }
377
378
2.46k
    if (!*dataptr)
379
0
    {
380
0
      snprintf(text, sizeof(text), "Unterminated key string on line %d of '%s'.", linenum, filename ? filename : "in-memory");
381
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
382
0
      ret = false;
383
0
      break;
384
0
    }
385
386
2.46k
    dataptr ++;
387
2.46k
    *ptr = '\0';
388
389
    // Parse separator...
390
4.93k
    while (*dataptr && isspace(*dataptr & 255))
391
2.46k
    {
392
2.46k
      if (*dataptr == '\n')
393
0
        linenum ++;
394
395
2.46k
      dataptr ++;
396
2.46k
    }
397
398
2.46k
    if (*dataptr != '=')
399
0
    {
400
0
      snprintf(text, sizeof(text), "Missing separator on line %d of '%s'.", linenum, filename ? filename : "in-memory");
401
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
402
0
      ret = false;
403
0
      break;
404
0
    }
405
406
2.46k
    dataptr ++;
407
4.93k
    while (*dataptr && isspace(*dataptr & 255))
408
2.46k
    {
409
2.46k
      if (*dataptr == '\n')
410
0
        linenum ++;
411
412
2.46k
      dataptr ++;
413
2.46k
    }
414
415
2.46k
    if (*dataptr != '\"')
416
0
    {
417
0
      snprintf(text, sizeof(text), "Missing text string on line %d of '%s'.", linenum, filename ? filename : "in-memory");
418
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
419
0
      ret = false;
420
0
      break;
421
0
    }
422
423
    // Parse text string...
424
2.46k
    dataptr ++;
425
56.5k
    for (ptr = text; *dataptr && *dataptr != '\"'; dataptr ++)
426
54.1k
    {
427
54.1k
      if (*dataptr == '\\')
428
145
      {
429
        // Escaped character...
430
145
        int ch;     // Character
431
432
145
        dataptr ++;
433
145
        if (*dataptr == '\\' || *dataptr == '\'' || *dataptr == '\"')
434
135
        {
435
135
          ch = *dataptr;
436
135
  }
437
10
  else if (*dataptr == 'n')
438
10
  {
439
10
    ch = '\n';
440
10
  }
441
0
  else if (*dataptr == 'r')
442
0
  {
443
0
    ch = '\r';
444
0
  }
445
0
  else if (*dataptr == 't')
446
0
  {
447
0
    ch = '\t';
448
0
  }
449
0
  else if (*dataptr >= '0' && *dataptr <= '3' && dataptr[1] >= '0' && dataptr[1] <= '7' && dataptr[2] >= '0' && dataptr[2] <= '7')
450
0
  {
451
    // Octal escape
452
0
    ch = ((*dataptr - '0') << 6) | ((dataptr[1] - '0') << 3) | (dataptr[2] - '0');
453
0
    dataptr += 2;
454
0
  }
455
0
  else
456
0
  {
457
0
    snprintf(text, sizeof(text), "Invalid escape in text string on line %d of '%s'.", linenum, filename ? filename : "in-memory");
458
0
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
459
0
    ret = false;
460
0
    break;
461
0
  }
462
463
145
        if (ptr < (text + sizeof(text) - 1))
464
145
          *ptr++ = (char)ch;
465
145
      }
466
53.9k
      else if (ptr < (text + sizeof(text) - 1))
467
53.9k
      {
468
53.9k
        *ptr++ = *dataptr;
469
53.9k
      }
470
54.1k
    }
471
472
2.46k
    if (!*dataptr)
473
0
    {
474
0
      snprintf(text, sizeof(text), "Unterminated text string on line %d of '%s'.", linenum, filename ? filename : "in-memory");
475
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
476
0
      ret = false;
477
0
      break;
478
0
    }
479
480
2.46k
    dataptr ++;
481
2.46k
    *ptr = '\0';
482
483
    // Look for terminator, then add the pair...
484
2.46k
    if (*dataptr != ';')
485
0
    {
486
0
      snprintf(text, sizeof(text), "Missing terminator on line %d of '%s'.", linenum, filename ? filename : "in-memory");
487
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0);
488
0
      ret = false;
489
0
      break;
490
0
    }
491
492
2.46k
    dataptr ++;
493
494
    // Add the message if it doesn't already exist...
495
2.46k
    if (lang->num_messages > 0 && bsearch(&mkey, lang->messages, lang->num_messages, sizeof(_cups_message_t), (int (*)(const void *, const void *))cups_message_compare))
496
0
      continue;
497
498
2.46k
    if (num_messages >= lang->alloc_messages)
499
3
    {
500
3
      if ((m = realloc(lang->messages, (lang->alloc_messages + 1024) * sizeof(_cups_message_t))) == NULL)
501
0
      {
502
0
        _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
503
0
        ret = false;
504
0
        break;
505
0
      }
506
507
3
      lang->messages       = m;
508
3
      lang->alloc_messages += 1024;
509
3
    }
510
511
2.46k
    m = lang->messages + num_messages;
512
513
2.46k
    if ((m->key = _cupsStrAlloc(key)) == NULL || (m->text = _cupsStrAlloc(text)) == NULL)
514
0
    {
515
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
516
0
      _cupsStrFree(m->key);
517
0
      _cupsStrFree(m->text);
518
0
      ret = false;
519
0
      break;
520
0
    }
521
522
2.46k
    num_messages ++;
523
2.46k
  }
524
525
  // Re-sort messages as needed...
526
1
  if (num_messages > lang->num_messages)
527
1
  {
528
1
    lang->num_messages = num_messages;
529
1
    qsort(lang->messages, lang->num_messages, sizeof(_cups_message_t), (int (*)(const void *, const void *))cups_message_compare);
530
1
  }
531
532
1
  cupsRWUnlock(&lang->rwlock);
533
534
  // Free temporary storage and return...
535
1
  if (data != strings)
536
0
    free((void *)data);
537
538
1
  return (ret);
539
1
}
540
541
542
//
543
// 'cupsLangSetDirectory()' - Set a directory containing localizations.
544
//
545
546
void
547
cupsLangSetDirectory(const char *d) // I - Directory name
548
0
{
549
0
  if (d)
550
0
  {
551
0
    cupsMutexLock(&lang_mutex);
552
553
0
    free(lang_directory);
554
0
    lang_directory = strdup(d);
555
556
0
    cupsMutexUnlock(&lang_mutex);
557
0
  }
558
0
}
559
560
561
//
562
// 'cups_lang_new()' - Create a new language.
563
//
564
565
static cups_lang_t *      // O - Language data
566
cups_lang_new(const char *language) // I - Language name
567
1
{
568
1
  cups_lang_t *lang;      // Language data
569
1
  char    filename[1024];   // Strings file...
570
1
  bool    status;     // Load status
571
572
573
  // Create an empty language data structure...
574
1
  if ((lang = calloc(1, sizeof(cups_lang_t))) == NULL)
575
0
    return (NULL);
576
577
1
  cupsRWInit(&lang->rwlock);
578
1
  cupsCopyString(lang->language, language, sizeof(lang->language));
579
580
  // Add strings...
581
1
  if (!_cups_strncasecmp(language, "ca", 2))
582
0
    status = cupsLangLoadStrings(lang, NULL, ca_strings);
583
1
  else if (!_cups_strncasecmp(language, "cs", 2))
584
0
    status = cupsLangLoadStrings(lang, NULL, cs_strings);
585
1
  else if (!_cups_strncasecmp(language, "da", 2))
586
0
    status = cupsLangLoadStrings(lang, NULL, da_strings);
587
1
  else if (!_cups_strncasecmp(language, "de", 2))
588
0
    status = cupsLangLoadStrings(lang, NULL, de_strings);
589
1
  else if (!_cups_strncasecmp(language, "es", 2))
590
0
    status = cupsLangLoadStrings(lang, NULL, es_strings);
591
1
  else if (!_cups_strncasecmp(language, "fr", 2))
592
0
    status = cupsLangLoadStrings(lang, NULL, fr_strings);
593
1
  else if (!_cups_strncasecmp(language, "it", 2))
594
0
    status = cupsLangLoadStrings(lang, NULL, it_strings);
595
1
  else if (!_cups_strncasecmp(language, "ja", 2))
596
0
    status = cupsLangLoadStrings(lang, NULL, ja_strings);
597
1
  else if (!_cups_strncasecmp(language, "pt", 2))
598
0
    status = cupsLangLoadStrings(lang, NULL, pt_BR_strings);
599
1
  else if (!_cups_strncasecmp(language, "ru", 2))
600
0
    status = cupsLangLoadStrings(lang, NULL, ru_strings);
601
1
  else if (!_cups_strncasecmp(language, "zh", 2))
602
0
    status = cupsLangLoadStrings(lang, NULL, zh_CN_strings);
603
1
  else
604
1
    status = cupsLangLoadStrings(lang, NULL, en_strings);
605
606
1
  if (status && lang_directory)
607
0
  {
608
0
    snprintf(filename, sizeof(filename), "%s/%s.strings", lang_directory, language);
609
0
    if (access(filename, 0) && language[2])
610
0
    {
611
0
      char  baselang[3];    // Base language name
612
613
0
      cupsCopyString(baselang, language, sizeof(baselang));
614
0
      snprintf(filename, sizeof(filename), "%s/%s.strings", lang_directory, baselang);
615
0
    }
616
617
0
    if (!access(filename, 0))
618
0
      status = cupsLangLoadStrings(lang, filename, NULL);
619
0
  }
620
621
1
  if (!status)
622
0
  {
623
    // Free memory if the load failed...
624
0
    size_t  i;      // Looping var
625
626
0
    for (i = 0; i < lang->num_messages; i ++)
627
0
    {
628
0
      _cupsStrFree(lang->messages[i].key);
629
0
      _cupsStrFree(lang->messages[i].text);
630
0
    }
631
632
0
    free(lang->messages);
633
0
    free(lang);
634
635
0
    return (NULL);
636
0
  }
637
638
  // Add this language to the front of the list...
639
1
  lang->next = lang_cache;
640
1
  lang_cache = lang;
641
642
1
  return (lang);
643
1
}
644
645
646
//
647
// 'cups_message_compare()' - Compare two messages.
648
//
649
650
static int        // O - Result of comparison
651
cups_message_compare(
652
    _cups_message_t *m1,    // I - First message
653
    _cups_message_t *m2)    // I - Second message
654
18.8k
{
655
18.8k
  return (strcmp(m1->key, m2->key));
656
18.8k
}