// 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 #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 "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 "crypto/sha2.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; namespace info_keys = extension_info_keys; 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::get(); } Extension::ScriptingWhitelist* whitelist() { return &scripting_whitelist_; } private: friend struct DefaultSingletonTraits; 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::OAuth2Info::OAuth2Info() {} Extension::OAuth2Info::~OAuth2Info() {} 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 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::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::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::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 manifest( new extensions::Manifest( location, scoped_ptr(value.DeepCopy()))); if (!InitExtensionID(manifest.get(), path, explicit_id, flags, &error) || !manifest->ValidateManifest(&error)) { *utf8_error = UTF16ToUTF8(error); return NULL; } scoped_refptr extension = new Extension(path, manifest.Pass()); if (!extension->InitFromValue(flags, &error)) { *utf8_error = UTF16ToUTF8(error); return NULL; } if (extension->is_platform_app() && !CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnablePlatformApps)) { *utf8_error = errors::kPlatformAppFlagRequired; 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(new_path.value().data()), new_path.value().size() * sizeof(FilePath::CharType)); std::string id; if (!GenerateId(path_bytes, &id)) return ""; return id; } void Extension::GetBasicInfo(bool enabled, DictionaryValue* info) const { info->SetString(info_keys::kIdKey, id()); info->SetString(info_keys::kNameKey, name()); info->SetBoolean(info_keys::kEnabledKey, enabled); info->SetBoolean(info_keys::kMayDisableKey, UserMayDisable(location())); info->SetBoolean(info_keys::kOfflineEnabledKey, offline_enabled()); info->SetString(info_keys::kVersionKey, VersionString()); info->SetString(info_keys::kDescriptionKey, description()); info->SetString(info_keys::kOptionsUrlKey, options_url().possibly_invalid_spec()); info->SetString(info_keys::kHomepageUrlKey, GetHomepageURL().possibly_invalid_spec()); } 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 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, ""); 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 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 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) || !LoadOAuth2Info(error)) return false; return true; } bool Extension::LoadOAuth2Info(string16* error) { if (!manifest_->HasKey(keys::kOAuth2)) return true; if (!manifest_->GetString(keys::kOAuth2ClientId, &oauth2_info_.client_id) || oauth2_info_.client_id.empty()) { *error = ASCIIToUTF16(errors::kInvalidOAuth2ClientId); return false; } ListValue* list = NULL; if (!manifest_->GetList(keys::kOAuth2Scopes, &list)) { *error = ASCIIToUTF16(errors::kInvalidOAuth2Scopes); return false; } for (size_t i = 0; i < list->GetSize(); ++i) { std::string scope; if (!list->GetString(i, &scope)) { *error = ASCIIToUTF16(errors::kInvalidOAuth2Scopes); return false; } oauth2_info_.scopes.push_back(scope); } 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(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 "". 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) || !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) || !LoadBackgroundTransient(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(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::LoadBackgroundTransient( const ExtensionAPIPermissionSet& api_permissions, string16* error) { Value* background_transient = NULL; if (!api_permissions.count(ExtensionAPIPermission::kExperimental) || !manifest_->Get(keys::kBackgroundTransient, &background_transient)) return true; if (!background_transient->IsType(Value::TYPE_BOOLEAN) || !background_transient->GetAsBoolean(&background_page_is_transient_)) { *error = ASCIIToUTF16(errors::kInvalidBackgroundTransient); return false; } if (!has_background_page()) { *error = ASCIIToUTF16(errors::kInvalidBackgroundTransientNoPage); 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; } 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 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 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 action( LoadFileBrowserHandler( reinterpret_cast(*iter), error)); if (!action.get()) return NULL; // Failed to parse file browser action definition. result->push_back(linked_ptr(action.release())); } return result.release(); } FileBrowserHandler* Extension::LoadFileBrowserHandler( const DictionaryValue* file_browser_handler, string16* error) { scoped_ptr 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 access permissions (optional). ListValue* access_list_value = NULL; if (file_browser_handler->HasKey(keys::kFileAccessList)) { if (!file_browser_handler->GetList(keys::kFileAccessList, &access_list_value) || access_list_value->empty()) { *error = ASCIIToUTF16(errors::kInvalidFileAccessList); return NULL; } for (size_t i = 0; i < access_list_value->GetSize(); ++i) { std::string access; if (!access_list_value->GetString(i, &access) || result->AddFileAccessPermission(access)) { *error = ExtensionErrorUtils::FormatErrorMessageUTF16( errors::kInvalidFileAccessValue, base::IntToString(i)); return NULL; } } } if (!result->ValidateFileAccessPermissions()) { *error = ASCIIToUTF16(errors::kInvalidFileAccessList); return NULL; } // Initialize file filters (mandatory, unless "create" access is specified, // in which case is ignored). if (!result->HasCreateAccessPermission()) { 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(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 manifest) : manifest_version_(0), incognito_split_mode_(false), offline_enabled_(false), converted_from_user_script_(false), background_page_is_transient_(false), 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(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* 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* result) { DecodeIcon(extension, icon_size, ExtensionIconSet::MATCH_EXACTLY, result); } // static void Extension::DecodeIconFromPath(const FilePath& icon_path, ExtensionIconSet::Icons icon_size, scoped_ptr* 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(file_contents.data()); webkit_glue::ImageDecoder decoder; scoped_ptr 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; // Important to load manifest version first because many other features // depend on its value. if (!LoadManifestVersion(error)) return false; // 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 Extension::GetBrowserImages() const { std::set 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* icon_paths = page_action()->icon_paths(); for (std::vector::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* icon_paths = browser_action()->icon_paths(); for (std::vector::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 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 Extension::GetPermissionMessageStrings() const { base::AutoLock auto_lock(runtime_data_lock_); if (IsTrustedId(id())) return std::vector(); 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 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 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) {}