1 /***
2 *getqloc_downlevel.c - get qualified locale for downlevel OS
3 *
4 *       Copyright (c) Microsoft Corporation. All rights reserved.
5 *
6 *Purpose:
7 *       defines __acrt_get_qualified_locale_downlevel - get complete locale information
8 *
9 *******************************************************************************/
10 #include <corecrt_internal.h>
11 #include <locale.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 extern "C" {
16 
17 //  local defines
18 #define __LCID_DEFAULT  0x1     //  default language locale for country
19 #define __LCID_PRIMARY  0x2     //  primary language locale for country
20 #define __LCID_FULL     0x4     //  fully matched language locale for country
21 #define __LCID_LANGUAGE 0x100   //  language default seen
22 #define __LCID_EXISTS   0x200   //  language is installed
23 
24 typedef struct tagRGLOCINFO
25 {
26     LCID        lcid;
27     wchar_t        chILanguage[8];
28     wchar_t *      pchSEngLanguage;
29     wchar_t        chSAbbrevLangName[4];
30     wchar_t *      pchSEngCountry;
31     wchar_t        chSAbbrevCtryName[4];
32     wchar_t        chIDefaultCodepage[8];
33     wchar_t        chIDefaultAnsiCodepage[8];
34 } RGLOCINFO;
35 
36 static bool TranslateName(const __crt_locale_string_table *, int, const wchar_t **);
37 
38 static void GetLcidFromLangCountry (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data);
39 static BOOL CALLBACK LangCountryEnumProc(_In_z_ PWSTR);
40 
41 static void GetLcidFromLanguage (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data);
42 static BOOL CALLBACK LanguageEnumProc(_In_z_ PWSTR);
43 
44 static void GetLcidFromCountry (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data);
45 static BOOL CALLBACK CountryEnumProc(_In_z_ PWSTR);
46 
47 static void GetLcidFromDefault (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data);
48 
49 static int ProcessCodePage (LPCWSTR lpCodePageStr, __crt_qualified_locale_data_downlevel* _psetloc_downlevel_data);
50 static BOOL TestDefaultCountry(LCID);
51 static BOOL TestDefaultLanguage (LCID lcid, BOOL bTestPrimary, __crt_qualified_locale_data_downlevel* _psetloc_downlevel_data);
52 
53 static LCID LcidFromHexString(_In_z_ PCWSTR);
54 static int GetPrimaryLen(wchar_t const*);
55 
56 //  LANGID's of locales of nondefault languages
57 extern __declspec(selectany) LANGID const __rglangidNotDefault[] =
58 {
59     MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_CANADIAN),
60     MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC),
61     MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_LUXEMBOURG),
62     MAKELANGID(LANG_AFRIKAANS, SUBLANG_DEFAULT),
63     MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_BELGIAN),
64     MAKELANGID(LANG_BASQUE, SUBLANG_DEFAULT),
65     MAKELANGID(LANG_CATALAN, SUBLANG_DEFAULT),
66     MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_SWISS),
67     MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN_SWISS),
68     MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH_FINLAND)
69 };
70 
71 /***
72 *BOOL __acrt_get_qualified_locale_downlevel - return fully qualified locale
73 *
74 *Purpose:
75 *       get default locale, qualify partially complete locales
76 *
77 *Entry:
78 *       lpInStr - input strings to be qualified
79 *       lpOutId - pointer to numeric LCIDs and codepage output
80 *       lpOutStr - pointer to string LCIDs and codepage output
81 *
82 *Exit:
83 *       TRUE if success, qualified locale is valid
84 *       FALSE if failure
85 *
86 *Exceptions:
87 *
88 *******************************************************************************/
89 BOOL __cdecl __acrt_get_qualified_locale_downlevel(const __crt_locale_strings* lpInStr, UINT* lpOutCodePage, __crt_locale_strings* lpOutStr)
90 {
91     int     iCodePage;
92 
93     // Get setloc data from per thread data struct
94     __crt_qualified_locale_data* _psetloc_data = &__acrt_getptd()->_setloc_data;
95 
96     // Set downlevel setloc data in per thread data struct for use by LCID downlevel APIs
97     __crt_qualified_locale_data_downlevel downlevel_setloc;
98     __crt_qualified_locale_data_downlevel* _psetloc_downlevel_data;
99 
100     memset(&downlevel_setloc, '\0', sizeof(__crt_qualified_locale_data_downlevel));
101     _psetloc_downlevel_data = __acrt_getptd()->_setloc_downlevel_data = &downlevel_setloc;
102 
103     //  initialize pointer to call locale info routine based on operating system
104 
105     _psetloc_data->pchLanguage = lpInStr->szLanguage;
106 
107     //  convert non-NLS country strings to three-letter abbreviations
108     _psetloc_data->pchCountry = lpInStr->szCountry;
109     if (_psetloc_data->pchCountry && *_psetloc_data->pchCountry)
110         TranslateName(__acrt_rg_country,
111                       static_cast<int>(__acrt_rg_country_count - 1),
112                       &_psetloc_data->pchCountry);
113 
114     _psetloc_downlevel_data->iLcidState = 0;
115 
116     if (_psetloc_data->pchLanguage && *_psetloc_data->pchLanguage)
117     {
118         if (_psetloc_data->pchCountry && *_psetloc_data->pchCountry)
119         {
120             //  both language and country strings defined
121             GetLcidFromLangCountry(_psetloc_downlevel_data);
122         }
123         else
124         {
125             //  language string defined, but country string undefined
126             GetLcidFromLanguage(_psetloc_downlevel_data);
127         }
128 
129         if (!_psetloc_downlevel_data->iLcidState) {
130             //  first attempt failed, try substituting the language name
131             //  convert non-NLS language strings to three-letter abbrevs
132             if (TranslateName(__acrt_rg_language,
133                               static_cast<int>(__acrt_rg_language_count - 1),
134                               &_psetloc_data->pchLanguage))
135             {
136                 if (_psetloc_data->pchCountry && *_psetloc_data->pchCountry)
137                 {
138                     GetLcidFromLangCountry(_psetloc_downlevel_data);
139                 }
140                 else
141                 {
142                     GetLcidFromLanguage(_psetloc_downlevel_data);
143                 }
144             }
145         }
146     }
147     else
148     {
149         if (_psetloc_data->pchCountry && *_psetloc_data->pchCountry)
150         {
151             //  country string defined, but language string undefined
152             GetLcidFromCountry(_psetloc_downlevel_data);
153         }
154         else
155         {
156             //  both language and country strings undefined
157             GetLcidFromDefault(_psetloc_downlevel_data);
158         }
159     }
160 
161     //  test for error in LCID processing
162     if (!_psetloc_downlevel_data->iLcidState)
163         return FALSE;
164 
165     //  process codepage value
166     iCodePage = ProcessCodePage(lpInStr ? lpInStr->szCodePage: nullptr, _psetloc_downlevel_data);
167 
168     //  verify codepage validity
169     if (!iCodePage || !IsValidCodePage((WORD)iCodePage))
170         return FALSE;
171 
172     //  verify locale is installed
173     if (!IsValidLocale(_psetloc_downlevel_data->lcidLanguage, LCID_INSTALLED))
174         return FALSE;
175 
176     //  set codepage
177     if (lpOutCodePage)
178     {
179         *lpOutCodePage = (UINT)iCodePage;
180     }
181 
182     // store locale name in cache
183     __acrt_LCIDToLocaleName(
184         _psetloc_downlevel_data->lcidLanguage,
185         _psetloc_data->_cacheLocaleName,
186         (int)_countof(_psetloc_data->_cacheLocaleName),
187         0);
188 
189     //  set locale name and codepage results
190     if (lpOutStr)
191     {
192         __acrt_LCIDToLocaleName(
193             _psetloc_downlevel_data->lcidLanguage,
194             lpOutStr->szLocaleName,
195             (int)_countof(lpOutStr->szLocaleName),
196             0);
197 
198         if (GetLocaleInfoW(_psetloc_downlevel_data->lcidLanguage, LOCALE_SENGLANGUAGE,
199                                  lpOutStr->szLanguage, MAX_LANG_LEN) == 0)
200             return FALSE;
201 
202         if (GetLocaleInfoW(_psetloc_downlevel_data->lcidCountry, LOCALE_SENGCOUNTRY,
203                                  lpOutStr->szCountry, MAX_CTRY_LEN) == 0)
204             return FALSE;
205 
206         _itow_s((int)iCodePage, (wchar_t *)lpOutStr->szCodePage, MAX_CP_LEN, 10);
207     }
208 
209     return TRUE;
210 }
211 
212 /***
213 *BOOL TranslateName - convert known non-NLS string to NLS equivalent
214 *
215 *Purpose:
216 *   Provide compatibility with existing code for non-NLS strings
217 *
218 *Entry:
219 *   lpTable  - pointer to __crt_locale_string_table used for translation
220 *   high     - maximum index of table (size - 1)
221 *   ppchName - pointer to pointer of string to translate
222 *
223 *Exit:
224 *   ppchName - pointer to pointer of string possibly translated
225 *   TRUE if string translated, FALSE if unchanged
226 *
227 *Exceptions:
228 *
229 *******************************************************************************/
230 static bool TranslateName (
231     const __crt_locale_string_table* lpTable,
232     int                              high,
233     const wchar_t **                 ppchName)
234 {
235     int low = 0;
236 
237     //  typical binary search - do until no more to search or match
238     while (low <= high)
239     {
240         int const i = (low + high) / 2;
241         int const cmp = _wcsicmp(*ppchName, lpTable[i].szName);
242 
243         if (cmp == 0)
244         {
245             *ppchName = lpTable[i].chAbbrev;
246             return true;
247         }
248         else if (cmp < 0)
249             high = i - 1;
250         else
251             low = i + 1;
252     }
253 
254     return false;
255 }
256 
257 /***
258 *void GetLcidFromLangCountry - get LCIDs from language and country strings
259 *
260 *Purpose:
261 *   Match the best LCIDs to the language and country string given.
262 *   After global variables are initialized, the LangCountryEnumProc
263 *   routine is registered as an EnumSystemLocalesA callback to actually
264 *   perform the matching as the LCIDs are enumerated.
265 *
266 *Entry:
267 *   pchLanguage     - language string
268 *   bAbbrevLanguage - language string is a three-letter abbreviation
269 *   pchCountry      - country string
270 *   bAbbrevCountry  - country string ia a three-letter abbreviation
271 *   iPrimaryLen     - length of language string with primary name
272 *
273 *Exit:
274 *   lcidLanguage - LCID of language string
275 *   lcidCountry  - LCID of country string
276 *
277 *Exceptions:
278 *
279 *******************************************************************************/
280 static void GetLcidFromLangCountry (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data)
281 {
282     __crt_qualified_locale_data*    _psetloc_data = &__acrt_getptd()->_setloc_data;
283 
284     //  initialize static variables for callback use
285     _psetloc_data->bAbbrevLanguage = wcslen(_psetloc_data->pchLanguage) == 3;
286     _psetloc_data->bAbbrevCountry = wcslen(_psetloc_data->pchCountry) == 3;
287     _psetloc_downlevel_data->lcidLanguage = 0;
288     _psetloc_data->iPrimaryLen = _psetloc_data->bAbbrevLanguage ?
289                              2 : GetPrimaryLen(_psetloc_data->pchLanguage);
290 
291     EnumSystemLocalesW(LangCountryEnumProc, LCID_INSTALLED);
292 
293     //  locale value is invalid if the language was not installed or the language
294     //  was not available for the country specified
295     if (!(_psetloc_downlevel_data->iLcidState & __LCID_LANGUAGE) ||
296         !(_psetloc_downlevel_data->iLcidState & __LCID_EXISTS) ||
297         !(_psetloc_downlevel_data->iLcidState & (__LCID_FULL |
298                                     __LCID_PRIMARY |
299                                     __LCID_DEFAULT)))
300         _psetloc_downlevel_data->iLcidState = 0;
301 }
302 
303 /***
304 *BOOL CALLBACK LangCountryEnumProc - callback routine for GetLcidFromLangCountry
305 *
306 *Purpose:
307 *   Determine if LCID given matches the language in pchLanguage
308 *   and country in pchCountry.
309 *
310 *Entry:
311 *   lpLcidString   - pointer to string with decimal LCID
312 *   pchCountry     - pointer to country name
313 *   bAbbrevCountry - set if country is three-letter abbreviation
314 *
315 *Exit:
316 *   iLcidState   - status of match
317 *       __LCID_FULL - both language and country match (best match)
318 *       __LCID_PRIMARY - primary language and country match (better)
319 *       __LCID_DEFAULT - default language and country match (good)
320 *       __LCID_LANGUAGE - default primary language exists
321 *       __LCID_EXISTS - full match of language string exists
322 *       (Overall match occurs for the best of FULL/PRIMARY/DEFAULT
323 *        and LANGUAGE/EXISTS both set.)
324 *   lcidLanguage - LCID matched
325 *   lcidCountry  - LCID matched
326 *   FALSE if match occurred to terminate enumeration, else TRUE.
327 *
328 *Exceptions:
329 *
330 *******************************************************************************/
331 static BOOL CALLBACK LangCountryEnumProc (_In_z_ PWSTR lpLcidString)
332 {
333     __crt_qualified_locale_data*    _psetloc_data = &__acrt_getptd()->_setloc_data;
334     __crt_qualified_locale_data_downlevel*    _psetloc_downlevel_data = __acrt_getptd()->_setloc_downlevel_data;
335     LCID    lcid = LcidFromHexString(lpLcidString);
336     wchar_t    rgcInfo[120];
337 
338     //  test locale country against input value
339     if (GetLocaleInfoW(lcid,
340                              _psetloc_data->bAbbrevCountry ?
341                              LOCALE_SABBREVCTRYNAME : LOCALE_SENGCOUNTRY,
342                              rgcInfo, _countof(rgcInfo)) == 0)
343     {
344         //  set error condition and exit
345         _psetloc_downlevel_data->iLcidState = 0;
346         return TRUE;
347     }
348     if (!_wcsicmp(_psetloc_data->pchCountry, rgcInfo))
349     {
350         //  country matched - test for language match
351         if (GetLocaleInfoW(lcid,
352                                  _psetloc_data->bAbbrevLanguage ?
353                                  LOCALE_SABBREVLANGNAME : LOCALE_SENGLANGUAGE,
354                                  rgcInfo, _countof(rgcInfo)) == 0)
355         {
356             //  set error condition and exit
357             _psetloc_downlevel_data->iLcidState = 0;
358             return TRUE;
359         }
360         if (!_wcsicmp(_psetloc_data->pchLanguage, rgcInfo))
361         {
362             //  language matched also - set state and value
363             _psetloc_downlevel_data->iLcidState |= (__LCID_FULL |
364                                        __LCID_LANGUAGE |
365                                        __LCID_EXISTS);
366             _psetloc_downlevel_data->lcidLanguage = _psetloc_downlevel_data->lcidCountry = lcid;
367         }
368 
369         //  test if match already for primary langauage
370         else if (!(_psetloc_downlevel_data->iLcidState & __LCID_PRIMARY))
371         {
372             //  if not, use _psetloc_data->iPrimaryLen to partial match language string
373             if (_psetloc_data->iPrimaryLen && !_wcsnicmp(_psetloc_data->pchLanguage, rgcInfo, _psetloc_data->iPrimaryLen))
374             {
375                 //  primary language matched - set state and country LCID
376                 _psetloc_downlevel_data->iLcidState |= __LCID_PRIMARY;
377                 _psetloc_downlevel_data->lcidCountry = lcid;
378 
379                 //  if language is primary only (no subtype), set language LCID
380                 if ((int)wcslen(_psetloc_data->pchLanguage) == _psetloc_data->iPrimaryLen)
381                     _psetloc_downlevel_data->lcidLanguage = lcid;
382             }
383 
384             //  test if default language already defined
385             else if (!(_psetloc_downlevel_data->iLcidState & __LCID_DEFAULT))
386             {
387                 //  if not, test if locale language is default for country
388                 if (TestDefaultCountry(lcid))
389                 {
390                     //  default language for country - set state, value
391                     _psetloc_downlevel_data->iLcidState |= __LCID_DEFAULT;
392                     _psetloc_downlevel_data->lcidCountry = lcid;
393                 }
394             }
395         }
396     }
397     //  test if input language both exists and default primary language defined
398     if ((_psetloc_downlevel_data->iLcidState & (__LCID_LANGUAGE | __LCID_EXISTS)) !=
399                       (__LCID_LANGUAGE | __LCID_EXISTS))
400     {
401         //  test language match to determine whether it is installed
402         if (GetLocaleInfoW(lcid, _psetloc_data->bAbbrevLanguage ? LOCALE_SABBREVLANGNAME
403                                                        : LOCALE_SENGLANGUAGE,
404                            rgcInfo, _countof(rgcInfo)) == 0)
405         {
406             //  set error condition and exit
407             _psetloc_downlevel_data->iLcidState = 0;
408             return TRUE;
409         }
410 
411         if (!_wcsicmp(_psetloc_data->pchLanguage, rgcInfo))
412         {
413             //  language matched - set bit for existance
414             _psetloc_downlevel_data->iLcidState |= __LCID_EXISTS;
415 
416             if (_psetloc_data->bAbbrevLanguage)
417             {
418                 //  abbreviation - set state
419                 //  also set language LCID if not set already
420                 _psetloc_downlevel_data->iLcidState |= __LCID_LANGUAGE;
421                 if (!_psetloc_downlevel_data->lcidLanguage)
422                     _psetloc_downlevel_data->lcidLanguage = lcid;
423             }
424 
425             //  test if language is primary only (no sublanguage)
426             else if (_psetloc_data->iPrimaryLen && ((int)wcslen(_psetloc_data->pchLanguage) == _psetloc_data->iPrimaryLen))
427             {
428                 //  primary language only - test if default LCID
429                 if (TestDefaultLanguage(lcid, TRUE, _psetloc_downlevel_data))
430                 {
431                     //  default primary language - set state
432                     //  also set LCID if not set already
433                     _psetloc_downlevel_data->iLcidState |= __LCID_LANGUAGE;
434                     if (!_psetloc_downlevel_data->lcidLanguage)
435                         _psetloc_downlevel_data->lcidLanguage = lcid;
436                 }
437             }
438             else
439             {
440                 //  language with sublanguage - set state
441                 //  also set LCID if not set already
442                 _psetloc_downlevel_data->iLcidState |= __LCID_LANGUAGE;
443                 if (!_psetloc_downlevel_data->lcidLanguage)
444                     _psetloc_downlevel_data->lcidLanguage = lcid;
445             }
446         }
447         else if (!_psetloc_data->bAbbrevLanguage && _psetloc_data->iPrimaryLen
448                                && !_wcsicmp(_psetloc_data->pchLanguage, rgcInfo))
449         {
450             //  primary language match - test for default language only
451             if (TestDefaultLanguage(lcid, FALSE, _psetloc_downlevel_data))
452             {
453                 //  default primary language - set state
454                 //  also set LCID if not set already
455                 _psetloc_downlevel_data->iLcidState |= __LCID_LANGUAGE;
456                 if (!_psetloc_downlevel_data->lcidLanguage)
457                     _psetloc_downlevel_data->lcidLanguage = lcid;
458             }
459         }
460     }
461 
462     //  if LOCALE_FULL set, return FALSE to stop enumeration,
463     //  else return TRUE to continue
464     return (_psetloc_downlevel_data->iLcidState & __LCID_FULL) == 0;
465 }
466 
467 /***
468 *void GetLcidFromLanguage - get LCIDs from language string
469 *
470 *Purpose:
471 *   Match the best LCIDs to the language string given.  After global
472 *   variables are initialized, the LanguageEnumProc routine is
473 *   registered as an EnumSystemLocalesA callback to actually perform
474 *   the matching as the LCIDs are enumerated.
475 *
476 *Entry:
477 *   pchLanguage     - language string
478 *   bAbbrevLanguage - language string is a three-letter abbreviation
479 *   iPrimaryLen     - length of language string with primary name
480 *
481 *Exit:
482 *   lcidLanguage - lcidCountry  - LCID of language with default
483 *                                 country
484 *
485 *Exceptions:
486 *
487 *******************************************************************************/
488 static void GetLcidFromLanguage (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data)
489 {
490     __crt_qualified_locale_data* _psetloc_data = &__acrt_getptd()->_setloc_data;
491 
492     //  initialize static variables for callback use
493     _psetloc_data->bAbbrevLanguage = wcslen(_psetloc_data->pchLanguage) == 3;
494     _psetloc_data->iPrimaryLen = _psetloc_data->bAbbrevLanguage ? 2 : GetPrimaryLen(_psetloc_data->pchLanguage);
495 
496     EnumSystemLocalesW(LanguageEnumProc, LCID_INSTALLED);
497 
498     //  locale value is invalid if the language was not installed
499     //  or the language was not available for the country specified
500     if (!(_psetloc_downlevel_data->iLcidState & __LCID_FULL))
501         _psetloc_downlevel_data->iLcidState = 0;
502 }
503 
504 /***
505 *BOOL CALLBACK LanguageEnumProc - callback routine for GetLcidFromLanguage
506 *
507 *Purpose:
508 *   Determine if LCID given matches the default country for the
509 *   language in pchLanguage.
510 *
511 *Entry:
512 *   lpLcidString    - pointer to string with decimal LCID
513 *   pchLanguage     - pointer to language name
514 *   bAbbrevLanguage - set if language is three-letter abbreviation
515 *
516 *Exit:
517 *   lcidLanguage - lcidCountry - LCID matched
518 *   FALSE if match occurred to terminate enumeration, else TRUE.
519 *
520 *Exceptions:
521 *
522 *******************************************************************************/
523 static BOOL CALLBACK LanguageEnumProc (_In_z_ PWSTR lpLcidString)
524 {
525     __crt_qualified_locale_data*    _psetloc_data = &__acrt_getptd()->_setloc_data;
526     __crt_qualified_locale_data_downlevel*    _psetloc_downlevel_data = __acrt_getptd()->_setloc_downlevel_data;
527 
528     LCID    lcid = LcidFromHexString(lpLcidString);
529     wchar_t    rgcInfo[120];
530 
531     //  test locale for language specified
532     if (GetLocaleInfoW(lcid, _psetloc_data->bAbbrevLanguage ? LOCALE_SABBREVLANGNAME
533                                                    : LOCALE_SENGLANGUAGE,
534                        rgcInfo, _countof(rgcInfo)) == 0)
535     {
536         //  set error condition and exit
537         _psetloc_downlevel_data->iLcidState = 0;
538         return TRUE;
539     }
540 
541     if (!_wcsicmp(_psetloc_data->pchLanguage, rgcInfo))
542     {
543         //  language matched - test if locale country is default
544         //  or if locale is implied in the language string
545         if (_psetloc_data->bAbbrevLanguage || TestDefaultLanguage(lcid, TRUE, _psetloc_downlevel_data))
546         {
547             //  this locale has the default country
548             _psetloc_downlevel_data->lcidLanguage = _psetloc_downlevel_data->lcidCountry = lcid;
549             _psetloc_downlevel_data->iLcidState |= __LCID_FULL;
550         }
551     }
552     else if (!_psetloc_data->bAbbrevLanguage && _psetloc_data->iPrimaryLen
553                               && !_wcsicmp(_psetloc_data->pchLanguage, rgcInfo))
554     {
555         //  primary language matched - test if locale country is default
556         if (TestDefaultLanguage(lcid, FALSE, _psetloc_downlevel_data))
557         {
558             //  this is the default country
559             _psetloc_downlevel_data->lcidLanguage = _psetloc_downlevel_data->lcidCountry = lcid;
560             _psetloc_downlevel_data->iLcidState |= __LCID_FULL;
561         }
562     }
563 
564     return (_psetloc_downlevel_data->iLcidState & __LCID_FULL) == 0;
565 }
566 
567 /***
568 *void GetLcidFromCountry - get LCIDs from country string
569 *
570 *Purpose:
571 *   Match the best LCIDs to the country string given.  After global
572 *   variables are initialized, the CountryEnumProc routine is
573 *   registered as an EnumSystemLocalesA callback to actually perform
574 *   the matching as the LCIDs are enumerated.
575 *
576 *Entry:
577 *   pchCountry     - country string
578 *   bAbbrevCountry - country string is a three-letter abbreviation
579 *
580 *Exit:
581 *   lcidLanguage - lcidCountry  - LCID of country with default
582 *                                 language
583 *
584 *Exceptions:
585 *
586 *******************************************************************************/
587 static void GetLcidFromCountry (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data)
588 {
589     __crt_qualified_locale_data*    _psetloc_data = &__acrt_getptd()->_setloc_data;
590     _psetloc_data->bAbbrevCountry = wcslen(_psetloc_data->pchCountry) == 3;
591 
592     EnumSystemLocalesW(CountryEnumProc, LCID_INSTALLED);
593 
594     //  locale value is invalid if the country was not defined or
595     //  no default language was found
596     if (!(_psetloc_downlevel_data->iLcidState & __LCID_FULL))
597         _psetloc_downlevel_data->iLcidState = 0;
598 }
599 
600 /***
601 *BOOL CALLBACK CountryEnumProc - callback routine for GetLcidFromCountry
602 *
603 *Purpose:
604 *   Determine if LCID given matches the default language for the
605 *   country in pchCountry.
606 *
607 *Entry:
608 *   lpLcidString   - pointer to string with decimal LCID
609 *   pchCountry     - pointer to country name
610 *   bAbbrevCountry - set if country is three-letter abbreviation
611 *
612 *Exit:
613 *   lcidLanguage - lcidCountry - LCID matched
614 *   FALSE if match occurred to terminate enumeration, else TRUE.
615 *
616 *Exceptions:
617 *
618 *******************************************************************************/
619 static BOOL CALLBACK CountryEnumProc (_In_z_ PWSTR lpLcidString)
620 {
621     __crt_qualified_locale_data*    _psetloc_data = &__acrt_getptd()->_setloc_data;
622     __crt_qualified_locale_data_downlevel*    _psetloc_downlevel_data = __acrt_getptd()->_setloc_downlevel_data;
623     LCID    lcid = LcidFromHexString(lpLcidString);
624     wchar_t    rgcInfo[120];
625 
626     //  test locale for country specified
627     if (GetLocaleInfoW(lcid, _psetloc_data->bAbbrevCountry ? LOCALE_SABBREVCTRYNAME
628                                                   : LOCALE_SENGCOUNTRY,
629                        rgcInfo, _countof(rgcInfo)) == 0)
630     {
631         //  set error condition and exit
632         _psetloc_downlevel_data->iLcidState = 0;
633         return TRUE;
634     }
635     if (!_wcsicmp(_psetloc_data->pchCountry, rgcInfo))
636     {
637         //  language matched - test if locale country is default
638         if (TestDefaultCountry(lcid))
639         {
640             //  this locale has the default language
641             _psetloc_downlevel_data->lcidLanguage = _psetloc_downlevel_data->lcidCountry = lcid;
642             _psetloc_downlevel_data->iLcidState |= __LCID_FULL;
643         }
644     }
645     return (_psetloc_downlevel_data->iLcidState & __LCID_FULL) == 0;
646 }
647 
648 /***
649 *void GetLcidFromDefault - get default LCIDs
650 *
651 *Purpose:
652 *   Set both language and country LCIDs to the system default.
653 *
654 *Entry:
655 *   None.
656 *
657 *Exit:
658 *   lcidLanguage - set to system LCID
659 *   lcidCountry  - set to system LCID
660 *
661 *Exceptions:
662 *
663 *******************************************************************************/
664 static void GetLcidFromDefault (__crt_qualified_locale_data_downlevel* _psetloc_downlevel_data)
665 {
666     _psetloc_downlevel_data->iLcidState |= (__LCID_FULL | __LCID_LANGUAGE);
667     _psetloc_downlevel_data->lcidLanguage = _psetloc_downlevel_data->lcidCountry = GetUserDefaultLCID();
668 }
669 
670 /***
671 *int ProcessCodePage - convert codepage string to numeric value
672 *
673 *Purpose:
674 *   Process codepage string consisting of a decimal string, or the
675 *   special case strings "ACP" and "OCP", for ANSI and OEM codepages,
676 *   respectively.  Null pointer or string returns the ANSI codepage.
677 *
678 *Entry:
679 *   lpCodePageStr - pointer to codepage string
680 *
681 *Exit:
682 *   Returns numeric value of codepage.
683 *
684 *Exceptions:
685 *
686 *******************************************************************************/
687 static int ProcessCodePage (LPCWSTR lpCodePageStr, __crt_qualified_locale_data_downlevel* _psetloc_downlevel_data)
688 {
689     int iCodePage;
690 
691     if (!lpCodePageStr || !*lpCodePageStr || !wcscmp(lpCodePageStr, L"ACP"))
692     {
693         //  get ANSI codepage for the country LCID
694         if (GetLocaleInfoW(_psetloc_downlevel_data->lcidCountry, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
695                                  (LPWSTR) &iCodePage, sizeof(iCodePage) / sizeof(wchar_t)) == 0)
696             return 0;
697 
698         if (iCodePage == 0) // for locales have no assoicated ANSI codepage, e.g. Hindi locale
699             return GetACP();
700     }
701     else if (!wcscmp(lpCodePageStr, L"OCP"))
702     {
703         //  get OEM codepage for the country LCID
704         if (GetLocaleInfoW(_psetloc_downlevel_data->lcidCountry, LOCALE_IDEFAULTCODEPAGE | LOCALE_RETURN_NUMBER,
705                                  (LPWSTR) &iCodePage, sizeof(iCodePage) / sizeof(wchar_t)) == 0)
706             return 0;
707     }
708     else
709     {
710          // convert decimal string to numeric value
711          iCodePage = (int)_wtol(lpCodePageStr);
712     }
713 
714     return iCodePage;
715 }
716 
717 /***
718 *BOOL TestDefaultCountry - determine if default locale for country
719 *
720 *Purpose:
721 *   Using a hardcoded list, determine if the locale of the given LCID
722 *   has the default sublanguage for the locale primary language.  The
723 *   list contains the locales NOT having the default sublanguage.
724 *
725 *Entry:
726 *   lcid - LCID of locale to test
727 *
728 *Exit:
729 *   Returns TRUE if default sublanguage, else FALSE.
730 *
731 *Exceptions:
732 *
733 *******************************************************************************/
734 static BOOL TestDefaultCountry (LCID lcid)
735 {
736     LANGID  langid = LANGIDFROMLCID(lcid);
737     int     i;
738 
739     for (i = 0; i < _countof(__rglangidNotDefault); i++)
740     {
741         if (langid == __rglangidNotDefault[i])
742             return FALSE;
743     }
744     return TRUE;
745 }
746 
747 /***
748 *BOOL TestDefaultLanguage - determine if default locale for language
749 *
750 *Purpose:
751 *   Determines if the given LCID has the default sublanguage.
752 *   If bTestPrimary is set, also allow TRUE when string contains an
753 *   implicit sublanguage.
754 *
755 *Entry:
756 *   LCID         - lcid of locale to test
757 *   bTestPrimary - set if testing if language is primary
758 *
759 *Exit:
760 *   Returns TRUE if sublanguage is default for locale tested.
761 *   If bTestPrimary set, TRUE is language has implied sublanguge.
762 *
763 *Exceptions:
764 *
765 *******************************************************************************/
766 static BOOL TestDefaultLanguage (LCID lcid, BOOL bTestPrimary, __crt_qualified_locale_data_downlevel* _psetloc_downlevel_data)
767 {
768     UNREFERENCED_PARAMETER(_psetloc_downlevel_data); // CRT_REFACTOR TODO
769 
770     DWORD dwLanguage;
771     LCID lcidDefault = MAKELCID(MAKELANGID(PRIMARYLANGID(LANGIDFROMLCID(lcid)), SUBLANG_DEFAULT), SORT_DEFAULT);
772     __crt_qualified_locale_data* _psetloc_data = &__acrt_getptd()->_setloc_data;
773 
774     if (GetLocaleInfoW(lcidDefault, LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER,
775                                           (LPWSTR) &dwLanguage, sizeof(dwLanguage) / sizeof(wchar_t)) == 0)
776         return FALSE;
777 
778     if (lcid != dwLanguage)
779     {
780         //  test if string contains an implicit sublanguage by
781         //  having a character other than upper/lowercase letters.
782         if (bTestPrimary && GetPrimaryLen(_psetloc_data->pchLanguage) == (int)wcslen(_psetloc_data->pchLanguage))
783             return FALSE;
784     }
785     return TRUE;
786 }
787 
788 /***
789 *LCID LcidFromHexString - convert hex string to value for LCID
790 *
791 *Purpose:
792 *   LCID values returned in hex ANSI strings - straight conversion
793 *
794 *Entry:
795 *   lpHexString - pointer to hex string to convert
796 *
797 *Exit:
798 *   Returns LCID computed.
799 *
800 *Exceptions:
801 *
802 *******************************************************************************/
803 static LCID LcidFromHexString (_In_z_ PCWSTR lpHexString)
804 {
805     wchar_t    ch;
806     DWORD   lcid = 0;
807 
808 #pragma warning(disable:__WARNING_POTENTIAL_BUFFER_OVERFLOW_NULLTERMINATED) // 26018 This is an idiomatic nul termination check that Prefast doesn't understand.
809     while ((ch = *lpHexString++) != '\0')
810     {
811         if (ch >= 'a' && ch <= 'f')
812             ch += static_cast<wchar_t>('9' + 1 - 'a');
813         else if (ch >= 'A' && ch <= 'F')
814             ch += static_cast<wchar_t>('9' + 1 - 'A');
815         lcid = lcid * 0x10 + ch - '0';
816     }
817 
818     return (LCID)lcid;
819 }
820 
821 /***
822 *int GetPrimaryLen - get length of primary language name
823 *
824 *Purpose:
825 *   Determine primary language string length by scanning until
826 *   first non-alphabetic character.
827 *
828 *Entry:
829 *   pchLanguage - string to scan
830 *
831 *Exit:
832 *   Returns length of primary language string.
833 *
834 *Exceptions:
835 *
836 *******************************************************************************/
837 static int GetPrimaryLen (wchar_t const* pchLanguage)
838 {
839     int     len = 0;
840     wchar_t    ch;
841 
842     ch = *pchLanguage++;
843     while ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
844     {
845         len++;
846         ch = *pchLanguage++;
847     }
848 
849     return len;
850 }
851 
852 } // extern "C"
853