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