// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/common/message_bundle.h" #include #include #include "base/containers/hash_tables.h" #include "base/i18n/rtl.h" #include "base/lazy_instance.h" #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "extensions/common/error_utils.h" #include "extensions/common/extension_l10n_util.h" #include "extensions/common/manifest_constants.h" namespace extensions { namespace errors = manifest_errors; const char* MessageBundle::kContentKey = "content"; const char* MessageBundle::kMessageKey = "message"; const char* MessageBundle::kPlaceholdersKey = "placeholders"; const char* MessageBundle::kPlaceholderBegin = "$"; const char* MessageBundle::kPlaceholderEnd = "$"; const char* MessageBundle::kMessageBegin = "__MSG_"; const char* MessageBundle::kMessageEnd = "__"; // Reserved messages names. const char* MessageBundle::kUILocaleKey = "@@ui_locale"; const char* MessageBundle::kBidiDirectionKey = "@@bidi_dir"; const char* MessageBundle::kBidiReversedDirectionKey = "@@bidi_reversed_dir"; const char* MessageBundle::kBidiStartEdgeKey = "@@bidi_start_edge"; const char* MessageBundle::kBidiEndEdgeKey = "@@bidi_end_edge"; const char* MessageBundle::kExtensionIdKey = "@@extension_id"; // Reserved messages values. const char* MessageBundle::kBidiLeftEdgeValue = "left"; const char* MessageBundle::kBidiRightEdgeValue = "right"; // Formats message in case we encounter a bad formed key in the JSON object. // Returns false and sets |error| to actual error message. static bool BadKeyMessage(const std::string& name, std::string* error) { *error = base::StringPrintf( "Name of a key \"%s\" is invalid. Only ASCII [a-z], " "[A-Z], [0-9] and \"_\" are allowed.", name.c_str()); return false; } // static MessageBundle* MessageBundle::Create(const CatalogVector& locale_catalogs, std::string* error) { scoped_ptr message_bundle(new MessageBundle); if (!message_bundle->Init(locale_catalogs, error)) return NULL; return message_bundle.release(); } bool MessageBundle::Init(const CatalogVector& locale_catalogs, std::string* error) { dictionary_.clear(); for (CatalogVector::const_reverse_iterator it = locale_catalogs.rbegin(); it != locale_catalogs.rend(); ++it) { base::DictionaryValue* catalog = (*it).get(); for (base::DictionaryValue::Iterator message_it(*catalog); !message_it.IsAtEnd(); message_it.Advance()) { std::string key(base::StringToLowerASCII(message_it.key())); if (!IsValidName(message_it.key())) return BadKeyMessage(key, error); std::string value; if (!GetMessageValue(message_it.key(), message_it.value(), &value, error)) return false; // Keys are not case-sensitive. dictionary_[key] = value; } } if (!AppendReservedMessagesForLocale( extension_l10n_util::CurrentLocaleOrDefault(), error)) return false; return true; } bool MessageBundle::AppendReservedMessagesForLocale( const std::string& app_locale, std::string* error) { SubstitutionMap append_messages; append_messages[kUILocaleKey] = app_locale; // Calling base::i18n::GetTextDirection on non-UI threads doesn't seems safe, // so we use GetTextDirectionForLocale instead. if (base::i18n::GetTextDirectionForLocale(app_locale.c_str()) == base::i18n::RIGHT_TO_LEFT) { append_messages[kBidiDirectionKey] = "rtl"; append_messages[kBidiReversedDirectionKey] = "ltr"; append_messages[kBidiStartEdgeKey] = kBidiRightEdgeValue; append_messages[kBidiEndEdgeKey] = kBidiLeftEdgeValue; } else { append_messages[kBidiDirectionKey] = "ltr"; append_messages[kBidiReversedDirectionKey] = "rtl"; append_messages[kBidiStartEdgeKey] = kBidiLeftEdgeValue; append_messages[kBidiEndEdgeKey] = kBidiRightEdgeValue; } // Add all reserved messages to the dictionary, but check for collisions. SubstitutionMap::iterator it = append_messages.begin(); for (; it != append_messages.end(); ++it) { if (ContainsKey(dictionary_, it->first)) { *error = ErrorUtils::FormatErrorMessage( errors::kReservedMessageFound, it->first); return false; } else { dictionary_[it->first] = it->second; } } return true; } bool MessageBundle::GetMessageValue(const std::string& key, const base::Value& name_value, std::string* value, std::string* error) const { // Get the top level tree for given key (name part). const base::DictionaryValue* name_tree; if (!name_value.GetAsDictionary(&name_tree)) { *error = base::StringPrintf("Not a valid tree for key %s.", key.c_str()); return false; } // Extract message from it. if (!name_tree->GetString(kMessageKey, value)) { *error = base::StringPrintf( "There is no \"%s\" element for key %s.", kMessageKey, key.c_str()); return false; } SubstitutionMap placeholders; if (!GetPlaceholders(*name_tree, key, &placeholders, error)) return false; if (!ReplacePlaceholders(placeholders, value, error)) return false; return true; } MessageBundle::MessageBundle() { } bool MessageBundle::GetPlaceholders(const base::DictionaryValue& name_tree, const std::string& name_key, SubstitutionMap* placeholders, std::string* error) const { if (!name_tree.HasKey(kPlaceholdersKey)) return true; const base::DictionaryValue* placeholders_tree; if (!name_tree.GetDictionary(kPlaceholdersKey, &placeholders_tree)) { *error = base::StringPrintf("Not a valid \"%s\" element for key %s.", kPlaceholdersKey, name_key.c_str()); return false; } for (base::DictionaryValue::Iterator it(*placeholders_tree); !it.IsAtEnd(); it.Advance()) { const base::DictionaryValue* placeholder; const std::string& content_key(it.key()); if (!IsValidName(content_key)) return BadKeyMessage(content_key, error); if (!it.value().GetAsDictionary(&placeholder)) { *error = base::StringPrintf("Invalid placeholder %s for key %s", content_key.c_str(), name_key.c_str()); return false; } std::string content; if (!placeholder->GetString(kContentKey, &content)) { *error = base::StringPrintf("Invalid \"%s\" element for key %s.", kContentKey, name_key.c_str()); return false; } (*placeholders)[base::StringToLowerASCII(content_key)] = content; } return true; } bool MessageBundle::ReplacePlaceholders(const SubstitutionMap& placeholders, std::string* message, std::string* error) const { return ReplaceVariables(placeholders, kPlaceholderBegin, kPlaceholderEnd, message, error); } bool MessageBundle::ReplaceMessages(std::string* text, std::string* error) const { return ReplaceMessagesWithExternalDictionary(dictionary_, text, error); } MessageBundle::~MessageBundle() { } // static bool MessageBundle::ReplaceMessagesWithExternalDictionary( const SubstitutionMap& dictionary, std::string* text, std::string* error) { return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error); } // static bool MessageBundle::ReplaceVariables(const SubstitutionMap& variables, const std::string& var_begin_delimiter, const std::string& var_end_delimiter, std::string* message, std::string* error) { std::string::size_type beg_index = 0; const std::string::size_type var_begin_delimiter_size = var_begin_delimiter.size(); while (true) { beg_index = message->find(var_begin_delimiter, beg_index); if (beg_index == message->npos) return true; // Advance it immediately to the begining of possible variable name. beg_index += var_begin_delimiter_size; if (beg_index >= message->size()) return true; std::string::size_type end_index = message->find(var_end_delimiter, beg_index); if (end_index == message->npos) return true; // Looking for 1 in substring of ...$1$.... const std::string& var_name = message->substr(beg_index, end_index - beg_index); if (!IsValidName(var_name)) continue; SubstitutionMap::const_iterator it = variables.find(base::StringToLowerASCII(var_name)); if (it == variables.end()) { *error = base::StringPrintf("Variable %s%s%s used but not defined.", var_begin_delimiter.c_str(), var_name.c_str(), var_end_delimiter.c_str()); return false; } // Replace variable with its value. std::string value = it->second; message->replace(beg_index - var_begin_delimiter_size, end_index - beg_index + var_begin_delimiter_size + var_end_delimiter.size(), value); // And position pointer to after the replacement. beg_index += value.size() - var_begin_delimiter_size; } return true; } // static bool MessageBundle::IsValidName(const std::string& name) { if (name.empty()) return false; std::string::const_iterator it = name.begin(); for (; it != name.end(); ++it) { // Allow only ascii 0-9, a-z, A-Z, and _ in the name. if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '_' && *it != '@') return false; } return true; } // Dictionary interface. std::string MessageBundle::GetL10nMessage(const std::string& name) const { return GetL10nMessage(name, dictionary_); } // static std::string MessageBundle::GetL10nMessage(const std::string& name, const SubstitutionMap& dictionary) { SubstitutionMap::const_iterator it = dictionary.find(base::StringToLowerASCII(name)); if (it != dictionary.end()) { return it->second; } return std::string(); } /////////////////////////////////////////////////////////////////////////////// // // Renderer helper functions. // /////////////////////////////////////////////////////////////////////////////// // Unique class for Singleton. struct ExtensionToMessagesMap { ExtensionToMessagesMap(); ~ExtensionToMessagesMap(); // Maps extension ID to message map. ExtensionToL10nMessagesMap messages_map; }; static base::LazyInstance g_extension_to_messages_map = LAZY_INSTANCE_INITIALIZER; ExtensionToMessagesMap::ExtensionToMessagesMap() {} ExtensionToMessagesMap::~ExtensionToMessagesMap() {} ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() { return &g_extension_to_messages_map.Get().messages_map; } L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id) { ExtensionToL10nMessagesMap::iterator it = g_extension_to_messages_map.Get().messages_map.find(extension_id); if (it != g_extension_to_messages_map.Get().messages_map.end()) return &(it->second); return NULL; } void EraseL10nMessagesMap(const std::string& extension_id) { g_extension_to_messages_map.Get().messages_map.erase(extension_id); } } // namespace extensions