blob: 26e9c97c4eb5440f840d9555943bbd16de5e927f [file] [log] [blame]
// Copyright (c) 2011 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 "chrome/common/extensions/extension_message_bundle.h"
#include <string>
#include <vector>
#include "base/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-inl.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "ui/base/l10n/l10n_util.h"
namespace errors = extension_manifest_errors;
const char* ExtensionMessageBundle::kContentKey = "content";
const char* ExtensionMessageBundle::kMessageKey = "message";
const char* ExtensionMessageBundle::kPlaceholdersKey = "placeholders";
const char* ExtensionMessageBundle::kPlaceholderBegin = "$";
const char* ExtensionMessageBundle::kPlaceholderEnd = "$";
const char* ExtensionMessageBundle::kMessageBegin = "__MSG_";
const char* ExtensionMessageBundle::kMessageEnd = "__";
// Reserved messages names.
const char* ExtensionMessageBundle::kUILocaleKey = "@@ui_locale";
const char* ExtensionMessageBundle::kBidiDirectionKey = "@@bidi_dir";
const char* ExtensionMessageBundle::kBidiReversedDirectionKey =
"@@bidi_reversed_dir";
const char* ExtensionMessageBundle::kBidiStartEdgeKey = "@@bidi_start_edge";
const char* ExtensionMessageBundle::kBidiEndEdgeKey = "@@bidi_end_edge";
const char* ExtensionMessageBundle::kExtensionIdKey = "@@extension_id";
// Reserved messages values.
const char* ExtensionMessageBundle::kBidiLeftEdgeValue = "left";
const char* ExtensionMessageBundle::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
ExtensionMessageBundle* ExtensionMessageBundle::Create(
const CatalogVector& locale_catalogs,
std::string* error) {
scoped_ptr<ExtensionMessageBundle> message_bundle(
new ExtensionMessageBundle);
if (!message_bundle->Init(locale_catalogs, error))
return NULL;
return message_bundle.release();
}
bool ExtensionMessageBundle::Init(const CatalogVector& locale_catalogs,
std::string* error) {
dictionary_.clear();
for (CatalogVector::const_reverse_iterator it = locale_catalogs.rbegin();
it != locale_catalogs.rend(); ++it) {
DictionaryValue* catalog = (*it).get();
for (DictionaryValue::key_iterator key_it = catalog->begin_keys();
key_it != catalog->end_keys(); ++key_it) {
std::string key(StringToLowerASCII(*key_it));
if (!IsValidName(*key_it))
return BadKeyMessage(key, error);
std::string value;
if (!GetMessageValue(*key_it, *catalog, &value, error))
return false;
// Keys are not case-sensitive.
dictionary_[key] = value;
}
}
if (!AppendReservedMessagesForLocale(
extension_l10n_util::CurrentLocaleOrDefault(), error))
return false;
return true;
}
bool ExtensionMessageBundle::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 = ExtensionErrorUtils::FormatErrorMessage(
errors::kReservedMessageFound, it->first);
return false;
} else {
dictionary_[it->first] = it->second;
}
}
return true;
}
bool ExtensionMessageBundle::GetMessageValue(const std::string& key,
const DictionaryValue& catalog,
std::string* value,
std::string* error) const {
// Get the top level tree for given key (name part).
DictionaryValue* name_tree;
if (!catalog.GetDictionaryWithoutPathExpansion(key, &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;
}
ExtensionMessageBundle::ExtensionMessageBundle() {
}
bool ExtensionMessageBundle::GetPlaceholders(const DictionaryValue& name_tree,
const std::string& name_key,
SubstitutionMap* placeholders,
std::string* error) const {
if (!name_tree.HasKey(kPlaceholdersKey))
return true;
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 (DictionaryValue::key_iterator key_it = placeholders_tree->begin_keys();
key_it != placeholders_tree->end_keys(); ++key_it) {
DictionaryValue* placeholder;
const std::string& content_key(*key_it);
if (!IsValidName(content_key))
return BadKeyMessage(content_key, error);
if (!placeholders_tree->GetDictionaryWithoutPathExpansion(content_key,
&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)[StringToLowerASCII(content_key)] = content;
}
return true;
}
bool ExtensionMessageBundle::ReplacePlaceholders(
const SubstitutionMap& placeholders,
std::string* message,
std::string* error) const {
return ReplaceVariables(placeholders,
kPlaceholderBegin,
kPlaceholderEnd,
message,
error);
}
bool ExtensionMessageBundle::ReplaceMessages(std::string* text,
std::string* error) const {
return ReplaceMessagesWithExternalDictionary(dictionary_, text, error);
}
ExtensionMessageBundle::~ExtensionMessageBundle() {
}
// static
bool ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary(
const SubstitutionMap& dictionary, std::string* text, std::string* error) {
return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error);
}
// static
bool ExtensionMessageBundle::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(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 ExtensionMessageBundle::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 ExtensionMessageBundle::GetL10nMessage(
const std::string& name) const {
return GetL10nMessage(name, dictionary_);
}
// static
std::string ExtensionMessageBundle::GetL10nMessage(
const std::string& name, const SubstitutionMap& dictionary) {
SubstitutionMap::const_iterator it =
dictionary.find(StringToLowerASCII(name));
if (it != dictionary.end()) {
return it->second;
}
return "";
}
///////////////////////////////////////////////////////////////////////////////
//
// Renderer helper functions.
//
///////////////////////////////////////////////////////////////////////////////
// Unique class for Singleton.
struct ExtensionToMessagesMap {
ExtensionToMessagesMap();
~ExtensionToMessagesMap();
// Maps extension ID to message map.
ExtensionToL10nMessagesMap messages_map;
};
static base::LazyInstance<ExtensionToMessagesMap> g_extension_to_messages_map(
base::LINKER_INITIALIZED);
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;
}