Coverage Report

Created: 2026-03-31 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wget/lib/nl_langinfo.c
Line
Count
Source
1
/* nl_langinfo() replacement: query locale dependent information.
2
3
   Copyright (C) 2007-2026 Free Software Foundation, Inc.
4
5
   This file is free software: you can redistribute it and/or modify
6
   it under the terms of the GNU Lesser General Public License as
7
   published by the Free Software Foundation; either version 2.1 of the
8
   License, or (at your option) any later version.
9
10
   This file is distributed in the hope that it will be useful,
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU Lesser General Public License for more details.
14
15
   You should have received a copy of the GNU Lesser General Public License
16
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17
18
#include <config.h>
19
20
/* Specification.  */
21
#include <langinfo.h>
22
23
#include <locale.h>
24
#include <stdlib.h>
25
#include <string.h>
26
#if defined _WIN32 && ! defined __CYGWIN__
27
# define WIN32_LEAN_AND_MEAN  /* avoid including junk */
28
# include <windows.h>
29
# include <stdio.h>
30
#endif
31
32
#if REPLACE_NL_LANGINFO && !NL_LANGINFO_MTSAFE
33
34
# if AVOID_ANY_THREADS
35
36
/* The option '--disable-threads' explicitly requests no locking.  */
37
38
# elif defined _WIN32 && !defined __CYGWIN__
39
40
#  define WIN32_LEAN_AND_MEAN  /* avoid including junk */
41
#  include <windows.h>
42
43
# elif HAVE_PTHREAD_API
44
45
#  include <pthread.h>
46
#  if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS
47
#   include <threads.h>
48
#   pragma weak thrd_exit
49
#   define c11_threads_in_use() (thrd_exit != NULL)
50
#  else
51
#   define c11_threads_in_use() 0
52
#  endif
53
54
# elif HAVE_THREADS_H
55
56
#  include <threads.h>
57
58
# endif
59
60
#endif
61
62
/* nl_langinfo() must be multithread-safe.  To achieve this without using
63
   thread-local storage:
64
     1. We use a specific static buffer for each possible argument.
65
        So that different threads can call nl_langinfo with different arguments,
66
        without interfering.
67
     2. We use a simple strcpy or memcpy to fill this static buffer.  Filling it
68
        through, for example, strcpy + strcat would not be guaranteed to leave
69
        the buffer's contents intact if another thread is currently accessing
70
        it.  If necessary, the contents is first assembled in a stack-allocated
71
        buffer.  */
72
73
#if !REPLACE_NL_LANGINFO || GNULIB_defined_CODESET
74
/* Return the codeset of the current locale, if this is easily deducible.
75
   Otherwise, return "".  */
76
static char *
77
ctype_codeset (void)
78
{
79
  /* This function is only used on platforms which don't have uselocale().
80
     Therefore we don't need to look at the per-thread locale first, here.  */
81
  static char result[2 + 10 + 1];
82
83
  char locale[SETLOCALE_NULL_MAX];
84
  if (setlocale_null_r (LC_CTYPE, locale, sizeof (locale)))
85
    locale[0] = '\0';
86
87
  char buf[2 + 10 + 1];
88
  char *codeset = buf;
89
  codeset[0] = '\0';
90
91
  if (locale[0])
92
    {
93
      /* If the locale name contains an encoding after the dot, return it.  */
94
      char *dot = strchr (locale, '.');
95
96
      if (dot)
97
        {
98
          /* Look for the possible @... trailer and remove it, if any.  */
99
          char *codeset_start = dot + 1;
100
          char const *modifier = strchr (codeset_start, '@');
101
102
          if (! modifier)
103
            codeset = codeset_start;
104
          else
105
            {
106
              size_t codesetlen = modifier - codeset_start;
107
              if (codesetlen < sizeof buf)
108
                {
109
                  codeset = memcpy (buf, codeset_start, codesetlen);
110
                  codeset[codesetlen] = '\0';
111
                }
112
            }
113
        }
114
    }
115
116
# if defined _WIN32 && ! defined __CYGWIN__
117
  /* If setlocale is successful, it returns the number of the
118
     codepage, as a string.  Otherwise, fall back on Windows API
119
     GetACP, which returns the locale's codepage as a number (although
120
     this doesn't change according to what the 'setlocale' call specified).
121
     Either way, prepend "CP" to make it a valid codeset name.  */
122
  size_t codesetlen = strlen (codeset);
123
  if (0 < codesetlen && codesetlen < sizeof buf - 2)
124
    memmove (buf + 2, codeset, codesetlen + 1);
125
  else
126
    sprintf (buf + 2, "%u", GetACP ());
127
  /* For a locale name such as "French_France.65001", in Windows 10,
128
     setlocale now returns "French_France.utf8" instead.  */
129
  if (streq (buf + 2, "65001") || streq (buf + 2, "utf8"))
130
    return (char *) "UTF-8";
131
  else
132
    {
133
      memcpy (buf, "CP", 2);
134
      strcpy (result, buf);
135
      return result;
136
    }
137
# else
138
  strcpy (result, codeset);
139
  return result;
140
#endif
141
}
142
#endif
143
144
145
#if REPLACE_NL_LANGINFO
146
147
/* Override nl_langinfo with support for added nl_item values.  */
148
149
# undef nl_langinfo
150
151
/* Without locking, on Solaris 11.3, test-nl_langinfo-mt fails, with message
152
   "thread5 disturbed by threadN!", even when threadN invokes only
153
      nl_langinfo (CODESET);
154
      nl_langinfo (CRNCYSTR);
155
   Similarly on Solaris 10 and macOS 26.  */
