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