1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 "extensions/common/extension_l10n_util.h" 6 7 #include <algorithm> 8 #include <set> 9 #include <string> 10 #include <vector> 11 12 #include "base/file_util.h" 13 #include "base/files/file_enumerator.h" 14 #include "base/json/json_file_value_serializer.h" 15 #include "base/logging.h" 16 #include "base/memory/linked_ptr.h" 17 #include "base/strings/stringprintf.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/values.h" 20 #include "extensions/common/constants.h" 21 #include "extensions/common/error_utils.h" 22 #include "extensions/common/file_util.h" 23 #include "extensions/common/manifest_constants.h" 24 #include "extensions/common/message_bundle.h" 25 #include "third_party/icu/source/common/unicode/uloc.h" 26 #include "ui/base/l10n/l10n_util.h" 27 28 namespace errors = extensions::manifest_errors; 29 namespace keys = extensions::manifest_keys; 30 31 namespace { 32 33 // Loads contents of the messages file for given locale. If file is not found, 34 // or there was parsing error we return NULL and set |error|. 35 // Caller owns the returned object. 36 base::DictionaryValue* LoadMessageFile(const base::FilePath& locale_path, 37 const std::string& locale, 38 std::string* error) { 39 base::FilePath file = 40 locale_path.AppendASCII(locale).Append(extensions::kMessagesFilename); 41 JSONFileValueSerializer messages_serializer(file); 42 base::Value* dictionary = messages_serializer.Deserialize(NULL, error); 43 if (!dictionary) { 44 if (error->empty()) { 45 // JSONFileValueSerializer just returns NULL if file cannot be found. It 46 // doesn't set the error, so we have to do it. 47 *error = base::StringPrintf("Catalog file is missing for locale %s.", 48 locale.c_str()); 49 } else { 50 *error = extensions::ErrorUtils::FormatErrorMessage( 51 errors::kLocalesInvalidLocale, 52 base::UTF16ToUTF8(file.LossyDisplayName()), 53 *error); 54 } 55 } 56 57 return static_cast<base::DictionaryValue*>(dictionary); 58 } 59 60 // Localizes manifest value of string type for a given key. 61 bool LocalizeManifestValue(const std::string& key, 62 const extensions::MessageBundle& messages, 63 base::DictionaryValue* manifest, 64 std::string* error) { 65 std::string result; 66 if (!manifest->GetString(key, &result)) 67 return true; 68 69 if (!messages.ReplaceMessages(&result, error)) 70 return false; 71 72 manifest->SetString(key, result); 73 return true; 74 } 75 76 // Localizes manifest value of list type for a given key. 77 bool LocalizeManifestListValue(const std::string& key, 78 const extensions::MessageBundle& messages, 79 base::DictionaryValue* manifest, 80 std::string* error) { 81 base::ListValue* list = NULL; 82 if (!manifest->GetList(key, &list)) 83 return true; 84 85 bool ret = true; 86 for (size_t i = 0; i < list->GetSize(); ++i) { 87 std::string result; 88 if (list->GetString(i, &result)) { 89 if (messages.ReplaceMessages(&result, error)) 90 list->Set(i, new base::StringValue(result)); 91 else 92 ret = false; 93 } 94 } 95 return ret; 96 } 97 98 std::string& GetProcessLocale() { 99 CR_DEFINE_STATIC_LOCAL(std::string, locale, ()); 100 return locale; 101 } 102 103 } // namespace 104 105 namespace extension_l10n_util { 106 107 void SetProcessLocale(const std::string& locale) { 108 GetProcessLocale() = locale; 109 } 110 111 std::string GetDefaultLocaleFromManifest(const base::DictionaryValue& manifest, 112 std::string* error) { 113 std::string default_locale; 114 if (manifest.GetString(keys::kDefaultLocale, &default_locale)) 115 return default_locale; 116 117 *error = errors::kInvalidDefaultLocale; 118 return std::string(); 119 } 120 121 bool ShouldRelocalizeManifest(const base::DictionaryValue* manifest) { 122 if (!manifest) 123 return false; 124 125 if (!manifest->HasKey(keys::kDefaultLocale)) 126 return false; 127 128 std::string manifest_current_locale; 129 manifest->GetString(keys::kCurrentLocale, &manifest_current_locale); 130 return manifest_current_locale != CurrentLocaleOrDefault(); 131 } 132 133 bool LocalizeManifest(const extensions::MessageBundle& messages, 134 base::DictionaryValue* manifest, 135 std::string* error) { 136 // Initialize name. 137 std::string result; 138 if (!manifest->GetString(keys::kName, &result)) { 139 *error = errors::kInvalidName; 140 return false; 141 } 142 if (!LocalizeManifestValue(keys::kName, messages, manifest, error)) { 143 return false; 144 } 145 146 // Initialize short name. 147 if (!LocalizeManifestValue(keys::kShortName, messages, manifest, error)) 148 return false; 149 150 // Initialize description. 151 if (!LocalizeManifestValue(keys::kDescription, messages, manifest, error)) 152 return false; 153 154 // Initialize browser_action.default_title 155 std::string key(keys::kBrowserAction); 156 key.append("."); 157 key.append(keys::kPageActionDefaultTitle); 158 if (!LocalizeManifestValue(key, messages, manifest, error)) 159 return false; 160 161 // Initialize page_action.default_title 162 key.assign(keys::kPageAction); 163 key.append("."); 164 key.append(keys::kPageActionDefaultTitle); 165 if (!LocalizeManifestValue(key, messages, manifest, error)) 166 return false; 167 168 // Initialize omnibox.keyword. 169 if (!LocalizeManifestValue(keys::kOmniboxKeyword, messages, manifest, error)) 170 return false; 171 172 base::ListValue* file_handlers = NULL; 173 if (manifest->GetList(keys::kFileBrowserHandlers, &file_handlers)) { 174 key.assign(keys::kFileBrowserHandlers); 175 for (size_t i = 0; i < file_handlers->GetSize(); i++) { 176 base::DictionaryValue* handler = NULL; 177 if (!file_handlers->GetDictionary(i, &handler)) { 178 *error = errors::kInvalidFileBrowserHandler; 179 return false; 180 } 181 if (!LocalizeManifestValue( 182 keys::kPageActionDefaultTitle, messages, handler, error)) 183 return false; 184 } 185 } 186 187 // Initialize all input_components 188 base::ListValue* input_components = NULL; 189 if (manifest->GetList(keys::kInputComponents, &input_components)) { 190 for (size_t i = 0; i < input_components->GetSize(); ++i) { 191 base::DictionaryValue* module = NULL; 192 if (!input_components->GetDictionary(i, &module)) { 193 *error = errors::kInvalidInputComponents; 194 return false; 195 } 196 if (!LocalizeManifestValue(keys::kName, messages, module, error)) 197 return false; 198 if (!LocalizeManifestValue(keys::kDescription, messages, module, error)) 199 return false; 200 } 201 } 202 203 // Initialize app.launch.local_path. 204 if (!LocalizeManifestValue(keys::kLaunchLocalPath, messages, manifest, error)) 205 return false; 206 207 // Initialize app.launch.web_url. 208 if (!LocalizeManifestValue(keys::kLaunchWebURL, messages, manifest, error)) 209 return false; 210 211 // Initialize description of commmands. 212 base::DictionaryValue* commands_handler = NULL; 213 if (manifest->GetDictionary(keys::kCommands, &commands_handler)) { 214 for (base::DictionaryValue::Iterator iter(*commands_handler); 215 !iter.IsAtEnd(); 216 iter.Advance()) { 217 key.assign( 218 base::StringPrintf("commands.%s.description", iter.key().c_str())); 219 if (!LocalizeManifestValue(key, messages, manifest, error)) 220 return false; 221 } 222 } 223 224 // Initialize search_provider fields. 225 base::DictionaryValue* search_provider = NULL; 226 if (manifest->GetDictionary(keys::kOverrideSearchProvider, 227 &search_provider)) { 228 for (base::DictionaryValue::Iterator iter(*search_provider); 229 !iter.IsAtEnd(); 230 iter.Advance()) { 231 key.assign(base::StringPrintf( 232 "%s.%s", keys::kOverrideSearchProvider, iter.key().c_str())); 233 bool success = 234 (key == keys::kSettingsOverrideAlternateUrls) 235 ? LocalizeManifestListValue(key, messages, manifest, error) 236 : LocalizeManifestValue(key, messages, manifest, error); 237 if (!success) 238 return false; 239 } 240 } 241 242 // Initialize chrome_settings_overrides.homepage. 243 if (!LocalizeManifestValue( 244 keys::kOverrideHomepage, messages, manifest, error)) 245 return false; 246 247 // Initialize chrome_settings_overrides.startup_pages. 248 if (!LocalizeManifestListValue( 249 keys::kOverrideStartupPage, messages, manifest, error)) 250 return false; 251 252 // Add current locale key to the manifest, so we can overwrite prefs 253 // with new manifest when chrome locale changes. 254 manifest->SetString(keys::kCurrentLocale, CurrentLocaleOrDefault()); 255 return true; 256 } 257 258 bool LocalizeExtension(const base::FilePath& extension_path, 259 base::DictionaryValue* manifest, 260 std::string* error) { 261 DCHECK(manifest); 262 263 std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error); 264 265 scoped_ptr<extensions::MessageBundle> message_bundle( 266 extensions::file_util::LoadMessageBundle( 267 extension_path, default_locale, error)); 268 269 if (!message_bundle.get() && !error->empty()) 270 return false; 271 272 if (message_bundle.get() && 273 !LocalizeManifest(*message_bundle, manifest, error)) 274 return false; 275 276 return true; 277 } 278 279 bool AddLocale(const std::set<std::string>& chrome_locales, 280 const base::FilePath& locale_folder, 281 const std::string& locale_name, 282 std::set<std::string>* valid_locales, 283 std::string* error) { 284 // Accept name that starts with a . but don't add it to the list of supported 285 // locales. 286 if (locale_name.find(".") == 0) 287 return true; 288 if (chrome_locales.find(locale_name) == chrome_locales.end()) { 289 // Warn if there is an extension locale that's not in the Chrome list, 290 // but don't fail. 291 DLOG(WARNING) << base::StringPrintf("Supplied locale %s is not supported.", 292 locale_name.c_str()); 293 return true; 294 } 295 // Check if messages file is actually present (but don't check content). 296 if (base::PathExists(locale_folder.Append(extensions::kMessagesFilename))) { 297 valid_locales->insert(locale_name); 298 } else { 299 *error = base::StringPrintf("Catalog file is missing for locale %s.", 300 locale_name.c_str()); 301 return false; 302 } 303 304 return true; 305 } 306 307 std::string CurrentLocaleOrDefault() { 308 std::string current_locale = l10n_util::NormalizeLocale(GetProcessLocale()); 309 if (current_locale.empty()) 310 current_locale = "en"; 311 312 return current_locale; 313 } 314 315 void GetAllLocales(std::set<std::string>* all_locales) { 316 const std::vector<std::string>& available_locales = 317 l10n_util::GetAvailableLocales(); 318 // Add all parents of the current locale to the available locales set. 319 // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr. 320 for (size_t i = 0; i < available_locales.size(); ++i) { 321 std::vector<std::string> result; 322 l10n_util::GetParentLocales(available_locales[i], &result); 323 all_locales->insert(result.begin(), result.end()); 324 } 325 } 326 327 void GetAllFallbackLocales(const std::string& application_locale, 328 const std::string& default_locale, 329 std::vector<std::string>* all_fallback_locales) { 330 DCHECK(all_fallback_locales); 331 if (!application_locale.empty() && application_locale != default_locale) 332 l10n_util::GetParentLocales(application_locale, all_fallback_locales); 333 all_fallback_locales->push_back(default_locale); 334 } 335 336 bool GetValidLocales(const base::FilePath& locale_path, 337 std::set<std::string>* valid_locales, 338 std::string* error) { 339 std::set<std::string> chrome_locales; 340 GetAllLocales(&chrome_locales); 341 342 // Enumerate all supplied locales in the extension. 343 base::FileEnumerator locales( 344 locale_path, false, base::FileEnumerator::DIRECTORIES); 345 base::FilePath locale_folder; 346 while (!(locale_folder = locales.Next()).empty()) { 347 std::string locale_name = locale_folder.BaseName().MaybeAsASCII(); 348 if (locale_name.empty()) { 349 NOTREACHED(); 350 continue; // Not ASCII. 351 } 352 if (!AddLocale( 353 chrome_locales, locale_folder, locale_name, valid_locales, error)) { 354 return false; 355 } 356 } 357 358 if (valid_locales->empty()) { 359 *error = errors::kLocalesNoValidLocaleNamesListed; 360 return false; 361 } 362 363 return true; 364 } 365 366 extensions::MessageBundle* LoadMessageCatalogs( 367 const base::FilePath& locale_path, 368 const std::string& default_locale, 369 const std::string& application_locale, 370 const std::set<std::string>& valid_locales, 371 std::string* error) { 372 std::vector<std::string> all_fallback_locales; 373 GetAllFallbackLocales( 374 application_locale, default_locale, &all_fallback_locales); 375 376 std::vector<linked_ptr<base::DictionaryValue> > catalogs; 377 for (size_t i = 0; i < all_fallback_locales.size(); ++i) { 378 // Skip all parent locales that are not supplied. 379 if (valid_locales.find(all_fallback_locales[i]) == valid_locales.end()) 380 continue; 381 linked_ptr<base::DictionaryValue> catalog( 382 LoadMessageFile(locale_path, all_fallback_locales[i], error)); 383 if (!catalog.get()) { 384 // If locale is valid, but messages.json is corrupted or missing, return 385 // an error. 386 return NULL; 387 } else { 388 catalogs.push_back(catalog); 389 } 390 } 391 392 return extensions::MessageBundle::Create(catalogs, error); 393 } 394 395 bool ValidateExtensionLocales(const base::FilePath& extension_path, 396 const base::DictionaryValue* manifest, 397 std::string* error) { 398 std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error); 399 400 if (default_locale.empty()) 401 return true; 402 403 base::FilePath locale_path = extension_path.Append(extensions::kLocaleFolder); 404 405 std::set<std::string> valid_locales; 406 if (!GetValidLocales(locale_path, &valid_locales, error)) 407 return false; 408 409 for (std::set<std::string>::const_iterator locale = valid_locales.begin(); 410 locale != valid_locales.end(); 411 ++locale) { 412 std::string locale_error; 413 scoped_ptr<base::DictionaryValue> catalog( 414 LoadMessageFile(locale_path, *locale, &locale_error)); 415 416 if (!locale_error.empty()) { 417 if (!error->empty()) 418 error->append(" "); 419 error->append(locale_error); 420 } 421 } 422 423 return error->empty(); 424 } 425 426 bool ShouldSkipValidation(const base::FilePath& locales_path, 427 const base::FilePath& locale_path, 428 const std::set<std::string>& all_locales) { 429 // Since we use this string as a key in a DictionaryValue, be paranoid about 430 // skipping any strings with '.'. This happens sometimes, for example with 431 // '.svn' directories. 432 base::FilePath relative_path; 433 if (!locales_path.AppendRelativePath(locale_path, &relative_path)) { 434 NOTREACHED(); 435 return true; 436 } 437 std::string subdir = relative_path.MaybeAsASCII(); 438 if (subdir.empty()) 439 return true; // Non-ASCII. 440 441 if (std::find(subdir.begin(), subdir.end(), '.') != subdir.end()) 442 return true; 443 444 if (all_locales.find(subdir) == all_locales.end()) 445 return true; 446 447 return false; 448 } 449 450 ScopedLocaleForTest::ScopedLocaleForTest() 451 : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {} 452 453 ScopedLocaleForTest::ScopedLocaleForTest(const std::string& locale) 454 : locale_(extension_l10n_util::CurrentLocaleOrDefault()) { 455 extension_l10n_util::SetProcessLocale(locale); 456 } 457 458 ScopedLocaleForTest::~ScopedLocaleForTest() { 459 extension_l10n_util::SetProcessLocale(locale_); 460 } 461 462 } // namespace extension_l10n_util 463