156
157
# if !NL_LANGINFO_MTSAFE /* macOS, Solaris */
158
159
#  ifdef __sun /* Solaris */
160
#   define ITEMS (MAXSTRMSG + 1)
161
#  else /* macOS */
162
#   define ITEMS (CRNCYSTR + 20)
163
#  endif
164
#  define MAX_RESULT_LEN 80
165
166
static char *
167
nl_langinfo_unlocked (nl_item item)
168
{
169
  static char result[ITEMS][MAX_RESULT_LEN];
170
171
  /* The result of nl_langinfo is in storage that can be overwritten by
172
     other calls to nl_langinfo.  */
173
  char *tmp = nl_langinfo (item);
174
  if (item >= 0 && item < ITEMS && tmp != NULL)
175
    {
176
      size_t tmp_len = strlen (tmp);
177
      if (tmp_len < MAX_RESULT_LEN)
178
        strcpy (result[item], tmp);
179
      else
180
        {
181
          /* Produce a truncated result.  Oh well...  */
182
          result[item][MAX_RESULT_LEN - 1] = '\0';
183
          memcpy (result[item], tmp, MAX_RESULT_LEN - 1);
184
        }
185
      return result[item];
186
    }
187
  else
188
    return tmp;
189
}
190
191
/* Use a lock, so that no two threads can invoke nl_langinfo_unlocked
192
   at the same time.  */
193
194
/* Prohibit renaming this symbol.  */
195
#  undef gl_get_nl_langinfo_lock
196
197
#  if AVOID_ANY_THREADS
198
199
/* The option '--disable-threads' explicitly requests no locking.  */
200
#   define nl_langinfo_with_lock nl_langinfo_unlocked
201
202
#  elif defined _WIN32 && !defined __CYGWIN__
203
204
extern __declspec(dllimport) CRITICAL_SECTION *gl_get_nl_langinfo_lock (void);
205
206
static char *
207
nl_langinfo_with_lock (nl_item item)
208
{
209
  CRITICAL_SECTION *lock = gl_get_nl_langinfo_lock ();
210
211
  EnterCriticalSection (lock);
212
  char *ret = nl_langinfo_unlocked (item);
213
  LeaveCriticalSection (lock);
214
215
  return ret;
216
}
217
218
#  elif HAVE_PTHREAD_API
219
220
extern
221
#   if defined _WIN32 || defined __CYGWIN__
222
  __declspec(dllimport)
223
#   endif
224
  pthread_mutex_t *gl_get_nl_langinfo_lock (void);
225
226
#   if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */
227
228
     /* Avoid the need to link with '-lpthread'.  */
229
#    pragma weak pthread_mutex_lock
230
#    pragma weak pthread_mutex_unlock
231
232
     /* Determine whether libpthread is in use.  */
233
#    pragma weak pthread_mutexattr_gettype
234
     /* See the comments in lock.h.  */
