blob: 484b731dcde8685bc9e0e365215593a48b83a8fe [file] [log] [blame]
// Copyright (c) 2012 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.h"
#include <algorithm>
#include "base/base64.h"
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/stl_util.h"
#include "base/string16.h"
#include "base/string_number_conversions.h"
#include "base/string_piece.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "base/version.h"
#include "crypto/sha2.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/csp_validator.h"
#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/extensions/feature.h"
#include "chrome/common/extensions/file_browser_handler.h"
#include "chrome/common/extensions/manifest.h"
#include "chrome/common/extensions/simple_feature_provider.h"
#include "chrome/common/extensions/user_script.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/url_util.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "net/base/registry_controlled_domain.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "webkit/glue/image_decoder.h"
#include "webkit/glue/web_intent_service_data.h"
namespace keys = extension_manifest_keys;
namespace values = extension_manifest_values;
namespace errors = extension_manifest_errors;
using extensions::csp_validator::ContentSecurityPolicyIsLegal;
using extensions::csp_validator::ContentSecurityPolicyIsSecure;
namespace {
const int kModernManifestVersion = 1;
const int kPEMOutputColumns = 65;
// KEY MARKERS
const char kKeyBeginHeaderMarker[] = "-----BEGIN";
const char kKeyBeginFooterMarker[] = "-----END";
const char kKeyInfoEndMarker[] = "KEY-----";
const char kPublic[] = "PUBLIC";
const char kPrivate[] = "PRIVATE";
const int kRSAKeySize = 1024;
const char kDefaultContentSecurityPolicy[] =
"script-src 'self'; object-src 'self'";
// Converts a normal hexadecimal string into the alphabet used by extensions.
// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a
// completely numeric host, since some software interprets that as an IP
// address.
static void ConvertHexadecimalToIDAlphabet(std::string* id) {
for (size_t i = 0; i < id->size(); ++i) {
int val;
if (base::HexStringToInt(base::StringPiece(id->begin() + i,
id->begin() + i + 1),
&val)) {
(*id)[i] = val + 'a';
} else {
(*id)[i] = 'a';
}
}
}
// A singleton object containing global data needed by the extension objects.
class ExtensionConfig {
public:
static ExtensionConfig* GetInstance() {
return Singleton<ExtensionConfig>::get();
}
Extension::ScriptingWhitelist* whitelist() { return &scripting_whitelist_; }
private:
friend struct DefaultSingletonTraits<ExtensionConfig>;
ExtensionConfig() {
// Whitelist ChromeVox, an accessibility extension from Google that needs
// the ability to script webui pages. This is temporary and is not
// meant to be a general solution.
// TODO(dmazzoni): remove this once we have an extension API that
// allows any extension to request read-only access to webui pages.
scripting_whitelist_.push_back("kgejglhpjiefppelpmljglcjbhoiplfn");
}
~ExtensionConfig() { }
// A whitelist of extensions that can script anywhere. Do not add to this
// list (except in tests) without consulting the Extensions team first.
// Note: Component extensions have this right implicitly and do not need to be
// added to this list.
Extension::ScriptingWhitelist scripting_whitelist_;
};
// Rank extension locations in a way that allows
// Extension::GetHigherPriorityLocation() to compare locations.
// An extension installed from two locations will have the location
// with the higher rank, as returned by this function. The actual
// integer values may change, and should never be persisted.
int GetLocationRank(Extension::Location location) {
const int kInvalidRank = -1;
int rank = kInvalidRank; // Will CHECK that rank is not kInvalidRank.
switch (location) {
// Component extensions can not be overriden by any other type.
case Extension::COMPONENT:
rank = 6;
break;
// Policy controlled extensions may not be overridden by any type
// that is not part of chrome.
case Extension::EXTERNAL_POLICY_DOWNLOAD:
rank = 5;
break;
// A developer-loaded extension should override any installed type
// that a user can disable.
case Extension::LOAD:
rank = 4;
break;
// The relative priority of various external sources is not important,
// but having some order ensures deterministic behavior.
case Extension::EXTERNAL_REGISTRY:
rank = 3;
break;
case Extension::EXTERNAL_PREF:
rank = 2;
break;
case Extension::EXTERNAL_PREF_DOWNLOAD:
rank = 1;
break;
// User installed extensions are overridden by any external type.
case Extension::INTERNAL:
rank = 0;
break;
default:
NOTREACHED() << "Need to add new extension locaton " << location;
}
CHECK(rank != kInvalidRank);
return rank;
}
bool ReadLaunchDimension(const extensions::Manifest* manifest,
const char* key,
int* target,
bool is_valid_container,
string16* error) {
Value* temp = NULL;
if (manifest->Get(key, &temp)) {
if (!is_valid_container) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValueContainer,
key);
return false;
}
if (!temp->GetAsInteger(target) || *target < 0) {
*target = 0;
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
key);
return false;
}
}
return true;
}
} // namespace
const FilePath::CharType Extension::kManifestFilename[] =
FILE_PATH_LITERAL("manifest.json");
const FilePath::CharType Extension::kLocaleFolder[] =
FILE_PATH_LITERAL("_locales");
const FilePath::CharType Extension::kMessagesFilename[] =
FILE_PATH_LITERAL("messages.json");
#if defined(OS_WIN)
const char Extension::kExtensionRegistryPath[] =
"Software\\Google\\Chrome\\Extensions";
#endif
// first 16 bytes of SHA256 hashed public key.
const size_t Extension::kIdSize = 16;
const char Extension::kMimeType[] = "application/x-chrome-extension";
const int Extension::kPageActionIconMaxSize = 19;
const int Extension::kBrowserActionIconMaxSize = 19;
const int Extension::kValidWebExtentSchemes =
URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
const int Extension::kValidHostPermissionSchemes =
UserScript::kValidUserScriptSchemes | URLPattern::SCHEME_CHROMEUI;
Extension::InputComponentInfo::InputComponentInfo()
: type(INPUT_COMPONENT_TYPE_NONE),
shortcut_alt(false),
shortcut_ctrl(false),
shortcut_shift(false) {
}
Extension::InputComponentInfo::~InputComponentInfo() {}
Extension::TtsVoice::TtsVoice() {}
Extension::TtsVoice::~TtsVoice() {}
Extension::ExtensionKeybinding::ExtensionKeybinding() {}
Extension::ExtensionKeybinding::~ExtensionKeybinding() {}
bool Extension::ExtensionKeybinding::Parse(DictionaryValue* command,
const std::string& command_name,
int index,
string16* error) {
DCHECK(!command_name.empty());
std::string key_binding;
if (!command->GetString(keys::kKey, &key_binding) ||
key_binding.empty()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidKeyBinding,
base::IntToString(index),
"Missing");
return false;
}
std::string original_keybinding = key_binding;
// Normalize '-' to '+'.
ReplaceSubstringsAfterOffset(&key_binding, 0, "-", "+");
// Remove all spaces.
ReplaceSubstringsAfterOffset(&key_binding, 0, " ", "");
// And finally, lower-case it.
key_binding = StringToLowerASCII(key_binding);
std::vector<std::string> tokens;
base::SplitString(key_binding, '+', &tokens);
if (tokens.size() < 2 || tokens.size() > 3) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidKeyBinding,
base::IntToString(index),
original_keybinding);
return false;
}
// Now, parse it into an accelerator.
bool ctrl = false;
bool alt = false;
bool shift = false;
ui::KeyboardCode key = ui::VKEY_UNKNOWN;
for (size_t i = 0; i < tokens.size(); i++) {
if (tokens[i] == "ctrl") {
ctrl = true;
} else if (tokens[i] == "alt") {
alt = true;
} else if (tokens[i] == "shift") {
shift = true;
} else if (tokens[i].size() == 1 &&
tokens[i][0] >= 'a' && tokens[i][0] <= 'z') {
if (key != ui::VKEY_UNKNOWN) {
// Multiple key assignments.
key = ui::VKEY_UNKNOWN;
break;
}
key = static_cast<ui::KeyboardCode>(ui::VKEY_A + (tokens[i][0] - 'a'));
} else {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidKeyBinding,
base::IntToString(index),
original_keybinding);
return false;
}
}
// We support Ctrl+foo, Alt+foo, Ctrl+Shift+foo, Alt+Shift+foo, but not
// Ctrl+Alt+foo. For a more detailed reason why we don't support Ctrl+Alt+foo:
// http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx.
if (key == ui::VKEY_UNKNOWN || (ctrl && alt)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidKeyBinding,
base::IntToString(index),
original_keybinding);
return false;
}
accelerator_ = ui::Accelerator(key, shift, ctrl, alt);
if (command_name !=
extension_manifest_values::kPageActionKeybindingEvent &&
command_name !=
extension_manifest_values::kBrowserActionKeybindingEvent) {
if (!command->GetString(keys::kDescription, &description_) ||
description_.empty()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidKeyBindingDescription,
base::IntToString(index));
return false;
}
}
command_name_ = command_name;
return true;
}
//
// Extension
//
// static
scoped_refptr<Extension> Extension::Create(const FilePath& path,
Location location,
const DictionaryValue& value,
int flags,
std::string* utf8_error) {
return Extension::Create(path,
location,
value,
flags,
std::string(), // ID is ignored if empty.
utf8_error);
}
scoped_refptr<Extension> Extension::Create(const FilePath& path,
Location location,
const DictionaryValue& value,
int flags,
const std::string& explicit_id,
std::string* utf8_error) {
DCHECK(utf8_error);
string16 error;
scoped_ptr<extensions::Manifest> manifest(
new extensions::Manifest(
location,
scoped_ptr<DictionaryValue>(value.DeepCopy())));
if (!InitExtensionID(manifest.get(), path, explicit_id, flags, &error) ||
!manifest->ValidateManifest(&error)) {
*utf8_error = UTF16ToUTF8(error);
return NULL;
}
scoped_refptr<Extension> extension = new Extension(path, manifest.Pass());
if (!extension->InitFromValue(flags, &error)) {
*utf8_error = UTF16ToUTF8(error);
return NULL;
}
return extension;
}
// static
Extension::Location Extension::GetHigherPriorityLocation(
Extension::Location loc1, Extension::Location loc2) {
if (loc1 == loc2)
return loc1;
int loc1_rank = GetLocationRank(loc1);
int loc2_rank = GetLocationRank(loc2);
// If two different locations have the same rank, then we can not
// deterministicly choose a location.
CHECK(loc1_rank != loc2_rank);
// Highest rank has highest priority.
return (loc1_rank > loc2_rank ? loc1 : loc2 );
}
void Extension::OverrideLaunchUrl(const GURL& override_url) {
GURL new_url(override_url);
if (!new_url.is_valid()) {
DLOG(WARNING) << "Invalid override url given for " << name();
} else {
if (new_url.has_port()) {
DLOG(WARNING) << "Override URL passed for " << name()
<< " should not contain a port. Removing it.";
GURL::Replacements remove_port;
remove_port.ClearPort();
new_url = new_url.ReplaceComponents(remove_port);
}
launch_web_url_ = new_url.spec();
URLPattern pattern(kValidWebExtentSchemes);
pattern.Parse(new_url.spec());
pattern.SetPath(pattern.path() + '*');
extent_.AddPattern(pattern);
}
}
FilePath Extension::MaybeNormalizePath(const FilePath& path) {
#if defined(OS_WIN)
// Normalize any drive letter to upper-case. We do this for consistency with
// net_utils::FilePathToFileURL(), which does the same thing, to make string
// comparisons simpler.
std::wstring path_str = path.value();
if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' &&
path_str[1] == ':')
path_str[0] += ('A' - 'a');
return FilePath(path_str);
#else
return path;
#endif
}
Extension::Location Extension::location() const {
return manifest_->location();
}
const std::string& Extension::id() const {
return manifest_->extension_id();
}
const std::string Extension::VersionString() const {
return version()->GetString();
}
// static
bool Extension::IsExtension(const FilePath& file_name) {
return file_name.MatchesExtension(chrome::kExtensionFileExtension);
}
// static
bool Extension::IdIsValid(const std::string& id) {
// Verify that the id is legal.
if (id.size() != (kIdSize * 2))
return false;
// We only support lowercase IDs, because IDs can be used as URL components
// (where GURL will lowercase it).
std::string temp = StringToLowerASCII(id);
for (size_t i = 0; i < temp.size(); i++)
if (temp[i] < 'a' || temp[i] > 'p')
return false;
return true;
}
// static
std::string Extension::GenerateIdForPath(const FilePath& path) {
FilePath new_path = Extension::MaybeNormalizePath(path);
std::string path_bytes =
std::string(reinterpret_cast<const char*>(new_path.value().data()),
new_path.value().size() * sizeof(FilePath::CharType));
std::string id;
if (!GenerateId(path_bytes, &id))
return "";
return id;
}
Extension::Type Extension::GetType() const {
if (converted_from_user_script())
return TYPE_USER_SCRIPT;
else
return manifest_->GetType();
}
// static
GURL Extension::GetResourceURL(const GURL& extension_url,
const std::string& relative_path) {
DCHECK(extension_url.SchemeIs(chrome::kExtensionScheme));
DCHECK_EQ("/", extension_url.path());
GURL ret_val = GURL(extension_url.spec() + relative_path);
DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false));
return ret_val;
}
bool Extension::is_platform_app() const {
return manifest_->IsPlatformApp();
}
bool Extension::is_hosted_app() const {
return manifest()->IsHostedApp();
}
bool Extension::is_packaged_app() const {
return manifest()->IsPackagedApp();
}
bool Extension::is_theme() const {
return manifest()->IsTheme();
}
GURL Extension::GetBackgroundURL() const {
if (!background_scripts_.empty()) {
return GetResourceURL(
extension_filenames::kGeneratedBackgroundPageFilename);
} else {
return background_url_;
}
}
bool Extension::IsResourceWebAccessible(const std::string& relative_path)
const {
// For old manifest versions which do not specify web_accessible_resources
// we always allow resource loads.
if (manifest_version_ < 2 && !HasWebAccessibleResources())
return true;
if (web_accessible_resources_.find(relative_path) !=
web_accessible_resources_.end())
return true;
return false;
}
bool Extension::HasWebAccessibleResources() const {
if (web_accessible_resources_.size())
return true;
return false;
}
bool Extension::GenerateId(const std::string& input, std::string* output) {
DCHECK(output);
uint8 hash[Extension::kIdSize];
crypto::SHA256HashString(input, hash, sizeof(hash));
*output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
ConvertHexadecimalToIDAlphabet(output);
return true;
}
// Helper method that loads a UserScript object from a dictionary in the
// content_script list of the manifest.
bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
int definition_index,
string16* error,
UserScript* result) {
// run_at
if (content_script->HasKey(keys::kRunAt)) {
std::string run_location;
if (!content_script->GetString(keys::kRunAt, &run_location)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidRunAt,
base::IntToString(definition_index));
return false;
}
if (run_location == values::kRunAtDocumentStart) {
result->set_run_location(UserScript::DOCUMENT_START);
} else if (run_location == values::kRunAtDocumentEnd) {
result->set_run_location(UserScript::DOCUMENT_END);
} else if (run_location == values::kRunAtDocumentIdle) {
result->set_run_location(UserScript::DOCUMENT_IDLE);
} else {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidRunAt,
base::IntToString(definition_index));
return false;
}
}
// all frames
if (content_script->HasKey(keys::kAllFrames)) {
bool all_frames = false;
if (!content_script->GetBoolean(keys::kAllFrames, &all_frames)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidAllFrames, base::IntToString(definition_index));
return false;
}
result->set_match_all_frames(all_frames);
}
// matches (required)
ListValue* matches = NULL;
if (!content_script->GetList(keys::kMatches, &matches)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidMatches,
base::IntToString(definition_index));
return false;
}
if (matches->GetSize() == 0) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidMatchCount,
base::IntToString(definition_index));
return false;
}
for (size_t j = 0; j < matches->GetSize(); ++j) {
std::string match_str;
if (!matches->GetString(j, &match_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidMatch,
base::IntToString(definition_index),
base::IntToString(j),
errors::kExpectString);
return false;
}
URLPattern pattern(UserScript::kValidUserScriptSchemes);
if (CanExecuteScriptEverywhere())
pattern.SetValidSchemes(URLPattern::SCHEME_ALL);
URLPattern::ParseResult parse_result = pattern.Parse(match_str);
if (parse_result != URLPattern::PARSE_SUCCESS) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidMatch,
base::IntToString(definition_index),
base::IntToString(j),
URLPattern::GetParseResultString(parse_result));
return false;
}
if (pattern.MatchesScheme(chrome::kFileScheme) &&
!CanExecuteScriptEverywhere()) {
wants_file_access_ = true;
if (!(creation_flags_ & ALLOW_FILE_ACCESS))
pattern.SetValidSchemes(
pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
}
result->add_url_pattern(pattern);
}
// exclude_matches
if (content_script->HasKey(keys::kExcludeMatches)) { // optional
ListValue* exclude_matches = NULL;
if (!content_script->GetList(keys::kExcludeMatches, &exclude_matches)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidExcludeMatches,
base::IntToString(definition_index));
return false;
}
for (size_t j = 0; j < exclude_matches->GetSize(); ++j) {
std::string match_str;
if (!exclude_matches->GetString(j, &match_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidExcludeMatch,
base::IntToString(definition_index),
base::IntToString(j),
errors::kExpectString);
return false;
}
URLPattern pattern(UserScript::kValidUserScriptSchemes);
if (CanExecuteScriptEverywhere())
pattern.SetValidSchemes(URLPattern::SCHEME_ALL);
URLPattern::ParseResult parse_result = pattern.Parse(match_str);
if (parse_result != URLPattern::PARSE_SUCCESS) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidExcludeMatch,
base::IntToString(definition_index), base::IntToString(j),
URLPattern::GetParseResultString(parse_result));
return false;
}
result->add_exclude_url_pattern(pattern);
}
}
// include/exclude globs (mostly for Greasemonkey compatibility)
if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
error, &UserScript::add_glob, result)) {
return false;
}
if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
error, &UserScript::add_exclude_glob, result)) {
return false;
}
// js and css keys
ListValue* js = NULL;
if (content_script->HasKey(keys::kJs) &&
!content_script->GetList(keys::kJs, &js)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidJsList,
base::IntToString(definition_index));
return false;
}
ListValue* css = NULL;
if (content_script->HasKey(keys::kCss) &&
!content_script->GetList(keys::kCss, &css)) {
*error = ExtensionErrorUtils::
FormatErrorMessageUTF16(errors::kInvalidCssList,
base::IntToString(definition_index));
return false;
}
// The manifest needs to have at least one js or css user script definition.
if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kMissingFile,
base::IntToString(definition_index));
return false;
}
if (js) {
for (size_t script_index = 0; script_index < js->GetSize();
++script_index) {
Value* value;
std::string relative;
if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidJs,
base::IntToString(definition_index),
base::IntToString(script_index));
return false;
}
GURL url = GetResourceURL(relative);
ExtensionResource resource = GetResource(relative);
result->js_scripts().push_back(UserScript::File(
resource.extension_root(), resource.relative_path(), url));
}
}
if (css) {
for (size_t script_index = 0; script_index < css->GetSize();
++script_index) {
Value* value;
std::string relative;
if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidCss,
base::IntToString(definition_index),
base::IntToString(script_index));
return false;
}
GURL url = GetResourceURL(relative);
ExtensionResource resource = GetResource(relative);
result->css_scripts().push_back(UserScript::File(
resource.extension_root(), resource.relative_path(), url));
}
}
return true;
}
bool Extension::LoadGlobsHelper(
const DictionaryValue* content_script,
int content_script_index,
const char* globs_property_name,
string16* error,
void(UserScript::*add_method)(const std::string& glob),
UserScript *instance) {
if (!content_script->HasKey(globs_property_name))
return true; // they are optional
ListValue* list = NULL;
if (!content_script->GetList(globs_property_name, &list)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidGlobList,
base::IntToString(content_script_index),
globs_property_name);
return false;
}
for (size_t i = 0; i < list->GetSize(); ++i) {
std::string glob;
if (!list->GetString(i, &glob)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidGlob,
base::IntToString(content_script_index),
globs_property_name,
base::IntToString(i));
return false;
}
(instance->*add_method)(glob);
}
return true;
}
ExtensionAction* Extension::LoadExtensionActionHelper(
const DictionaryValue* extension_action, string16* error) {
scoped_ptr<ExtensionAction> result(new ExtensionAction());
result->set_extension_id(id());
// Page actions are hidden by default, and browser actions ignore
// visibility.
result->SetIsVisible(ExtensionAction::kDefaultTabId, false);
if (manifest_version_ == 1) {
ListValue* icons = NULL;
if (extension_action->HasKey(keys::kPageActionIcons) &&
extension_action->GetList(keys::kPageActionIcons, &icons)) {
for (ListValue::const_iterator iter = icons->begin();
iter != icons->end(); ++iter) {
std::string path;
if (!(*iter)->GetAsString(&path) || path.empty()) {
*error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
return NULL;
}
result->icon_paths()->push_back(path);
}
}
std::string id;
if (extension_action->HasKey(keys::kPageActionId)) {
if (!extension_action->GetString(keys::kPageActionId, &id)) {
*error = ASCIIToUTF16(errors::kInvalidPageActionId);
return NULL;
}
result->set_id(id);
}
}
std::string default_icon;
// Read the page action |default_icon| (optional).
if (extension_action->HasKey(keys::kPageActionDefaultIcon)) {
if (!extension_action->GetString(keys::kPageActionDefaultIcon,
&default_icon) ||
default_icon.empty()) {
*error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
return NULL;
}
result->set_default_icon_path(default_icon);
}
// Read the page action title from |default_title| if present, |name| if not
// (both optional).
std::string title;
if (extension_action->HasKey(keys::kPageActionDefaultTitle)) {
if (!extension_action->GetString(keys::kPageActionDefaultTitle, &title)) {
*error = ASCIIToUTF16(errors::kInvalidPageActionDefaultTitle);
return NULL;
}
} else if (manifest_version_ == 1 && extension_action->HasKey(keys::kName)) {
if (!extension_action->GetString(keys::kName, &title)) {
*error = ASCIIToUTF16(errors::kInvalidPageActionName);
return NULL;
}
}
result->SetTitle(ExtensionAction::kDefaultTabId, title);
// Read the action's |popup| (optional).
const char* popup_key = NULL;
if (extension_action->HasKey(keys::kPageActionDefaultPopup))
popup_key = keys::kPageActionDefaultPopup;
if (manifest_version_ == 1 &&
extension_action->HasKey(keys::kPageActionPopup)) {
if (popup_key) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPageActionOldAndNewKeys,
keys::kPageActionDefaultPopup,
keys::kPageActionPopup);
return NULL;
}
popup_key = keys::kPageActionPopup;
}
if (popup_key) {
DictionaryValue* popup = NULL;
std::string url_str;
if (extension_action->GetString(popup_key, &url_str)) {
// On success, |url_str| is set. Nothing else to do.
} else if (manifest_version_ == 1 &&
extension_action->GetDictionary(popup_key, &popup)) {
if (!popup->GetString(keys::kPageActionPopupPath, &url_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPageActionPopupPath, "<missing>");
return NULL;
}
} else {
*error = ASCIIToUTF16(errors::kInvalidPageActionPopup);
return NULL;
}
if (!url_str.empty()) {
// An empty string is treated as having no popup.
GURL url = GetResourceURL(url_str);
if (!url.is_valid()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPageActionPopupPath, url_str);
return NULL;
}
result->SetPopupUrl(ExtensionAction::kDefaultTabId, url);
} else {
DCHECK(!result->HasPopup(ExtensionAction::kDefaultTabId))
<< "Shouldn't be possible for the popup to be set.";
}
}
return result.release();
}
// static
bool Extension::InitExtensionID(extensions::Manifest* manifest,
const FilePath& path,
const std::string& explicit_id,
int creation_flags,
string16* error) {
if (!explicit_id.empty()) {
manifest->set_extension_id(explicit_id);
return true;
}
if (manifest->HasKey(keys::kPublicKey)) {
std::string public_key;
std::string public_key_bytes;
std::string extension_id;
if (!manifest->GetString(keys::kPublicKey, &public_key) ||
!ParsePEMKeyBytes(public_key, &public_key_bytes) ||
!GenerateId(public_key_bytes, &extension_id)) {
*error = ASCIIToUTF16(errors::kInvalidKey);
return false;
}
manifest->set_extension_id(extension_id);
return true;
}
if (creation_flags & REQUIRE_KEY) {
*error = ASCIIToUTF16(errors::kInvalidKey);
return false;
} else {
// If there is a path, we generate the ID from it. This is useful for
// development mode, because it keeps the ID stable across restarts and
// reloading the extension.
std::string extension_id = GenerateIdForPath(path);
if (extension_id.empty()) {
NOTREACHED() << "Could not create ID from path.";
return false;
}
manifest->set_extension_id(extension_id);
return true;
}
}
bool Extension::CheckMinimumChromeVersion(string16* error) {
if (!manifest_->HasKey(keys::kMinimumChromeVersion))
return true;
std::string minimum_version_string;
if (!manifest_->GetString(keys::kMinimumChromeVersion,
&minimum_version_string)) {
*error = ASCIIToUTF16(errors::kInvalidMinimumChromeVersion);
return false;
}
scoped_ptr<Version> minimum_version(
Version::GetVersionFromString(minimum_version_string));
if (!minimum_version.get()) {
*error = ASCIIToUTF16(errors::kInvalidMinimumChromeVersion);
return false;
}
chrome::VersionInfo current_version_info;
if (!current_version_info.is_valid()) {
NOTREACHED();
return false;
}
scoped_ptr<Version> current_version(
Version::GetVersionFromString(current_version_info.Version()));
if (!current_version.get()) {
DCHECK(false);
return false;
}
if (current_version->CompareTo(*minimum_version) < 0) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kChromeVersionTooLow,
l10n_util::GetStringUTF8(IDS_PRODUCT_NAME),
minimum_version_string);
return false;
}
return true;
}
bool Extension::LoadRequiredFeatures(string16* error) {
if (!LoadName(error) ||
!LoadVersion(error))
return false;
return true;
}
bool Extension::LoadName(string16* error) {
string16 localized_name;
if (!manifest_->GetString(keys::kName, &localized_name)) {
*error = ASCIIToUTF16(errors::kInvalidName);
return false;
}
base::i18n::AdjustStringForLocaleDirection(&localized_name);
name_ = UTF16ToUTF8(localized_name);
return true;
}
bool Extension::LoadDescription(string16* error) {
if (manifest_->HasKey(keys::kDescription) &&
!manifest_->GetString(keys::kDescription, &description_)) {
*error = ASCIIToUTF16(errors::kInvalidDescription);
return false;
}
return true;
}
bool Extension::LoadAppFeatures(string16* error) {
if (!LoadExtent(keys::kWebURLs, &extent_,
errors::kInvalidWebURLs, errors::kInvalidWebURL, error) ||
!LoadLaunchURL(error) ||
!LoadLaunchContainer(error))
return false;
return true;
}
bool Extension::LoadExtent(const char* key,
URLPatternSet* extent,
const char* list_error,
const char* value_error,
string16* error) {
Value* temp = NULL;
if (!manifest_->Get(key, &temp))
return true;
if (temp->GetType() != Value::TYPE_LIST) {
*error = ASCIIToUTF16(list_error);
return false;
}
ListValue* pattern_list = static_cast<ListValue*>(temp);
for (size_t i = 0; i < pattern_list->GetSize(); ++i) {
std::string pattern_string;
if (!pattern_list->GetString(i, &pattern_string)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(value_error,
base::UintToString(i),
errors::kExpectString);
return false;
}
URLPattern pattern(kValidWebExtentSchemes);
URLPattern::ParseResult parse_result = pattern.Parse(pattern_string);
if (parse_result == URLPattern::PARSE_ERROR_EMPTY_PATH) {
pattern_string += "/";
parse_result = pattern.Parse(pattern_string);
}
if (parse_result != URLPattern::PARSE_SUCCESS) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
value_error,
base::UintToString(i),
URLPattern::GetParseResultString(parse_result));
return false;
}
// Do not allow authors to claim "<all_urls>".
if (pattern.match_all_urls()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
value_error,
base::UintToString(i),
errors::kCannotClaimAllURLsInExtent);
return false;
}
// Do not allow authors to claim "*" for host.
if (pattern.host().empty()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
value_error,
base::UintToString(i),
errors::kCannotClaimAllHostsInExtent);
return false;
}
// We do not allow authors to put wildcards in their paths. Instead, we
// imply one at the end.
if (pattern.path().find('*') != std::string::npos) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
value_error,
base::UintToString(i),
errors::kNoWildCardsInPaths);
return false;
}
pattern.SetPath(pattern.path() + '*');
extent->AddPattern(pattern);
}
return true;
}
bool Extension::LoadLaunchURL(string16* error) {
Value* temp = NULL;
// launch URL can be either local (to chrome-extension:// root) or an absolute
// web URL.
if (manifest_->Get(keys::kLaunchLocalPath, &temp)) {
if (manifest_->Get(keys::kLaunchWebURL, NULL)) {
*error = ASCIIToUTF16(errors::kLaunchPathAndURLAreExclusive);
return false;
}
if (manifest_->Get(keys::kWebURLs, NULL)) {
*error = ASCIIToUTF16(errors::kLaunchPathAndExtentAreExclusive);
return false;
}
std::string launch_path;
if (!temp->GetAsString(&launch_path)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchLocalPath);
return false;
}
// Ensure the launch path is a valid relative URL.
GURL resolved = url().Resolve(launch_path);
if (!resolved.is_valid() || resolved.GetOrigin() != url()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchLocalPath);
return false;
}
launch_local_path_ = launch_path;
} else if (manifest_->Get(keys::kLaunchWebURL, &temp)) {
std::string launch_url;
if (!temp->GetAsString(&launch_url)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchWebURL);
return false;
}
// Ensure the launch URL is a valid absolute URL and web extent scheme.
GURL url(launch_url);
URLPattern pattern(kValidWebExtentSchemes);
if (!url.is_valid() || !pattern.SetScheme(url.scheme())) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchWebURL);
return false;
}
launch_web_url_ = launch_url;
} else if (is_app()) {
*error = ASCIIToUTF16(errors::kLaunchURLRequired);
return false;
}
// If there is no extent, we default the extent based on the launch URL.
if (web_extent().is_empty() && !launch_web_url().empty()) {
GURL launch_url(launch_web_url());
URLPattern pattern(kValidWebExtentSchemes);
if (!pattern.SetScheme("*")) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchWebURL);
return false;
}
pattern.SetHost(launch_url.host());
pattern.SetPath("/*");
extent_.AddPattern(pattern);
}
// In order for the --apps-gallery-url switch to work with the gallery
// process isolation, we must insert any provided value into the component
// app's launch url and web extent.
if (id() == extension_misc::kWebStoreAppId) {
std::string gallery_url_str = CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switches::kAppsGalleryURL);
// Empty string means option was not used.
if (!gallery_url_str.empty()) {
GURL gallery_url(gallery_url_str);
OverrideLaunchUrl(gallery_url);
}
} else if (id() == extension_misc::kCloudPrintAppId) {
// In order for the --cloud-print-service switch to work, we must update
// the launch URL and web extent.
// TODO(sanjeevr): Ideally we want to use CloudPrintURL here but that is
// currently under chrome/browser.
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
GURL cloud_print_service_url = GURL(command_line.GetSwitchValueASCII(
switches::kCloudPrintServiceURL));
if (!cloud_print_service_url.is_empty()) {
std::string path(
cloud_print_service_url.path() + "/enable_chrome_connector");
GURL::Replacements replacements;
replacements.SetPathStr(path);
GURL cloud_print_enable_connector_url =
cloud_print_service_url.ReplaceComponents(replacements);
OverrideLaunchUrl(cloud_print_enable_connector_url);
}
}
return true;
}
bool Extension::LoadLaunchContainer(string16* error) {
Value* temp = NULL;
if (!manifest_->Get(keys::kLaunchContainer, &temp))
return true;
std::string launch_container_string;
if (!temp->GetAsString(&launch_container_string)) {
*error = ASCIIToUTF16(errors::kInvalidLaunchContainer);
return false;
}
if (launch_container_string == values::kLaunchContainerShell) {
launch_container_ = extension_misc::LAUNCH_SHELL;
} else if (launch_container_string == values::kLaunchContainerPanel) {
launch_container_ = extension_misc::LAUNCH_PANEL;
} else if (launch_container_string == values::kLaunchContainerTab) {
launch_container_ = extension_misc::LAUNCH_TAB;
} else {
*error = ASCIIToUTF16(errors::kInvalidLaunchContainer);
return false;
}
bool can_specify_initial_size =
launch_container_ == extension_misc::LAUNCH_PANEL ||
launch_container_ == extension_misc::LAUNCH_WINDOW ||
launch_container_ == extension_misc::LAUNCH_SHELL;
// Validate the container width if present.
if (!ReadLaunchDimension(manifest_,
keys::kLaunchWidth,
&launch_width_,
can_specify_initial_size,
error))
return false;
// Validate container height if present.
if (!ReadLaunchDimension(manifest_,
keys::kLaunchHeight,
&launch_height_,
can_specify_initial_size,
error))
return false;
bool can_specify_size_range =
launch_container_ == extension_misc::LAUNCH_SHELL;
// Validate min size if present.
if (!ReadLaunchDimension(manifest_,
keys::kLaunchMinWidth,
&launch_min_width_,
can_specify_size_range,
error))
return false;
if (!ReadLaunchDimension(manifest_,
keys::kLaunchMinHeight,
&launch_min_height_,
can_specify_size_range,
error))
return false;
if (!ReadLaunchDimension(manifest_,
keys::kLaunchMaxWidth,
&launch_max_width_,
can_specify_size_range,
error))
return false;
if (!ReadLaunchDimension(manifest_,
keys::kLaunchMaxHeight,
&launch_max_height_,
can_specify_size_range,
error))
return false;
if (launch_container_ == extension_misc::LAUNCH_SHELL) {
if (!manifest_->Get(keys::kLaunchWidth, &temp)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchWidth);
return false;
}
if (!manifest_->Get(keys::kLaunchHeight, &temp)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchHeight);
return false;
}
if (launch_max_width_ > 0 && launch_max_width_ < launch_min_width_) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchMaxWidth);
return false;
}
if (launch_max_height_ > 0 && launch_max_height_ < launch_min_height_) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidLaunchValue,
keys::kLaunchMaxHeight);
return false;
}
}
if (is_platform_app()) {
if (launch_container_ != extension_misc::LAUNCH_SHELL) {
*error = ASCIIToUTF16(errors::kInvalidLaunchContainerForPlatform);
return false;
}
} else if (launch_container_ == extension_misc::LAUNCH_SHELL) {
*error = ASCIIToUTF16(errors::kInvalidLaunchContainerForNonPlatform);
return false;
}
return true;
}
bool Extension::LoadSharedFeatures(
const ExtensionAPIPermissionSet& api_permissions,
string16* error) {
if (!LoadDescription(error) ||
!LoadManifestVersion(error) ||
!LoadHomepageURL(error) ||
!LoadUpdateURL(error) ||
!LoadIcons(error) ||
!LoadCommands(error) ||
!LoadPlugins(error) ||
!LoadNaClModules(error) ||
!LoadWebAccessibleResources(error) ||
!CheckRequirements(error) ||
!LoadDefaultLocale(error) ||
!LoadOfflineEnabled(error) ||
!LoadOptionsPage(error) ||
// LoadBackgroundScripts() must be called before LoadBackgroundPage().
!LoadBackgroundScripts(error) ||
!LoadBackgroundPage(api_permissions, error) ||
!LoadBackgroundPersistent(api_permissions, error) ||
!LoadBackgroundAllowJSAccess(api_permissions, error) ||
!LoadWebIntentServices(error))
return false;
return true;
}
bool Extension::LoadVersion(string16* error) {
std::string version_str;
if (!manifest_->GetString(keys::kVersion, &version_str)) {
*error = ASCIIToUTF16(errors::kInvalidVersion);
return false;
}
version_.reset(Version::GetVersionFromString(version_str));
if (!version_.get() ||
version_->components().size() > 4) {
*error = ASCIIToUTF16(errors::kInvalidVersion);
return false;
}
return true;
}
bool Extension::LoadManifestVersion(string16* error) {
// Get the original value out of the dictionary so that we can validate it
// more strictly.
if (manifest_->value()->HasKey(keys::kManifestVersion)) {
int manifest_version = 1;
if (!manifest_->GetInteger(keys::kManifestVersion, &manifest_version) ||
manifest_version < 1) {
*error = ASCIIToUTF16(errors::kInvalidManifestVersion);
return false;
}
}
manifest_version_ = manifest_->GetManifestVersion();
if (creation_flags_ & REQUIRE_MODERN_MANIFEST_VERSION &&
manifest_version_ < kModernManifestVersion &&
!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowLegacyExtensionManifests)) {
*error = ASCIIToUTF16(errors::kInvalidManifestVersion);
return false;
}
return true;
}
bool Extension::LoadHomepageURL(string16* error) {
if (!manifest_->HasKey(keys::kHomepageURL))
return true;
std::string tmp;
if (!manifest_->GetString(keys::kHomepageURL, &tmp)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidHomepageURL, "");
return false;
}
homepage_url_ = GURL(tmp);
if (!homepage_url_.is_valid() ||
(!homepage_url_.SchemeIs("http") &&
!homepage_url_.SchemeIs("https"))) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidHomepageURL, tmp);
return false;
}
return true;
}
bool Extension::LoadUpdateURL(string16* error) {
if (!manifest_->HasKey(keys::kUpdateURL))
return true;
std::string tmp;
if (!manifest_->GetString(keys::kUpdateURL, &tmp)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidUpdateURL, "");
return false;
}
update_url_ = GURL(tmp);
if (!update_url_.is_valid() ||
update_url_.has_ref()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidUpdateURL, tmp);
return false;
}
return true;
}
bool Extension::LoadIcons(string16* error) {
if (!manifest_->HasKey(keys::kIcons))
return true;
DictionaryValue* icons_value = NULL;
if (!manifest_->GetDictionary(keys::kIcons, &icons_value)) {
*error = ASCIIToUTF16(errors::kInvalidIcons);
return false;
}
for (size_t i = 0; i < ExtensionIconSet::kNumIconSizes; ++i) {
std::string key = base::IntToString(ExtensionIconSet::kIconSizes[i]);
if (icons_value->HasKey(key)) {
std::string icon_path;
if (!icons_value->GetString(key, &icon_path)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIconPath, key);
return false;
}
if (!icon_path.empty() && icon_path[0] == '/')
icon_path = icon_path.substr(1);
if (icon_path.empty()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIconPath, key);
return false;
}
icons_.Add(ExtensionIconSet::kIconSizes[i], icon_path);
}
}
return true;
}
bool Extension::LoadCommands(string16* error) {
if (manifest_->HasKey(keys::kCommands)) {
DictionaryValue* commands = NULL;
if (!manifest_->GetDictionary(keys::kCommands, &commands)) {
*error = ASCIIToUTF16(errors::kInvalidCommandsKey);
return false;
}
int command_index = 0;
for (DictionaryValue::key_iterator iter = commands->begin_keys();
iter != commands->end_keys(); ++iter) {
++command_index;
DictionaryValue* command = NULL;
if (!commands->GetDictionary(*iter, &command)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidKeyBindingDictionary,
base::IntToString(command_index));
return false;
}
ExtensionKeybinding binding;
if (!binding.Parse(command, *iter, command_index, error))
return false; // |error| already set.
commands_.push_back(binding);
}
}
return true;
}
bool Extension::LoadPlugins(string16* error) {
if (!manifest_->HasKey(keys::kPlugins))
return true;
ListValue* list_value = NULL;
if (!manifest_->GetList(keys::kPlugins, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidPlugins);
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* plugin_value = NULL;
std::string path_str;
bool is_public = false;
if (!list_value->GetDictionary(i, &plugin_value)) {
*error = ASCIIToUTF16(errors::kInvalidPlugins);
return false;
}
// Get plugins[i].path.
if (!plugin_value->GetString(keys::kPluginsPath, &path_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPluginsPath, base::IntToString(i));
return false;
}
// Get plugins[i].content (optional).
if (plugin_value->HasKey(keys::kPluginsPublic)) {
if (!plugin_value->GetBoolean(keys::kPluginsPublic, &is_public)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPluginsPublic, base::IntToString(i));
return false;
}
}
// We don't allow extension plugins to run on Chrome OS. We still
// parse the manifest entry so that error messages are consistently
// displayed across platforms.
#if !defined(OS_CHROMEOS)
plugins_.push_back(PluginInfo());
plugins_.back().path = path().Append(FilePath::FromUTF8Unsafe(path_str));
plugins_.back().is_public = is_public;
#endif
}
return true;
}
bool Extension::LoadNaClModules(string16* error) {
if (!manifest_->HasKey(keys::kNaClModules))
return true;
ListValue* list_value = NULL;
if (!manifest_->GetList(keys::kNaClModules, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidNaClModules);
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* module_value = NULL;
std::string path_str;
std::string mime_type;
if (!list_value->GetDictionary(i, &module_value)) {
*error = ASCIIToUTF16(errors::kInvalidNaClModules);
return false;
}
// Get nacl_modules[i].path.
if (!module_value->GetString(keys::kNaClModulesPath, &path_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidNaClModulesPath, base::IntToString(i));
return false;
}
// Get nacl_modules[i].mime_type.
if (!module_value->GetString(keys::kNaClModulesMIMEType, &mime_type)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidNaClModulesMIMEType, base::IntToString(i));
return false;
}
nacl_modules_.push_back(NaClModuleInfo());
nacl_modules_.back().url = GetResourceURL(path_str);
nacl_modules_.back().mime_type = mime_type;
}
return true;
}
bool Extension::LoadWebAccessibleResources(string16* error) {
if (!manifest_->HasKey(keys::kWebAccessibleResources))
return true;
ListValue* list_value;
if (!manifest_->GetList(keys::kWebAccessibleResources, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidWebAccessibleResourcesList);
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
std::string relative_path;
if (!list_value->GetString(i, &relative_path)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidWebAccessibleResource, base::IntToString(i));
return false;
}
if (relative_path[0] != '/')
relative_path = '/' + relative_path;
web_accessible_resources_.insert(relative_path);
}
return true;
}
// These are not actually persisted (they're only used by the store), but
// still validated.
bool Extension::CheckRequirements(string16* error) {
if (!manifest_->HasKey(keys::kRequirements))
return true;
DictionaryValue* requirements_value = NULL;
if (!manifest_->GetDictionary(keys::kRequirements, &requirements_value)) {
*error = ASCIIToUTF16(errors::kInvalidRequirements);
return false;
}
for (DictionaryValue::key_iterator it = requirements_value->begin_keys();
it != requirements_value->end_keys(); ++it) {
DictionaryValue* requirement_value;
if (!requirements_value->GetDictionaryWithoutPathExpansion(
*it, &requirement_value)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidRequirement, *it);
return false;
}
}
return true;
}
bool Extension::LoadDefaultLocale(string16* error) {
if (!manifest_->HasKey(keys::kDefaultLocale))
return true;
if (!manifest_->GetString(keys::kDefaultLocale, &default_locale_) ||
!l10n_util::IsValidLocaleSyntax(default_locale_)) {
*error = ASCIIToUTF16(errors::kInvalidDefaultLocale);
return false;
}
return true;
}
bool Extension::LoadOfflineEnabled(string16* error) {
// Defaults to false.
if (manifest_->HasKey(keys::kOfflineEnabled) &&
!manifest_->GetBoolean(keys::kOfflineEnabled, &offline_enabled_)) {
*error = ASCIIToUTF16(errors::kInvalidOfflineEnabled);
return false;
}
return true;
}
bool Extension::LoadOptionsPage(string16* error) {
if (!manifest_->HasKey(keys::kOptionsPage))
return true;
std::string options_str;
if (!manifest_->GetString(keys::kOptionsPage, &options_str)) {
*error = ASCIIToUTF16(errors::kInvalidOptionsPage);
return false;
}
if (is_hosted_app()) {
// hosted apps require an absolute URL.
GURL options_url(options_str);
if (!options_url.is_valid() ||
!(options_url.SchemeIs("http") || options_url.SchemeIs("https"))) {
*error = ASCIIToUTF16(errors::kInvalidOptionsPageInHostedApp);
return false;
}
options_url_ = options_url;
} else {
GURL absolute(options_str);
if (absolute.is_valid()) {
*error = ASCIIToUTF16(errors::kInvalidOptionsPageExpectUrlInPackage);
return false;
}
options_url_ = GetResourceURL(options_str);
if (!options_url_.is_valid()) {
*error = ASCIIToUTF16(errors::kInvalidOptionsPage);
return false;
}
}
return true;
}
bool Extension::LoadBackgroundScripts(string16* error) {
Value* background_scripts_value = NULL;
if (!manifest_->Get(keys::kBackgroundScripts, &background_scripts_value))
return true;
CHECK(background_scripts_value);
if (background_scripts_value->GetType() != Value::TYPE_LIST) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundScripts);
return false;
}
ListValue* background_scripts =
static_cast<ListValue*>(background_scripts_value);
for (size_t i = 0; i < background_scripts->GetSize(); ++i) {
std::string script;
if (!background_scripts->GetString(i, &script)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidBackgroundScript, base::IntToString(i));
return false;
}
background_scripts_.push_back(script);
}
return true;
}
bool Extension::LoadBackgroundPage(
const ExtensionAPIPermissionSet& api_permissions,
string16* error) {
base::Value* background_page_value = NULL;
if (!manifest_->Get(keys::kBackgroundPage, &background_page_value))
manifest_->Get(keys::kBackgroundPageLegacy, &background_page_value);
if (!background_page_value)
return true;
std::string background_str;
if (!background_page_value->GetAsString(&background_str)) {
*error = ASCIIToUTF16(errors::kInvalidBackground);
return false;
}
if (!background_scripts_.empty()) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundCombination);
return false;
}
if (is_hosted_app()) {
// Make sure "background" permission is set.
if (!api_permissions.count(ExtensionAPIPermission::kBackground)) {
*error = ASCIIToUTF16(errors::kBackgroundPermissionNeeded);
return false;
}
// Hosted apps require an absolute URL.
GURL bg_page(background_str);
if (!bg_page.is_valid()) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundInHostedApp);
return false;
}
if (!(bg_page.SchemeIs("https") ||
(CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowHTTPBackgroundPage) &&
bg_page.SchemeIs("http")))) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundInHostedApp);
return false;
}
background_url_ = bg_page;
} else {
background_url_ = GetResourceURL(background_str);
}
return true;
}
bool Extension::LoadBackgroundPersistent(
const ExtensionAPIPermissionSet& api_permissions,
string16* error) {
Value* background_persistent = NULL;
if (!api_permissions.count(ExtensionAPIPermission::kExperimental) ||
!manifest_->Get(keys::kBackgroundPersistent, &background_persistent))
return true;
if (!background_persistent->IsType(Value::TYPE_BOOLEAN) ||
!background_persistent->GetAsBoolean(&background_page_persists_)) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundPersistent);
return false;
}
if (!has_background_page()) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundPersistentNoPage);
return false;
}
return true;
}
bool Extension::LoadBackgroundAllowJSAccess(
const ExtensionAPIPermissionSet& api_permissions,
string16* error) {
Value* allow_js_access = NULL;
if (!manifest_->Get(keys::kBackgroundAllowJsAccess, &allow_js_access))
return true;
if (!allow_js_access->IsType(Value::TYPE_BOOLEAN) ||
!allow_js_access->GetAsBoolean(&allow_background_js_access_)) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundAllowJsAccess);
return false;
}
if (!has_background_page()) {
*error = ASCIIToUTF16(errors::kInvalidBackgroundAllowJsAccessNoPage);
return false;
}
return true;
}
bool Extension::LoadWebIntentAction(const std::string& action_name,
const DictionaryValue& intent_service,
string16* error) {
DCHECK(error);
webkit_glue::WebIntentServiceData service;
std::string value;
service.action = UTF8ToUTF16(action_name);
ListValue* mime_types = NULL;
if (!intent_service.HasKey(keys::kIntentType) ||
!intent_service.GetList(keys::kIntentType, &mime_types) ||
mime_types->GetSize() == 0) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIntentType, action_name);
return false;
}
std::string href;
if (intent_service.HasKey(keys::kIntentPath)) {
if (!intent_service.GetString(keys::kIntentPath, &href)) {
*error = ASCIIToUTF16(errors::kInvalidIntentHref);
return false;
}
}
if (intent_service.HasKey(keys::kIntentHref)) {
if (!href.empty()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIntentHrefOldAndNewKey, action_name,
keys::kIntentPath, keys::kIntentHref);
return false;
}
if (!intent_service.GetString(keys::kIntentHref, &href)) {
*error = ASCIIToUTF16(errors::kInvalidIntentHref);
return false;
}
}
// For packaged/hosted apps, empty href implies the respective launch URLs.
if (href.empty()) {
if (is_hosted_app()) {
href = launch_web_url();
} else if (is_packaged_app()) {
href = launch_local_path();
}
}
// If we still don't have an href, the manifest is malformed.
if (href.empty()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIntentHrefEmpty, action_name);
return false;
}
GURL service_url(href);
if (is_hosted_app()) {
// Hosted apps require an absolute URL for intents.
if (!service_url.is_valid() ||
!(web_extent().MatchesURL(service_url))) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIntentPageInHostedApp, action_name);
return false;
}
service.service_url = service_url;
} else {
// We do not allow absolute intent URLs in non-hosted apps.
if (service_url.is_valid()) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kCannotAccessPage, href);
return false;
}
service.service_url = GetResourceURL(href);
}
if (intent_service.HasKey(keys::kIntentTitle) &&
!intent_service.GetString(keys::kIntentTitle, &service.title)) {
*error = ASCIIToUTF16(errors::kInvalidIntentTitle);
return false;
}
if (intent_service.HasKey(keys::kIntentDisposition)) {
if (!intent_service.GetString(keys::kIntentDisposition, &value) ||
(value != values::kIntentDispositionWindow &&
value != values::kIntentDispositionInline)) {
*error = ASCIIToUTF16(errors::kInvalidIntentDisposition);
return false;
}
if (value == values::kIntentDispositionInline) {
service.disposition =
webkit_glue::WebIntentServiceData::DISPOSITION_INLINE;
} else {
service.disposition =
webkit_glue::WebIntentServiceData::DISPOSITION_WINDOW;
}
}
for (size_t i = 0; i < mime_types->GetSize(); ++i) {
if (!mime_types->GetString(i, &service.type)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIntentTypeElement, action_name,
std::string(base::IntToString(i)));
return false;
}
intents_services_.push_back(service);
}
return true;
}
bool Extension::LoadWebIntentServices(string16* error) {
DCHECK(error);
if (!manifest_->HasKey(keys::kIntents))
return true;
DictionaryValue* all_services = NULL;
if (!manifest_->GetDictionary(keys::kIntents, &all_services)) {
*error = ASCIIToUTF16(errors::kInvalidIntents);
return false;
}
for (DictionaryValue::key_iterator iter(all_services->begin_keys());
iter != all_services->end_keys(); ++iter) {
// Any entry in the intents dictionary can either have a list of
// dictionaries, or just a single dictionary attached to that. Try
// lists first, fall back to single dictionary.
ListValue* service_list = NULL;
DictionaryValue* one_service = NULL;
if (all_services->GetListWithoutPathExpansion(*iter, &service_list)) {
for (size_t i = 0; i < service_list->GetSize(); ++i) {
if (!service_list->GetDictionary(i, &one_service)) {
*error = ASCIIToUTF16(errors::kInvalidIntent);
return false;
}
if (!LoadWebIntentAction(*iter, *one_service, error))
return false;
}
} else {
if (!all_services->GetDictionaryWithoutPathExpansion(*iter,
&one_service)) {
*error = ASCIIToUTF16(errors::kInvalidIntent);
return false;
}
if (!LoadWebIntentAction(*iter, *one_service, error))
return false;
}
}
return true;
}
bool Extension::LoadExtensionFeatures(
const ExtensionAPIPermissionSet& api_permissions,
string16* error) {
if (manifest_->HasKey(keys::kConvertedFromUserScript))
manifest_->GetBoolean(keys::kConvertedFromUserScript,
&converted_from_user_script_);
if (!LoadDevToolsPage(error) ||
!LoadInputComponents(api_permissions, error) ||
!LoadContentScripts(error) ||
!LoadPageAction(error) ||
!LoadBrowserAction(error) ||
!LoadFileBrowserHandlers(error) ||
!LoadChromeURLOverrides(error) ||
!LoadOmnibox(error) ||
!LoadTextToSpeechVoices(error) ||
!LoadIncognitoMode(error) ||
!LoadContentSecurityPolicy(error))
return false;
return true;
}
bool Extension::LoadDevToolsPage(string16* error) {
if (!manifest_->HasKey(keys::kDevToolsPage))
return true;
std::string devtools_str;
if (!manifest_->GetString(keys::kDevToolsPage, &devtools_str)) {
*error = ASCIIToUTF16(errors::kInvalidDevToolsPage);
return false;
}
devtools_url_ = GetResourceURL(devtools_str);
return true;
}
bool Extension::LoadInputComponents(
const ExtensionAPIPermissionSet& api_permissions,
string16* error) {
if (!manifest_->HasKey(keys::kInputComponents))
return true;
ListValue* list_value = NULL;
if (!manifest_->GetList(keys::kInputComponents, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidInputComponents);
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* module_value = NULL;
std::string name_str;
InputComponentType type;
std::string id_str;
std::string description_str;
std::string language_str;
std::set<std::string> layouts;
std::string shortcut_keycode_str;
bool shortcut_alt = false;
bool shortcut_ctrl = false;
bool shortcut_shift = false;
if (!list_value->GetDictionary(i, &module_value)) {
*error = ASCIIToUTF16(errors::kInvalidInputComponents);
return false;
}
// Get input_components[i].name.
if (!module_value->GetString(keys::kName, &name_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentName, base::IntToString(i));
return false;
}
// Get input_components[i].type.
std::string type_str;
if (module_value->GetString(keys::kType, &type_str)) {
if (type_str == "ime") {
type = INPUT_COMPONENT_TYPE_IME;
} else if (type_str == "virtual_keyboard") {
if (!api_permissions.count(ExtensionAPIPermission::kExperimental)) {
// Virtual Keyboards require the experimental flag.
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentType, base::IntToString(i));
return false;
}
type = INPUT_COMPONENT_TYPE_VIRTUAL_KEYBOARD;
} else {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentType, base::IntToString(i));
return false;
}
} else {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentType, base::IntToString(i));
return false;
}
// Get input_components[i].id.
if (!module_value->GetString(keys::kId, &id_str)) {
id_str = "";
}
// Get input_components[i].description.
if (!module_value->GetString(keys::kDescription, &description_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentDescription, base::IntToString(i));
return false;
}
// Get input_components[i].language.
if (!module_value->GetString(keys::kLanguage, &language_str)) {
language_str = "";
}
// Get input_components[i].layouts.
ListValue* layouts_value = NULL;
if (!module_value->GetList(keys::kLayouts, &layouts_value)) {
*error = ASCIIToUTF16(errors::kInvalidInputComponentLayouts);
return false;
}
for (size_t j = 0; j < layouts_value->GetSize(); ++j) {
std::string layout_name_str;
if (!layouts_value->GetString(j, &layout_name_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentLayoutName, base::IntToString(i),
base::IntToString(j));
return false;
}
layouts.insert(layout_name_str);
}
if (module_value->HasKey(keys::kShortcutKey)) {
DictionaryValue* shortcut_value = NULL;
if (!module_value->GetDictionary(keys::kShortcutKey, &shortcut_value)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentShortcutKey, base::IntToString(i));
return false;
}
// Get input_components[i].shortcut_keycode.
if (!shortcut_value->GetString(keys::kKeycode, &shortcut_keycode_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidInputComponentShortcutKeycode,
base::IntToString(i));
return false;
}
// Get input_components[i].shortcut_alt.
if (!shortcut_value->GetBoolean(keys::kAltKey, &shortcut_alt)) {
shortcut_alt = false;
}
// Get input_components[i].shortcut_ctrl.
if (!shortcut_value->GetBoolean(keys::kCtrlKey, &shortcut_ctrl)) {
shortcut_ctrl = false;
}
// Get input_components[i].shortcut_shift.
if (!shortcut_value->GetBoolean(keys::kShiftKey, &shortcut_shift)) {
shortcut_shift = false;
}
}
input_components_.push_back(InputComponentInfo());
input_components_.back().name = name_str;
input_components_.back().type = type;
input_components_.back().id = id_str;
input_components_.back().description = description_str;
input_components_.back().language = language_str;
input_components_.back().layouts.insert(layouts.begin(), layouts.end());
input_components_.back().shortcut_keycode = shortcut_keycode_str;
input_components_.back().shortcut_alt = shortcut_alt;
input_components_.back().shortcut_ctrl = shortcut_ctrl;
input_components_.back().shortcut_shift = shortcut_shift;
}
return true;
}
bool Extension::LoadContentScripts(string16* error) {
if (!manifest_->HasKey(keys::kContentScripts))
return true;
ListValue* list_value;
if (!manifest_->GetList(keys::kContentScripts, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidContentScriptsList);
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* content_script = NULL;
if (!list_value->GetDictionary(i, &content_script)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidContentScript, base::IntToString(i));
return false;
}
UserScript script;
if (!LoadUserScriptHelper(content_script, i, error, &script))
return false; // Failed to parse script context definition.
script.set_extension_id(id());
if (converted_from_user_script_) {
script.set_emulate_greasemonkey(true);
script.set_match_all_frames(true); // Greasemonkey matches all frames.
}
content_scripts_.push_back(script);
}
return true;
}
bool Extension::LoadPageAction(string16* error) {
DictionaryValue* page_action_value = NULL;
if (manifest_->HasKey(keys::kPageActions)) {
ListValue* list_value = NULL;
if (!manifest_->GetList(keys::kPageActions, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidPageActionsList);
return false;
}
size_t list_value_length = list_value->GetSize();
if (list_value_length == 0u) {
// A list with zero items is allowed, and is equivalent to not having
// a page_actions key in the manifest. Don't set |page_action_value|.
} else if (list_value_length == 1u) {
if (!list_value->GetDictionary(0, &page_action_value)) {
*error = ASCIIToUTF16(errors::kInvalidPageAction);
return false;
}
} else { // list_value_length > 1u.
*error = ASCIIToUTF16(errors::kInvalidPageActionsListSize);
return false;
}
} else if (manifest_->HasKey(keys::kPageAction)) {
if (!manifest_->GetDictionary(keys::kPageAction, &page_action_value)) {
*error = ASCIIToUTF16(errors::kInvalidPageAction);
return false;
}
}
// If page_action_value is not NULL, then there was a valid page action.
if (page_action_value) {
page_action_.reset(
LoadExtensionActionHelper(page_action_value, error));
if (!page_action_.get())
return false; // Failed to parse page action definition.
}
return true;
}
bool Extension::LoadBrowserAction(string16* error) {
if (!manifest_->HasKey(keys::kBrowserAction))
return true;
DictionaryValue* browser_action_value = NULL;
if (!manifest_->GetDictionary(keys::kBrowserAction, &browser_action_value)) {
*error = ASCIIToUTF16(errors::kInvalidBrowserAction);
return false;
}
browser_action_.reset(
LoadExtensionActionHelper(browser_action_value, error));
if (!browser_action_.get())
return false; // Failed to parse browser action definition.
return true;
}
bool Extension::LoadFileBrowserHandlers(string16* error) {
if (!manifest_->HasKey(keys::kFileBrowserHandlers))
return true;
ListValue* file_browser_handlers_value = NULL;
if (!manifest_->GetList(keys::kFileBrowserHandlers,
&file_browser_handlers_value)) {
*error = ASCIIToUTF16(errors::kInvalidFileBrowserHandler);
return false;
}
file_browser_handlers_.reset(
LoadFileBrowserHandlersHelper(file_browser_handlers_value, error));
if (!file_browser_handlers_.get())
return false; // Failed to parse file browser actions definition.
return true;
}
Extension::FileBrowserHandlerList* Extension::LoadFileBrowserHandlersHelper(
const ListValue* extension_actions, string16* error) {
scoped_ptr<FileBrowserHandlerList> result(
new FileBrowserHandlerList());
for (ListValue::const_iterator iter = extension_actions->begin();
iter != extension_actions->end();
++iter) {
if (!(*iter)->IsType(Value::TYPE_DICTIONARY)) {
*error = ASCIIToUTF16(errors::kInvalidFileBrowserHandler);
return NULL;
}
scoped_ptr<FileBrowserHandler> action(
LoadFileBrowserHandler(
reinterpret_cast<DictionaryValue*>(*iter), error));
if (!action.get())
return NULL; // Failed to parse file browser action definition.
result->push_back(linked_ptr<FileBrowserHandler>(action.release()));
}
return result.release();
}
FileBrowserHandler* Extension::LoadFileBrowserHandler(
const DictionaryValue* file_browser_handler, string16* error) {
scoped_ptr<FileBrowserHandler> result(
new FileBrowserHandler());
result->set_extension_id(id());
std::string id;
// Read the file action |id| (mandatory).
if (!file_browser_handler->HasKey(keys::kPageActionId) ||
!file_browser_handler->GetString(keys::kPageActionId, &id)) {
*error = ASCIIToUTF16(errors::kInvalidPageActionId);
return NULL;
}
result->set_id(id);
// Read the page action title from |default_title| (mandatory).
std::string title;
if (!file_browser_handler->HasKey(keys::kPageActionDefaultTitle) ||
!file_browser_handler->GetString(keys::kPageActionDefaultTitle, &title)) {
*error = ASCIIToUTF16(errors::kInvalidPageActionDefaultTitle);
return NULL;
}
result->set_title(title);
// Initialize file filters (mandatory).
ListValue* list_value = NULL;
if (!file_browser_handler->HasKey(keys::kFileFilters) ||
!file_browser_handler->GetList(keys::kFileFilters, &list_value) ||
list_value->empty()) {
*error = ASCIIToUTF16(errors::kInvalidFileFiltersList);
return NULL;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
std::string filter;
if (!list_value->GetString(i, &filter)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidFileFilterValue, base::IntToString(i));
return NULL;
}
StringToLowerASCII(&filter);
URLPattern pattern(URLPattern::SCHEME_FILESYSTEM);
if (pattern.Parse(filter) != URLPattern::PARSE_SUCCESS) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidURLPatternError, filter);
return NULL;
}
std::string path = pattern.path();
bool allowed = path == "*" || path == "*.*" ||
(path.compare(0, 2, "*.") == 0 &&
path.find_first_of('*', 2) == std::string::npos);
if (!allowed) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidURLPatternError, filter);
return NULL;
}
result->AddPattern(pattern);
}
std::string default_icon;
// Read the file browser action |default_icon| (optional).
if (file_browser_handler->HasKey(keys::kPageActionDefaultIcon)) {
if (!file_browser_handler->GetString(
keys::kPageActionDefaultIcon, &default_icon) ||
default_icon.empty()) {
*error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
return NULL;
}
result->set_icon_path(default_icon);
}
return result.release();
}
bool Extension::LoadChromeURLOverrides(string16* error) {
if (!manifest_->HasKey(keys::kChromeURLOverrides))
return true;
DictionaryValue* overrides = NULL;
if (!manifest_->GetDictionary(keys::kChromeURLOverrides, &overrides)) {
*error = ASCIIToUTF16(errors::kInvalidChromeURLOverrides);
return false;
}
// Validate that the overrides are all strings
for (DictionaryValue::key_iterator iter = overrides->begin_keys();
iter != overrides->end_keys(); ++iter) {
std::string page = *iter;
std::string val;
// Restrict override pages to a list of supported URLs.
if ((page != chrome::kChromeUINewTabHost &&
#if defined(USE_VIRTUAL_KEYBOARD)
page != chrome::kChromeUIKeyboardHost &&
#endif
#if defined(OS_CHROMEOS)
page != chrome::kChromeUIActivationMessageHost &&
#endif
page != chrome::kChromeUIBookmarksHost &&
page != chrome::kChromeUIHistoryHost
#if defined(FILE_MANAGER_EXTENSION)
&&
!(location() == COMPONENT &&
page == chrome::kChromeUIFileManagerHost)
#endif
) ||
!overrides->GetStringWithoutPathExpansion(*iter, &val)) {
*error = ASCIIToUTF16(errors::kInvalidChromeURLOverrides);
return false;
}
// Replace the entry with a fully qualified chrome-extension:// URL.
chrome_url_overrides_[page] = GetResourceURL(val);
}
// An extension may override at most one page.
if (overrides->size() > 1) {
*error = ASCIIToUTF16(errors::kMultipleOverrides);
return false;
}
return true;
}
bool Extension::LoadOmnibox(string16* error) {
if (!manifest_->HasKey(keys::kOmnibox))
return true;
if (!manifest_->GetString(keys::kOmniboxKeyword, &omnibox_keyword_) ||
omnibox_keyword_.empty()) {
*error = ASCIIToUTF16(errors::kInvalidOmniboxKeyword);
return false;
}
return true;
}
bool Extension::LoadTextToSpeechVoices(string16* error) {
if (!manifest_->HasKey(keys::kTtsEngine))
return true;
DictionaryValue* tts_dict = NULL;
if (!manifest_->GetDictionary(keys::kTtsEngine, &tts_dict)) {
*error = ASCIIToUTF16(errors::kInvalidTts);
return false;
}
if (tts_dict->HasKey(keys::kTtsVoices)) {
ListValue* tts_voices = NULL;
if (!tts_dict->GetList(keys::kTtsVoices, &tts_voices)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoices);
return false;
}
for (size_t i = 0; i < tts_voices->GetSize(); i++) {
DictionaryValue* one_tts_voice = NULL;
if (!tts_voices->GetDictionary(i, &one_tts_voice)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoices);
return false;
}
TtsVoice voice_data;
if (one_tts_voice->HasKey(keys::kTtsVoicesVoiceName)) {
if (!one_tts_voice->GetString(
keys::kTtsVoicesVoiceName, &voice_data.voice_name)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesVoiceName);
return false;
}
}
if (one_tts_voice->HasKey(keys::kTtsVoicesLang)) {
if (!one_tts_voice->GetString(
keys::kTtsVoicesLang, &voice_data.lang) ||
!l10n_util::IsValidLocaleSyntax(voice_data.lang)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesLang);
return false;
}
}
if (one_tts_voice->HasKey(keys::kTtsVoicesGender)) {
if (!one_tts_voice->GetString(
keys::kTtsVoicesGender, &voice_data.gender) ||
(voice_data.gender != keys::kTtsGenderMale &&
voice_data.gender != keys::kTtsGenderFemale)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesGender);
return false;
}
}
if (one_tts_voice->HasKey(keys::kTtsVoicesEventTypes)) {
ListValue* event_types_list;
if (!one_tts_voice->GetList(
keys::kTtsVoicesEventTypes, &event_types_list)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
return false;
}
for (size_t i = 0; i < event_types_list->GetSize(); i++) {
std::string event_type;
if (!event_types_list->GetString(i, &event_type)) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
return false;
}
if (event_type != keys::kTtsVoicesEventTypeEnd &&
event_type != keys::kTtsVoicesEventTypeError &&
event_type != keys::kTtsVoicesEventTypeMarker &&
event_type != keys::kTtsVoicesEventTypeSentence &&
event_type != keys::kTtsVoicesEventTypeStart &&
event_type != keys::kTtsVoicesEventTypeWord) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
return false;
}
if (voice_data.event_types.find(event_type) !=
voice_data.event_types.end()) {
*error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
return false;
}
voice_data.event_types.insert(event_type);
}
}
tts_voices_.push_back(voice_data);
}
}
return true;
}
bool Extension::LoadIncognitoMode(string16* error) {
// Apps default to split mode, extensions default to spanning.
incognito_split_mode_ = is_app();
if (!manifest_->HasKey(keys::kIncognito))
return true;
std::string value;
if (!manifest_->GetString(keys::kIncognito, &value)) {
*error = ASCIIToUTF16(errors::kInvalidIncognitoBehavior);
return false;
}
if (value == values::kIncognitoSpanning) {
incognito_split_mode_ = false;
} else if (value == values::kIncognitoSplit) {
incognito_split_mode_ = true;
} else {
*error = ASCIIToUTF16(errors::kInvalidIncognitoBehavior);
return false;
}
return true;
}
bool Extension::LoadContentSecurityPolicy(string16* error) {
if (manifest_->HasKey(keys::kContentSecurityPolicy)) {
std::string content_security_policy;
if (!manifest_->GetString(keys::kContentSecurityPolicy,
&content_security_policy)) {
*error = ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
return false;
}
if (!ContentSecurityPolicyIsLegal(content_security_policy)) {
*error = ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
return false;
}
if (manifest_version_ >= 2 &&
!ContentSecurityPolicyIsSecure(content_security_policy)) {
*error = ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
return false;
}
content_security_policy_ = content_security_policy;
} else if (manifest_version_ >= 2) {
// Manifest version 2 introduced a default Content-Security-Policy.
// TODO(abarth): Should we continue to let extensions override the
// default Content-Security-Policy?
content_security_policy_ = kDefaultContentSecurityPolicy;
CHECK(ContentSecurityPolicyIsSecure(content_security_policy_));
}
return true;
}
bool Extension::LoadAppIsolation(string16* error) {
Value* temp = NULL;
if (!manifest_->Get(keys::kIsolation, &temp))
return true;
if (temp->GetType() != Value::TYPE_LIST) {
*error = ASCIIToUTF16(errors::kInvalidIsolation);
return false;
}
ListValue* isolation_list = static_cast<ListValue*>(temp);
for (size_t i = 0; i < isolation_list->GetSize(); ++i) {
std::string isolation_string;
if (!isolation_list->GetString(i, &isolation_string)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidIsolationValue,
base::UintToString(i));
return false;
}
// Check for isolated storage.
if (isolation_string == values::kIsolatedStorage) {
is_storage_isolated_ = true;
} else {
DLOG(WARNING) << "Did not recognize isolation type: "
<< isolation_string;
}
}
return true;
}
bool Extension::LoadThemeFeatures(string16* error) {
if (!manifest_->HasKey(keys::kTheme))
return true;
DictionaryValue* theme_value = NULL;
if (!manifest_->GetDictionary(keys::kTheme, &theme_value)) {
*error = ASCIIToUTF16(errors::kInvalidTheme);
return false;
}
if (!LoadThemeImages(theme_value, error))
return false;
if (!LoadThemeColors(theme_value, error))
return false;
if (!LoadThemeTints(theme_value, error))
return false;
if (!LoadThemeDisplayProperties(theme_value, error))
return false;
return true;
}
bool Extension::LoadThemeImages(const DictionaryValue* theme_value,
string16* error) {
DictionaryValue* images_value = NULL;
if (theme_value->GetDictionary(keys::kThemeImages, &images_value)) {
// Validate that the images are all strings
for (DictionaryValue::key_iterator iter = images_value->begin_keys();
iter != images_value->end_keys(); ++iter) {
std::string val;
if (!images_value->GetString(*iter, &val)) {
*error = ASCIIToUTF16(errors::kInvalidThemeImages);
return false;
}
}
theme_images_.reset(images_value->DeepCopy());
}
return true;
}
bool Extension::LoadThemeColors(const DictionaryValue* theme_value,
string16* error) {
DictionaryValue* colors_value = NULL;
if (theme_value->GetDictionary(keys::kThemeColors, &colors_value)) {
// Validate that the colors are RGB or RGBA lists
for (DictionaryValue::key_iterator iter = colors_value->begin_keys();
iter != colors_value->end_keys(); ++iter) {
ListValue* color_list = NULL;
double alpha = 0.0;
int color = 0;
// The color must be a list
if (!colors_value->GetListWithoutPathExpansion(*iter, &color_list) ||
// And either 3 items (RGB) or 4 (RGBA)
((color_list->GetSize() != 3) &&
((color_list->GetSize() != 4) ||
// For RGBA, the fourth item must be a real or int alpha value.
// Note that GetDouble() can get an integer value.
!color_list->GetDouble(3, &alpha))) ||
// For both RGB and RGBA, the first three items must be ints (R,G,B)
!color_list->GetInteger(0, &color) ||
!color_list->GetInteger(1, &color) ||
!color_list->GetInteger(2, &color)) {
*error = ASCIIToUTF16(errors::kInvalidThemeColors);
return false;
}
}
theme_colors_.reset(colors_value->DeepCopy());
}
return true;
}
bool Extension::LoadThemeTints(const DictionaryValue* theme_value,
string16* error) {
DictionaryValue* tints_value = NULL;
if (theme_value->GetDictionary(keys::kThemeTints, &tints_value)) {
// Validate that the tints are all reals.
for (DictionaryValue::key_iterator iter = tints_value->begin_keys();
iter != tints_value->end_keys(); ++iter) {
ListValue* tint_list = NULL;
double v = 0.0;
if (!tints_value->GetListWithoutPathExpansion(*iter, &tint_list) ||
tint_list->GetSize() != 3 ||
!tint_list->GetDouble(0, &v) ||
!tint_list->GetDouble(1, &v) ||
!tint_list->GetDouble(2, &v)) {
*error = ASCIIToUTF16(errors::kInvalidThemeTints);
return false;
}
}
theme_tints_.reset(tints_value->DeepCopy());
}
return true;
}
bool Extension::LoadThemeDisplayProperties(const DictionaryValue* theme_value,
string16* error) {
DictionaryValue* display_properties_value = NULL;
if (theme_value->GetDictionary(keys::kThemeDisplayProperties,
&display_properties_value)) {
theme_display_properties_.reset(
display_properties_value->DeepCopy());
}
return true;
}
// static
bool Extension::IsTrustedId(const std::string& id) {
// See http://b/4946060 for more details.
return id == std::string("nckgahadagoaajjgafhacjanaoiihapd");
}
Extension::Extension(const FilePath& path,
scoped_ptr<extensions::Manifest> manifest)
: manifest_version_(0),
incognito_split_mode_(false),
offline_enabled_(false),
converted_from_user_script_(false),
background_page_persists_(true),
allow_background_js_access_(true),
manifest_(manifest.release()),
is_storage_isolated_(false),
launch_container_(extension_misc::LAUNCH_TAB),
launch_width_(0),
launch_height_(0),
launch_min_width_(0),
launch_min_height_(0),
launch_max_width_(0),
launch_max_height_(0),
wants_file_access_(false),
creation_flags_(0) {
DCHECK(path.empty() || path.IsAbsolute());
path_ = MaybeNormalizePath(path);
}
Extension::~Extension() {
if (manifest_)
delete manifest_;
}
ExtensionResource Extension::GetResource(
const std::string& relative_path) const {
#if defined(OS_POSIX)
FilePath relative_file_path(relative_path);
#elif defined(OS_WIN)
FilePath relative_file_path(UTF8ToWide(relative_path));
#endif
return ExtensionResource(id(), path(), relative_file_path);
}
ExtensionResource Extension::GetResource(
const FilePath& relative_file_path) const {
return ExtensionResource(id(), path(), relative_file_path);
}
// TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a
// util class in base:
// http://code.google.com/p/chromium/issues/detail?id=13572
bool Extension::ParsePEMKeyBytes(const std::string& input,
std::string* output) {
DCHECK(output);
if (!output)
return false;
if (input.length() == 0)
return false;
std::string working = input;
if (StartsWithASCII(working, kKeyBeginHeaderMarker, true)) {
working = CollapseWhitespaceASCII(working, true);
size_t header_pos = working.find(kKeyInfoEndMarker,
sizeof(kKeyBeginHeaderMarker) - 1);
if (header_pos == std::string::npos)
return false;
size_t start_pos = header_pos + sizeof(kKeyInfoEndMarker) - 1;
size_t end_pos = working.rfind(kKeyBeginFooterMarker);
if (end_pos == std::string::npos)
return false;
if (start_pos >= end_pos)
return false;
working = working.substr(start_pos, end_pos - start_pos);
if (working.length() == 0)
return false;
}
return base::Base64Decode(working, output);
}
bool Extension::ProducePEM(const std::string& input, std::string* output) {
DCHECK(output);
if (input.length() == 0)
return false;
return base::Base64Encode(input, output);
}
bool Extension::FormatPEMForFileOutput(const std::string& input,
std::string* output,
bool is_public) {
DCHECK(output);
if (input.length() == 0)
return false;
*output = "";
output->append(kKeyBeginHeaderMarker);
output->append(" ");
output->append(is_public ? kPublic : kPrivate);
output->append(" ");
output->append(kKeyInfoEndMarker);
output->append("\n");
for (size_t i = 0; i < input.length(); ) {
int slice = std::min<int>(input.length() - i, kPEMOutputColumns);
output->append(input.substr(i, slice));
output->append("\n");
i += slice;
}
output->append(kKeyBeginFooterMarker);
output->append(" ");
output->append(is_public ? kPublic : kPrivate);
output->append(" ");
output->append(kKeyInfoEndMarker);
output->append("\n");
return true;
}
// static
void Extension::DecodeIcon(const Extension* extension,
ExtensionIconSet::Icons preferred_icon_size,
ExtensionIconSet::MatchType match_type,
scoped_ptr<SkBitmap>* result) {
std::string path = extension->icons().Get(preferred_icon_size, match_type);
ExtensionIconSet::Icons size = extension->icons().GetIconSizeFromPath(path);
ExtensionResource icon_resource = extension->GetResource(path);
DecodeIconFromPath(icon_resource.GetFilePath(), size, result);
}
// static
void Extension::DecodeIcon(const Extension* extension,
ExtensionIconSet::Icons icon_size,
scoped_ptr<SkBitmap>* result) {
DecodeIcon(extension, icon_size, ExtensionIconSet::MATCH_EXACTLY, result);
}
// static
void Extension::DecodeIconFromPath(const FilePath& icon_path,
ExtensionIconSet::Icons icon_size,
scoped_ptr<SkBitmap>* result) {
if (icon_path.empty())
return;
std::string file_contents;
if (!file_util::ReadFileToString(icon_path, &file_contents)) {
DLOG(ERROR) << "Could not read icon file: " << icon_path.LossyDisplayName();
return;
}
// Decode the image using WebKit's image decoder.
const unsigned char* data =
reinterpret_cast<const unsigned char*>(file_contents.data());
webkit_glue::ImageDecoder decoder;
scoped_ptr<SkBitmap> decoded(new SkBitmap());
*decoded = decoder.Decode(data, file_contents.length());
if (decoded->empty()) {
DLOG(ERROR) << "Could not decode icon file: "
<< icon_path.LossyDisplayName();
return;
}
if (decoded->width() != icon_size || decoded->height() != icon_size) {
DLOG(ERROR) << "Icon file has unexpected size: "
<< base::IntToString(decoded->width()) << "x"
<< base::IntToString(decoded->height());
return;
}
result->swap(decoded);
}
// static
const SkBitmap& Extension::GetDefaultIcon(bool is_app) {
if (is_app) {
return *ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_APP_DEFAULT_ICON);
} else {
return *ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_EXTENSION_DEFAULT_ICON);
}
}
// static
GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) {
return GURL(std::string(chrome::kExtensionScheme) +
chrome::kStandardSchemeSeparator + extension_id + "/");
}
bool Extension::InitFromValue(int flags, string16* error) {
DCHECK(error);
base::AutoLock auto_lock(runtime_data_lock_);
// Initialize permissions with an empty, default permission set.
runtime_data_.SetActivePermissions(new ExtensionPermissionSet());
optional_permission_set_ = new ExtensionPermissionSet();
required_permission_set_ = new ExtensionPermissionSet();
creation_flags_ = flags;
// Validate minimum Chrome version. We don't need to store this, since the
// extension is not valid if it is incorrect
if (!CheckMinimumChromeVersion(error))
return false;
if (!LoadRequiredFeatures(error))
return false;
// We don't ned to validate because InitExtensionID already did that.
manifest_->GetString(keys::kPublicKey, &public_key_);
extension_url_ = Extension::GetBaseURLFromExtensionId(id());
// Load App settings. LoadExtent at least has to be done before
// ParsePermissions(), because the valid permissions depend on what type of
// package this is.
if (is_app() && !LoadAppFeatures(error))
return false;
ExtensionAPIPermissionSet api_permissions;
URLPatternSet host_permissions;
if (!ParsePermissions(keys::kPermissions,
error,
&api_permissions,
&host_permissions)) {
return false;
}
ExtensionAPIPermissionSet optional_api_permissions;
URLPatternSet optional_host_permissions;
if (!ParsePermissions(keys::kOptionalPermissions,
error,
&optional_api_permissions,
&optional_host_permissions)) {
return false;
}
// App isolation.
if (api_permissions.count(ExtensionAPIPermission::kExperimental) &&
is_app() && !LoadAppIsolation(error))
return false;
if (!LoadSharedFeatures(api_permissions, error))
return false;
if (!LoadExtensionFeatures(api_permissions, error))
return false;
if (!LoadThemeFeatures(error))
return false;
if (HasMultipleUISurfaces()) {
*error = ASCIIToUTF16(errors::kOneUISurfaceOnly);
return false;
}
runtime_data_.SetActivePermissions(new ExtensionPermissionSet(
this, api_permissions, host_permissions));
required_permission_set_ = new ExtensionPermissionSet(
this, api_permissions, host_permissions);
optional_permission_set_ = new ExtensionPermissionSet(
optional_api_permissions, optional_host_permissions, URLPatternSet());
return true;
}
GURL Extension::GetHomepageURL() const {
if (homepage_url_.is_valid())
return homepage_url_;
if (!UpdatesFromGallery())
return GURL();
GURL url(extension_urls::GetWebstoreItemDetailURLPrefix() + id());
return url;
}
std::set<FilePath> Extension::GetBrowserImages() const {
std::set<FilePath> image_paths;
// TODO(viettrungluu): These |FilePath::FromWStringHack(UTF8ToWide())|
// indicate that we're doing something wrong.
// Extension icons.
for (ExtensionIconSet::IconMap::const_iterator iter = icons().map().begin();
iter != icons().map().end(); ++iter) {
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
}
// Theme images.
DictionaryValue* theme_images = GetThemeImages();
if (theme_images) {
for (DictionaryValue::key_iterator it = theme_images->begin_keys();
it != theme_images->end_keys(); ++it) {
std::string val;
if (theme_images->GetStringWithoutPathExpansion(*it, &val))
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(val)));
}
}
// Page action icons.
if (page_action()) {
std::vector<std::string>* icon_paths = page_action()->icon_paths();
for (std::vector<std::string>::iterator iter = icon_paths->begin();
iter != icon_paths->end(); ++iter) {
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(*iter)));
}
}
// Browser action icons.
if (browser_action()) {
std::vector<std::string>* icon_paths = browser_action()->icon_paths();
for (std::vector<std::string>::iterator iter = icon_paths->begin();
iter != icon_paths->end(); ++iter) {
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(*iter)));
}
}
return image_paths;
}
GURL Extension::GetFullLaunchURL() const {
if (!launch_local_path().empty())
return url().Resolve(launch_local_path());
else
return GURL(launch_web_url());
}
static std::string SizeToString(const gfx::Size& max_size) {
return base::IntToString(max_size.width()) + "x" +
base::IntToString(max_size.height());
}
// static
void Extension::SetScriptingWhitelist(
const Extension::ScriptingWhitelist& whitelist) {
ScriptingWhitelist* current_whitelist =
ExtensionConfig::GetInstance()->whitelist();
current_whitelist->clear();
for (ScriptingWhitelist::const_iterator it = whitelist.begin();
it != whitelist.end(); ++it) {
current_whitelist->push_back(*it);
}
}
// static
const Extension::ScriptingWhitelist* Extension::GetScriptingWhitelist() {
return ExtensionConfig::GetInstance()->whitelist();
}
void Extension::SetCachedImage(const ExtensionResource& source,
const SkBitmap& image,
const gfx::Size& original_size) const {
DCHECK(source.extension_root() == path()); // The resource must come from
// this extension.
const FilePath& path = source.relative_path();
gfx::Size actual_size(image.width(), image.height());
if (actual_size == original_size) {
image_cache_[ImageCacheKey(path, std::string())] = image;
} else {
image_cache_[ImageCacheKey(path, SizeToString(actual_size))] = image;
}
}
bool Extension::HasCachedImage(const ExtensionResource& source,
const gfx::Size& max_size) const {
DCHECK(source.extension_root() == path()); // The resource must come from
// this extension.
return GetCachedImageImpl(source, max_size) != NULL;
}
SkBitmap Extension::GetCachedImage(const ExtensionResource& source,
const gfx::Size& max_size) const {
DCHECK(source.extension_root() == path()); // The resource must come from
// this extension.
SkBitmap* image = GetCachedImageImpl(source, max_size);
return image ? *image : SkBitmap();
}
SkBitmap* Extension::GetCachedImageImpl(const ExtensionResource& source,
const gfx::Size& max_size) const {
const FilePath& path = source.relative_path();
// Look for exact size match.
ImageCache::iterator i = image_cache_.find(
ImageCacheKey(path, SizeToString(max_size)));
if (i != image_cache_.end())
return &(i->second);
// If we have the original size version cached, return that if it's small
// enough.
i = image_cache_.find(ImageCacheKey(path, std::string()));
if (i != image_cache_.end()) {
SkBitmap& image = i->second;
if (image.width() <= max_size.width() &&
image.height() <= max_size.height())
return &(i->second);
}
return NULL;
}
ExtensionResource Extension::GetIconResource(
int size, ExtensionIconSet::MatchType match_type) const {
std::string path = icons().Get(size, match_type);
if (path.empty())
return ExtensionResource();
return GetResource(path);
}
GURL Extension::GetIconURL(int size,
ExtensionIconSet::MatchType match_type) const {
std::string path = icons().Get(size, match_type);
if (path.empty())
return GURL();
else
return GetResourceURL(path);
}
bool Extension::ParsePermissions(const char* key,
string16* error,
ExtensionAPIPermissionSet* api_permissions,
URLPatternSet* host_permissions) {
if (manifest_->HasKey(key)) {
ListValue* permissions = NULL;
if (!manifest_->GetList(key, &permissions)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPermissions, "");
return false;
}
for (size_t i = 0; i < permissions->GetSize(); ++i) {
std::string permission_str;
if (!permissions->GetString(i, &permission_str)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPermission, base::IntToString(i));
return false;
}
// NOTE: We need to get the ExtensionAPIPermission before the Feature
// object because the feature system does not know about aliases.
ExtensionAPIPermission* permission =
ExtensionPermissionsInfo::GetInstance()->GetByName(permission_str);
if (permission) {
extensions::SimpleFeatureProvider* permission_features =
extensions::SimpleFeatureProvider::GetPermissionFeatures();
scoped_ptr<extensions::Feature> feature(
permission_features->GetFeature(permission->name()));
// The feature should exist since we just got an ExtensionAPIPermission
// for it. The two systems should be updated together whenever a
// permission is added.
CHECK(feature.get());
extensions::Feature::Availability availability =
feature->IsAvailable(this);
if (availability != extensions::Feature::IS_AVAILABLE) {
// We special case hosted apps because some old versions of Chrome did
// not return errors here and we ended up with extensions in the store
// containing bad data: crbug.com/101993.
if (availability != extensions::Feature::INVALID_TYPE ||
!is_hosted_app()) {
*error = ASCIIToUTF16(feature->GetErrorMessage(availability));
return false;
}
}
if (permission->id() == ExtensionAPIPermission::kExperimental) {
if (!CanSpecifyExperimentalPermission()) {
*error = ASCIIToUTF16(errors::kExperimentalFlagRequired);
return false;
}
}
api_permissions->insert(permission->id());
}
// Check if it's a host pattern permission.
const int kAllowedSchemes = CanExecuteScriptEverywhere() ?
URLPattern::SCHEME_ALL : kValidHostPermissionSchemes;
URLPattern pattern = URLPattern(kAllowedSchemes);
URLPattern::ParseResult parse_result = pattern.Parse(permission_str);
if (parse_result == URLPattern::PARSE_SUCCESS) {
if (!CanSpecifyHostPermission(pattern, *api_permissions)) {
*error = ExtensionErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidPermissionScheme, base::IntToString(i));
return false;
}
// The path component is not used for host permissions, so we force it
// to match all paths.
pattern.SetPath("/*");
if (pattern.MatchesScheme(chrome::kFileScheme) &&
!CanExecuteScriptEverywhere()) {
wants_file_access_ = true;
if (!(creation_flags_ & ALLOW_FILE_ACCESS))
pattern.SetValidSchemes(
pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
}
host_permissions->AddPattern(pattern);
}
// If it's not a host permission, then it's probably an unknown API
// permission. Do not throw an error so extensions can retain
// backwards compatability (http://crbug.com/42742).
// TODO(jstritar): We can improve error messages by adding better
// validation of API permissions here.
// TODO(skerner): Consider showing the reason |permission_str| is not
// a valid URL pattern if it is almost valid. For example, if it has
// a valid scheme, and failed to parse because it has a port, show an
// error.
}
}
return true;
}
bool Extension::CanSilentlyIncreasePermissions() const {
return location() != INTERNAL;
}
bool Extension::CanSpecifyHostPermission(const URLPattern& pattern,
const ExtensionAPIPermissionSet& permissions) const {
if (!pattern.match_all_urls() &&
pattern.MatchesScheme(chrome::kChromeUIScheme)) {
// Regular extensions are only allowed access to chrome://favicon.
if (pattern.host() == chrome::kChromeUIFaviconHost)
return true;
// Experimental extensions are also allowed chrome://thumb.
if (pattern.host() == chrome::kChromeUIThumbnailHost) {
return permissions.find(ExtensionAPIPermission::kExperimental) !=
permissions.end();
}
// Component extensions can have access to all of chrome://*.
if (CanExecuteScriptEverywhere())
return true;
return false;
}
// Otherwise, the valid schemes were handled by URLPattern.
return true;
}
bool Extension::HasAPIPermission(
ExtensionAPIPermission::ID permission) const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->HasAPIPermission(permission);
}
bool Extension::HasAPIPermission(
const std::string& function_name) const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->
HasAccessToFunction(function_name);
}
const URLPatternSet& Extension::GetEffectiveHostPermissions() const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->effective_hosts();
}
bool Extension::HasHostPermission(const GURL& url) const {
if (url.SchemeIs(chrome::kChromeUIScheme) &&
url.host() != chrome::kChromeUIFaviconHost &&
url.host() != chrome::kChromeUIThumbnailHost &&
location() != Extension::COMPONENT) {
return false;
}
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->
HasExplicitAccessToOrigin(url);
}
bool Extension::HasEffectiveAccessToAllHosts() const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->HasEffectiveAccessToAllHosts();
}
bool Extension::HasFullPermissions() const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->HasEffectiveFullAccess();
}
ExtensionPermissionMessages Extension::GetPermissionMessages() const {
base::AutoLock auto_lock(runtime_data_lock_);
if (IsTrustedId(id()))
return ExtensionPermissionMessages();
else
return runtime_data_.GetActivePermissions()->GetPermissionMessages();
}
std::vector<string16> Extension::GetPermissionMessageStrings() const {
base::AutoLock auto_lock(runtime_data_lock_);
if (IsTrustedId(id()))
return std::vector<string16>();
else
return runtime_data_.GetActivePermissions()->GetWarningMessages();
}
void Extension::SetActivePermissions(
const ExtensionPermissionSet* permissions) const {
base::AutoLock auto_lock(runtime_data_lock_);
runtime_data_.SetActivePermissions(permissions);
}
scoped_refptr<const ExtensionPermissionSet>
Extension::GetActivePermissions() const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions();
}
bool Extension::HasMultipleUISurfaces() const {
int num_surfaces = 0;
if (page_action())
++num_surfaces;
if (browser_action())
++num_surfaces;
if (is_app())
++num_surfaces;
return num_surfaces > 1;
}
bool Extension::CanExecuteScriptOnPage(const GURL& page_url,
const UserScript* script,
std::string* error) const {
base::AutoLock auto_lock(runtime_data_lock_);
// The gallery is special-cased as a restricted URL for scripting to prevent
// access to special JS bindings we expose to the gallery (and avoid things
// like extensions removing the "report abuse" link).
// TODO(erikkay): This seems like the wrong test. Shouldn't we we testing
// against the store app extent?
GURL store_url(extension_urls::GetWebstoreLaunchURL());
if ((page_url.host() == store_url.host()) &&
!CanExecuteScriptEverywhere() &&
!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowScriptingGallery)) {
if (error)
*error = errors::kCannotScriptGallery;
return false;
}
if (page_url.SchemeIs(chrome::kChromeUIScheme) &&
!CanExecuteScriptEverywhere())
return false;
// If a script is specified, use its matches.
if (script)
return script->MatchesURL(page_url);
// Otherwise, see if this extension has permission to execute script
// programmatically on pages.
if (runtime_data_.GetActivePermissions()->HasExplicitAccessToOrigin(
page_url))
return true;
if (error) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage,
page_url.spec());
}
return false;
}
bool Extension::ShowConfigureContextMenus() const {
// Don't show context menu for component extensions. We might want to show
// options for component extension button but now there is no component
// extension with options. All other menu items like uninstall have
// no sense for component extensions.
return location() != Extension::COMPONENT;
}
bool Extension::CanSpecifyExperimentalPermission() const {
if (location() == Extension::COMPONENT)
return true;
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalExtensionApis)) {
return true;
}
// We rely on the webstore to check access to experimental. This way we can
// whitelist extensions to have access to experimental in just the store, and
// not have to push a new version of the client.
if (from_webstore())
return true;
return false;
}
bool Extension::CanExecuteScriptEverywhere() const {
if (location() == Extension::COMPONENT)
return true;
ScriptingWhitelist* whitelist =
ExtensionConfig::GetInstance()->whitelist();
for (ScriptingWhitelist::const_iterator it = whitelist->begin();
it != whitelist->end(); ++it) {
if (id() == *it) {
return true;
}
}
return false;
}
bool Extension::CanCaptureVisiblePage(const GURL& page_url,
std::string *error) const {
if (HasHostPermission(page_url) || page_url.GetOrigin() == url())
return true;
if (error) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage,
page_url.spec());
}
return false;
}
bool Extension::UpdatesFromGallery() const {
return extension_urls::IsWebstoreUpdateUrl(update_url());
}
bool Extension::OverlapsWithOrigin(const GURL& origin) const {
if (url() == origin)
return true;
if (web_extent().is_empty())
return false;
// Note: patterns and extents ignore port numbers.
URLPattern origin_only_pattern(kValidWebExtentSchemes);
if (!origin_only_pattern.SetScheme(origin.scheme()))
return false;
origin_only_pattern.SetHost(origin.host());
origin_only_pattern.SetPath("/*");
URLPatternSet origin_only_pattern_list;
origin_only_pattern_list.AddPattern(origin_only_pattern);
return web_extent().OverlapsWith(origin_only_pattern_list);
}
Extension::SyncType Extension::GetSyncType() const {
if (!IsSyncable()) {
// We have a non-standard location.
return SYNC_TYPE_NONE;
}
// Disallow extensions with non-gallery auto-update URLs for now.
//
// TODO(akalin): Relax this restriction once we've put in UI to
// approve synced extensions.
if (!update_url().is_empty() && !UpdatesFromGallery())
return SYNC_TYPE_NONE;
// Disallow extensions with native code plugins.
//
// TODO(akalin): Relax this restriction once we've put in UI to
// approve synced extensions.
if (!plugins().empty()) {
return SYNC_TYPE_NONE;
}
switch (GetType()) {
case Extension::TYPE_EXTENSION:
return SYNC_TYPE_EXTENSION;
case Extension::TYPE_USER_SCRIPT:
// We only want to sync user scripts with gallery update URLs.
if (UpdatesFromGallery())
return SYNC_TYPE_EXTENSION;
else
return SYNC_TYPE_NONE;
case Extension::TYPE_HOSTED_APP:
case Extension::TYPE_PACKAGED_APP:
return SYNC_TYPE_APP;
default:
return SYNC_TYPE_NONE;
}
}
bool Extension::IsSyncable() const {
// TODO(akalin): Figure out if we need to allow some other types.
// We want to sync any extensions that are shown in the launcher because
// their positions should sync.
return location() == Extension::INTERNAL ||
ShouldDisplayInLauncher();
}
bool Extension::ShouldDisplayInLauncher() const {
// All apps should be displayed on the NTP except for the Cloud Print App.
return is_app() && id() != extension_misc::kCloudPrintAppId;
}
ExtensionInfo::ExtensionInfo(const DictionaryValue* manifest,
const std::string& id,
const FilePath& path,
Extension::Location location)
: extension_id(id),
extension_path(path),
extension_location(location) {
if (manifest)
extension_manifest.reset(manifest->DeepCopy());
}
ExtensionInfo::~ExtensionInfo() {}
Extension::RuntimeData::RuntimeData() {}
Extension::RuntimeData::RuntimeData(const ExtensionPermissionSet* active)
: active_permissions_(active) {}
Extension::RuntimeData::~RuntimeData() {}
scoped_refptr<const ExtensionPermissionSet>
Extension::RuntimeData::GetActivePermissions() const {
return active_permissions_;
}
void Extension::RuntimeData::SetActivePermissions(
const ExtensionPermissionSet* active) {
active_permissions_ = active;
}
UnloadedExtensionInfo::UnloadedExtensionInfo(
const Extension* extension,
extension_misc::UnloadedExtensionReason reason)
: reason(reason),
already_disabled(false),
extension(extension) {}
UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo(
const Extension* extension,
const ExtensionPermissionSet* permissions,
Reason reason)
: reason(reason),
extension(extension),
permissions(permissions) {}