extension_message_bundle.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 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_message_bundle.h"
6
7#include <string>
8#include <vector>
9
10#include "base/hash_tables.h"
11#include "base/i18n/rtl.h"
12#include "base/lazy_instance.h"
13#include "base/linked_ptr.h"
14#include "base/scoped_ptr.h"
15#include "base/stl_util-inl.h"
16#include "base/string_util.h"
17#include "base/utf_string_conversions.h"
18#include "base/values.h"
19#include "chrome/common/extensions/extension_constants.h"
20#include "chrome/common/extensions/extension_error_utils.h"
21#include "chrome/common/extensions/extension_l10n_util.h"
22#include "ui/base/l10n/l10n_util.h"
23
24namespace errors = extension_manifest_errors;
25
26const char* ExtensionMessageBundle::kContentKey = "content";
27const char* ExtensionMessageBundle::kMessageKey = "message";
28const char* ExtensionMessageBundle::kPlaceholdersKey = "placeholders";
29
30const char* ExtensionMessageBundle::kPlaceholderBegin = "$";
31const char* ExtensionMessageBundle::kPlaceholderEnd = "$";
32const char* ExtensionMessageBundle::kMessageBegin = "__MSG_";
33const char* ExtensionMessageBundle::kMessageEnd = "__";
34
35// Reserved messages names.
36const char* ExtensionMessageBundle::kUILocaleKey = "@@ui_locale";
37const char* ExtensionMessageBundle::kBidiDirectionKey = "@@bidi_dir";
38const char* ExtensionMessageBundle::kBidiReversedDirectionKey =
39    "@@bidi_reversed_dir";
40const char* ExtensionMessageBundle::kBidiStartEdgeKey = "@@bidi_start_edge";
41const char* ExtensionMessageBundle::kBidiEndEdgeKey = "@@bidi_end_edge";
42const char* ExtensionMessageBundle::kExtensionIdKey = "@@extension_id";
43
44// Reserved messages values.
45const char* ExtensionMessageBundle::kBidiLeftEdgeValue = "left";
46const char* ExtensionMessageBundle::kBidiRightEdgeValue = "right";
47
48// Formats message in case we encounter a bad formed key in the JSON object.
49// Returns false and sets |error| to actual error message.
50static bool BadKeyMessage(const std::string& name, std::string* error) {
51  *error = base::StringPrintf(
52      "Name of a key \"%s\" is invalid. Only ASCII [a-z], "
53      "[A-Z], [0-9] and \"_\" are allowed.",
54      name.c_str());
55  return false;
56}
57
58// static
59ExtensionMessageBundle* ExtensionMessageBundle::Create(
60    const CatalogVector& locale_catalogs,
61    std::string* error) {
62  scoped_ptr<ExtensionMessageBundle> message_bundle(
63      new ExtensionMessageBundle);
64  if (!message_bundle->Init(locale_catalogs, error))
65    return NULL;
66
67  return message_bundle.release();
68}
69
70bool ExtensionMessageBundle::Init(const CatalogVector& locale_catalogs,
71                                  std::string* error) {
72  dictionary_.clear();
73
74  for (CatalogVector::const_reverse_iterator it = locale_catalogs.rbegin();
75       it != locale_catalogs.rend(); ++it) {
76    DictionaryValue* catalog = (*it).get();
77    for (DictionaryValue::key_iterator key_it = catalog->begin_keys();
78         key_it != catalog->end_keys(); ++key_it) {
79      std::string key(StringToLowerASCII(*key_it));
80      if (!IsValidName(*key_it))
81        return BadKeyMessage(key, error);
82      std::string value;
83      if (!GetMessageValue(*key_it, *catalog, &value, error))
84        return false;
85      // Keys are not case-sensitive.
86      dictionary_[key] = value;
87    }
88  }
89
90  if (!AppendReservedMessagesForLocale(
91      extension_l10n_util::CurrentLocaleOrDefault(), error))
92    return false;
93
94  return true;
95}
96
97bool ExtensionMessageBundle::AppendReservedMessagesForLocale(
98    const std::string& app_locale, std::string* error) {
99  SubstitutionMap append_messages;
100  append_messages[kUILocaleKey] = app_locale;
101
102  // Calling base::i18n::GetTextDirection on non-UI threads doesn't seems safe,
103  // so we use GetTextDirectionForLocale instead.
104  if (base::i18n::GetTextDirectionForLocale(app_locale.c_str()) ==
105      base::i18n::RIGHT_TO_LEFT) {
106    append_messages[kBidiDirectionKey] = "rtl";
107    append_messages[kBidiReversedDirectionKey] = "ltr";
108    append_messages[kBidiStartEdgeKey] = kBidiRightEdgeValue;
109    append_messages[kBidiEndEdgeKey] = kBidiLeftEdgeValue;
110  } else {
111    append_messages[kBidiDirectionKey] = "ltr";
112    append_messages[kBidiReversedDirectionKey] = "rtl";
113    append_messages[kBidiStartEdgeKey] = kBidiLeftEdgeValue;
114    append_messages[kBidiEndEdgeKey] = kBidiRightEdgeValue;
115  }
116
117  // Add all reserved messages to the dictionary, but check for collisions.
118  SubstitutionMap::iterator it = append_messages.begin();
119  for (; it != append_messages.end(); ++it) {
120    if (ContainsKey(dictionary_, it->first)) {
121      *error = ExtensionErrorUtils::FormatErrorMessage(
122          errors::kReservedMessageFound, it->first);
123      return false;
124    } else {
125      dictionary_[it->first] = it->second;
126    }
127  }
128
129  return true;
130}
131
132bool ExtensionMessageBundle::GetMessageValue(const std::string& key,
133                                             const DictionaryValue& catalog,
134                                             std::string* value,
135                                             std::string* error) const {
136  // Get the top level tree for given key (name part).
137  DictionaryValue* name_tree;
138  if (!catalog.GetDictionaryWithoutPathExpansion(key, &name_tree)) {
139    *error = base::StringPrintf("Not a valid tree for key %s.", key.c_str());
140    return false;
141  }
142  // Extract message from it.
143  if (!name_tree->GetString(kMessageKey, value)) {
144    *error = base::StringPrintf(
145        "There is no \"%s\" element for key %s.", kMessageKey, key.c_str());
146    return false;
147  }
148
149  SubstitutionMap placeholders;
150  if (!GetPlaceholders(*name_tree, key, &placeholders, error))
151    return false;
152
153  if (!ReplacePlaceholders(placeholders, value, error))
154    return false;
155
156  return true;
157}
158
159ExtensionMessageBundle::ExtensionMessageBundle() {
160}
161
162bool ExtensionMessageBundle::GetPlaceholders(const DictionaryValue& name_tree,
163                                             const std::string& name_key,
164                                             SubstitutionMap* placeholders,
165                                             std::string* error) const {
166  if (!name_tree.HasKey(kPlaceholdersKey))
167    return true;
168
169  DictionaryValue* placeholders_tree;
170  if (!name_tree.GetDictionary(kPlaceholdersKey, &placeholders_tree)) {
171    *error = base::StringPrintf("Not a valid \"%s\" element for key %s.",
172                                kPlaceholdersKey, name_key.c_str());
173    return false;
174  }
175
176  for (DictionaryValue::key_iterator key_it = placeholders_tree->begin_keys();
177       key_it != placeholders_tree->end_keys(); ++key_it) {
178    DictionaryValue* placeholder;
179    const std::string& content_key(*key_it);
180    if (!IsValidName(content_key))
181      return BadKeyMessage(content_key, error);
182    if (!placeholders_tree->GetDictionaryWithoutPathExpansion(content_key,
183                                                              &placeholder)) {
184      *error = base::StringPrintf("Invalid placeholder %s for key %s",
185                                  content_key.c_str(),
186                                  name_key.c_str());
187      return false;
188    }
189    std::string content;
190    if (!placeholder->GetString(kContentKey, &content)) {
191      *error = base::StringPrintf("Invalid \"%s\" element for key %s.",
192                                  kContentKey, name_key.c_str());
193      return false;
194    }
195    (*placeholders)[StringToLowerASCII(content_key)] = content;
196  }
197
198  return true;
199}
200
201bool ExtensionMessageBundle::ReplacePlaceholders(
202    const SubstitutionMap& placeholders,
203    std::string* message,
204    std::string* error) const {
205  return ReplaceVariables(placeholders,
206                          kPlaceholderBegin,
207                          kPlaceholderEnd,
208                          message,
209                          error);
210}
211
212bool ExtensionMessageBundle::ReplaceMessages(std::string* text,
213                                             std::string* error) const {
214  return ReplaceMessagesWithExternalDictionary(dictionary_, text, error);
215}
216
217ExtensionMessageBundle::~ExtensionMessageBundle() {
218}
219
220// static
221bool ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary(
222    const SubstitutionMap& dictionary, std::string* text, std::string* error) {
223  return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error);
224}
225
226// static
227bool ExtensionMessageBundle::ReplaceVariables(
228    const SubstitutionMap& variables,
229    const std::string& var_begin_delimiter,
230    const std::string& var_end_delimiter,
231    std::string* message,
232    std::string* error) {
233  std::string::size_type beg_index = 0;
234  const std::string::size_type var_begin_delimiter_size =
235    var_begin_delimiter.size();
236  while (true) {
237    beg_index = message->find(var_begin_delimiter, beg_index);
238    if (beg_index == message->npos)
239      return true;
240
241    // Advance it immediately to the begining of possible variable name.
242    beg_index += var_begin_delimiter_size;
243    if (beg_index >= message->size())
244      return true;
245    std::string::size_type end_index =
246      message->find(var_end_delimiter, beg_index);
247    if (end_index == message->npos)
248      return true;
249
250    // Looking for 1 in substring of ...$1$....
251    const std::string& var_name =
252      message->substr(beg_index, end_index - beg_index);
253    if (!IsValidName(var_name))
254      continue;
255    SubstitutionMap::const_iterator it =
256      variables.find(StringToLowerASCII(var_name));
257    if (it == variables.end()) {
258      *error = base::StringPrintf("Variable %s%s%s used but not defined.",
259                                  var_begin_delimiter.c_str(),
260                                  var_name.c_str(),
261                                  var_end_delimiter.c_str());
262      return false;
263    }
264
265    // Replace variable with its value.
266    std::string value = it->second;
267    message->replace(beg_index - var_begin_delimiter_size,
268                     end_index - beg_index + var_begin_delimiter_size +
269                       var_end_delimiter.size(),
270                     value);
271
272    // And position pointer to after the replacement.
273    beg_index += value.size() - var_begin_delimiter_size;
274  }
275
276  return true;
277}
278
279// static
280bool ExtensionMessageBundle::IsValidName(const std::string& name) {
281  if (name.empty())
282    return false;
283
284  std::string::const_iterator it = name.begin();
285  for (; it != name.end(); ++it) {
286    // Allow only ascii 0-9, a-z, A-Z, and _ in the name.
287    if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '_' && *it != '@')
288      return false;
289  }
290
291  return true;
292}
293
294// Dictionary interface.
295
296std::string ExtensionMessageBundle::GetL10nMessage(
297    const std::string& name) const {
298  return GetL10nMessage(name, dictionary_);
299}
300
301// static
302std::string ExtensionMessageBundle::GetL10nMessage(
303    const std::string& name, const SubstitutionMap& dictionary) {
304  SubstitutionMap::const_iterator it =
305    dictionary.find(StringToLowerASCII(name));
306  if (it != dictionary.end()) {
307    return it->second;
308  }
309
310  return "";
311}
312
313///////////////////////////////////////////////////////////////////////////////
314//
315// Renderer helper functions.
316//
317///////////////////////////////////////////////////////////////////////////////
318
319// Unique class for Singleton.
320struct ExtensionToMessagesMap {
321  ExtensionToMessagesMap();
322  ~ExtensionToMessagesMap();
323
324  // Maps extension ID to message map.
325  ExtensionToL10nMessagesMap messages_map;
326};
327
328static base::LazyInstance<ExtensionToMessagesMap> g_extension_to_messages_map(
329    base::LINKER_INITIALIZED);
330
331ExtensionToMessagesMap::ExtensionToMessagesMap() {}
332
333ExtensionToMessagesMap::~ExtensionToMessagesMap() {}
334
335ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() {
336  return &g_extension_to_messages_map.Get().messages_map;
337}
338
339L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) {
340  ExtensionToL10nMessagesMap::iterator it =
341      g_extension_to_messages_map.Get().messages_map.find(extension_id);
342  if (it != g_extension_to_messages_map.Get().messages_map.end())
343    return &(it->second);
344
345  return NULL;
346}
347