Coverage Report

Created: 2025-12-05 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libcups/cups/langprintf.c
Line
Count
Source
1
//
2
// Localized printf/puts functions for CUPS.
3
//
4
// Copyright © 2022-2024 by OpenPrinting.
5
// Copyright © 2007-2014 by Apple Inc.
6
// Copyright © 2002-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
#ifdef HAVE_LANGINFO_H
14
#  include <langinfo.h>
15
#endif // HAVE_LANGINFO_H
16
#ifdef HAVE_COREFOUNDATION_H
17
#  include <CoreFoundation/CoreFoundation.h>
18
#endif // HAVE_COREFOUNDATION_H
19
20
21
//
22
// Local functions...
23
//
24
25
static const char *cups_lang_default(void);
26
27
28
//
29
// 'cupsLangDefault()' - Return the default language.
30
//
31
32
cups_lang_t *       // O - Language data
33
cupsLangDefault(void)
34
5.23k
{
35
5.23k
  _cups_globals_t *cg = _cupsGlobals(); // Global data
36
37
38
  // Load the default language as needed...
39
5.23k
  if (!cg->lang_default)
40
1
    cg->lang_default = cupsLangFind(cups_lang_default());
41
42
5.23k
  return (cg->lang_default);
43
5.23k
}
44
45
46
//
47
// 'cupsLangGetEncoding()' - Get the default encoding for the current locale.
48
//
49
50
cups_encoding_t       // O - Character encoding
51
cupsLangGetEncoding(void)
52
0
{
53
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
54
55
56
0
  if (!cg->lang_default)
57
0
    cg->lang_default = cupsLangDefault();
58
59
0
  return (cg->lang_encoding);
60
0
}
61
62
63
//
64
// 'cupsLangPrintf()' - Print a formatted message string to a file.
65
//
66
67
ssize_t         // O - Number of bytes written
68
cupsLangPrintf(FILE       *fp,    // I - File to write to
69
         const char *format,  // I - Message string to use
70
         ...)     // I - Additional arguments as needed
71
0
{
72
0
  ssize_t bytes;      // Number of bytes formatted
73
0
  char    buffer[2048],   // Message buffer
74
0
    output[8192];   // Output buffer
75
0
  va_list   ap;     // Pointer to additional arguments
76
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
77
78
79
  // Range check...
80
0
  if (!fp || !format)
81
0
    return (-1);
82
83
0
  if (!cg->lang_default)
84
0
    cg->lang_default = cupsLangDefault();
85
86
  // Format the string...
87
0
  va_start(ap, format);
88
0
  vsnprintf(buffer, sizeof(buffer) - 1, cupsLangGetString(cg->lang_default, format), ap);
89
0
  va_end(ap);
90
91
0
  cupsConcatString(buffer, "\n", sizeof(buffer));
92
93
  // Transcode to the destination charset...
94
0
  bytes = cupsUTF8ToCharset(output, buffer, sizeof(output), cg->lang_encoding);
95
96
  // Write the string and return the number of bytes written...
97
0
  if (bytes > 0)
98
0
    return ((ssize_t)fwrite(output, 1, (size_t)bytes, fp));
99
0
  else
100
0
    return (bytes);
101
0
}
102
103
104
//
105
// 'cupsLangPuts()' - Print a static message string to a file.
106
//
107
108
ssize_t         // O - Number of bytes written
109
cupsLangPuts(FILE       *fp,    // I - File to write to
110
             const char *message) // I - Message string to use
111
0
{
112
0
  ssize_t bytes;      // Number of bytes formatted
113
0
  size_t  length = 0;   // Total length
114
0
  char    output[8192];   // Message buffer
115
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
116
117
118
  // Range check...
119
0
  if (!fp || !message)
120
0
    return (-1);
121
122
0
  if (!cg->lang_default)
123
0
    cg->lang_default = cupsLangDefault();
124
125
  // Transcode to the destination charset...
126
0
  if ((bytes = cupsUTF8ToCharset(output, cupsLangGetString(cg->lang_default, message), sizeof(output) - 4, cg->lang_encoding)) > 0)
127
0
    length += (size_t)bytes;
128
0
  if ((bytes = cupsUTF8ToCharset(output + length, "\n", sizeof(output) - length, cg->lang_encoding)) > 0)
129
0
    length += (size_t)bytes;
130
131
  // Write the string and return the number of bytes written...
132
0
  if (length > 0)
133
0
    return ((ssize_t)fwrite(output, 1, length, fp));
134
0
  else
135
0
    return (bytes);
136
0
}
137
138
139
//
140
// 'cupsLangSetLocale()' - Set the current locale and transcode the command-line.
141
//
142
143
void
144
cupsLangSetLocale(char *argv[])   // IO - Command-line arguments
145
0
{
146
0
  int   i;      // Looping var
147
0
  char    buffer[8192];   // Command-line argument buffer
148
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
149
0
#ifdef LC_TIME
150
0
  const char  *lc_time;   // Current LC_TIME value
151
0
  char    new_lc_time[255], // New LC_TIME value
152
0
    *charset;   // Pointer to character set
153
0
#endif // LC_TIME
154
155
156
  // Set the locale so that times, etc. are displayed properly.
157
  //
158
  // Unfortunately, while we need the localized time value, we *don't*
159
  // want to use the localized charset for the time value, so we need
160
  // to set LC_TIME to the locale name with .UTF-8 on the end (if
161
  // the locale includes a character set specifier...)
162
0
  setlocale(LC_ALL, "");
163
164
0
#ifdef LC_TIME
165
0
  if ((lc_time = setlocale(LC_TIME, NULL)) == NULL)
166
0
    lc_time = setlocale(LC_ALL, NULL);
167
168
0
  if (lc_time)
169
0
  {
170
0
    cupsCopyString(new_lc_time, lc_time, sizeof(new_lc_time));
171
0
    if ((charset = strchr(new_lc_time, '.')) == NULL)
172
0
      charset = new_lc_time + strlen(new_lc_time);
173
174
0
    cupsCopyString(charset, ".UTF-8", sizeof(new_lc_time) - (size_t)(charset - new_lc_time));
175
0
  }
176
0
  else
177
0
    cupsCopyString(new_lc_time, "C", sizeof(new_lc_time));
178
179
0
  setlocale(LC_TIME, new_lc_time);
180
0
#endif // LC_TIME
181
182
  // Initialize the default language info...
183
0
  if (!cg->lang_default)
184
0
    cg->lang_default = cupsLangDefault();
185
186
  // Transcode the command-line arguments from the locale charset to UTF-8...
187
0
  if (cg->lang_encoding != CUPS_ENCODING_US_ASCII && cg->lang_encoding != CUPS_ENCODING_UTF_8)
188
0
  {
189
0
    for (i = 1; argv[i]; i ++)
190
0
    {
191
      // Try converting from the locale charset to UTF-8...
192
0
      if (cupsCharsetToUTF8(buffer, argv[i], sizeof(buffer), cg->lang_encoding) < 0)
193
0
        continue;
194
195
      // Save the new string if it differs from the original...
196
0
      if (strcmp(buffer, argv[i]))
197
0
        argv[i] = strdup(buffer);
198
0
    }
199
0
  }
200
0
}
201
202
203
//
204
// 'cups_lang_default()' - Get the default locale name...
205
//
206
207
static const char *     // O - Locale string
208
cups_lang_default(void)
209
1
{
210
1
  _cups_globals_t *cg = _cupsGlobals();
211
            // Pointer to library globals
212
1
#ifndef __APPLE__
213
1
  static const char * const locale_encodings[] =
214
1
  {         // Locale charset names
215
1
    "ASCII",  "ISO88591", "ISO88592", "ISO88593",
216
1
    "ISO88594", "ISO88595", "ISO88596", "ISO88597",
217
1
    "ISO88598", "ISO88599", "ISO885910",  "UTF8",
218
1
    "ISO885913",  "ISO885914",  "ISO885915",  "CP874",
219
1
    "CP1250", "CP1251", "CP1252", "CP1253",
220
1
    "CP1254", "CP1255", "CP1256", "CP1257",
221
1
    "CP1258", "KOI8R",  "KOI8U",  "ISO885911",
222
1
    "ISO885916",  "MACROMAN", "",   "",
223
224
1
    "",   "",   "",   "",
225
1
    "",   "",   "",   "",
226
1
    "",   "",   "",   "",
227
1
    "",   "",   "",   "",
228
1
    "",   "",   "",   "",
229
1
    "",   "",   "",   "",
230
1
    "",   "",   "",   "",
231
1
    "",   "",   "",   "",
232
233
1
    "CP932",  "CP936",  "CP949",  "CP950",
234
1
    "CP1361", "GB18030",  "",   "",
235
1
    "",   "",   "",   "",
236
1
    "",   "",   "",   "",
237
1
    "",   "",   "",   "",
238
1
    "",   "",   "",   "",
239
1
    "",   "",   "",   "",
240
1
    "",   "",   "",   "",
241
242
1
    "",   "",   "",   "",
243
1
    "",   "",   "",   "",
244
1
    "",   "",   "",   "",
245
1
    "",   "",   "",   "",
246
1
    "",   "",   "",   "",
247
1
    "",   "",   "",   "",
248
1
    "",   "",   "",   "",
249
1
    "",   "",   "",   "",
250
251
1
    "EUCCN",  "EUCJP",  "EUCKR",  "EUCTW",
252
1
    "SHIFT_JISX0213"
253
1
  };
254
1
#endif // !__APPLE__
255
256
257
1
  DEBUG_puts("2cups_lang_default()");
258
259
  // Only lookup the locale the first time.
260
1
  if (!cg->lang_name[0])
261
1
  {
262
1
    const char    *lang;    // LANG environment variable
263
#ifdef __APPLE__
264
    CFBundleRef   bundle;   // Main bundle (if any)
265
    CFArrayRef    bundleList; // List of localizations in bundle
266
    CFPropertyListRef   localizationList = NULL;
267
          // List of localization data
268
    CFStringRef   languageName, // Current language name
269
      localeName; // Current locale name
270
271
    if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
272
    {
273
      DEBUG_printf("3cups_lang_default: Using LANG=%s", lang);
274
      cupsCopyString(cg->lang_name, lang, sizeof(cg->lang_name));
275
      return (cg->lang_name);
276
    }
277
    else if ((bundle = CFBundleGetMainBundle()) != NULL && (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
278
    {
279
      CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
280
          // Resource directory for program
281
282
      DEBUG_puts("3cups_lang_default: Getting localizationList from bundle.");
283
284
      if (resources)
285
      {
286
        CFStringRef cfpath = CFURLCopyPath(resources);
287
          // Directory path of bundle
288
  char    path[1024]; // C string with path
289
290
        if (cfpath)
291
  {
292
    // See if we have an Info.plist file in the bundle...
293
    CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
294
    DEBUG_printf("3cups_lang_default: Got a resource URL (\"%s\")", path);
295
    cupsConcatString(path, "Contents/Info.plist", sizeof(path));
296
297
          if (!access(path, R_OK))
298
      localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList);
299
    else
300
      DEBUG_puts("3cups_lang_default: No Info.plist, ignoring resource URL...");
301
302
    CFRelease(cfpath);
303
  }
304
305
  CFRelease(resources);
306
      }
307
      else
308
      {
309
        DEBUG_puts("3cups_lang_default: No resource URL.");
310
      }
311
312
      CFRelease(bundleList);
313
    }
314
315
    if (!localizationList)
316
    {
317
      DEBUG_puts("3cups_lang_default: Getting localizationList from preferences.");
318
319
      localizationList = CFPreferencesCopyAppValue(CFSTR("AppleLanguages"), kCFPreferencesCurrentApplication);
320
    }
321
322
    if (localizationList)
323
    {
324
#  ifdef DEBUG
325
      if (CFGetTypeID(localizationList) == CFArrayGetTypeID())
326
        DEBUG_printf("3cups_lang_default: Got localizationList, %d entries.", (int)CFArrayGetCount(localizationList));
327
      else
328
        DEBUG_puts("3cups_lang_default: Got localizationList but not an array.");
329
#  endif // DEBUG
330
331
      // Make sure the localization list is an array...
332
      if (CFGetTypeID(localizationList) == CFArrayGetTypeID() && CFArrayGetCount(localizationList) > 0)
333
      {
334
        // Make sure the first element is a string...
335
  languageName = CFArrayGetValueAtIndex(localizationList, 0);
336
337
  if (languageName && CFGetTypeID(languageName) == CFStringGetTypeID())
338
  {
339
    // Get the locale identifier for the given language...
340
    if ((localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName)) != NULL)
341
    {
342
      // Use the locale name...
343
      if (!CFStringGetCString(localeName, cg->lang_name, (CFIndex)sizeof(cg->lang_name), kCFStringEncodingASCII))
344
      {
345
        // Use default locale...
346
        cg->lang_name[0] = '\0';
347
      }
348
349
            CFRelease(localeName);
350
    }
351
    else if (!CFStringGetCString(languageName, cg->lang_name, (CFIndex)sizeof(cg->lang_name), kCFStringEncodingASCII))
352
    {
353
      // Use default locale...
354
      cg->lang_name[0] = '\0';
355
    }
356
  }
357
      }
358
359
      CFRelease(localizationList);
360
    }
361
362
    // Always use UTF-8 on macOS...
363
    cg->lang_encoding = CUPS_ENCODING_UTF_8;
364
365
#else // !__APPLE__
366
1
    size_t    i;    // Looping var
367
1
    const char    *lptr;    // Pointer into the language
368
1
    char    charset[32],  // Character set name
369
1
      *csptr;   // Pointer into character set
370
371
    // See if the locale has been set; if it is still "C" or "POSIX", use the
372
    // environment to get the default...
373
1
#  ifdef LC_MESSAGES
374
1
    lang = setlocale(LC_MESSAGES, NULL);
375
#  else
376
    lang = setlocale(LC_ALL, NULL);
377
#  endif // LC_MESSAGES
378
379
1
    DEBUG_printf("3cups_lang_default: Current locale is \"%s\".", lang);
380
381
1
    charset[0] = '\0';
382
383
1
    if (!lang || !strcmp(lang, "C") || !strcmp(lang, "POSIX"))
384
1
    {
385
      // Get the character set from the LC_xxx locale setting...
386
1
      if ((lang = getenv("LC_CTYPE")) == NULL)
387
1
      {
388
1
        if ((lang = getenv("LC_ALL")) == NULL)
389
1
  {
390
1
    if ((lang = getenv("LANG")) == NULL)
391
1
      lang = "en_US";
392
1
  }
393
1
      }
394
395
1
      if ((lptr = strchr(lang, '.')) != NULL)
396
0
      {
397
        // Extract the character set from the environment...
398
0
  for (csptr = charset, lptr ++; *lptr; lptr ++)
399
0
  {
400
0
    if (csptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*lptr))
401
0
      *csptr++ = *lptr;
402
0
  }
403
404
0
        *csptr = '\0';
405
0
        DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via environment.", charset);
406
0
      }
407
408
      // Get the locale for messages from the LC_MESSAGES locale setting...
409
1
      if ((lang = getenv("LC_MESSAGES")) == NULL)
410
1
      {
411
1
        if ((lang = getenv("LC_ALL")) == NULL)
412
1
        {
413
1
    if ((lang = getenv("LANG")) == NULL)
414
1
      lang = "en_US";
415
1
  }
416
1
      }
417
1
    }
418
419
1
    if (lang)
420
1
    {
421
      // Copy the language over...
422
1
      cupsCopyString(cg->lang_name, lang, sizeof(cg->lang_name));
423
424
1
      if ((lptr = strchr(lang, '.')) != NULL)
425
0
      {
426
        // Extract the character set from the environment...
427
0
  for (csptr = charset, lptr ++; *lptr; lptr ++)
428
0
  {
429
0
    if (csptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*lptr))
430
0
      *csptr++ = *lptr;
431
0
  }
432
433
0
        *csptr = '\0';
434
435
0
        DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via setlocale().", charset);
436
437
0
        if ((csptr = strchr(cg->lang_name, '.')) != NULL)
438
0
          *csptr = '\0';   // Strip charset from locale name...
439
0
      }
440
1
    }
