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