1 // Copyright (c) 2012 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/message_bundle.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/containers/hash_tables.h" 11 #include "base/i18n/rtl.h" 12 #include "base/lazy_instance.h" 13 #include "base/memory/linked_ptr.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/stl_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/values.h" 19 #include "chrome/common/extensions/extension_l10n_util.h" 20 #include "extensions/common/error_utils.h" 21 #include "extensions/common/manifest_constants.h" 22 #include "ui/base/l10n/l10n_util.h" 23 24 namespace extensions { 25 26 namespace errors = manifest_errors; 27 28 const char* MessageBundle::kContentKey = "content"; 29 const char* MessageBundle::kMessageKey = "message"; 30 const char* MessageBundle::kPlaceholdersKey = "placeholders"; 31 32 const char* MessageBundle::kPlaceholderBegin = "$"; 33 const char* MessageBundle::kPlaceholderEnd = "$"; 34 const char* MessageBundle::kMessageBegin = "__MSG_"; 35 const char* MessageBundle::kMessageEnd = "__"; 36 37 // Reserved messages names. 38 const char* MessageBundle::kUILocaleKey = "@@ui_locale"; 39 const char* MessageBundle::kBidiDirectionKey = "@@bidi_dir"; 40 const char* MessageBundle::kBidiReversedDirectionKey = 41 "@@bidi_reversed_dir"; 42 const char* MessageBundle::kBidiStartEdgeKey = "@@bidi_start_edge"; 43 const char* MessageBundle::kBidiEndEdgeKey = "@@bidi_end_edge"; 44 const char* MessageBundle::kExtensionIdKey = "@@extension_id"; 45 46 // Reserved messages values. 47 const char* MessageBundle::kBidiLeftEdgeValue = "left"; 48 const char* MessageBundle::kBidiRightEdgeValue = "right"; 49 50 // Formats message in case we encounter a bad formed key in the JSON object. 51 // Returns false and sets |error| to actual error message. 52 static bool BadKeyMessage(const std::string& name, std::string* error) { 53 *error = base::StringPrintf( 54 "Name of a key \"%s\" is invalid. Only ASCII [a-z], " 55 "[A-Z], [0-9] and \"_\" are allowed.", 56 name.c_str()); 57 return false; 58 } 59 60 // static 61 MessageBundle* MessageBundle::Create(const CatalogVector& locale_catalogs, 62 std::string* error) { 63 scoped_ptr<MessageBundle> message_bundle(new MessageBundle); 64 if (!message_bundle->Init(locale_catalogs, error)) 65 return NULL; 66 67 return message_bundle.release(); 68 } 69 70 bool MessageBundle::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 base::DictionaryValue* catalog = (*it).get(); 77 for (base::DictionaryValue::Iterator message_it(*catalog); 78 !message_it.IsAtEnd(); message_it.Advance()) { 79 std::string key(StringToLowerASCII(message_it.key())); 80 if (!IsValidName(message_it.key())) 81 return BadKeyMessage(key, error); 82 std::string value; 83 if (!GetMessageValue(message_it.key(), message_it.value(), &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 97 bool MessageBundle::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 = ErrorUtils::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 132 bool MessageBundle::GetMessageValue(const std::string& key, 133 const base::Value& name_value, 134 std::string* value, 135 std::string* error) const { 136 // Get the top level tree for given key (name part). 137 const base::DictionaryValue* name_tree; 138 if (!name_value.GetAsDictionary(&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 159 MessageBundle::MessageBundle() { 160 } 161 162 bool MessageBundle::GetPlaceholders(const base::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 const base::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 (base::DictionaryValue::Iterator it(*placeholders_tree); !it.IsAtEnd(); 177 it.Advance()) { 178 const base::DictionaryValue* placeholder; 179 const std::string& content_key(it.key()); 180 if (!IsValidName(content_key)) 181 return BadKeyMessage(content_key, error); 182 if (!it.value().GetAsDictionary(&placeholder)) { 183 *error = base::StringPrintf("Invalid placeholder %s for key %s", 184 content_key.c_str(), 185 name_key.c_str()); 186 return false; 187 } 188 std::string content; 189 if (!placeholder->GetString(kContentKey, &content)) { 190 *error = base::StringPrintf("Invalid \"%s\" element for key %s.", 191 kContentKey, name_key.c_str()); 192 return false; 193 } 194 (*placeholders)[StringToLowerASCII(content_key)] = content; 195 } 196 197 return true; 198 } 199 200 bool MessageBundle::ReplacePlaceholders(const SubstitutionMap& placeholders, 201 std::string* message, 202 std::string* error) const { 203 return ReplaceVariables(placeholders, 204 kPlaceholderBegin, 205 kPlaceholderEnd, 206 message, 207 error); 208 } 209 210 bool MessageBundle::ReplaceMessages(std::string* text, 211 std::string* error) const { 212 return ReplaceMessagesWithExternalDictionary(dictionary_, text, error); 213 } 214 215 MessageBundle::~MessageBundle() { 216 } 217 218 // static 219 bool MessageBundle::ReplaceMessagesWithExternalDictionary( 220 const SubstitutionMap& dictionary, std::string* text, std::string* error) { 221 return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error); 222 } 223 224 // static 225 bool MessageBundle::ReplaceVariables(const SubstitutionMap& variables, 226 const std::string& var_begin_delimiter, 227 const std::string& var_end_delimiter, 228 std::string* message, 229 std::string* error) { 230 std::string::size_type beg_index = 0; 231 const std::string::size_type var_begin_delimiter_size = 232 var_begin_delimiter.size(); 233 while (true) { 234 beg_index = message->find(var_begin_delimiter, beg_index); 235 if (beg_index == message->npos) 236 return true; 237 238 // Advance it immediately to the begining of possible variable name. 239 beg_index += var_begin_delimiter_size; 240 if (beg_index >= message->size()) 241 return true; 242 std::string::size_type end_index = 243 message->find(var_end_delimiter, beg_index); 244 if (end_index == message->npos) 245 return true; 246 247 // Looking for 1 in substring of ...$1$.... 248 const std::string& var_name = 249 message->substr(beg_index, end_index - beg_index); 250 if (!IsValidName(var_name)) 251 continue; 252 SubstitutionMap::const_iterator it = 253 variables.find(StringToLowerASCII(var_name)); 254 if (it == variables.end()) { 255 *error = base::StringPrintf("Variable %s%s%s used but not defined.", 256 var_begin_delimiter.c_str(), 257 var_name.c_str(), 258 var_end_delimiter.c_str()); 259 return false; 260 } 261 262 // Replace variable with its value. 263 std::string value = it->second; 264 message->replace(beg_index - var_begin_delimiter_size, 265 end_index - beg_index + var_begin_delimiter_size + 266 var_end_delimiter.size(), 267 value); 268 269 // And position pointer to after the replacement. 270 beg_index += value.size() - var_begin_delimiter_size; 271 } 272 273 return true; 274 } 275 276 // static 277 bool MessageBundle::IsValidName(const std::string& name) { 278 if (name.empty()) 279 return false; 280 281 std::string::const_iterator it = name.begin(); 282 for (; it != name.end(); ++it) { 283 // Allow only ascii 0-9, a-z, A-Z, and _ in the name. 284 if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '_' && *it != '@') 285 return false; 286 } 287 288 return true; 289 } 290 291 // Dictionary interface. 292 293 std::string MessageBundle::GetL10nMessage(const std::string& name) const { 294 return GetL10nMessage(name, dictionary_); 295 } 296 297 // static 298 std::string MessageBundle::GetL10nMessage(const std::string& name, 299 const SubstitutionMap& dictionary) { 300 SubstitutionMap::const_iterator it = 301 dictionary.find(StringToLowerASCII(name)); 302 if (it != dictionary.end()) { 303 return it->second; 304 } 305 306 return std::string(); 307 } 308 309 /////////////////////////////////////////////////////////////////////////////// 310 // 311 // Renderer helper functions. 312 // 313 /////////////////////////////////////////////////////////////////////////////// 314 315 // Unique class for Singleton. 316 struct ExtensionToMessagesMap { 317 ExtensionToMessagesMap(); 318 ~ExtensionToMessagesMap(); 319 320 // Maps extension ID to message map. 321 ExtensionToL10nMessagesMap messages_map; 322 }; 323 324 static base::LazyInstance<ExtensionToMessagesMap> g_extension_to_messages_map = 325 LAZY_INSTANCE_INITIALIZER; 326 327 ExtensionToMessagesMap::ExtensionToMessagesMap() {} 328 329 ExtensionToMessagesMap::~ExtensionToMessagesMap() {} 330 331 ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() { 332 return &g_extension_to_messages_map.Get().messages_map; 333 } 334 335 L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id) { 336 ExtensionToL10nMessagesMap::iterator it = 337 g_extension_to_messages_map.Get().messages_map.find(extension_id); 338 if (it != g_extension_to_messages_map.Get().messages_map.end()) 339 return &(it->second); 340 341 return NULL; 342 } 343 344 void EraseL10nMessagesMap(const std::string& extension_id) { 345 g_extension_to_messages_map.Get().messages_map.erase(extension_id); 346 } 347 348 } // namespace extensions 349