441
442
1
#ifdef CODESET
443
    // On systems that support the nl_langinfo(CODESET) call, use this value as
444
    // the character set...
445
1
    if (!charset[0] && (lptr = nl_langinfo(CODESET)) != NULL)
446
1
    {
447
      // Copy all of the letters and numbers in the CODESET string...
448
15
      for (csptr = charset; *lptr; lptr ++)
449
14
      {
450
14
  if (_cups_isalnum(*lptr) && csptr < (charset + sizeof(charset) - 1))
451
11
    *csptr++ = *lptr;
452
14
      }
453
1
      *csptr = '\0';
454
455
1
      DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via nl_langinfo(CODESET).", charset);
456
1
    }
457
1
#endif // CODESET
458
459
    // Set the encoding, defaulting to UTF-8...
460
1
    cg->lang_encoding = CUPS_ENCODING_AUTO;
461
462
134
    for (i = 0; i < (sizeof(locale_encodings) / sizeof(locale_encodings[0])); i ++)
463
133
    {
464
133
      if (!_cups_strcasecmp(charset, locale_encodings[i]))
465
0
      {
466
0
        cg->lang_encoding = (cups_encoding_t)i;
467
0
        break;
468
0
      }
469
133
    }
470
471
1
    if (cg->lang_encoding == CUPS_ENCODING_AUTO)
