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