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