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/files/file_enumerator.h"
13#include "base/files/file_util.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
28namespace errors = extensions::manifest_errors;
29namespace keys = extensions::manifest_keys;
30
31namespace {
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.
36base::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.
61bool 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.
77bool 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
98std::string& GetProcessLocale() {
99  CR_DEFINE_STATIC_LOCAL(std::string, locale, ());
100  return locale;
101}
102
103}  // namespace
104
105namespace extension_l10n_util {
106
107void SetProcessLocale(const std::string& locale) {
108  GetProcessLocale() = locale;
109}
110
111std::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
121bool 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
133bool 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
258bool 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
279bool 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
307std::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
315void 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
327void 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
336bool 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
366extensions::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
395bool 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
426bool 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
450ScopedLocaleForTest::ScopedLocaleForTest()
451    : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {}
452
453ScopedLocaleForTest::ScopedLocaleForTest(const std::string& locale)
454    : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {
455  extension_l10n_util::SetProcessLocale(locale);
456}
457
458ScopedLocaleForTest::~ScopedLocaleForTest() {
459  extension_l10n_util::SetProcessLocale(locale_);
460}
461
462}  // namespace extension_l10n_util
463