1 /* Determine the user's language preferences.
2    Copyright (C) 2004-2007, 2018-2019 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU Lesser General Public License as published by
6    the Free Software Foundation; either version 2.1 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public License
15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16 
17 /* Written by Bruno Haible <bruno@clisp.org>.
18    Win32 code originally by Michele Cicciotti <hackbunny@reactos.com>.  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #include <stdlib.h>
25 
26 #if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
27 # include <string.h>
28 # if HAVE_CFLOCALECOPYPREFERREDLANGUAGES
29 #  include <CoreFoundation/CFLocale.h>
30 # elif HAVE_CFPREFERENCESCOPYAPPVALUE
31 #  include <CoreFoundation/CFPreferences.h>
32 # endif
33 # include <CoreFoundation/CFPropertyList.h>
34 # include <CoreFoundation/CFArray.h>
35 # include <CoreFoundation/CFString.h>
36 extern void _nl_locale_name_canonicalize (char *name);
37 #endif
38 
39 #if defined _WIN32
40 # define WIN32_NATIVE
41 #endif
42 
43 #ifdef WIN32_NATIVE
44 # define WIN32_LEAN_AND_MEAN
45 # include <windows.h>
46 
47 # ifndef MUI_LANGUAGE_NAME
48 # define MUI_LANGUAGE_NAME 8
49 # endif
50 # ifndef STATUS_BUFFER_OVERFLOW
51 # define STATUS_BUFFER_OVERFLOW 0x80000005
52 # endif
53 
54 extern void _nl_locale_name_canonicalize (char *name);
55 extern const char *_nl_locale_name_from_win32_LANGID (LANGID langid);
56 extern const char *_nl_locale_name_from_win32_LCID (LCID lcid);
57 
58 /* Get the preferences list through the MUI APIs. This works on Windows Vista
59    and newer.  */
60 static const char *
_nl_language_preferences_win32_mui(HMODULE kernel32)61 _nl_language_preferences_win32_mui (HMODULE kernel32)
62 {
63   /* DWORD GetUserPreferredUILanguages (ULONG dwFlags,
64                                         PULONG pulNumLanguages,
65                                         PWSTR pwszLanguagesBuffer,
66                                         PULONG pcchLanguagesBuffer);  */
67   typedef DWORD (WINAPI *GetUserPreferredUILanguages_func) (ULONG, PULONG, PWSTR, PULONG);
68   GetUserPreferredUILanguages_func p_GetUserPreferredUILanguages;
69 
70   p_GetUserPreferredUILanguages =
71    (GetUserPreferredUILanguages_func)
72    GetProcAddress (kernel32, "GetUserPreferredUILanguages");
73   if (p_GetUserPreferredUILanguages != NULL)
74     {
75       ULONG num_languages;
76       ULONG bufsize;
77       DWORD ret;
78 
79       bufsize = 0;
80       ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
81                                            &num_languages,
82                                            NULL, &bufsize);
83       if (ret == 0
84           && GetLastError () == STATUS_BUFFER_OVERFLOW
85           && bufsize > 0)
86         {
87           WCHAR *buffer = (WCHAR *) malloc (bufsize * sizeof (WCHAR));
88           if (buffer != NULL)
89             {
90               ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
91                                                    &num_languages,
92                                                    buffer, &bufsize);
93               if (ret)
94                 {
95                   /* Convert the list from NUL-delimited WCHAR[] Win32 locale
96                      names to colon-delimited char[] Unix locale names.
97                      We assume that all these locale names are in ASCII,
98                      nonempty and contain no colons.  */
99                   char *languages =
100                     (char *) malloc (bufsize + num_languages * 10 + 1);
101                   if (languages != NULL)
102                     {
103                       const WCHAR *p = buffer;
104                       char *q = languages;
105                       ULONG i;
106                       for (i = 0; i < num_languages; i++)
107                         {
108                           char *q1;
109                           char *q2;
110 
111                           q1 = q;
112                           if (i > 0)
113                             *q++ = ':';
114                           q2 = q;
115                           for (; *p != (WCHAR)'\0'; p++)
116                             {
117                               if ((unsigned char) *p != *p || *p == ':')
118                                 {
119                                   /* A non-ASCII character or a colon inside
120                                      the Win32 locale name! Punt.  */
121                                   q = q1;
122                                   break;
123                                 }
124                               *q++ = (unsigned char) *p;
125                             }
126                           if (q == q1)
127                             /* An unexpected Win32 locale name occurred.  */
128                             break;
129                           *q = '\0';
130                           _nl_locale_name_canonicalize (q2);
131                           q = q2 + strlen (q2);
132                           p++;
133                         }
134                       *q = '\0';
135                       if (q > languages)
136                         {
137                           free (buffer);
138                           return languages;
139                         }
140                       free (languages);
141                     }
142                 }
143               free (buffer);
144             }
145         }
146     }
147   return NULL;
148 }
149 
150 /* Get a preference.  This works on Windows ME and newer.  */
151 static const char *
_nl_language_preferences_win32_ME(HMODULE kernel32)152 _nl_language_preferences_win32_ME (HMODULE kernel32)
153 {
154   /* LANGID GetUserDefaultUILanguage (void);  */
155   typedef LANGID (WINAPI *GetUserDefaultUILanguage_func) (void);
156   GetUserDefaultUILanguage_func p_GetUserDefaultUILanguage;
157 
158   p_GetUserDefaultUILanguage =
159    (GetUserDefaultUILanguage_func)
160    GetProcAddress (kernel32, "GetUserDefaultUILanguage");
161   if (p_GetUserDefaultUILanguage != NULL)
162     return _nl_locale_name_from_win32_LANGID (p_GetUserDefaultUILanguage ());
163   return NULL;
164 }
165 
166 /* Get a preference.  This works on Windows 95 and newer.  */
167 static const char *
_nl_language_preferences_win32_95()168 _nl_language_preferences_win32_95 ()
169 {
170   HKEY desktop_resource_locale_key;
171 
172   if (RegOpenKeyExA (HKEY_CURRENT_USER,
173                      "Control Panel\\Desktop\\ResourceLocale",
174                      0, KEY_QUERY_VALUE, &desktop_resource_locale_key)
175       == NO_ERROR)
176     {
177       DWORD type;
178       BYTE data[8 + 1];
179       DWORD data_size = sizeof (data);
180       DWORD ret;
181 
182       ret = RegQueryValueExA (desktop_resource_locale_key, NULL, NULL,
183                               &type, data, &data_size);
184       RegCloseKey (desktop_resource_locale_key);
185 
186       if (ret == NO_ERROR)
187         {
188           /* We expect a string, at most 8 bytes long, that parses as a
189              hexadecimal number.  */
190           if (type == REG_SZ
191               && data_size <= sizeof (data)
192               && (data_size < sizeof (data)
193                   || data[sizeof (data) - 1] == '\0'))
194             {
195               LCID lcid;
196               char *endp;
197               /* Ensure it's NUL terminated.  */
198               if (data_size < sizeof (data))
199                 data[data_size] = '\0';
200               /* Parse it as a hexadecimal number.  */
201               lcid = strtoul ((char *) data, &endp, 16);
202               if (endp > (char *) data && *endp == '\0')
203                 return _nl_locale_name_from_win32_LCID (lcid);
204             }
205         }
206     }
207   return NULL;
208 }
209 
210 /* Get the system's preference.  This can be used as a fallback.  */
211 static BOOL CALLBACK
ret_first_language(HMODULE h,LPCSTR type,LPCSTR name,WORD lang,LONG_PTR param)212 ret_first_language (HMODULE h, LPCSTR type, LPCSTR name, WORD lang, LONG_PTR param)
213 {
214   *(const char **)param = _nl_locale_name_from_win32_LANGID (lang);
215   return FALSE;
216 }
217 static const char *
_nl_language_preferences_win32_system(HMODULE kernel32)218 _nl_language_preferences_win32_system (HMODULE kernel32)
219 {
220   const char *languages = NULL;
221   /* Ignore the warning on mingw here. mingw has a wrong definition of the last
222      parameter type of ENUMRESLANGPROC.  */
223   EnumResourceLanguages (kernel32, RT_VERSION, MAKEINTRESOURCE (1),
224                          ret_first_language, (LONG_PTR)&languages);
225   return languages;
226 }
227 
228 #endif
229 
230 /* Determine the user's language preferences, as a colon separated list of
231    locale names in XPG syntax
232      language[_territory][.codeset][@modifier]
233    The result must not be freed; it is statically allocated.
234    The LANGUAGE environment variable does not need to be considered; it is
235    already taken into account by the caller.  */
236 
237 const char *
_nl_language_preferences_default(void)238 _nl_language_preferences_default (void)
239 {
240 #if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
241   /* MacOS X 10.4 or newer */
242   {
243     /* Cache the preferences list, since CoreFoundation calls are expensive.  */
244     static const char *cached_languages;
245     static int cache_initialized;
246 
247     if (!cache_initialized)
248       {
249 # if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
250         CFArrayRef prefArray = CFLocaleCopyPreferredLanguages ();
251 # elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
252         CFTypeRef preferences =
253           CFPreferencesCopyAppValue (CFSTR ("AppleLanguages"),
254                                      kCFPreferencesCurrentApplication);
255         if (preferences != NULL
256             && CFGetTypeID (preferences) == CFArrayGetTypeID ())
257           {
258             CFArrayRef prefArray = (CFArrayRef)preferences;
259 # endif
260 
261             int n = CFArrayGetCount (prefArray);
262             char buf[256];
263             char buf2[256];
264             size_t size = 0;
265             int i;
266 
267             for (i = 0; i < n; i++)
268               {
269                 CFTypeRef element = CFArrayGetValueAtIndex (prefArray, i);
270                 if (element != NULL
271                     && CFGetTypeID (element) == CFStringGetTypeID ()
272                     && CFStringGetCString ((CFStringRef)element,
273                                            buf, sizeof (buf),
274                                            kCFStringEncodingASCII))
275                   {
276                     strcpy (buf2, buf);
277                     _nl_locale_name_canonicalize (buf);
278                     size += strlen (buf) + 1;
279                     /* Mac OS X 10.12 or newer returns an array of elements of
280                        the form "ll-CC" or "ll-Scrp-CC" where ll is a language
281                        code, CC is a country code, and Scrp (optional) is a
282                        script code.
283                        _nl_locale_name_canonicalize converts this to "ll_CC" or
284                        "ll_Scrp_CC".
285                        Sometimes ll and CC are unrelated, i.e. there is no
286                        translation for "ll_CC" but one for "ll".
287                        Similarly, in the case with a script, sometimes there is
288                        no translation for "ll_Scrp_CC" but one for "ll_Scrp"
289                        (after proper canonicalization).
290                        Therefore, in the result, we return "ll_CC" followed
291                        by "ll", or similarly for the case with a script.  */
292                     {
293                       char *last_minus = strrchr (buf2, '-');
294                       if (last_minus != NULL)
295                         {
296                           *last_minus = '\0';
297                           _nl_locale_name_canonicalize (buf2);
298                           size += strlen (buf2) + 1;
299                         }
300                     }
301                     /* Most GNU programs use msgids in English and don't ship
302                        an en.mo message catalog.  Therefore when we see "en" or
303                        "en-CC" in the preferences list, arrange for gettext()
304                        to return the msgid, and ignore all further elements of
305                        the preferences list.  */
306                     if (buf[0] == 'e' && buf[1] == 'n'
307                         && (buf[2] == '\0' || buf[2] == '_'))
308                       break;
309                   }
310                 else
311                   break;
312               }
313             if (size > 0)
314               {
315                 char *languages = (char *) malloc (size);
316 
317                 if (languages != NULL)
318                   {
319                     char *p = languages;
320 
321                     for (i = 0; i < n; i++)
322                       {
323                         CFTypeRef element =
324                           CFArrayGetValueAtIndex (prefArray, i);
325                         if (element != NULL
326                             && CFGetTypeID (element) == CFStringGetTypeID ()
327                             && CFStringGetCString ((CFStringRef)element,
328                                                    buf, sizeof (buf),
329                                                    kCFStringEncodingASCII))
330                           {
331                             strcpy (buf2, buf);
332                             _nl_locale_name_canonicalize (buf);
333                             strcpy (p, buf);
334                             p += strlen (buf);
335                             *p++ = ':';
336                             {
337                               char *last_minus = strrchr (buf2, '-');
338                               if (last_minus != NULL)
339                                 {
340                                   *last_minus = '\0';
341                                   _nl_locale_name_canonicalize (buf2);
342                                   strcpy (p, buf2);
343                                   p += strlen (buf2);
344                                   *p++ = ':';
345                                 }
346                             }
347                             if (buf[0] == 'e' && buf[1] == 'n'
348                                  && (buf[2] == '\0' || buf[2] == '_'))
349                               break;
350                           }
351                         else
352                           break;
353                       }
354                     *--p = '\0';
355 
356                     cached_languages = languages;
357                   }
358               }
359 
360 # if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
361         CFRelease (prefArray);
362 # elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
363           }
364 # endif
365         cache_initialized = 1;
366       }
367     if (cached_languages != NULL)
368       return cached_languages;
369   }
370 #endif
371 
372 #ifdef WIN32_NATIVE
373   {
374     /* Cache the preferences list, since computing it is expensive.  */
375     static const char *cached_languages;
376     static int cache_initialized;
377 
378     /* Activate the new code only when the GETTEXT_MUI environment variable is
379        set, for the time being, since the new code is not well tested.  */
380     if (!cache_initialized && getenv ("GETTEXT_MUI") != NULL)
381       {
382         const char *languages = NULL;
383         HMODULE kernel32 = GetModuleHandle ("kernel32");
384 
385         if (kernel32 != NULL)
386           languages = _nl_language_preferences_win32_mui (kernel32);
387 
388         if (languages == NULL && kernel32 != NULL)
389           languages = _nl_language_preferences_win32_ME (kernel32);
390 
391         if (languages == NULL)
392           languages = _nl_language_preferences_win32_95 ();
393 
394         if (languages == NULL && kernel32 != NULL)
395           languages = _nl_language_preferences_win32_system (kernel32);
396 
397         cached_languages = languages;
398         cache_initialized = 1;
399       }
400     if (cached_languages != NULL)
401       return cached_languages;
402   }
403 #endif
404 
405   return NULL;
406 }
407