Coverage Report

Created: 2026-07-16 06:54

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.37k
{
35
5.37k
  _cups_globals_t *cg = _cupsGlobals(); // Global data
36
37
38
  // Load the default language as needed...
39
5.37k
  if (!cg->lang_default)
40
1
    cg->lang_default = cupsLangFind(cups_lang_default());
41
42
5.37k
  return (cg->lang_default);
43
5.37k
}
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
#  ifdef _WIN32
374
    WCHAR wlocname[32];   // Locale name as wide chars
375
    char  locname[32];    // Locale name
376
377
    if (GetUserDefaultLocaleName(wlocname, sizeof(wlocname)) > 0)
378
    {
379
      size_t  i;      // Looping var
380
381
      for (i = 0; i < (sizeof(locname) / sizeof(locname[0]) - 1); i ++)
382
        locname[i] = (char)wlocname[i];
383
384
      locname[i] = '\0';
385
      lang       = locname;
386
    }
387
    else
388
    {
389
      lang = "en";
390
    }
391
392
#  elif defined(LC_MESSAGES)
393
1
    lang = setlocale(LC_MESSAGES, NULL);
394
#  else
395
    lang = setlocale(LC_ALL, NULL);
396
#  endif // LC_MESSAGES
397
398
1
    DEBUG_printf("3cups_lang_default: Current locale is \"%s\".", lang);
399
400
1
    charset[0] = '\0';
401
402
1
    if (!lang || !strcmp(lang, "C") || !strcmp(lang, "POSIX"))
403
1
    {
404
      // Get the character set from the LC_xxx locale setting...
405
1
      if ((lang = getenv("LC_CTYPE")) == NULL)
406
1
      {
407
1
        if ((lang = getenv("LC_ALL")) == NULL)
408
1
  {
409
1
    if ((lang = getenv("LANG")) == NULL)
410
1
      lang = "en_US";
411
1
  }
412
1
      }
413
414
1
      if ((lptr = strchr(lang, '.')) != NULL)
415
0
      {
416
        // Extract the character set from the environment...
417
0
  for (csptr = charset, lptr ++; *lptr; lptr ++)
418
0
  {
419
0
    if (csptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*lptr))
420
0
      *csptr++ = *lptr;
421
0
  }
422
423
0
        *csptr = '\0';
424
0
        DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via environment.", charset);
425
0
      }
426
427
      // Get the locale for messages from the LC_MESSAGES locale setting...
428
1
      if ((lang = getenv("LC_MESSAGES")) == NULL)
429
1
      {
430
1
        if ((lang = getenv("LC_ALL")) == NULL)
431
1
        {
432
1
    if ((lang = getenv("LANG")) == NULL)
433
1
      lang = "en_US";
434
1
  }
435
1
      }
436
1
    }
437
438
1
    if (lang)
439
1
    {
440
      // Copy the language over...
441
1
      cupsCopyString(cg->lang_name, lang, sizeof(cg->lang_name));
442
443
1
      if ((lptr = strchr(lang, '.')) != NULL)
444
0
      {
445
        // Extract the character set from the environment...
446
0
  for (csptr = charset, lptr ++; *lptr; lptr ++)
447
0
  {
448
0
    if (csptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*lptr))
449
0
      *csptr++ = *lptr;
450
0
  }
451
452
0
        *csptr = '\0';
453
454
0
        DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via setlocale().", charset);
455
456
0
        if ((csptr = strchr(cg->lang_name, '.')) != NULL)
457
0
          *csptr = '\0';   // Strip charset from locale name...
458
0
      }
459
1
    }
460
461
1
#ifdef CODESET
462
    // On systems that support the nl_langinfo(CODESET) call, use this value as
463
    // the character set...
464
1
    if (!charset[0] && (lptr = nl_langinfo(CODESET)) != NULL)
465
1
    {
466
      // Copy all of the letters and numbers in the CODESET string...
467
15
      for (csptr = charset; *lptr; lptr ++)
468
14
      {
469
14
  if (_cups_isalnum(*lptr) && csptr < (charset + sizeof(charset) - 1))
470
11
    *csptr++ = *lptr;
471
14
      }
472
1
      *csptr = '\0';
473
474
1
      DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via nl_langinfo(CODESET).", charset);
475
1
    }
476
1
#endif // CODESET
477
478
    // Set the encoding, defaulting to UTF-8...
479
1
    cg->lang_encoding = CUPS_ENCODING_AUTO;
480
481
134
    for (i = 0; i < (sizeof(locale_encodings) / sizeof(locale_encodings[0])); i ++)
482
133
    {
483
133
      if (!_cups_strcasecmp(charset, locale_encodings[i]))
484
0
      {
485
0
        cg->lang_encoding = (cups_encoding_t)i;
486
0
        break;
487
0
      }
488
133
    }
489
490
1
    if (cg->lang_encoding == CUPS_ENCODING_AUTO)
491
1
    {
492
      // Map alternate names for various character sets...
493
1
      if (!_cups_strcasecmp(charset, "iso-2022-jp") || !_cups_strcasecmp(charset, "sjis"))
494
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_932;
495
1
      else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
496
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_936;
497
1
      else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
498
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_949;
499
1
      else if (!_cups_strcasecmp(charset, "big5"))
500
0
  cg->lang_encoding = CUPS_ENCODING_WINDOWS_950;
501
1
      else
502
1
        cg->lang_encoding = CUPS_ENCODING_UTF_8;
503
1
    }
504
1
#endif // __APPLE__
505
506
    // Map default/new locales to their legacy counterparts...
507
1
    if (!cg->lang_name[0] || !strcmp(cg->lang_name, "en"))
508
0
    {
509
      // Default to en_US...
510
0
      cupsCopyString(cg->lang_name, "en_US", sizeof(cg->lang_name));
511
0
    }
512
1
    else if (!strncmp(cg->lang_name, "nb", 2))
513
0
    {
514
      // "nb" == Norwegian Bokmal, "no" is the legacy name...
515
0
      cupsCopyString(cg->lang_name, "no", sizeof(cg->lang_name));
516
0
    }
517
1
    else if (!strncmp(cg->lang_name, "zh-Hans", 7) || !strncmp(cg->lang_name, "zh_HANS", 7))
518
0
    {
519
      // Simplified Chinese (China)
520
0
      cupsCopyString(cg->lang_name, "zh_CN", sizeof(cg->lang_name));
521
0
    }
522
1
    else if (!strncmp(cg->lang_name, "zh-Hant", 7) || !strncmp(cg->lang_name, "zh_HANT", 7))
523
0
    {
524
      // Traditional Chinese (Taiwan)
525
0
      cupsCopyString(cg->lang_name, "zh_TW", sizeof(cg->lang_name));
526
0
    }
527
528
1
    DEBUG_printf("3cups_lang_default: Using locale \"%s\".", cg->lang_name);
529
1
  }
530
0
  else
531
0
  {
532
0
    DEBUG_printf("3cups_lang_default: Using previous locale \"%s\".", cg->lang_name);
533
0
  }
534
535
  // Return the cached locale...
536
1
  return (cg->lang_name);
537
1
}