1 /***
2 *getqloc.c - get qualified locale
3 *
4 * Copyright (c) Microsoft Corporation. All rights reserved.
5 *
6 *Purpose:
7 * defines __acrt_get_qualified_locale - get complete locale information
8 *
9 *******************************************************************************/
10 #include <corecrt_internal.h>
11 #include <locale.h>
12 #include <stdlib.h>
13
14 extern "C" {
15
16
17
18 // local defines
19 #define __LOC_DEFAULT 0x1 // default language locale for country
20 #define __LOC_PRIMARY 0x2 // primary language locale for country
21 #define __LOC_FULL 0x4 // fully matched language locale for country
22 #define __LOC_LANGUAGE 0x100 // language default seen
23 #define __LOC_EXISTS 0x200 // language is installed
24
25
26
27 // non-NLS language string table
28 // The three letter Windows names are non-standard and very limited and should not be used.
29 extern __crt_locale_string_table const __acrt_rg_language[]
30 {
31 { L"american", L"ENU" },
32 { L"american english", L"ENU" },
33 { L"american-english", L"ENU" },
34 { L"australian", L"ENA" },
35 { L"belgian", L"NLB" },
36 { L"canadian", L"ENC" },
37 { L"chh", L"ZHH" },
38 { L"chi", L"ZHI" },
39 { L"chinese", L"CHS" },
40 { L"chinese-hongkong", L"ZHH" },
41 { L"chinese-simplified", L"CHS" },
42 { L"chinese-singapore", L"ZHI" },
43 { L"chinese-traditional", L"CHT" },
44 { L"dutch-belgian", L"NLB" },
45 { L"english-american", L"ENU" },
46 { L"english-aus", L"ENA" },
47 { L"english-belize", L"ENL" },
48 { L"english-can", L"ENC" },
49 { L"english-caribbean", L"ENB" },
50 { L"english-ire", L"ENI" },
51 { L"english-jamaica", L"ENJ" },
52 { L"english-nz", L"ENZ" },
53 { L"english-south africa", L"ENS" },
54 { L"english-trinidad y tobago", L"ENT" },
55 { L"english-uk", L"ENG" },
56 { L"english-us", L"ENU" },
57 { L"english-usa", L"ENU" },
58 { L"french-belgian", L"FRB" },
59 { L"french-canadian", L"FRC" },
60 { L"french-luxembourg", L"FRL" },
61 { L"french-swiss", L"FRS" },
62 { L"german-austrian", L"DEA" },
63 { L"german-lichtenstein", L"DEC" },
64 { L"german-luxembourg", L"DEL" },
65 { L"german-swiss", L"DES" },
66 { L"irish-english", L"ENI" },
67 { L"italian-swiss", L"ITS" },
68 { L"norwegian", L"NOR" },
69 { L"norwegian-bokmal", L"NOR" },
70 { L"norwegian-nynorsk", L"NON" },
71 { L"portuguese-brazilian", L"PTB" },
72 { L"spanish-argentina", L"ESS" },
73 { L"spanish-bolivia", L"ESB" },
74 { L"spanish-chile", L"ESL" },
75 { L"spanish-colombia", L"ESO" },
76 { L"spanish-costa rica", L"ESC" },
77 { L"spanish-dominican republic", L"ESD" },
78 { L"spanish-ecuador", L"ESF" },
79 { L"spanish-el salvador", L"ESE" },
80 { L"spanish-guatemala", L"ESG" },
81 { L"spanish-honduras", L"ESH" },
82 { L"spanish-mexican", L"ESM" },
83 { L"spanish-modern", L"ESN" },
84 { L"spanish-nicaragua", L"ESI" },
85 { L"spanish-panama", L"ESA" },
86 { L"spanish-paraguay", L"ESZ" },
87 { L"spanish-peru", L"ESR" },
88 { L"spanish-puerto rico", L"ESU" },
89 { L"spanish-uruguay", L"ESY" },
90 { L"spanish-venezuela", L"ESV" },
91 { L"swedish-finland", L"SVF" },
92 { L"swiss", L"DES" },
93 { L"uk", L"ENG" },
94 { L"us", L"ENU" },
95 { L"usa", L"ENU" }
96 };
97
98 // non-NLS country/region string table
99 // The three letter Windows names are non-standard and very limited and should not be used.
100 extern __crt_locale_string_table const __acrt_rg_country[]
101 {
102 { L"america", L"USA" },
103 { L"britain", L"GBR" },
104 { L"china", L"CHN" },
105 { L"czech", L"CZE" },
106 { L"england", L"GBR" },
107 { L"great britain", L"GBR" },
108 { L"holland", L"NLD" },
109 { L"hong-kong", L"HKG" },
110 { L"new-zealand", L"NZL" },
111 { L"nz", L"NZL" },
112 { L"pr china", L"CHN" },
113 { L"pr-china", L"CHN" },
114 { L"puerto-rico", L"PRI" },
115 { L"slovak", L"SVK" },
116 { L"south africa", L"ZAF" },
117 { L"south korea", L"KOR" },
118 { L"south-africa", L"ZAF" },
119 { L"south-korea", L"KOR" },
120 { L"trinidad & tobago", L"TTO" },
121 { L"uk", L"GBR" },
122 { L"united-kingdom", L"GBR" },
123 { L"united-states", L"USA" },
124 { L"us", L"USA" },
125 };
126
127 // Number of entries in the language and country tables
128 extern size_t const __acrt_rg_language_count{_countof(__acrt_rg_language)};
129 extern size_t const __acrt_rg_country_count {_countof(__acrt_rg_country )};
130
131
132
133
134 // function prototypes
135 BOOL __cdecl __acrt_get_qualified_locale(const __crt_locale_strings*, UINT*, __crt_locale_strings*);
136 static BOOL TranslateName(const __crt_locale_string_table *, int, const wchar_t **);
137
138 static void GetLocaleNameFromLangCountry (__crt_qualified_locale_data* _psetloc_data);
139 static BOOL CALLBACK LangCountryEnumProcEx(_In_z_ LPWSTR, DWORD, LPARAM);
140
141 static void GetLocaleNameFromLanguage (__crt_qualified_locale_data* _psetloc_data);
142 static BOOL CALLBACK LanguageEnumProcEx(_In_z_ LPWSTR, DWORD, LPARAM);
143
144 static void GetLocaleNameFromDefault (__crt_qualified_locale_data* _psetloc_data);
145
146 static int ProcessCodePage (LPCWSTR lpCodePageStr, __crt_qualified_locale_data* _psetloc_data);
147 static BOOL TestDefaultCountry(LPCWSTR localeName);
148 static BOOL TestDefaultLanguage (LPCWSTR localeName, BOOL bTestPrimary, __crt_qualified_locale_data* _psetloc_data);
149
150 static int GetPrimaryLen(LPCWSTR);
151
152
153 /***
154 *BOOL __acrt_get_qualified_locale - return fully qualified locale
155 *
156 *Purpose:
157 * get default locale, qualify partially complete locales
158 *
159 *Entry:
160 * lpInStr - input strings to be qualified
161 * lpOutStr - pointer to string locale names and codepage output
162 *
163 *Exit:
164 * TRUE if success, qualified locale is valid
165 * FALSE if failure
166 *
167 *Exceptions:
168 *
169 *******************************************************************************/
__acrt_get_qualified_locale(const __crt_locale_strings * lpInStr,UINT * lpOutCodePage,__crt_locale_strings * lpOutStr)170 BOOL __cdecl __acrt_get_qualified_locale(const __crt_locale_strings* lpInStr, UINT* lpOutCodePage, __crt_locale_strings* lpOutStr)
171 {
172 int iCodePage;
173 __crt_qualified_locale_data* _psetloc_data = &__acrt_getptd()->_setloc_data;
174 _psetloc_data->_cacheLocaleName[0] = L'\x0'; // Initialize to invariant localename
175
176 // initialize pointer to call locale info routine based on operating system
177
178 _psetloc_data->iLocState = 0;
179 _psetloc_data->pchLanguage = lpInStr->szLanguage;
180 _psetloc_data->pchCountry = lpInStr->szCountry;
181
182 // if country defined
183 // convert non-NLS country strings to three-letter abbreviations
184 if (*_psetloc_data->pchCountry)
185 TranslateName(__acrt_rg_country, static_cast<int>(__acrt_rg_country_count - 1),
186 &_psetloc_data->pchCountry);
187
188
189 // if language defined ...
190 if (*_psetloc_data->pchLanguage)
191 {
192 // and country defined
193 if (*_psetloc_data->pchCountry)
194 {
195 // both language and country strings defined
196 // get locale info using language and country
197 GetLocaleNameFromLangCountry(_psetloc_data);
198 }
199 else
200 {
201 // language string defined, but country string undefined
202 // get locale info using language only
203 GetLocaleNameFromLanguage(_psetloc_data);
204 }
205
206 // still not done?
207 if (_psetloc_data->iLocState == 0)
208 {
209 // first attempt failed, try substituting the language name
210 // convert non-NLS language strings to three-letter abbrevs
211 if (TranslateName(__acrt_rg_language, static_cast<int>(__acrt_rg_language_count - 1),
212 &_psetloc_data->pchLanguage))
213 {
214 if (*_psetloc_data->pchCountry)
215 {
216 // get locale info using language and country
217 GetLocaleNameFromLangCountry(_psetloc_data);
218 }
219 else
220 {
221 // get locale info using language only
222 GetLocaleNameFromLanguage(_psetloc_data);
223 }
224 }
225 }
226 }
227 else
228 {
229 // language is an empty string, use the User Default locale name
230 GetLocaleNameFromDefault(_psetloc_data);
231 }
232
233 // test for error in locale processing
234 if (_psetloc_data->iLocState == 0)
235 return FALSE;
236
237 // process codepage value
238 if (lpInStr == nullptr || *lpInStr->szLanguage || *lpInStr->szCodePage )
239 {
240 // If there's no input, then get the current codepage
241 // If they explicitly chose a language, use that default codepage
242 // If they explicilty set a codepage, then use that
243 iCodePage = ProcessCodePage(lpInStr ? lpInStr->szCodePage : nullptr, _psetloc_data);
244 }
245 else
246 {
247 // No language or codepage means that they want to set to the
248 // user default settings, get that codepage (could be UTF-8)
249 iCodePage = GetACP();
250 }
251
252 // verify codepage validity
253 // CP_UTF7 is unexpected and has never been previously permitted.
254 // CP_UTF8 is the current preferred codepage
255 if (!iCodePage || iCodePage == CP_UTF7 || !IsValidCodePage((WORD)iCodePage))
256 return FALSE;
257
258 // set codepage
259 if (lpOutCodePage)
260 {
261 *lpOutCodePage = (UINT)iCodePage;
262 }
263
264 // set locale name and codepage results
265 if (lpOutStr)
266 {
267 lpOutStr->szLocaleName[0] = L'\x0'; // Init the locale name to empty string
268
269 _ERRCHECK(wcsncpy_s(lpOutStr->szLocaleName, _countof(lpOutStr->szLocaleName), _psetloc_data->_cacheLocaleName, wcslen(_psetloc_data->_cacheLocaleName) + 1));
270
271 // Get and store the English language lang name, to be returned to user
272 if (__acrt_GetLocaleInfoEx(lpOutStr->szLocaleName, LOCALE_SENGLISHLANGUAGENAME,
273 lpOutStr->szLanguage, MAX_LANG_LEN) == 0)
274 return FALSE;
275
276 // Get and store the English language country name, to be returned to user
277 if (__acrt_GetLocaleInfoEx(lpOutStr->szLocaleName, LOCALE_SENGLISHCOUNTRYNAME,
278 lpOutStr->szCountry, MAX_CTRY_LEN) == 0)
279 return FALSE;
280
281 // Special case: Both '.' and '_' are separators in string passed to setlocale,
282 // so if found in Country we use abbreviated name instead.
283 if (wcschr(lpOutStr->szCountry, L'_') || wcschr(lpOutStr->szCountry, L'.'))
284 if (__acrt_GetLocaleInfoEx(lpOutStr->szLocaleName, LOCALE_SABBREVCTRYNAME,
285 lpOutStr->szCountry, MAX_CTRY_LEN) == 0)
286 return FALSE;
287
288 if (iCodePage == CP_UTF8)
289 {
290 // We want UTF-8 to look like utf8, not 65001
291 _ERRCHECK(wcsncpy_s(lpOutStr->szCodePage, _countof(lpOutStr->szCodePage), L"utf8", 5));
292 }
293 else
294 {
295 _itow_s((int)iCodePage, (wchar_t *)lpOutStr->szCodePage, MAX_CP_LEN, 10);
296 }
297 }
298
299 return TRUE;
300 }
301
302 /***
303 *BOOL TranslateName - convert known non-NLS string to NLS equivalent
304 *
305 *Purpose:
306 * Provide compatibility with existing code for non-NLS strings
307 *
308 *Entry:
309 * lpTable - pointer to __crt_locale_string_table used for translation
310 * high - maximum index of table (size - 1)
311 * ppchName - pointer to pointer of string to translate
312 *
313 *Exit:
314 * ppchName - pointer to pointer of string possibly translated
315 * TRUE if string translated, FALSE if unchanged
316 *
317 *Exceptions:
318 *
319 *******************************************************************************/
TranslateName(const __crt_locale_string_table * lpTable,int high,const wchar_t ** ppchName)320 static BOOL TranslateName (
321 const __crt_locale_string_table * lpTable,
322 int high,
323 const wchar_t** ppchName)
324 {
325 int i;
326 int cmp = 1;
327 int low = 0;
328
329 // typical binary search - do until no more to search or match
330 while (low <= high && cmp != 0)
331 {
332 i = (low + high) / 2;
333 cmp = _wcsicmp(*ppchName, (const wchar_t *)(*(lpTable + i)).szName);
334
335 if (cmp == 0)
336 *ppchName = (*(lpTable + i)).chAbbrev;
337 else if (cmp < 0)
338 high = i - 1;
339 else
340 low = i + 1;
341 }
342
343 return !cmp;
344 }
345
346 /***
347 *void GetLocaleNameFromLangCountry - get locale names from language and country strings
348 *
349 *Purpose:
350 * Match the best locale names to the language and country string given.
351 * After global variables are initialized, the LangCountryEnumProcEx
352 * routine is registered as an EnumSystemLocalesEx callback to actually
353 * perform the matching as the locale names are enumerated.
354 *
355 *
356 *WARNING:
357 * This depends on an exact match with a localized string that can change!
358 * It is strongly recommended that locales be selected with valid BCP-47
359 * tags instead of the English names.
360 *
361 * This API is also very brute-force and resource intensive, reading in all
362 * of the locales, forcing them to be cached, and looking up their names.
363 *
364 *WARNING:
365 * In the event of a 2 or 3 letter friendly name (Asu, Edo, Ewe, Yi, ...)
366 * then this function will fail
367 *
368 *Entry:
369 * pchLanguage - language string
370 * bAbbrevLanguage - language string is a three-letter abbreviation
371 * pchCountry - country string
372 * bAbbrevCountry - country string ia a three-letter abbreviation
373 * iPrimaryLen - length of language string with primary name
374 *
375 *Exit:
376 * localeName - locale name of given language and country
377 *
378 *Exceptions:
379 *
380 *******************************************************************************/
GetLocaleNameFromLangCountry(__crt_qualified_locale_data * _psetloc_data)381 static void GetLocaleNameFromLangCountry (__crt_qualified_locale_data* _psetloc_data)
382 {
383 // initialize static variables for callback use
384 _psetloc_data->bAbbrevLanguage = wcslen(_psetloc_data->pchLanguage) == 3;
385 _psetloc_data->bAbbrevCountry = wcslen(_psetloc_data->pchCountry) == 3;
386
387 _psetloc_data->iPrimaryLen = _psetloc_data->bAbbrevLanguage ?
388 2 : GetPrimaryLen(_psetloc_data->pchLanguage);
389
390 // Enumerate all locales that come with the operating system,
391 // including replacement locales, but excluding alternate sorts.
392 __acrt_EnumSystemLocalesEx(LangCountryEnumProcEx, LOCALE_WINDOWS | LOCALE_SUPPLEMENTAL, 0, nullptr);
393
394 // locale value is invalid if the language was not installed or the language
395 // was not available for the country specified
396 if (!(_psetloc_data->iLocState & __LOC_LANGUAGE) ||
397 !(_psetloc_data->iLocState & __LOC_EXISTS) ||
398 !(_psetloc_data->iLocState & (__LOC_FULL |
399 __LOC_PRIMARY |
400 __LOC_DEFAULT)))
401 _psetloc_data->iLocState = 0;
402 }
403
404 /***
405 *BOOL CALLBACK LangCountryEnumProcEx - callback routine for GetLocaleNameFromLangCountry
406 *
407 *Purpose:
408 * Determine if locale name given matches the language in pchLanguage
409 * and country in pchCountry.
410 *
411 *Entry:
412 * lpLocaleString - pointer to locale name string string
413 * pchCountry - pointer to country name
414 * bAbbrevCountry - set if country is three-letter abbreviation
415 *
416 *Exit:
417 * iLocState - status of match
418 * __LOC_FULL - both language and country match (best match)
419 * __LOC_PRIMARY - primary language and country match (better)
420 * __LOC_DEFAULT - default language and country match (good)
421 * __LOC_LANGUAGE - default primary language exists
422 * __LOC_EXISTS - full match of language string exists
423 * (Overall match occurs for the best of FULL/PRIMARY/DEFAULT
424 * and LANGUAGE/EXISTS both set.)
425 * localeName - lpLocaleString matched
426 * FALSE if match occurred to terminate enumeration, else TRUE.
427 *
428 *Exceptions:
429 *
430 *******************************************************************************/
LangCountryEnumProcEx(LPWSTR lpLocaleString,DWORD dwFlags,LPARAM lParam)431 static BOOL CALLBACK LangCountryEnumProcEx(LPWSTR lpLocaleString, DWORD dwFlags, LPARAM lParam)
432 {
433 UNREFERENCED_PARAMETER(dwFlags);
434 UNREFERENCED_PARAMETER(lParam);
435
436 __crt_qualified_locale_data* _psetloc_data = &__acrt_getptd()->_setloc_data;
437 wchar_t rgcInfo[MAX_LANG_LEN]; // MAX_LANG_LEN == MAX_CTRY_LEN == 64
438
439 // test locale country against input value
440 if (__acrt_GetLocaleInfoEx(lpLocaleString,
441 _psetloc_data->bAbbrevCountry ? LOCALE_SABBREVCTRYNAME : LOCALE_SENGLISHCOUNTRYNAME,
442 rgcInfo, _countof(rgcInfo)) == 0)
443 {
444 // set error condition and exit
445 _psetloc_data->iLocState = 0;
446 return TRUE;
447 }
448
449 // if country names matched
450 if (_wcsicmp(_psetloc_data->pchCountry, rgcInfo) == 0)
451 {
452 // test for language match
453 if (__acrt_GetLocaleInfoEx(lpLocaleString,
454 _psetloc_data->bAbbrevLanguage ?
455 LOCALE_SABBREVLANGNAME : LOCALE_SENGLISHLANGUAGENAME,
456 rgcInfo, _countof(rgcInfo)) == 0)
457 {
458 // set error condition and exit
459 _psetloc_data->iLocState = 0;
460 return TRUE;
461 }
462
463 if (_wcsicmp(_psetloc_data->pchLanguage, rgcInfo) == 0)
464 {
465 // language matched also - set state and value
466 // this is the best match
467 _psetloc_data->iLocState |= (__LOC_FULL |
468 __LOC_LANGUAGE |
469 __LOC_EXISTS);
470
471 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
472 }
473 // test if match already for primary langauage
474 else if (!(_psetloc_data->iLocState & __LOC_PRIMARY))
475 {
476 // if not, use _psetloc_data->iPrimaryLen to partial match language string
477 if (_psetloc_data->iPrimaryLen && !_wcsnicmp(_psetloc_data->pchLanguage, rgcInfo, _psetloc_data->iPrimaryLen))
478 {
479 // primary language matched - set locale name
480 _psetloc_data->iLocState |= __LOC_PRIMARY;
481 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
482 }
483
484 // test if default language already defined
485 else if (!(_psetloc_data->iLocState & __LOC_DEFAULT))
486 {
487 // if not, test if locale language is default for country
488 if (TestDefaultCountry(lpLocaleString))
489 {
490 // default language for country - set state, value
491 _psetloc_data->iLocState |= __LOC_DEFAULT;
492 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
493 }
494 }
495 }
496 }
497
498 // test if input language both exists and default primary language defined
499 if ((_psetloc_data->iLocState & (__LOC_LANGUAGE | __LOC_EXISTS)) !=
500 (__LOC_LANGUAGE | __LOC_EXISTS))
501 {
502 // test language match to determine whether it is installed
503 if (__acrt_GetLocaleInfoEx(lpLocaleString, _psetloc_data->bAbbrevLanguage ? LOCALE_SABBREVLANGNAME
504 : LOCALE_SENGLISHLANGUAGENAME,
505 rgcInfo, _countof(rgcInfo)) == 0)
506 {
507 // set error condition and exit
508 _psetloc_data->iLocState = 0;
509 return TRUE;
510 }
511
512 // the input language matches
513 if (_wcsicmp(_psetloc_data->pchLanguage, rgcInfo) == 0)
514 {
515 // language matched - set bit for existance
516 _psetloc_data->iLocState |= __LOC_EXISTS;
517
518 if (_psetloc_data->bAbbrevLanguage)
519 {
520 // abbreviation - set state
521 // also set language locale name if not set already
522 _psetloc_data->iLocState |= __LOC_LANGUAGE;
523 if (!_psetloc_data->_cacheLocaleName[0])
524 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
525 }
526
527 // test if language is primary only (no sublanguage)
528 else if (_psetloc_data->iPrimaryLen && ((int)wcslen(_psetloc_data->pchLanguage) == _psetloc_data->iPrimaryLen))
529 {
530 // primary language only - test if default locale name
531 if (TestDefaultLanguage(lpLocaleString, TRUE, _psetloc_data))
532 {
533 // default primary language - set state
534 // also set locale name if not set already
535 _psetloc_data->iLocState |= __LOC_LANGUAGE;
536 if (!_psetloc_data->_cacheLocaleName[0])
537 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
538 }
539 }
540 else
541 {
542 // language with sublanguage - set state
543 // also set locale name if not set already
544 _psetloc_data->iLocState |= __LOC_LANGUAGE;
545 if (!_psetloc_data->_cacheLocaleName[0])
546 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
547 }
548 }
549 }
550
551 // if LOCALE_FULL set, return FALSE to stop enumeration,
552 // else return TRUE to continue
553 return (_psetloc_data->iLocState & __LOC_FULL) == 0;
554 }
555
556 /***
557 *void GetLocaleNameFromLanguage - get locale name from language string
558 *
559 *Purpose:
560 * Match the best locale name to the language string given. After global
561 * variables are initialized, the LanguageEnumProcEx routine is
562 * registered as an EnumSystemLocalesEx callback to actually perform
563 * the matching as the locale names are enumerated.
564 *
565 *WARNING:
566 * This depends on an exact match with a localized string that can change!
567 * It is strongly recommended that locales be selected with valid BCP-47
568 * tags instead of the English names.
569 *
570 * This API is also very brute-force and resource intensive, reading in all
571 * of the locales, forcing them to be cached, and looking up their names.
572 *
573 *WARNING:
574 * In the event of a 3 letter BCP-47 tag that happens to match a Windows
575 * propriatary language code, this function will return the wrong answer!
576 *
577 *WARNING:
578 * In the event of a 2 or 3 letter friendly name (Asu, Edo, Ewe, Yi, ...)
579 * then this function will fail
580 *
581 *Entry:
582 * pchLanguage - language string
583 * bAbbrevLanguage - language string is a three-letter abbreviation
584 * iPrimaryLen - length of language string with primary name
585 *
586 *Exit:
587 * localeName - locale name of language with default country
588 *
589 *Exceptions:
590 *
591 *******************************************************************************/
GetLocaleNameFromLanguage(__crt_qualified_locale_data * _psetloc_data)592 static void GetLocaleNameFromLanguage (__crt_qualified_locale_data* _psetloc_data)
593 {
594 // initialize static variables for callback use
595 _psetloc_data->bAbbrevLanguage = wcslen(_psetloc_data->pchLanguage) == 3;
596 _psetloc_data->iPrimaryLen = _psetloc_data->bAbbrevLanguage ? 2 : GetPrimaryLen(_psetloc_data->pchLanguage);
597
598 // Enumerate all locales that come with the operating system, including replacement locales,
599 // but excluding alternate sorts.
600 __acrt_EnumSystemLocalesEx(LanguageEnumProcEx, LOCALE_WINDOWS | LOCALE_SUPPLEMENTAL, 0, nullptr);
601
602 // locale value is invalid if the language was not installed
603 // or the language was not available for the country specified
604 if ((_psetloc_data->iLocState & __LOC_FULL) == 0)
605 _psetloc_data->iLocState = 0;
606 }
607
608 /***
609 *BOOL CALLBACK LanguageEnumProcEx - callback routine for GetLocaleNameFromLanguage
610 *
611 *Purpose:
612 * Determine if locale name given matches the default country for the
613 * language in pchLanguage.
614 *
615 *Entry:
616 * lpLocaleString - pointer to string with locale name
617 * dwFlags - not used
618 * lParam - not used
619 *
620 *Exit:
621 * localeName - locale name matched
622 * FALSE if match occurred to terminate enumeration, else TRUE.
623 *
624 *Exceptions:
625 *
626 *******************************************************************************/
LanguageEnumProcEx(LPWSTR lpLocaleString,DWORD dwFlags,LPARAM lParam)627 static BOOL CALLBACK LanguageEnumProcEx (LPWSTR lpLocaleString, DWORD dwFlags, LPARAM lParam)
628 {
629 UNREFERENCED_PARAMETER(dwFlags);
630 UNREFERENCED_PARAMETER(lParam);
631
632 __crt_qualified_locale_data* _psetloc_data = &__acrt_getptd()->_setloc_data;
633 wchar_t rgcInfo[120];
634
635 // test locale for language specified
636 if (__acrt_GetLocaleInfoEx(lpLocaleString, _psetloc_data->bAbbrevLanguage ? LOCALE_SABBREVLANGNAME
637 : LOCALE_SENGLISHLANGUAGENAME,
638 rgcInfo, _countof(rgcInfo)) == 0)
639 {
640 // set error condition and exit
641 _psetloc_data->iLocState = 0;
642 return TRUE;
643 }
644
645 if (_wcsicmp(_psetloc_data->pchLanguage, rgcInfo) == 0)
646 {
647 // language matches
648 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), lpLocaleString, wcslen(lpLocaleString) + 1));
649
650 _psetloc_data->iLocState |= __LOC_FULL;
651 }
652
653 return (_psetloc_data->iLocState & __LOC_FULL) == 0;
654 }
655
656
657 /***
658 *void GetLocaleNameFromDefault - get default locale names
659 *
660 *Purpose:
661 * Set both language and country locale names to the user default.
662 *
663 *Entry:
664 * None.
665 *
666 *Exceptions:
667 *
668 *******************************************************************************/
GetLocaleNameFromDefault(__crt_qualified_locale_data * _psetloc_data)669 static void GetLocaleNameFromDefault (__crt_qualified_locale_data* _psetloc_data)
670 {
671 wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
672 _psetloc_data->iLocState |= (__LOC_FULL | __LOC_LANGUAGE);
673
674 // Store the default user locale name. The returned buffer size includes the
675 // terminating null character, so only store if the size returned is > 1
676 if (__acrt_GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) > 1)
677 {
678 _ERRCHECK(wcsncpy_s(_psetloc_data->_cacheLocaleName, _countof(_psetloc_data->_cacheLocaleName), localeName, wcslen(localeName) + 1));
679 }
680 }
681
682 /***
683 *int ProcessCodePage - convert codepage string to numeric value
684 *
685 *Purpose:
686 * Process codepage string consisting of a decimal string, or the
687 * special case strings "ACP" and "OCP", for ANSI and OEM codepages,
688 * respectively. Null pointer or string returns the ANSI codepage.
689 *
690 *Entry:
691 * lpCodePageStr - pointer to codepage string
692 *
693 *Exit:
694 * Returns numeric value of codepage, zero if GetLocaleInfoEx failed.
695 * (which then would mean caller aborts and locale is not set)
696 *
697 *Exceptions:
698 *
699 *******************************************************************************/
ProcessCodePage(LPCWSTR lpCodePageStr,__crt_qualified_locale_data * _psetloc_data)700 static int ProcessCodePage (LPCWSTR lpCodePageStr, __crt_qualified_locale_data* _psetloc_data)
701 {
702 int iCodePage;
703
704 if (!lpCodePageStr || !*lpCodePageStr || wcscmp(lpCodePageStr, L"ACP") == 0)
705 {
706 // get ANSI codepage for the country locale name
707 // CONSIDER: If system is running UTF-8 ACP, then always return UTF-8?
708 if (__acrt_GetLocaleInfoEx(_psetloc_data->_cacheLocaleName, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
709 (LPWSTR) &iCodePage, sizeof(iCodePage) / sizeof(wchar_t)) == 0)
710 return 0;
711
712 // Locales with no code page ("Unicode only locales") should return UTF-8
713 // (0, 1 & 2 are Unicode-Only ACP, OEMCP & MacCP flags)
714 if (iCodePage < 3)
715 {
716 return CP_UTF8;
717 }
718
719 }
720 else if (_wcsicmp(lpCodePageStr, L"utf8") == 0 ||
721 _wcsicmp(lpCodePageStr, L"utf-8") == 0)
722 {
723 // Use UTF-8
724 return CP_UTF8;
725 }
726 else if (wcscmp(lpCodePageStr, L"OCP") == 0)
727 {
728 // get OEM codepage for the country locale name
729 // CONSIDER: If system is running UTF-8 ACP, then always return UTF-8?
730 if (__acrt_GetLocaleInfoEx(_psetloc_data->_cacheLocaleName, LOCALE_IDEFAULTCODEPAGE | LOCALE_RETURN_NUMBER,
731 (LPWSTR) &iCodePage, sizeof(iCodePage) / sizeof(wchar_t)) == 0)
732 return 0;
733
734 // Locales with no code page ("unicode only locales") should return UTF-8
735 // (0, 1 & 2 are Unicode-Only ACP, OEMCP & MacCP flags)
736 if (iCodePage < 3)
737 {
738 return CP_UTF8;
739 }
740 }
741 else
742 {
743 // convert decimal string to numeric value
744 iCodePage = (int)_wtol(lpCodePageStr);
745 }
746
747 return iCodePage;
748 }
749
750 /***
751 *BOOL TestDefaultCountry - determine if default locale for country
752 *
753 *Purpose:
754 * Determine if the locale of the given locale name has the default sublanguage.
755 * This is determined by checking if the given language is neutral.
756 *
757 *Entry:
758 * localeName - name of locale to test
759 *
760 *Exit:
761 * Returns TRUE if default sublanguage, else FALSE.
762 *
763 *Exceptions:
764 *
765 *******************************************************************************/
TestDefaultCountry(LPCWSTR localeName)766 static BOOL TestDefaultCountry (LPCWSTR localeName)
767 {
768 wchar_t sIso639LangName[9]; // The maximum length for LOCALE_SISO3166CTRYNAME
769 // is 9 including nullptr
770
771 // Get 2-letter ISO Standard 639 or 3-letter ISO 639-2 value
772 if (__acrt_GetLocaleInfoEx(localeName, LOCALE_SISO639LANGNAME,
773 sIso639LangName, _countof(sIso639LangName)) == 0)
774 return FALSE;
775
776 // Determine if this is a neutral language
777 if (wcsncmp(sIso639LangName, localeName, _countof(sIso639LangName)) == 0)
778 return TRUE;
779
780 return FALSE;
781 }
782
783 /***
784 *BOOL TestDefaultLanguage - determine if default locale for language
785 *
786 *Purpose:
787 * Determines if the given locale name has the default sublanguage.
788 * If bTestPrimary is set, also allow TRUE when string contains an
789 * implicit sublanguage.
790 *
791 *Entry:
792 * localeName - locale name of locale to test
793 * bTestPrimary - set if testing if language is primary
794 *
795 *Exit:
796 * Returns TRUE if sublanguage is default for locale tested.
797 * If bTestPrimary set, TRUE is language has implied sublanguge.
798 *
799 *Exceptions:
800 *
801 *******************************************************************************/
TestDefaultLanguage(LPCWSTR localeName,BOOL bTestPrimary,__crt_qualified_locale_data * _psetloc_data)802 static BOOL TestDefaultLanguage(LPCWSTR localeName, BOOL bTestPrimary, __crt_qualified_locale_data* _psetloc_data)
803 {
804 if (!TestDefaultCountry (localeName))
805 {
806 // test if string contains an implicit sublanguage by
807 // having a character other than upper/lowercase letters.
808 if (bTestPrimary && GetPrimaryLen(_psetloc_data->pchLanguage) == (int)wcslen(_psetloc_data->pchLanguage))
809 return FALSE;
810 }
811
812 return TRUE;
813 }
814
815 /***
816 *int GetPrimaryLen - get length of primary language name
817 *
818 *Purpose:
819 * Determine primary language string length by scanning until
820 * first non-alphabetic character.
821 *
822 *Entry:
823 * pchLanguage - string to scan
824 *
825 *Exit:
826 * Returns length of primary language string.
827 *
828 *Exceptions:
829 *
830 *******************************************************************************/
GetPrimaryLen(LPCWSTR pchLanguage)831 static int GetPrimaryLen(LPCWSTR pchLanguage)
832 {
833 int len = 0;
834 wchar_t ch;
835
836 if (!pchLanguage)
837 return 0;
838
839 ch = *pchLanguage++;
840 while ((ch >= L'A' && ch <= L'Z') || (ch >= L'a' && ch <= L'z'))
841 {
842 len++;
843 ch = *pchLanguage++;
844 }
845
846 return len;
847 }
848
849
850
851 } // extern "C"
852