1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Runtime.InteropServices;
9 using System.Security;
10 using System.Text;
11 
12 namespace System.Globalization
13 {
14     internal partial class CultureData
15     {
16         // ICU constants
17         const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value
18         const int ICU_ULOC_FULLNAME_CAPACITY = 157;           // max size of locale name
19         const string ICU_COLLATION_KEYWORD = "@collation=";
20 
21 
22         /// <summary>
23         /// This method uses the sRealName field (which is initialized by the constructor before this is called) to
24         /// initialize the rest of the state of CultureData based on the underlying OS globalization library.
25         /// </summary>
InitCultureData()26         private unsafe bool InitCultureData()
27         {
28             Debug.Assert(_sRealName != null);
29 
30             Debug.Assert(!GlobalizationMode.Invariant);
31 
32             string alternateSortName = string.Empty;
33             string realNameBuffer = _sRealName;
34 
35             // Basic validation
36             if (realNameBuffer.Contains("@"))
37             {
38                 return false; // don't allow ICU variants to come in directly
39             }
40 
41             // Replace _ (alternate sort) with @collation= for ICU
42             int index = realNameBuffer.IndexOf('_');
43             if (index > 0)
44             {
45                 if (index >= (realNameBuffer.Length - 1) // must have characters after _
46                     || realNameBuffer.Substring(index + 1).Contains("_")) // only one _ allowed
47                 {
48                     return false; // fail
49                 }
50                 alternateSortName = realNameBuffer.Substring(index + 1);
51                 realNameBuffer = realNameBuffer.Substring(0, index) + ICU_COLLATION_KEYWORD + alternateSortName;
52             }
53 
54             // Get the locale name from ICU
55             if (!GetLocaleName(realNameBuffer, out _sWindowsName))
56             {
57                 return false; // fail
58             }
59 
60             // Replace the ICU collation keyword with an _
61             index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal);
62             if (index >= 0)
63             {
64                 _sName = _sWindowsName.Substring(0, index) + "_" + alternateSortName;
65             }
66             else
67             {
68                 _sName = _sWindowsName;
69             }
70             _sRealName = _sName;
71 
72             _iLanguage = this.ILANGUAGE;
73             if (_iLanguage == 0)
74             {
75                 _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
76             }
77 
78             _bNeutral = (this.SISO3166CTRYNAME.Length == 0);
79 
80             _sSpecificCulture = _bNeutral ? LocaleData.GetSpecificCultureName(_sRealName) : _sRealName;
81 
82             // Remove the sort from sName unless custom culture
83             if (index>0 && !_bNeutral && !IsCustomCultureId(_iLanguage))
84             {
85                 _sName = _sWindowsName.Substring(0, index);
86             }
87             return true;
88         }
89 
GetLocaleName(string localeName, out string windowsName)90         internal static bool GetLocaleName(string localeName, out string windowsName)
91         {
92             // Get the locale name from ICU
93             StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY);
94             if (!Interop.GlobalizationInterop.GetLocaleName(localeName, sb, sb.Capacity))
95             {
96                 StringBuilderCache.Release(sb);
97                 windowsName = null;
98                 return false; // fail
99             }
100 
101             // Success - use the locale name returned which may be different than realNameBuffer (casing)
102             windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls
103             return true;
104         }
105 
GetDefaultLocaleName(out string windowsName)106         internal static bool GetDefaultLocaleName(out string windowsName)
107         {
108             // Get the default (system) locale name from ICU
109             StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY);
110             if (!Interop.GlobalizationInterop.GetDefaultLocaleName(sb, sb.Capacity))
111             {
112                 StringBuilderCache.Release(sb);
113                 windowsName = null;
114                 return false; // fail
115             }
116 
117             // Success - use the locale name returned which may be different than realNameBuffer (casing)
118             windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls
119             return true;
120         }
121 
GetLocaleInfo(LocaleStringData type)122         private string GetLocaleInfo(LocaleStringData type)
123         {
124             Debug.Assert(!GlobalizationMode.Invariant);
125 
126             Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo] Expected _sWindowsName to be populated already");
127             return GetLocaleInfo(_sWindowsName, type);
128         }
129 
130         // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
131         // "windows" name, which can be specific for downlevel (< windows 7) os's.
GetLocaleInfo(string localeName, LocaleStringData type)132         private string GetLocaleInfo(string localeName, LocaleStringData type)
133         {
134             Debug.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null");
135 
136             switch (type)
137             {
138                 case LocaleStringData.NegativeInfinitySymbol:
139                     // not an equivalent in ICU; prefix the PositiveInfinitySymbol with NegativeSign
140                     return GetLocaleInfo(localeName, LocaleStringData.NegativeSign) +
141                         GetLocaleInfo(localeName, LocaleStringData.PositiveInfinitySymbol);
142             }
143 
144             StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
145 
146             bool result = Interop.GlobalizationInterop.GetLocaleInfoString(localeName, (uint)type, sb, sb.Capacity);
147             if (!result)
148             {
149                 // Failed, just use empty string
150                 StringBuilderCache.Release(sb);
151                 Debug.Fail("[CultureData.GetLocaleInfo(LocaleStringData)] Failed");
152                 return String.Empty;
153             }
154             return StringBuilderCache.GetStringAndRelease(sb);
155         }
156 
GetLocaleInfo(LocaleNumberData type)157         private int GetLocaleInfo(LocaleNumberData type)
158         {
159             Debug.Assert(!GlobalizationMode.Invariant);
160 
161             Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleNumberData)] Expected _sWindowsName to be populated already");
162 
163             switch (type)
164             {
165                 case LocaleNumberData.CalendarType:
166                     // returning 0 will cause the first supported calendar to be returned, which is the preferred calendar
167                     return 0;
168             }
169 
170 
171             int value = 0;
172             bool result = Interop.GlobalizationInterop.GetLocaleInfoInt(_sWindowsName, (uint)type, ref value);
173             if (!result)
174             {
175                 // Failed, just use 0
176                 Debug.Fail("[CultureData.GetLocaleInfo(LocaleNumberData)] failed");
177             }
178 
179             return value;
180         }
181 
GetLocaleInfo(LocaleGroupingData type)182         private int[] GetLocaleInfo(LocaleGroupingData type)
183         {
184             Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleGroupingData)] Expected _sWindowsName to be populated already");
185 
186             int primaryGroupingSize = 0;
187             int secondaryGroupingSize = 0;
188             bool result = Interop.GlobalizationInterop.GetLocaleInfoGroupingSizes(_sWindowsName, (uint)type, ref primaryGroupingSize, ref secondaryGroupingSize);
189             if (!result)
190             {
191                 Debug.Fail("[CultureData.GetLocaleInfo(LocaleGroupingData type)] failed");
192             }
193 
194             if (secondaryGroupingSize == 0)
195             {
196                 return new int[] { primaryGroupingSize };
197             }
198 
199             return new int[] { primaryGroupingSize, secondaryGroupingSize };
200         }
201 
GetTimeFormatString()202         private string GetTimeFormatString()
203         {
204             return GetTimeFormatString(false);
205         }
206 
GetTimeFormatString(bool shortFormat)207         private string GetTimeFormatString(bool shortFormat)
208         {
209             Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already");
210 
211             StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
212 
213             bool result = Interop.GlobalizationInterop.GetLocaleTimeFormat(_sWindowsName, shortFormat, sb, sb.Capacity);
214             if (!result)
215             {
216                 // Failed, just use empty string
217                 StringBuilderCache.Release(sb);
218                 Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
219                 return String.Empty;
220             }
221 
222             return ConvertIcuTimeFormatString(StringBuilderCache.GetStringAndRelease(sb));
223         }
224 
GetFirstDayOfWeek()225         private int GetFirstDayOfWeek()
226         {
227             return this.GetLocaleInfo(LocaleNumberData.FirstDayOfWeek);
228         }
229 
GetTimeFormats()230         private String[] GetTimeFormats()
231         {
232             string format = GetTimeFormatString(false);
233             return new string[] { format };
234         }
235 
GetShortTimeFormats()236         private String[] GetShortTimeFormats()
237         {
238             string format = GetTimeFormatString(true);
239             return new string[] { format };
240         }
241 
GetCultureDataFromRegionName(String regionName)242         private static CultureData GetCultureDataFromRegionName(String regionName)
243         {
244             // no support to lookup by region name, other than the hard-coded list in CultureData
245             return null;
246         }
247 
GetLanguageDisplayName(string cultureName)248         private static string GetLanguageDisplayName(string cultureName)
249         {
250             return new CultureInfo(cultureName)._cultureData.GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName);
251         }
252 
GetRegionDisplayName(string isoCountryCode)253         private static string GetRegionDisplayName(string isoCountryCode)
254         {
255             // use the fallback which is to return NativeName
256             return null;
257         }
258 
GetUserDefaultCulture()259         private static CultureInfo GetUserDefaultCulture()
260         {
261             return CultureInfo.GetUserDefaultCulture();
262         }
263 
ConvertIcuTimeFormatString(string icuFormatString)264         private static string ConvertIcuTimeFormatString(string icuFormatString)
265         {
266             StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY);
267             bool amPmAdded = false;
268 
269             for (int i = 0; i < icuFormatString.Length; i++)
270             {
271                 switch(icuFormatString[i])
272                 {
273                     case ':':
274                     case '.':
275                     case 'H':
276                     case 'h':
277                     case 'm':
278                     case 's':
279                         sb.Append(icuFormatString[i]);
280                         break;
281 
282                     case ' ':
283                     case '\u00A0':
284                         // Convert nonbreaking spaces into regular spaces
285                         sb.Append(' ');
286                         break;
287 
288                     case 'a': // AM/PM
289                         if (!amPmAdded)
290                         {
291                             amPmAdded = true;
292                             sb.Append("tt");
293                         }
294                         break;
295 
296                 }
297             }
298 
299             return StringBuilderCache.GetStringAndRelease(sb);
300         }
301 
LCIDToLocaleName(int culture)302         private static string LCIDToLocaleName(int culture)
303         {
304             Debug.Assert(!GlobalizationMode.Invariant);
305 
306             return LocaleData.LCIDToLocaleName(culture);
307         }
308 
LocaleNameToLCID(string cultureName)309         private static int LocaleNameToLCID(string cultureName)
310         {
311             Debug.Assert(!GlobalizationMode.Invariant);
312 
313             int lcid = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.Lcid);
314             return lcid == -1 ? CultureInfo.LOCALE_CUSTOM_UNSPECIFIED : lcid;
315         }
316 
GetAnsiCodePage(string cultureName)317         private static int GetAnsiCodePage(string cultureName)
318         {
319             int ansiCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.AnsiCodePage);
320             return ansiCodePage == -1 ? CultureData.Invariant.IDEFAULTANSICODEPAGE : ansiCodePage;
321         }
322 
GetOemCodePage(string cultureName)323         private static int GetOemCodePage(string cultureName)
324         {
325             int oemCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.OemCodePage);
326             return oemCodePage == -1 ? CultureData.Invariant.IDEFAULTOEMCODEPAGE : oemCodePage;
327         }
328 
GetMacCodePage(string cultureName)329         private static int GetMacCodePage(string cultureName)
330         {
331             int macCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.MacCodePage);
332             return macCodePage == -1 ? CultureData.Invariant.IDEFAULTMACCODEPAGE : macCodePage;
333         }
334 
GetEbcdicCodePage(string cultureName)335         private static int GetEbcdicCodePage(string cultureName)
336         {
337             int ebcdicCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.EbcdicCodePage);
338             return ebcdicCodePage == -1 ? CultureData.Invariant.IDEFAULTEBCDICCODEPAGE : ebcdicCodePage;
339         }
340 
GetGeoId(string cultureName)341         private static int GetGeoId(string cultureName)
342         {
343             int geoId = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.GeoId);
344             return geoId == -1 ? CultureData.Invariant.IGEOID : geoId;
345         }
346 
GetDigitSubstitution(string cultureName)347         private static int GetDigitSubstitution(string cultureName)
348         {
349             int digitSubstitution = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.DigitSubstitution);
350             return digitSubstitution == -1 ? (int) DigitShapes.None : digitSubstitution;
351         }
352 
GetThreeLetterWindowsLanguageName(string cultureName)353         private static string GetThreeLetterWindowsLanguageName(string cultureName)
354         {
355             string langName = LocaleData.GetThreeLetterWindowsLangageName(cultureName);
356             return langName == null ? "ZZZ" /* default lang name */ : langName;
357         }
358 
EnumCultures(CultureTypes types)359         private static CultureInfo[] EnumCultures(CultureTypes types)
360         {
361             Debug.Assert(!GlobalizationMode.Invariant);
362 
363             if ((types & (CultureTypes.NeutralCultures | CultureTypes.SpecificCultures)) == 0)
364             {
365                 return Array.Empty<CultureInfo>();
366             }
367 
368             int bufferLength = Interop.GlobalizationInterop.GetLocales(null, 0);
369             if (bufferLength <= 0)
370             {
371                 return Array.Empty<CultureInfo>();
372             }
373 
374             Char [] chars = new Char[bufferLength];
375 
376             bufferLength = Interop.GlobalizationInterop.GetLocales(chars, bufferLength);
377             if (bufferLength <= 0)
378             {
379                 return Array.Empty<CultureInfo>();
380             }
381 
382             bool enumNeutrals   = (types & CultureTypes.NeutralCultures) != 0;
383             bool enumSpecificss = (types & CultureTypes.SpecificCultures) != 0;
384 
385             List<CultureInfo> list = new List<CultureInfo>();
386             if (enumNeutrals)
387             {
388                 list.Add(CultureInfo.InvariantCulture);
389             }
390 
391             int index = 0;
392             while (index < bufferLength)
393             {
394                 int length = (int) chars[index++];
395                 if (index + length <= bufferLength)
396                 {
397                     CultureInfo ci = CultureInfo.GetCultureInfo(new String(chars, index, length));
398                     if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecificss && !ci.IsNeutralCulture))
399                     {
400                         list.Add(ci);
401                     }
402                 }
403 
404                 index += length;
405             }
406 
407             return list.ToArray();
408         }
409 
GetConsoleFallbackName(string cultureName)410         private static string GetConsoleFallbackName(string cultureName)
411         {
412             return LocaleData.GetConsoleUICulture(cultureName);
413         }
414 
415         internal bool IsFramework // not applicable on Linux based systems
416         {
417             get { return false; }
418         }
419 
420         internal bool IsWin32Installed // not applicable on Linux based systems
421         {
422             get { return false; }
423         }
424 
425         internal bool IsReplacementCulture // not applicable on Linux based systems
426         {
427             get { return false; }
428         }
429     }
430 }
431