1 /* gcharset.c - Charset information
2  *
3  * Copyright (C) 2011 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include "gcharset.h"
22 #include "gcharsetprivate.h"
23 
24 #include "garray.h"
25 #include "genviron.h"
26 #include "ghash.h"
27 #include "gmessages.h"
28 #include "gstrfuncs.h"
29 #include "gthread.h"
30 #include "gthreadprivate.h"
31 #ifdef G_OS_WIN32
32 #include "gwin32.h"
33 #endif
34 
35 #include "libcharset/libcharset.h"
36 
37 #include <string.h>
38 #include <stdio.h>
39 
40 #if (HAVE_LANGINFO_TIME_CODESET || HAVE_LANGINFO_CODESET)
41 #include <langinfo.h>
42 #endif
43 
44 #include <locale.h>
45 #ifdef G_OS_WIN32
46 #define WIN32_LEAN_AND_MEAN
47 #include <windows.h>
48 #endif
49 
50 G_LOCK_DEFINE_STATIC (aliases);
51 
52 static GHashTable *
get_alias_hash(void)53 get_alias_hash (void)
54 {
55   static GHashTable *alias_hash = NULL;
56   const char *aliases;
57 
58   G_LOCK (aliases);
59 
60   if (!alias_hash)
61     {
62       alias_hash = g_hash_table_new (g_str_hash, g_str_equal);
63 
64       aliases = _g_locale_get_charset_aliases ();
65       while (*aliases != '\0')
66         {
67           const char *canonical;
68           const char *alias;
69           const char **alias_array;
70           int count = 0;
71 
72           alias = aliases;
73           aliases += strlen (aliases) + 1;
74           canonical = aliases;
75           aliases += strlen (aliases) + 1;
76 
77           alias_array = g_hash_table_lookup (alias_hash, canonical);
78           if (alias_array)
79             {
80               while (alias_array[count])
81                 count++;
82             }
83 
84           alias_array = g_renew (const char *, alias_array, count + 2);
85           alias_array[count] = alias;
86           alias_array[count + 1] = NULL;
87 
88           g_hash_table_insert (alias_hash, (char *)canonical, alias_array);
89         }
90     }
91 
92   G_UNLOCK (aliases);
93 
94   return alias_hash;
95 }
96 
97 /* As an abuse of the alias table, the following routines gets
98  * the charsets that are aliases for the canonical name.
99  */
100 const char **
_g_charset_get_aliases(const char * canonical_name)101 _g_charset_get_aliases (const char *canonical_name)
102 {
103   GHashTable *alias_hash = get_alias_hash ();
104 
105   return g_hash_table_lookup (alias_hash, canonical_name);
106 }
107 
108 static gboolean
g_utf8_get_charset_internal(const char * raw_data,const char ** a)109 g_utf8_get_charset_internal (const char  *raw_data,
110                              const char **a)
111 {
112   const char *charset = g_getenv ("CHARSET");
113 
114   if (charset && *charset)
115     {
116       *a = charset;
117 
118       if (charset && strstr (charset, "UTF-8"))
119         return TRUE;
120       else
121         return FALSE;
122     }
123 
124   /* The libcharset code tries to be thread-safe without
125    * a lock, but has a memory leak and a missing memory
126    * barrier, so we lock for it
127    */
128   G_LOCK (aliases);
129   charset = _g_locale_charset_unalias (raw_data);
130   G_UNLOCK (aliases);
131 
132   if (charset && *charset)
133     {
134       *a = charset;
135 
136       if (charset && strstr (charset, "UTF-8"))
137         return TRUE;
138       else
139         return FALSE;
140     }
141 
142   /* Assume this for compatibility at present.  */
143   *a = "US-ASCII";
144 
145   return FALSE;
146 }
147 
148 typedef struct _GCharsetCache GCharsetCache;
149 
150 struct _GCharsetCache {
151   gboolean is_utf8;
152   gchar *raw;
153   gchar *charset;
154 };
155 
156 static void
charset_cache_free(gpointer data)157 charset_cache_free (gpointer data)
158 {
159   GCharsetCache *cache = data;
160   g_free (cache->raw);
161   g_free (cache->charset);
162   g_free (cache);
163 }
164 
165 /**
166  * g_get_charset:
167  * @charset: (out) (optional) (transfer none): return location for character set
168  *   name, or %NULL.
169  *
170  * Obtains the character set for the [current locale][setlocale]; you
171  * might use this character set as an argument to g_convert(), to convert
172  * from the current locale's encoding to some other encoding. (Frequently
173  * g_locale_to_utf8() and g_locale_from_utf8() are nice shortcuts, though.)
174  *
175  * On Windows the character set returned by this function is the
176  * so-called system default ANSI code-page. That is the character set
177  * used by the "narrow" versions of C library and Win32 functions that
178  * handle file names. It might be different from the character set
179  * used by the C library's current locale.
180  *
181  * On Linux, the character set is found by consulting nl_langinfo() if
182  * available. If not, the environment variables `LC_ALL`, `LC_CTYPE`, `LANG`
183  * and `CHARSET` are queried in order.
184  *
185  * The return value is %TRUE if the locale's encoding is UTF-8, in that
186  * case you can perhaps avoid calling g_convert().
187  *
188  * The string returned in @charset is not allocated, and should not be
189  * freed.
190  *
191  * Returns: %TRUE if the returned charset is UTF-8
192  */
193 gboolean
g_get_charset(const char ** charset)194 g_get_charset (const char **charset)
195 {
196   static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free);
197   GCharsetCache *cache = g_private_get (&cache_private);
198   const gchar *raw;
199 
200   if (!cache)
201     cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache));
202 
203   G_LOCK (aliases);
204   raw = _g_locale_charset_raw ();
205   G_UNLOCK (aliases);
206 
207   if (cache->raw == NULL || strcmp (cache->raw, raw) != 0)
208     {
209       const gchar *new_charset;
210 
211       g_free (cache->raw);
212       g_free (cache->charset);
213       cache->raw = g_strdup (raw);
214       cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset);
215       cache->charset = g_strdup (new_charset);
216     }
217 
218   if (charset)
219     *charset = cache->charset;
220 
221   return cache->is_utf8;
222 }
223 
224 /*
225  * Do the same as g_get_charset() but it temporarily set locale (LC_ALL to
226  * LC_TIME) to correctly check for charset about time conversion relatives.
227  *
228  * Returns: %TRUE if the returned charset is UTF-8
229  */
230 gboolean
_g_get_time_charset(const char ** charset)231 _g_get_time_charset (const char **charset)
232 {
233   static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free);
234   GCharsetCache *cache = g_private_get (&cache_private);
235   const gchar *raw;
236 
237   if (!cache)
238     cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache));
239 
240 #ifdef HAVE_LANGINFO_TIME_CODESET
241   raw = nl_langinfo (_NL_TIME_CODESET);
242 #else
243   G_LOCK (aliases);
244   raw = _g_locale_charset_raw ();
245   G_UNLOCK (aliases);
246 #endif
247 
248   if (cache->raw == NULL || strcmp (cache->raw, raw) != 0)
249     {
250       const gchar *new_charset;
251 
252       g_free (cache->raw);
253       g_free (cache->charset);
254       cache->raw = g_strdup (raw);
255       cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset);
256       cache->charset = g_strdup (new_charset);
257     }
258 
259   if (charset)
260     *charset = cache->charset;
261 
262   return cache->is_utf8;
263 }
264 /*
265  * Do the same as g_get_charset() but it temporarily set locale (LC_ALL to
266  * LC_CTYPE) to correctly check for charset about CTYPE conversion relatives.
267  *
268  * Returns: %TRUE if the returned charset is UTF-8
269  */
270 gboolean
_g_get_ctype_charset(const char ** charset)271 _g_get_ctype_charset (const char **charset)
272 {
273   static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free);
274   GCharsetCache *cache = g_private_get (&cache_private);
275   const gchar *raw;
276 
277   if (!cache)
278     cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache));
279 
280 #ifdef HAVE_LANGINFO_CODESET
281   raw = nl_langinfo (CODESET);
282 #else
283   G_LOCK (aliases);
284   raw = _g_locale_charset_raw ();
285   G_UNLOCK (aliases);
286 #endif
287 
288   if (cache->raw == NULL || strcmp (cache->raw, raw) != 0)
289     {
290       const gchar *new_charset;
291 
292       g_free (cache->raw);
293       g_free (cache->charset);
294       cache->raw = g_strdup (raw);
295       cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset);
296       cache->charset = g_strdup (new_charset);
297     }
298 
299   if (charset)
300     *charset = cache->charset;
301 
302   return cache->is_utf8;
303 }
304 
305 /**
306  * g_get_codeset:
307  *
308  * Gets the character set for the current locale.
309  *
310  * Returns: a newly allocated string containing the name
311  *     of the character set. This string must be freed with g_free().
312  */
313 gchar *
g_get_codeset(void)314 g_get_codeset (void)
315 {
316   const gchar *charset;
317 
318   g_get_charset (&charset);
319 
320   return g_strdup (charset);
321 }
322 
323 /**
324  * g_get_console_charset:
325  * @charset: (out) (optional) (transfer none): return location for character set
326  *   name, or %NULL.
327  *
328  * Obtains the character set used by the console attached to the process,
329  * which is suitable for printing output to the terminal.
330  *
331  * Usually this matches the result returned by g_get_charset(), but in
332  * environments where the locale's character set does not match the encoding
333  * of the console this function tries to guess a more suitable value instead.
334  *
335  * On Windows the character set returned by this function is the
336  * output code page used by the console associated with the calling process.
337  * If the codepage can't be determined (for example because there is no
338  * console attached) UTF-8 is assumed.
339  *
340  * The return value is %TRUE if the locale's encoding is UTF-8, in that
341  * case you can perhaps avoid calling g_convert().
342  *
343  * The string returned in @charset is not allocated, and should not be
344  * freed.
345  *
346  * Returns: %TRUE if the returned charset is UTF-8
347  *
348  * Since: 2.62
349  */
350 gboolean
g_get_console_charset(const char ** charset)351 g_get_console_charset (const char **charset)
352 {
353 #ifdef G_OS_WIN32
354   static GPrivate cache_private = G_PRIVATE_INIT (charset_cache_free);
355   GCharsetCache *cache = g_private_get (&cache_private);
356   const gchar *locale;
357   unsigned int cp;
358   char buf[2 + 20 + 1]; /* "CP" + G_MAXUINT64 (to be safe) in decimal form (20 bytes) + "\0" */
359   const gchar *raw = NULL;
360 
361   if (!cache)
362     cache = g_private_set_alloc0 (&cache_private, sizeof (GCharsetCache));
363 
364   /* first try to query $LANG (works for Cygwin/MSYS/MSYS2 and others using mintty) */
365   locale = g_getenv ("LANG");
366   if (locale != NULL && locale[0] != '\0')
367     {
368       /* If the locale name contains an encoding after the dot, return it.  */
369       const char *dot = strchr (locale, '.');
370 
371       if (dot != NULL)
372         {
373           const char *modifier;
374 
375           dot++;
376           /* Look for the possible @... trailer and remove it, if any.  */
377           modifier = strchr (dot, '@');
378           if (modifier == NULL)
379             raw = dot;
380           else if (modifier - dot < sizeof (buf))
381             {
382               memcpy (buf, dot, modifier - dot);
383               buf[modifier - dot] = '\0';
384               raw = buf;
385             }
386         }
387     }
388   /* next try querying console codepage using native win32 API */
389   if (raw == NULL)
390     {
391       cp = GetConsoleOutputCP ();
392       if (cp)
393         {
394           sprintf (buf, "CP%u", cp);
395           raw = buf;
396         }
397       else if (GetLastError () != ERROR_INVALID_HANDLE)
398         {
399           gchar *emsg = g_win32_error_message (GetLastError ());
400           g_warning ("Failed to determine console output code page: %s. "
401                      "Falling back to UTF-8", emsg);
402           g_free (emsg);
403         }
404     }
405   /* fall-back to UTF-8 if the rest failed (it's a universal default) */
406   if (raw == NULL)
407     raw = "UTF-8";
408 
409   if (cache->raw == NULL || strcmp (cache->raw, raw) != 0)
410     {
411       const gchar *new_charset;
412 
413       g_free (cache->raw);
414       g_free (cache->charset);
415       cache->raw = g_strdup (raw);
416       cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset);
417       cache->charset = g_strdup (new_charset);
418     }
419 
420   if (charset)
421     *charset = cache->charset;
422 
423   return cache->is_utf8;
424 #else
425   /* assume the locale settings match the console encoding on non-Windows OSs */
426   return g_get_charset (charset);
427 #endif
428 }
429 
430 #ifndef G_OS_WIN32
431 
432 /* read an alias file for the locales */
433 static void
read_aliases(const gchar * file,GHashTable * alias_table)434 read_aliases (const gchar *file,
435               GHashTable  *alias_table)
436 {
437   FILE *fp;
438   char buf[256];
439 
440   fp = fopen (file,"r");
441   if (!fp)
442     return;
443   while (fgets (buf, 256, fp))
444     {
445       char *p, *q;
446 
447       g_strstrip (buf);
448 
449       /* Line is a comment */
450       if ((buf[0] == '#') || (buf[0] == '\0'))
451         continue;
452 
453       /* Reads first column */
454       for (p = buf, q = NULL; *p; p++) {
455         if ((*p == '\t') || (*p == ' ') || (*p == ':')) {
456           *p = '\0';
457           q = p+1;
458           while ((*q == '\t') || (*q == ' ')) {
459             q++;
460           }
461           break;
462         }
463       }
464       /* The line only had one column */
465       if (!q || *q == '\0')
466         continue;
467 
468       /* Read second column */
469       for (p = q; *p; p++) {
470         if ((*p == '\t') || (*p == ' ')) {
471           *p = '\0';
472           break;
473         }
474       }
475 
476       /* Add to alias table if necessary */
477       if (!g_hash_table_lookup (alias_table, buf)) {
478         g_hash_table_insert (alias_table, g_strdup (buf), g_strdup (q));
479       }
480     }
481   fclose (fp);
482 }
483 
484 #endif
485 
486 static char *
unalias_lang(char * lang)487 unalias_lang (char *lang)
488 {
489 #ifndef G_OS_WIN32
490   static GHashTable *alias_table = NULL;
491   char *p;
492   int i;
493 
494   if (g_once_init_enter (&alias_table))
495     {
496       GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
497       read_aliases ("/usr/share/locale/locale.alias", table);
498       g_once_init_leave (&alias_table, table);
499     }
500 
501   i = 0;
502   while ((p = g_hash_table_lookup (alias_table, lang)) && (strcmp (p, lang) != 0))
503     {
504       lang = p;
505       if (i++ == 30)
506         {
507           static gboolean said_before = FALSE;
508           if (!said_before)
509             g_warning ("Too many alias levels for a locale, "
510                        "may indicate a loop");
511           said_before = TRUE;
512           return lang;
513         }
514     }
515 #endif
516   return lang;
517 }
518 
519 /* Mask for components of locale spec. The ordering here is from
520  * least significant to most significant
521  */
522 enum
523 {
524   COMPONENT_CODESET =   1 << 0,
525   COMPONENT_TERRITORY = 1 << 1,
526   COMPONENT_MODIFIER =  1 << 2
527 };
528 
529 /* Break an X/Open style locale specification into components
530  */
531 static guint
explode_locale(const gchar * locale,gchar ** language,gchar ** territory,gchar ** codeset,gchar ** modifier)532 explode_locale (const gchar *locale,
533                 gchar      **language,
534                 gchar      **territory,
535                 gchar      **codeset,
536                 gchar      **modifier)
537 {
538   const gchar *uscore_pos;
539   const gchar *at_pos;
540   const gchar *dot_pos;
541 
542   guint mask = 0;
543 
544   uscore_pos = strchr (locale, '_');
545   dot_pos = strchr (uscore_pos ? uscore_pos : locale, '.');
546   at_pos = strchr (dot_pos ? dot_pos : (uscore_pos ? uscore_pos : locale), '@');
547 
548   if (at_pos)
549     {
550       mask |= COMPONENT_MODIFIER;
551       *modifier = g_strdup (at_pos);
552     }
553   else
554     at_pos = locale + strlen (locale);
555 
556   if (dot_pos)
557     {
558       mask |= COMPONENT_CODESET;
559       *codeset = g_strndup (dot_pos, at_pos - dot_pos);
560     }
561   else
562     dot_pos = at_pos;
563 
564   if (uscore_pos)
565     {
566       mask |= COMPONENT_TERRITORY;
567       *territory = g_strndup (uscore_pos, dot_pos - uscore_pos);
568     }
569   else
570     uscore_pos = dot_pos;
571 
572   *language = g_strndup (locale, uscore_pos - locale);
573 
574   return mask;
575 }
576 
577 /*
578  * Compute all interesting variants for a given locale name -
579  * by stripping off different components of the value.
580  *
581  * For simplicity, we assume that the locale is in
582  * X/Open format: language[_territory][.codeset][@modifier]
583  *
584  * TODO: Extend this to handle the CEN format (see the GNUlibc docs)
585  *       as well. We could just copy the code from glibc wholesale
586  *       but it is big, ugly, and complicated, so I'm reluctant
587  *       to do so when this should handle 99% of the time...
588  */
589 static void
append_locale_variants(GPtrArray * array,const gchar * locale)590 append_locale_variants (GPtrArray *array,
591                         const gchar *locale)
592 {
593   gchar *language = NULL;
594   gchar *territory = NULL;
595   gchar *codeset = NULL;
596   gchar *modifier = NULL;
597 
598   guint mask;
599   guint i, j;
600 
601   g_return_if_fail (locale != NULL);
602 
603   mask = explode_locale (locale, &language, &territory, &codeset, &modifier);
604 
605   /* Iterate through all possible combinations, from least attractive
606    * to most attractive.
607    */
608   for (j = 0; j <= mask; ++j)
609     {
610       i = mask - j;
611 
612       if ((i & ~mask) == 0)
613         {
614           gchar *val = g_strconcat (language,
615                                     (i & COMPONENT_TERRITORY) ? territory : "",
616                                     (i & COMPONENT_CODESET) ? codeset : "",
617                                     (i & COMPONENT_MODIFIER) ? modifier : "",
618                                     NULL);
619           g_ptr_array_add (array, val);
620         }
621     }
622 
623   g_free (language);
624   if (mask & COMPONENT_CODESET)
625     g_free (codeset);
626   if (mask & COMPONENT_TERRITORY)
627     g_free (territory);
628   if (mask & COMPONENT_MODIFIER)
629     g_free (modifier);
630 }
631 
632 /**
633  * g_get_locale_variants:
634  * @locale: a locale identifier
635  *
636  * Returns a list of derived variants of @locale, which can be used to
637  * e.g. construct locale-dependent filenames or search paths. The returned
638  * list is sorted from most desirable to least desirable.
639  * This function handles territory, charset and extra locale modifiers. See
640  * [`setlocale(3)`](man:setlocale) for information about locales and their format.
641  *
642  * @locale itself is guaranteed to be returned in the output.
643  *
644  * For example, if @locale is `fr_BE`, then the returned list
645  * is `fr_BE`, `fr`. If @locale is `en_GB.UTF-8@euro`, then the returned list
646  * is `en_GB.UTF-8@euro`, `en_GB.UTF-8`, `en_GB@euro`, `en_GB`, `en.UTF-8@euro`,
647  * `en.UTF-8`, `en@euro`, `en`.
648  *
649  * If you need the list of variants for the current locale,
650  * use g_get_language_names().
651  *
652  * Returns: (transfer full) (array zero-terminated=1) (element-type utf8): a newly
653  *   allocated array of newly allocated strings with the locale variants. Free with
654  *   g_strfreev().
655  *
656  * Since: 2.28
657  */
658 gchar **
g_get_locale_variants(const gchar * locale)659 g_get_locale_variants (const gchar *locale)
660 {
661   GPtrArray *array;
662 
663   g_return_val_if_fail (locale != NULL, NULL);
664 
665   array = g_ptr_array_sized_new (8);
666   append_locale_variants (array, locale);
667   g_ptr_array_add (array, NULL);
668 
669   return (gchar **) g_ptr_array_free (array, FALSE);
670 }
671 
672 /* The following is (partly) taken from the gettext package.
673    Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.  */
674 
675 static const gchar *
guess_category_value(const gchar * category_name)676 guess_category_value (const gchar *category_name)
677 {
678   const gchar *retval;
679 
680   /* The highest priority value is the 'LANGUAGE' environment
681      variable.  This is a GNU extension.  */
682   retval = g_getenv ("LANGUAGE");
683   if ((retval != NULL) && (retval[0] != '\0'))
684     return retval;
685 
686   /* 'LANGUAGE' is not set.  So we have to proceed with the POSIX
687      methods of looking to 'LC_ALL', 'LC_xxx', and 'LANG'.  On some
688      systems this can be done by the 'setlocale' function itself.  */
689 
690   /* Setting of LC_ALL overwrites all other.  */
691   retval = g_getenv ("LC_ALL");
692   if ((retval != NULL) && (retval[0] != '\0'))
693     return retval;
694 
695   /* Next comes the name of the desired category.  */
696   retval = g_getenv (category_name);
697   if ((retval != NULL) && (retval[0] != '\0'))
698     return retval;
699 
700   /* Last possibility is the LANG environment variable.  */
701   retval = g_getenv ("LANG");
702   if ((retval != NULL) && (retval[0] != '\0'))
703     return retval;
704 
705 #ifdef G_PLATFORM_WIN32
706   /* g_win32_getlocale() first checks for LC_ALL, LC_MESSAGES and
707    * LANG, which we already did above. Oh well. The main point of
708    * calling g_win32_getlocale() is to get the thread's locale as used
709    * by Windows and the Microsoft C runtime (in the "English_United
710    * States" format) translated into the Unixish format.
711    */
712   {
713     char *locale = g_win32_getlocale ();
714     retval = g_intern_string (locale);
715     g_free (locale);
716     return retval;
717   }
718 #endif
719 
720   return NULL;
721 }
722 
723 typedef struct _GLanguageNamesCache GLanguageNamesCache;
724 
725 struct _GLanguageNamesCache {
726   gchar *languages;
727   gchar **language_names;
728 };
729 
730 static void
language_names_cache_free(gpointer data)731 language_names_cache_free (gpointer data)
732 {
733   GLanguageNamesCache *cache = data;
734   g_free (cache->languages);
735   g_strfreev (cache->language_names);
736   g_free (cache);
737 }
738 
739 /**
740  * g_get_language_names:
741  *
742  * Computes a list of applicable locale names, which can be used to
743  * e.g. construct locale-dependent filenames or search paths. The returned
744  * list is sorted from most desirable to least desirable and always contains
745  * the default locale "C".
746  *
747  * For example, if LANGUAGE=de:en_US, then the returned list is
748  * "de", "en_US", "en", "C".
749  *
750  * This function consults the environment variables `LANGUAGE`, `LC_ALL`,
751  * `LC_MESSAGES` and `LANG` to find the list of locales specified by the
752  * user.
753  *
754  * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of strings owned by GLib
755  *    that must not be modified or freed.
756  *
757  * Since: 2.6
758  */
759 const gchar * const *
g_get_language_names(void)760 g_get_language_names (void)
761 {
762   return g_get_language_names_with_category ("LC_MESSAGES");
763 }
764 
765 /**
766  * g_get_language_names_with_category:
767  * @category_name: a locale category name
768  *
769  * Computes a list of applicable locale names with a locale category name,
770  * which can be used to construct the fallback locale-dependent filenames
771  * or search paths. The returned list is sorted from most desirable to
772  * least desirable and always contains the default locale "C".
773  *
774  * This function consults the environment variables `LANGUAGE`, `LC_ALL`,
775  * @category_name, and `LANG` to find the list of locales specified by the
776  * user.
777  *
778  * g_get_language_names() returns g_get_language_names_with_category("LC_MESSAGES").
779  *
780  * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of strings owned by
781  *    the thread g_get_language_names_with_category was called from.
782  *    It must not be modified or freed. It must be copied if planned to be used in another thread.
783  *
784  * Since: 2.58
785  */
786 const gchar * const *
g_get_language_names_with_category(const gchar * category_name)787 g_get_language_names_with_category (const gchar *category_name)
788 {
789   static GPrivate cache_private = G_PRIVATE_INIT ((void (*)(gpointer)) g_hash_table_unref);
790   GHashTable *cache = g_private_get (&cache_private);
791   const gchar *languages;
792   GLanguageNamesCache *name_cache;
793 
794   g_return_val_if_fail (category_name != NULL, NULL);
795 
796   if (!cache)
797     {
798       cache = g_hash_table_new_full (g_str_hash, g_str_equal,
799                                      g_free, language_names_cache_free);
800       g_private_set (&cache_private, cache);
801     }
802 
803   languages = guess_category_value (category_name);
804   if (!languages)
805     languages = "C";
806 
807   name_cache = (GLanguageNamesCache *) g_hash_table_lookup (cache, category_name);
808   if (!(name_cache && name_cache->languages &&
809         strcmp (name_cache->languages, languages) == 0))
810     {
811       GPtrArray *array;
812       gchar **alist, **a;
813 
814       g_hash_table_remove (cache, category_name);
815 
816       array = g_ptr_array_sized_new (8);
817 
818       alist = g_strsplit (languages, ":", 0);
819       for (a = alist; *a; a++)
820         append_locale_variants (array, unalias_lang (*a));
821       g_strfreev (alist);
822       g_ptr_array_add (array, g_strdup ("C"));
823       g_ptr_array_add (array, NULL);
824 
825       name_cache = g_new0 (GLanguageNamesCache, 1);
826       name_cache->languages = g_strdup (languages);
827       name_cache->language_names = (gchar **) g_ptr_array_free (array, FALSE);
828       g_hash_table_insert (cache, g_strdup (category_name), name_cache);
829     }
830 
831   return (const gchar * const *) name_cache->language_names;
832 }
833