blob: d0eba168725b498aaa636ac1fde338d994df87c2 [file] [log] [blame]
Avi Drissman8ba1bad2022-09-13 19:22:361// Copyright 2018 The Chromium Authors
Scott Chen68aafc32018-11-20 20:19:152// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/country_codes/country_codes.h"
6
Xiaohan Wangbca91f92022-01-15 19:56:217#include "build/build_config.h"
8
9#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
Scott Chen68aafc32018-11-20 20:19:1510#include <locale.h>
11#endif
12
13#include "base/strings/string_util.h"
Matt Jonesc5b8e262022-06-10 16:48:2314#include "components/prefs/pref_registry_simple.h"
Scott Chen68aafc32018-11-20 20:19:1515#include "components/prefs/pref_service.h"
16
Xiaohan Wangbca91f92022-01-15 19:56:2117#if BUILDFLAG(IS_WIN)
Scott Chen68aafc32018-11-20 20:19:1518#include <windows.h>
19#undef IN // On Windows, windef.h defines this, which screws up "India" cases.
Xiaohan Wangbca91f92022-01-15 19:56:2120#elif BUILDFLAG(IS_APPLE)
Avi Drissmana09d7dd2023-08-17 16:26:5821#include "base/apple/scoped_cftyperef.h"
Scott Chen68aafc32018-11-20 20:19:1522#endif
23
Xiaohan Wangbca91f92022-01-15 19:56:2124#if BUILDFLAG(IS_ANDROID)
Scott Chen68aafc32018-11-20 20:19:1525#include "base/android/locale_utils.h"
26#endif
27
28namespace country_codes {
29
30namespace {
31
Scott Chen2c0afe52019-04-04 00:37:3832// TODO(hcarmona/johntlee): remove this function after confirming if it only
33// pertains to obsolete OSes.
Scott Chen68aafc32018-11-20 20:19:1534int CountryCharsToCountryIDWithUpdate(char c1, char c2) {
35 // SPECIAL CASE: In 2003, Yugoslavia renamed itself to Serbia and Montenegro.
36 // Serbia and Montenegro dissolved their union in June 2006. Yugoslavia was
37 // ISO 'YU' and Serbia and Montenegro were ISO 'CS'. Serbia was subsequently
38 // issued 'RS' and Montenegro 'ME'. Windows XP and Mac OS X Leopard still use
39 // the value 'YU'. If we get a value of 'YU' or 'CS' we will map it to 'RS'.
40 if ((c1 == 'Y' && c2 == 'U') || (c1 == 'C' && c2 == 'S')) {
41 c1 = 'R';
42 c2 = 'S';
43 }
44
45 // SPECIAL CASE: Timor-Leste changed from 'TP' to 'TL' in 2002. Windows XP
46 // predates this; we therefore map this value.
47 if (c1 == 'T' && c2 == 'P')
48 c2 = 'L';
49
50 return CountryCharsToCountryID(c1, c2);
51}
52
Xiaohan Wangbca91f92022-01-15 19:56:2153#if BUILDFLAG(IS_WIN)
Scott Chen68aafc32018-11-20 20:19:1554
55// For reference, a list of GeoIDs can be found at
56// http://msdn.microsoft.com/en-us/library/dd374073.aspx .
57int GeoIDToCountryID(GEOID geo_id) {
58 const int kISOBufferSize = 3; // Two plus one for the terminator.
59 wchar_t isobuf[kISOBufferSize] = {};
60 int retval = GetGeoInfo(geo_id, GEO_ISO2, isobuf, kISOBufferSize, 0);
61
62 if (retval == kISOBufferSize && !(isobuf[0] == L'X' && isobuf[1] == L'X')) {
63 return CountryCharsToCountryIDWithUpdate(static_cast<char>(isobuf[0]),
64 static_cast<char>(isobuf[1]));
65 }
66
67 // Various locations have ISO codes that Windows does not return.
68 switch (geo_id) {
69 case 0x144: // Guernsey
70 return CountryCharsToCountryID('G', 'G');
71 case 0x148: // Jersey
72 return CountryCharsToCountryID('J', 'E');
73 case 0x3B16: // Isle of Man
74 return CountryCharsToCountryID('I', 'M');
75
76 // 'UM' (U.S. Minor Outlying Islands)
77 case 0x7F: // Johnston Atoll
78 case 0x102: // Wake Island
79 case 0x131: // Baker Island
80 case 0x146: // Howland Island
81 case 0x147: // Jarvis Island
82 case 0x149: // Kingman Reef
83 case 0x152: // Palmyra Atoll
84 case 0x52FA: // Midway Islands
85 return CountryCharsToCountryID('U', 'M');
86
87 // 'SH' (Saint Helena)
88 case 0x12F: // Ascension Island
89 case 0x15C: // Tristan da Cunha
90 return CountryCharsToCountryID('S', 'H');
91
92 // 'IO' (British Indian Ocean Territory)
93 case 0x13A: // Diego Garcia
94 return CountryCharsToCountryID('I', 'O');
95
96 // Other cases where there is no ISO country code; we assign countries that
97 // can serve as reasonable defaults.
98 case 0x154: // Rota Island
99 case 0x155: // Saipan
100 case 0x15A: // Tinian Island
101 return CountryCharsToCountryID('U', 'S');
102 case 0x134: // Channel Islands
103 return CountryCharsToCountryID('G', 'B');
104 case 0x143: // Guantanamo Bay
105 default:
106 return kCountryIDUnknown;
107 }
108}
109
Xiaohan Wangbca91f92022-01-15 19:56:21110#endif // BUILDFLAG(IS_WIN)
Scott Chen68aafc32018-11-20 20:19:15111
112} // namespace
113
114const char kCountryIDAtInstall[] = "countryid_at_install";
115
Scott Chen68aafc32018-11-20 20:19:15116int CountryStringToCountryID(const std::string& country) {
117 return (country.length() == 2)
118 ? CountryCharsToCountryIDWithUpdate(country[0], country[1])
119 : kCountryIDUnknown;
120}
121
Scott Chen68aafc32018-11-20 20:19:15122int GetCountryIDFromPrefs(PrefService* prefs) {
123 if (!prefs)
124 return GetCurrentCountryID();
125
126 // Cache first run Country ID value in prefs, and use it afterwards. This
127 // ensures that just because the user moves around, we won't automatically
128 // make major changes to their available search providers, which would feel
129 // surprising.
130 if (!prefs->HasPrefPath(country_codes::kCountryIDAtInstall)) {
131 prefs->SetInteger(country_codes::kCountryIDAtInstall,
132 GetCurrentCountryID());
133 }
134 return prefs->GetInteger(country_codes::kCountryIDAtInstall);
135}
136
Matt Jonesc5b8e262022-06-10 16:48:23137void RegisterProfilePrefs(PrefRegistrySimple* registry) {
Scott Chen68aafc32018-11-20 20:19:15138 registry->RegisterIntegerPref(country_codes::kCountryIDAtInstall,
139 kCountryIDUnknown);
140}
141
Xiaohan Wangbca91f92022-01-15 19:56:21142#if BUILDFLAG(IS_WIN)
Scott Chen68aafc32018-11-20 20:19:15143
144int GetCurrentCountryID() {
Chris Davis2c48b26c2023-01-25 01:18:49145 // Calls to GetCurrentCountryID occur fairly frequently and incur a heavy
146 // registry hit within the GetUserGeoID api call. Registry hits can be
147 // impactful to perf, particularly on virtualized systems. To mitigate this
148 // we store the result of the first call in a static. The Id is only
149 // updated by calls to SetUserGeoID or the user manually updating the
150 // language and region settings. It is expected that if it changes the user
151 // would need to restart applications to ensure the updated value is
152 // respected.
153 static int id = GeoIDToCountryID(GetUserGeoID(GEOCLASS_NATION));
154 return id;
Scott Chen68aafc32018-11-20 20:19:15155}
156
Xiaohan Wangbca91f92022-01-15 19:56:21157#elif BUILDFLAG(IS_APPLE)
Scott Chen68aafc32018-11-20 20:19:15158
159int GetCurrentCountryID() {
Avi Drissman28154a62023-08-22 04:06:45160 base::apple::ScopedCFTypeRef<CFLocaleRef> locale(CFLocaleCopyCurrent());
Scott Chen68aafc32018-11-20 20:19:15161 CFStringRef country =
162 (CFStringRef)CFLocaleGetValue(locale.get(), kCFLocaleCountryCode);
163 if (!country)
164 return kCountryIDUnknown;
165
166 UniChar isobuf[2];
167 CFRange char_range = CFRangeMake(0, 2);
168 CFStringGetCharacters(country, char_range, isobuf);
169
170 return CountryCharsToCountryIDWithUpdate(static_cast<char>(isobuf[0]),
171 static_cast<char>(isobuf[1]));
172}
173
Xiaohan Wangbca91f92022-01-15 19:56:21174#elif BUILDFLAG(IS_ANDROID)
Scott Chen68aafc32018-11-20 20:19:15175
176int GetCurrentCountryID() {
177 return CountryStringToCountryID(base::android::GetDefaultCountryCode());
178}
179
Xiaohan Wangbca91f92022-01-15 19:56:21180#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
Scott Chen68aafc32018-11-20 20:19:15181
182int GetCurrentCountryID() {
183 const char* locale = setlocale(LC_MESSAGES, nullptr);
184 if (!locale)
185 return kCountryIDUnknown;
186
187 // The format of a locale name is:
188 // language[_territory][.codeset][@modifier], where territory is an ISO 3166
189 // country code, which is what we want.
190
191 // First remove the language portion.
192 std::string locale_str(locale);
193 size_t territory_delim = locale_str.find('_');
194 if (territory_delim == std::string::npos)
195 return kCountryIDUnknown;
196 locale_str.erase(0, territory_delim + 1);
197
198 // Next remove any codeset/modifier portion and uppercase.
199 return CountryStringToCountryID(
200 base::ToUpperASCII(locale_str.substr(0, locale_str.find_first_of(".@"))));
201}
202
203#endif // OS_*
204
Matt Jones8b1ecd372022-11-30 18:32:02205std::string CountryIDToCountryString(int country_id) {
206 // We only use the lowest 16 bits to build two ASCII characters. If there is
207 // more than that, the ID is invalid. The check for positive integers also
208 // handles the |kCountryIDUnknown| case.
209 if ((country_id & 0xFFFF) != country_id || country_id < 0)
210 return kCountryCodeUnknown;
211
212 // Decode the country code string from the provided integer. The first two
213 // bytes of the country ID represent two ASCII chars.
214 std::string country_code = {static_cast<char>(country_id >> 8),
215 static_cast<char>(country_id)};
216 country_code = base::ToUpperASCII(country_code);
217
218 // Validate the code that was produced by feeding it back into the system.
219 return (CountryStringToCountryID(country_code) == country_id)
220 ? country_code
221 : kCountryCodeUnknown;
222}
223
224std::string GetCurrentCountryCode() {
225 return CountryIDToCountryString(GetCurrentCountryID());
226}
227
Scott Chen68aafc32018-11-20 20:19:15228} // namespace country_codes