| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |