Avi Drissman | 8ba1bad | 2022-09-13 19:22:36 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 2 | // 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 Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 7 | #include "build/build_config.h" |
| 8 | |
| 9 | #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 10 | #include <locale.h> |
| 11 | #endif |
| 12 | |
| 13 | #include "base/strings/string_util.h" |
Matt Jones | c5b8e26 | 2022-06-10 16:48:23 | [diff] [blame] | 14 | #include "components/prefs/pref_registry_simple.h" |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 15 | #include "components/prefs/pref_service.h" |
| 16 | |
Xiaohan Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 17 | #if BUILDFLAG(IS_WIN) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 18 | #include <windows.h> |
| 19 | #undef IN // On Windows, windef.h defines this, which screws up "India" cases. |
Xiaohan Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 20 | #elif BUILDFLAG(IS_APPLE) |
Avi Drissman | a09d7dd | 2023-08-17 16:26:58 | [diff] [blame] | 21 | #include "base/apple/scoped_cftyperef.h" |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 22 | #endif |
| 23 | |
Xiaohan Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 24 | #if BUILDFLAG(IS_ANDROID) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 25 | #include "base/android/locale_utils.h" |
| 26 | #endif |
| 27 | |
| 28 | namespace country_codes { |
| 29 | |
| 30 | namespace { |
| 31 | |
Scott Chen | 2c0afe5 | 2019-04-04 00:37:38 | [diff] [blame] | 32 | // TODO(hcarmona/johntlee): remove this function after confirming if it only |
| 33 | // pertains to obsolete OSes. |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 34 | int 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 Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 53 | #if BUILDFLAG(IS_WIN) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 54 | |
| 55 | // For reference, a list of GeoIDs can be found at |
| 56 | // http://msdn.microsoft.com/en-us/library/dd374073.aspx . |
| 57 | int 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 Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 110 | #endif // BUILDFLAG(IS_WIN) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 111 | |
| 112 | } // namespace |
| 113 | |
| 114 | const char kCountryIDAtInstall[] = "countryid_at_install"; |
| 115 | |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 116 | int CountryStringToCountryID(const std::string& country) { |
| 117 | return (country.length() == 2) |
| 118 | ? CountryCharsToCountryIDWithUpdate(country[0], country[1]) |
| 119 | : kCountryIDUnknown; |
| 120 | } |
| 121 | |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 122 | int 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 Jones | c5b8e26 | 2022-06-10 16:48:23 | [diff] [blame] | 137 | void RegisterProfilePrefs(PrefRegistrySimple* registry) { |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 138 | registry->RegisterIntegerPref(country_codes::kCountryIDAtInstall, |
| 139 | kCountryIDUnknown); |
| 140 | } |
| 141 | |
Xiaohan Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 142 | #if BUILDFLAG(IS_WIN) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 143 | |
| 144 | int GetCurrentCountryID() { |
Chris Davis | 2c48b26c | 2023-01-25 01:18:49 | [diff] [blame] | 145 | // 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 Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 155 | } |
| 156 | |
Xiaohan Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 157 | #elif BUILDFLAG(IS_APPLE) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 158 | |
| 159 | int GetCurrentCountryID() { |
Avi Drissman | 28154a6 | 2023-08-22 04:06:45 | [diff] [blame] | 160 | base::apple::ScopedCFTypeRef<CFLocaleRef> locale(CFLocaleCopyCurrent()); |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 161 | 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 Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 174 | #elif BUILDFLAG(IS_ANDROID) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 175 | |
| 176 | int GetCurrentCountryID() { |
| 177 | return CountryStringToCountryID(base::android::GetDefaultCountryCode()); |
| 178 | } |
| 179 | |
Xiaohan Wang | bca91f9 | 2022-01-15 19:56:21 | [diff] [blame] | 180 | #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 181 | |
| 182 | int 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 Jones | 8b1ecd37 | 2022-11-30 18:32:02 | [diff] [blame] | 205 | std::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 | |
| 224 | std::string GetCurrentCountryCode() { |
| 225 | return CountryIDToCountryString(GetCurrentCountryID()); |
| 226 | } |
| 227 | |
Scott Chen | 68aafc3 | 2018-11-20 20:19:15 | [diff] [blame] | 228 | } // namespace country_codes |