235
#    define pthread_in_use() \
236
       (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
237
238
#   else
239
#    define pthread_in_use() 1
240
#   endif
241
242
static char *
243
nl_langinfo_with_lock (nl_item item)
244
{
245
  if (pthread_in_use())
246
    {
247
      pthread_mutex_t *lock = gl_get_nl_langinfo_lock ();
248
249
      if (pthread_mutex_lock (lock))
250
        abort ();
251
      char *ret = nl_langinfo_unlocked (item);
252
      if (pthread_mutex_unlock (lock))
253
        abort ();
254
255
      return ret;
256
    }
257
  else
258
    return nl_langinfo_unlocked (item);
259
}
260
261
#  elif HAVE_THREADS_H
262
263
extern mtx_t *gl_get_nl_langinfo_lock (void);
264
265
static char *
266
nl_langinfo_with_lock (nl_item item)
267
{
268
  mtx_t *lock = gl_get_nl_langinfo_lock ();
269
270
  if (mtx_lock (lock) != thrd_success)
271
    abort ();
272
  char *ret = nl_langinfo_unlocked (item);
273
  if (mtx_unlock (lock) != thrd_success)
274
    abort ();
275
276
  return ret;
277
}
278
279
#  endif
280
281
# else
282
283
/* On other platforms, no lock is needed.  */
284
0
#  define nl_langinfo_with_lock nl_langinfo
285
286
# endif
287
288
char *
289
rpl_nl_langinfo (nl_item item)
290
0
{
291
0
  switch (item)
292
0
    {
293
# if GNULIB_defined_CODESET
294
    case CODESET:
295
      return ctype_codeset ();
296
# endif
297
# if GNULIB_defined_ALTMON
298
    case ALTMON_1:
299
    case ALTMON_2:
300
    case ALTMON_3:
301
    case ALTMON_4:
302
    case ALTMON_5:
303
    case ALTMON_6:
304
    case ALTMON_7:
305
    case ALTMON_8:
306
    case ALTMON_9:
307
    case ALTMON_10:
308
    case ALTMON_11:
309
    case ALTMON_12:
310
      /* We don't ship the appropriate localizations with gnulib.  Therefore,
311
         treat ALTMON_i like MON_i.  */
312
      item = item - ALTMON_1 + MON_1;
313
      break;
314
# endif
315
# if GNULIB_defined_ABALTMON
316
    case ABALTMON_1:
317
    case ABALTMON_2:
318
    case ABALTMON_3:
319
    case ABALTMON_4:
320
    case ABALTMON_5:
321
    case ABALTMON_6:
322
    case ABALTMON_7:
323
    case ABALTMON_8:
324
    case ABALTMON_9:
325
    case ABALTMON_10:
326
    case ABALTMON_11:
327
    case ABALTMON_12:
328
      /* We don't ship the appropriate localizations with gnulib.  Therefore,
329
         treat ABALTMON_i like ABMON_i.  */
330
      item = item - ABALTMON_1 + ABMON_1;
331
      break;
332
# endif
333
# if GNULIB_defined_ERA
334
    case ERA:
335
      /* The format is not standardized.  In glibc it is a sequence of strings
336
         of the form "direction:offset:start_date:end_date:era_name:era_format"
337
         with an empty string at the end.  */
338
      return (char *) "";
339
    case ERA_D_FMT:
340
      /* The %Ex conversion in strftime behaves like %x if the locale does not
341
         have an alternative time format.  */
342
      item = D_FMT;
343
      break;
344
    case ERA_D_T_FMT:
345
      /* The %Ec conversion in strftime behaves like %c if the locale does not
346
         have an alternative time format.  */
347
      item = D_T_FMT;
348
      break;
349
    case ERA_T_FMT:
350
      /* The %EX conversion in strftime behaves like %X if the locale does not
351
         have an alternative time format.  */
352
      item = T_FMT;
353
      break;
354
    case ALT_DIGITS:
355
      /* The format is not standardized.  In glibc it is a sequence of 10
356
         strings, appended in memory.  */
357
      return (char *) "\0\0\0\0\0\0\0\0\0\0";
358
# endif
359
0
    default:
360
0
      break;
361
0
    }
362
0
  return nl_langinfo_with_lock (item);
363
0
}
364
365
#else
366
367
/* Provide nl_langinfo from scratch, either for native MS-Windows, or
368
   for old Unix platforms without locales, such as Linux libc5 or
369
   BeOS.  */
370
371
# include <time.h>
372
373
char *
374
nl_langinfo (nl_item item)
375
{
376
  char buf[100];
377
  struct tm tmm = { 0 };
378
379
  switch (item)
380
    {
381
    /* nl_langinfo items of the LC_CTYPE category */
382
    case CODESET:
383
      {
384
        char *codeset = ctype_codeset ();
385
        if (*codeset)
386
          return codeset;
387
      }
388
# ifdef __BEOS__
389
      return (char *) "UTF-8";
390
# else
391
      return (char *) "ISO-8859-1";
392
# endif
393
    /* nl_langinfo items of the LC_NUMERIC category */
394
    case RADIXCHAR:
395
      return localeconv () ->decimal_point;
396
    case THOUSEP:
397
      return localeconv () ->thousands_sep;
398
# ifdef GROUPING
399
    case GROUPING:
400
      return localeconv () ->grouping;
401
# endif
402
    /* nl_langinfo items of the LC_TIME category.
403
       TODO: Really use the locale.  */
404
    case D_T_FMT:
405
    case ERA_D_T_FMT:
406
      return (char *) "%a %b %e %H:%M:%S %Y";
407
    case D_FMT:
408
    case ERA_D_FMT:
409
      return (char *) "%m/%d/%y";
410
    case T_FMT:
411
    case ERA_T_FMT:
412
      return (char *) "%H:%M:%S";
413
    case T_FMT_AMPM:
414
      return (char *) "%I:%M:%S %p";
415
    case AM_STR:
416
      {
417
        static char result[80];
418
        if (!strftime (buf, sizeof result, "%p", &tmm))
419
          return (char *) "AM";
420
        strcpy (result, buf);
421
        return result;
422
      }
423
    case PM_STR:
424
      {
425
        static char result[80];
426
        tmm.tm_hour = 12;
427
        if (!strftime (buf, sizeof result, "%p", &tmm))
428
          return (char *) "PM";
429
        strcpy (result, buf);
430
        return result;
431
      }
432
    case DAY_1:
433
    case DAY_2:
434
    case DAY_3:
435
    case DAY_4:
436
    case DAY_5:
437
    case DAY_6:
438
    case DAY_7:
439
      {
440
        static char result[7][50];
441
        static char const days[][sizeof "Wednesday"] = {
442
          "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
443
          "Friday", "Saturday"
444
        };
445
        tmm.tm_wday = item - DAY_1;
446
        if (!strftime (buf, sizeof result[0], "%A", &tmm))
447
          return (char *) days[item - DAY_1];
448
        strcpy (result[item - DAY_1], buf);
449
        return result[item - DAY_1];
450
      }
451
    case ABDAY_1:
452
    case ABDAY_2:
453
    case ABDAY_3:
454
    case ABDAY_4:
455
    case ABDAY_5:
456
    case ABDAY_6:
457
    case ABDAY_7:
458
      {
459
        static char result[7][30];
460
        static char const abdays[][sizeof "Sun"] = {
461
          "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
462
        };
463
        tmm.tm_wday = item - ABDAY_1;
464
        if (!strftime (buf, sizeof result[0], "%a", &tmm))
465
          return (char *) abdays[item - ABDAY_1];
466
        strcpy (result[item - ABDAY_1], buf);
467
        return result[item - ABDAY_1];
468
      }
469
    {
470
      static char const months[][sizeof "September"] = {
471
        "January", "February", "March", "April", "May", "June", "July",
472
        "August", "September", "October", "November", "December"
473
      };
474
      case MON_1:
475
      case MON_2:
476
      case MON_3:
477
      case MON_4:
478
      case MON_5:
479
      case MON_6:
480
      case MON_7:
481
      case MON_8:
482
      case MON_9:
483
      case MON_10:
484
      case MON_11:
485
      case MON_12:
486
        {
487
          static char result[12][50];
488
          tmm.tm_mon = item - MON_1;
489
          if (!strftime (buf, sizeof result[0], "%B", &tmm))
490
            return (char *) months[item - MON_1];
491
          strcpy (result[item - MON_1], buf);
492
          return result[item - MON_1];
493
        }
494
      case ALTMON_1:
495
      case ALTMON_2:
496
      case ALTMON_3:
497
      case ALTMON_4:
498
      case ALTMON_5:
499
      case ALTMON_6:
500
      case ALTMON_7:
501
      case ALTMON_8:
502
      case ALTMON_9:
503
      case ALTMON_10:
504
      case ALTMON_11:
505
      case ALTMON_12:
506
        {
507
          static char result[12][50];
508
          tmm.tm_mon = item - ALTMON_1;
509
          /* The platforms without nl_langinfo() don't support strftime with
510
             %OB.  We don't even need to try.  */
511
          #if 0
512
          if (!strftime (buf, sizeof result[0], "%OB", &tmm))
513
          #endif
514
            if (!strftime (buf, sizeof result[0], "%B", &tmm))
515
              return (char *) months[item - ALTMON_1];
516
          strcpy (result[item - ALTMON_1], buf);
517
          return result[item - ALTMON_1];
518
        }
519
    }
520
    {
521
      static char const abmonths[][sizeof "Jan"] = {
522
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
523
        "Aug", "Sep", "Oct", "Nov", "Dec"
524
      };
525
      case ABMON_1:
526
      case ABMON_2:
527
      case ABMON_3:
528
      case ABMON_4:
529
      case ABMON_5:
530
      case ABMON_6:
531
      case ABMON_7:
532
      case ABMON_8:
533
      case ABMON_9:
534
      case ABMON_10:
535
      case ABMON_11:
536
      case ABMON_12:
537
        {
538
          static char result[12][30];
539
          tmm.tm_mon = item - ABMON_1;
540
          if (!strftime (buf, sizeof result[0], "%b", &tmm))
541
            return (char *) abmonths[item - ABMON_1];
542
          strcpy (result[item - ABMON_1], buf);
543
          return result[item - ABMON_1];
544
        }
545
      case ABALTMON_1:
546
      case ABALTMON_2:
547
      case ABALTMON_3:
548
      case ABALTMON_4:
549
      case ABALTMON_5:
550
      case ABALTMON_6:
551
      case ABALTMON_7:
552
      case ABALTMON_8:
553
      case ABALTMON_9:
554
      case ABALTMON_10:
555
      case ABALTMON_11:
556
      case ABALTMON_12:
557
        {
558
          static char result[12][50];
559
          tmm.tm_mon = item - ABALTMON_1;
560
          /* The platforms without nl_langinfo() don't support strftime with
561
             %Ob.  We don't even need to try.  */
562
          #if 0
563
          if (!strftime (buf, sizeof result[0], "%Ob", &tmm))
564
          #endif
565
            if (!strftime (buf, sizeof result[0], "%b", &tmm))
566
              return (char *) abmonths[item - ABALTMON_1];
567
          strcpy (result[item - ABALTMON_1], buf);
568
          return result[item - ABALTMON_1];
569
        }
570
    }
571
    case ERA:
572
      return (char *) "";
573
    case ALT_DIGITS:
574
      return (char *) "\0\0\0\0\0\0\0\0\0\0";
575
    /* nl_langinfo items of the LC_MONETARY category.  */
576
    case CRNCYSTR:
577
      return localeconv () ->currency_symbol;
578
# ifdef INT_CURR_SYMBOL
579
    case INT_CURR_SYMBOL:
580
      return localeconv () ->int_curr_symbol;
581
    case MON_DECIMAL_POINT:
582
      return localeconv () ->mon_decimal_point;
583
    case MON_THOUSANDS_SEP:
584
      return localeconv () ->mon_thousands_sep;
585
    case MON_GROUPING:
586
      return localeconv () ->mon_grouping;
587
    case POSITIVE_SIGN:
588
      return localeconv () ->positive_sign;
589
    case NEGATIVE_SIGN:
590
      return localeconv () ->negative_sign;
591
    case FRAC_DIGITS:
592
      return & localeconv () ->frac_digits;
593
    case INT_FRAC_DIGITS:
594
      return & localeconv () ->int_frac_digits;
595
    case P_CS_PRECEDES:
596
      return & localeconv () ->p_cs_precedes;
597
    case N_CS_PRECEDES:
598
      return & localeconv () ->n_cs_precedes;
599
    case P_SEP_BY_SPACE:
600
      return & localeconv () ->p_sep_by_space;
601
    case N_SEP_BY_SPACE:
602
      return & localeconv () ->n_sep_by_space;
603
    case P_SIGN_POSN:
604
      return & localeconv () ->p_sign_posn;
605
    case N_SIGN_POSN:
606
      return & localeconv () ->n_sign_posn;
607
# endif
608
    /* nl_langinfo items of the LC_MESSAGES category
609
       TODO: Really use the locale. */
610
    case YESEXPR:
611
      return (char *) "^[yY]";
612
    case NOEXPR:
613
      return (char *) "^[nN]";
614
    default:
615
      return (char *) "";
616
    }
617
}
618
619
#endif