• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/logging.h"
14 #include "base/memory/linked_ptr.h"
15 #include "base/string_util.h"
16 #include "base/values.h"
17 #include "chrome/common/extensions/extension.h"
18 #include "chrome/common/extensions/extension_constants.h"
19 #include "chrome/common/extensions/extension_file_util.h"
20 #include "chrome/common/extensions/extension_message_bundle.h"
21 #include "chrome/common/url_constants.h"
22 #include "content/common/json_value_serializer.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "unicode/uloc.h"
25 
26 namespace errors = extension_manifest_errors;
27 namespace keys = extension_manifest_keys;
28 
GetProcessLocale()29 static std::string* GetProcessLocale() {
30   static std::string locale;
31   return &locale;
32 }
33 
34 namespace extension_l10n_util {
35 
SetProcessLocale(const std::string & locale)36 void SetProcessLocale(const std::string& locale) {
37   *(GetProcessLocale()) = locale;
38 }
39 
GetDefaultLocaleFromManifest(const DictionaryValue & manifest,std::string * error)40 std::string GetDefaultLocaleFromManifest(const DictionaryValue& manifest,
41                                          std::string* error) {
42   std::string default_locale;
43   if (manifest.GetString(keys::kDefaultLocale, &default_locale))
44     return default_locale;
45 
46   *error = errors::kInvalidDefaultLocale;
47   return "";
48 
49 }
50 
ShouldRelocalizeManifest(const ExtensionInfo & info)51 bool ShouldRelocalizeManifest(const ExtensionInfo& info) {
52   DictionaryValue* manifest = info.extension_manifest.get();
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.
LocalizeManifestValue(const std::string & key,const ExtensionMessageBundle & messages,DictionaryValue * manifest,std::string * error)65 static bool LocalizeManifestValue(const std::string& key,
66                                   const ExtensionMessageBundle& messages,
67                                   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 
LocalizeManifest(const ExtensionMessageBundle & messages,DictionaryValue * manifest,std::string * error)80 bool LocalizeManifest(const ExtensionMessageBundle& messages,
81                       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   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       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   // Add current locale key to the manifest, so we can overwrite prefs
130   // with new manifest when chrome locale changes.
131   manifest->SetString(keys::kCurrentLocale, CurrentLocaleOrDefault());
132   return true;
133 }
134 
LocalizeExtension(const FilePath & extension_path,DictionaryValue * manifest,std::string * error)135 bool LocalizeExtension(const FilePath& extension_path,
136                        DictionaryValue* manifest,
137                        std::string* error) {
138   DCHECK(manifest);
139 
140   std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error);
141 
142   scoped_ptr<ExtensionMessageBundle> message_bundle(
143       extension_file_util::LoadExtensionMessageBundle(
144           extension_path, default_locale, error));
145 
146   if (!message_bundle.get() && !error->empty())
147     return false;
148 
149   if (message_bundle.get() &&
150       !LocalizeManifest(*message_bundle, manifest, error))
151     return false;
152 
153   return true;
154 }
155 
AddLocale(const std::set<std::string> & chrome_locales,const FilePath & locale_folder,const std::string & locale_name,std::set<std::string> * valid_locales,std::string * error)156 bool AddLocale(const std::set<std::string>& chrome_locales,
157                const FilePath& locale_folder,
158                const std::string& locale_name,
159                std::set<std::string>* valid_locales,
160                std::string* error) {
161   // Accept name that starts with a . but don't add it to the list of supported
162   // locales.
163   if (locale_name.find(".") == 0)
164     return true;
165   if (chrome_locales.find(locale_name) == chrome_locales.end()) {
166     // Warn if there is an extension locale that's not in the Chrome list,
167     // but don't fail.
168     LOG(WARNING) << base::StringPrintf("Supplied locale %s is not supported.",
169                                        locale_name.c_str());
170     return true;
171   }
172   // Check if messages file is actually present (but don't check content).
173   if (file_util::PathExists(
174       locale_folder.Append(Extension::kMessagesFilename))) {
175     valid_locales->insert(locale_name);
176   } else {
177     *error = base::StringPrintf("Catalog file is missing for locale %s.",
178                                 locale_name.c_str());
179     return false;
180   }
181 
182   return true;
183 }
184 
CurrentLocaleOrDefault()185 std::string CurrentLocaleOrDefault() {
186   std::string current_locale = l10n_util::NormalizeLocale(*GetProcessLocale());
187   if (current_locale.empty())
188     current_locale = "en";
189 
190   return current_locale;
191 }
192 
GetAllLocales(std::set<std::string> * all_locales)193 void GetAllLocales(std::set<std::string>* all_locales) {
194   const std::vector<std::string>& available_locales =
195       l10n_util::GetAvailableLocales();
196   // Add all parents of the current locale to the available locales set.
197   // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
198   for (size_t i = 0; i < available_locales.size(); ++i) {
199     std::vector<std::string> result;
200     l10n_util::GetParentLocales(available_locales[i], &result);
201     all_locales->insert(result.begin(), result.end());
202   }
203 }
204 
GetValidLocales(const FilePath & locale_path,std::set<std::string> * valid_locales,std::string * error)205 bool GetValidLocales(const FilePath& locale_path,
206                      std::set<std::string>* valid_locales,
207                      std::string* error) {
208   static std::set<std::string> chrome_locales;
209   GetAllLocales(&chrome_locales);
210 
211   // Enumerate all supplied locales in the extension.
212   file_util::FileEnumerator locales(locale_path,
213                                     false,
214                                     file_util::FileEnumerator::DIRECTORIES);
215   FilePath locale_folder;
216   while (!(locale_folder = locales.Next()).empty()) {
217     std::string locale_name = locale_folder.BaseName().MaybeAsASCII();
218     if (locale_name.empty()) {
219       NOTREACHED();
220       continue;  // Not ASCII.
221     }
222     if (!AddLocale(chrome_locales,
223                    locale_folder,
224                    locale_name,
225                    valid_locales,
226                    error)) {
227       return false;
228     }
229   }
230 
231   if (valid_locales->empty()) {
232     *error = extension_manifest_errors::kLocalesNoValidLocaleNamesListed;
233     return false;
234   }
235 
236   return true;
237 }
238 
239 // Loads contents of the messages file for given locale. If file is not found,
240 // or there was parsing error we return NULL and set |error|.
241 // Caller owns the returned object.
LoadMessageFile(const FilePath & locale_path,const std::string & locale,std::string * error)242 static DictionaryValue* LoadMessageFile(const FilePath& locale_path,
243                                         const std::string& locale,
244                                         std::string* error) {
245   std::string extension_locale = locale;
246   FilePath file = locale_path.AppendASCII(extension_locale)
247       .Append(Extension::kMessagesFilename);
248   JSONFileValueSerializer messages_serializer(file);
249   Value *dictionary = messages_serializer.Deserialize(NULL, error);
250   if (!dictionary && error->empty()) {
251     // JSONFileValueSerializer just returns NULL if file cannot be found. It
252     // doesn't set the error, so we have to do it.
253     *error = base::StringPrintf("Catalog file is missing for locale %s.",
254                                 extension_locale.c_str());
255   }
256 
257   return static_cast<DictionaryValue*>(dictionary);
258 }
259 
LoadMessageCatalogs(const FilePath & locale_path,const std::string & default_locale,const std::string & application_locale,const std::set<std::string> & valid_locales,std::string * error)260 ExtensionMessageBundle* LoadMessageCatalogs(
261     const FilePath& locale_path,
262     const std::string& default_locale,
263     const std::string& application_locale,
264     const std::set<std::string>& valid_locales,
265     std::string* error) {
266   // Order locales to load as current_locale, first_parent, ..., default_locale.
267   std::vector<std::string> all_fallback_locales;
268   if (!application_locale.empty() && application_locale != default_locale)
269     l10n_util::GetParentLocales(application_locale, &all_fallback_locales);
270   all_fallback_locales.push_back(default_locale);
271 
272   std::vector<linked_ptr<DictionaryValue> > catalogs;
273   for (size_t i = 0; i < all_fallback_locales.size(); ++i) {
274     // Skip all parent locales that are not supplied.
275     if (valid_locales.find(all_fallback_locales[i]) == valid_locales.end())
276       continue;
277     linked_ptr<DictionaryValue> catalog(
278       LoadMessageFile(locale_path, all_fallback_locales[i], error));
279     if (!catalog.get()) {
280       // If locale is valid, but messages.json is corrupted or missing, return
281       // an error.
282       return NULL;
283     } else {
284       catalogs.push_back(catalog);
285     }
286   }
287 
288   return ExtensionMessageBundle::Create(catalogs, error);
289 }
290 
ShouldSkipValidation(const FilePath & locales_path,const FilePath & locale_path,const std::set<std::string> & all_locales)291 bool ShouldSkipValidation(const FilePath& locales_path,
292                           const FilePath& locale_path,
293                           const std::set<std::string>& all_locales) {
294   // Since we use this string as a key in a DictionaryValue, be paranoid about
295   // skipping any strings with '.'. This happens sometimes, for example with
296   // '.svn' directories.
297   FilePath relative_path;
298   if (!locales_path.AppendRelativePath(locale_path, &relative_path)) {
299     NOTREACHED();
300     return true;
301   }
302   std::string subdir = relative_path.MaybeAsASCII();
303   if (subdir.empty())
304     return true;  // Non-ASCII.
305 
306   if (std::find(subdir.begin(), subdir.end(), '.') != subdir.end())
307     return true;
308 
309   if (all_locales.find(subdir) == all_locales.end())
310     return true;
311 
312   return false;
313 }
314 
315 }  // namespace extension_l10n_util
316