472
1
    {
473
      // Map alternate names for various character sets...
474
1
      if (!_cups_strcasecmp(charset, "iso-2022-jp") || !_cups_strcasecmp(charset, "sjis"))
475
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_932;
476
1
      else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
477
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_936;
478
1
      else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
479
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_949;
480
1
      else if (!_cups_strcasecmp(charset, "big5"))
481
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_950;
482
1
      else
483
1
        cg->lang_encoding = CUPS_ENCODING_UTF_8;
484
1
    }
485
1
#endif // __APPLE__
486
487
    // Map default/new locales to their legacy counterparts...
488
1
    if (!cg->lang_name[0] || !strcmp(cg->lang_name, "en"))
489
0
    {
490
      // Default to en_US...
491
0
      cupsCopyString(cg->lang_name, "en_US", sizeof(cg->lang_name));
492
0
    }
493
1
    else if (!strncmp(cg->lang_name, "nb", 2))
494
0
    {
495
      // "nb" == Norwegian Bokmal, "no" is the legacy name...
496
0
      cupsCopyString(cg->lang_name, "no", sizeof(cg->lang_name));
497
0
    }
498
1
    else if (!strncmp(cg->lang_name, "zh-Hans", 7) || !strncmp(cg->lang_name, "zh_HANS", 7))
499
0
    {
500
      // Simplified Chinese (China)
501
0
      cupsCopyString(cg->lang_name, "zh_CN", sizeof(cg->lang_name));
502
0
    }
503
1
    else if (!strncmp(cg->lang_name, "zh-Hant", 7) || !strncmp(cg->lang_name, "zh_HANT", 7))
504
0
    {
505
      // Traditional Chinese (Taiwan)
506
0
      cupsCopyString(cg->lang_name, "zh_TW", sizeof(cg->lang_name));
507
0
    }
508
509
1
    DEBUG_printf("3cups_lang_default: Using locale \"%s\".", cg->lang_name);
510
1
  }
511
0
  else
512
0
  {
513
0
    DEBUG_printf("3cups_lang_default: Using previous locale \"%s\".", cg->lang_name);
514
0
  }
515
516
  // Return the cached locale...
517
1
  return (cg->lang_name);
518
1
}