Coverage Report

Created: 2025-03-18 06:55

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