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