diff options
Diffstat (limited to 'extensions')
25 files changed, 2820 insertions, 20 deletions
diff --git a/extensions/DEPS b/extensions/DEPS index 2b0dd51..f286a2e 100644 --- a/extensions/DEPS +++ b/extensions/DEPS @@ -17,4 +17,10 @@ specific_include_rules = { "^manifest_permission_set_unittest\.cc$": [ "+chrome/common/extensions/extension_messages.h", ], + "^permissions_data_unittest\.cc$": [ + "+chrome/common/chrome_version_info.h", + "+chrome/common/extensions/extension_test_util.h", + "+chrome/common/extensions/features/feature_channel.h", + "+chrome/common/extensions/permissions/socket_permission.h", + ], } diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS index eb64dd1..55fbba0 100644 --- a/extensions/browser/DEPS +++ b/extensions/browser/DEPS @@ -17,7 +17,6 @@ include_rules = [ "+chrome/browser/extensions/extension_system.h", "+chrome/browser/extensions/extension_util.h", "+chrome/browser/renderer_host/chrome_render_message_filter.h", - "+chrome/common/extensions/extension.h", "+chrome/common/extensions/extension_messages.h", "+chrome/common/extensions/extension_set.h", "+grit/generated_resources.h", diff --git a/extensions/browser/admin_policy.cc b/extensions/browser/admin_policy.cc index 2cc0b57..903c4e0 100644 --- a/extensions/browser/admin_policy.cc +++ b/extensions/browser/admin_policy.cc @@ -5,7 +5,7 @@ #include "extensions/browser/admin_policy.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" diff --git a/extensions/browser/admin_policy_unittest.cc b/extensions/browser/admin_policy_unittest.cc index 02d606d..f85e3ef 100644 --- a/extensions/browser/admin_policy_unittest.cc +++ b/extensions/browser/admin_policy_unittest.cc @@ -5,7 +5,7 @@ #include "extensions/browser/admin_policy.h" #include "base/values.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/extensions/browser/extension_function.h b/extensions/browser/extension_function.h index 69effe0..aa0ed03 100644 --- a/extensions/browser/extension_function.h +++ b/extensions/browser/extension_function.h @@ -16,10 +16,10 @@ #include "base/process/process.h" #include "base/sequenced_task_runner_helpers.h" #include "chrome/browser/extensions/extension_function_histogram_value.h" -#include "chrome/common/extensions/extension.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/console_message_level.h" #include "extensions/browser/info_map.h" +#include "extensions/common/extension.h" #include "ipc/ipc_message.h" class ChromeRenderMessageFilter; diff --git a/extensions/browser/info_map.cc b/extensions/browser/info_map.cc index 012f719..2bff206 100644 --- a/extensions/browser/info_map.cc +++ b/extensions/browser/info_map.cc @@ -4,10 +4,10 @@ #include "extensions/browser/info_map.h" -#include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_set.h" #include "content/public/browser/browser_thread.h" #include "extensions/common/constants.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/incognito_info.h" using content::BrowserThread; diff --git a/extensions/browser/info_map_unittest.cc b/extensions/browser/info_map_unittest.cc index b023eed..8eaf488 100644 --- a/extensions/browser/info_map_unittest.cc +++ b/extensions/browser/info_map_unittest.cc @@ -6,9 +6,9 @@ #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "chrome/common/chrome_paths.h" -#include "chrome/common/extensions/extension.h" #include "content/public/test/test_browser_thread.h" #include "extensions/browser/info_map.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_constants.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/extensions/browser/lazy_background_task_queue.cc b/extensions/browser/lazy_background_task_queue.cc index 987908b..f53cdd3 100644 --- a/extensions/browser/lazy_background_task_queue.cc +++ b/extensions/browser/lazy_background_task_queue.cc @@ -9,7 +9,6 @@ #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" -#include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_messages.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/notification_service.h" @@ -20,6 +19,7 @@ #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/process_map.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/background_info.h" #include "extensions/common/view_type.h" diff --git a/extensions/browser/lazy_background_task_queue_unittest.cc b/extensions/browser/lazy_background_task_queue_unittest.cc index 863a520..35c02b8 100644 --- a/extensions/browser/lazy_background_task_queue_unittest.cc +++ b/extensions/browser/lazy_background_task_queue_unittest.cc @@ -8,10 +8,10 @@ #include "base/command_line.h" #include "chrome/browser/extensions/extension_service_unittest.h" #include "chrome/browser/extensions/test_extension_system.h" -#include "chrome/common/extensions/extension.h" #include "chrome/test/base/testing_profile.h" #include "content/public/test/test_browser_thread_bundle.h" #include "extensions/browser/process_manager.h" +#include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc index 3ae5be4..cccb800 100644 --- a/extensions/browser/process_manager.cc +++ b/extensions/browser/process_manager.cc @@ -19,7 +19,6 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/extension_util.h" -#include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_messages.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" @@ -36,6 +35,7 @@ #include "content/public/common/renderer_preferences.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/view_type_utils.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/background_info.h" #include "extensions/common/manifest_handlers/incognito_info.h" #include "extensions/common/switches.h" diff --git a/extensions/common/DEPS b/extensions/common/DEPS index 34509b4..6a9adce 100644 --- a/extensions/common/DEPS +++ b/extensions/common/DEPS @@ -9,10 +9,10 @@ include_rules = [ # # TODO(benwells): Remove these. http://crbug.com/162530 "+chrome/common/extensions/api/generated_schemas.h", - "+chrome/common/extensions/extension.h", - "+chrome/common/extensions/permissions/permissions_data.h", + "+grit/chromium_strings.h", "+grit/common_resources.h", "+grit/extensions_api_resources.h", + "+grit/theme_resources.h", # TODO(jamescook): Extract extensions-related strings from this file. "+grit/generated_resources.h", ] diff --git a/extensions/common/extension.cc b/extensions/common/extension.cc new file mode 100644 index 0000000..f38d547 --- /dev/null +++ b/extensions/common/extension.cc @@ -0,0 +1,792 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/extension.h" + +#include "base/base64.h" +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "base/version.h" +#include "content/public/common/url_constants.h" +#include "extensions/common/constants.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/id_util.h" +#include "extensions/common/manifest.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/manifest_handler.h" +#include "extensions/common/permissions/api_permission_set.h" +#include "extensions/common/permissions/permission_set.h" +#include "extensions/common/permissions/permissions_data.h" +#include "extensions/common/permissions/permissions_info.h" +#include "extensions/common/switches.h" +#include "extensions/common/url_pattern_set.h" +#include "grit/chromium_strings.h" +#include "grit/theme_resources.h" +#include "net/base/net_util.h" +#include "url/url_util.h" + +#if defined(OS_WIN) +#include "grit/generated_resources.h" +#endif + +namespace extensions { + +namespace keys = manifest_keys; +namespace values = manifest_values; +namespace errors = manifest_errors; + +namespace { + +const int kModernManifestVersion = 2; +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"; + +bool ContainsReservedCharacters(const base::FilePath& path) { + // We should disallow backslash '\\' as file path separator even on Windows, + // because the backslash is not regarded as file path separator on Linux/Mac. + // Extensions are cross-platform. + // Since FilePath uses backslash '\\' as file path separator on Windows, so we + // need to check manually. + if (path.value().find('\\') != path.value().npos) + return true; + return !net::IsSafePortableRelativePath(path); +} + +} // namespace + +const char Extension::kMimeType[] = "application/x-chrome-extension"; + +const int Extension::kValidWebExtentSchemes = + URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS; + +const int Extension::kValidHostPermissionSchemes = URLPattern::SCHEME_CHROMEUI | + URLPattern::SCHEME_HTTP | + URLPattern::SCHEME_HTTPS | + URLPattern::SCHEME_FILE | + URLPattern::SCHEME_FTP; + +// +// Extension +// + +// static +scoped_refptr<Extension> Extension::Create(const base::FilePath& path, + Manifest::Location location, + const base::DictionaryValue& value, + int flags, + std::string* utf8_error) { + return Extension::Create(path, + location, + value, + flags, + std::string(), // ID is ignored if empty. + utf8_error); +} + +// TODO(sungguk): Continue removing std::string errors and replacing +// with string16. See http://crbug.com/71980. +scoped_refptr<Extension> Extension::Create(const base::FilePath& path, + Manifest::Location location, + const base::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<base::DictionaryValue>(value.DeepCopy()))); + + if (!InitExtensionID(manifest.get(), path, explicit_id, flags, &error)) { + *utf8_error = UTF16ToUTF8(error); + return NULL; + } + + std::vector<InstallWarning> install_warnings; + if (!manifest->ValidateManifest(utf8_error, &install_warnings)) { + return NULL; + } + + scoped_refptr<Extension> extension = new Extension(path, manifest.Pass()); + extension->install_warnings_.swap(install_warnings); + + if (!extension->InitFromValue(flags, &error)) { + *utf8_error = UTF16ToUTF8(error); + return NULL; + } + + return extension; +} + +// static +bool Extension::IdIsValid(const std::string& id) { + // Verify that the id is legal. + if (id.size() != (id_util::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; +} + +Manifest::Type Extension::GetType() const { + return converted_from_user_script() ? + Manifest::TYPE_USER_SCRIPT : manifest_->type(); +} + +// static +GURL Extension::GetResourceURL(const GURL& extension_url, + const std::string& relative_path) { + DCHECK(extension_url.SchemeIs(extensions::kExtensionScheme)); + DCHECK_EQ("/", extension_url.path()); + + std::string path = relative_path; + + // If the relative path starts with "/", it is "absolute" relative to the + // extension base directory, but extension_url is already specified to refer + // to that base directory, so strip the leading "/" if present. + if (relative_path.size() > 0 && relative_path[0] == '/') + path = relative_path.substr(1); + + GURL ret_val = GURL(extension_url.spec() + path); + DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false)); + + return ret_val; +} + +bool Extension::ResourceMatches(const URLPatternSet& pattern_set, + const std::string& resource) const { + return pattern_set.MatchesURL(extension_url_.Resolve(resource)); +} + +ExtensionResource Extension::GetResource( + const std::string& relative_path) const { + std::string new_path = relative_path; + // We have some legacy data where resources have leading slashes. + // See: http://crbug.com/121164 + if (!new_path.empty() && new_path.at(0) == '/') + new_path.erase(0, 1); + base::FilePath relative_file_path = base::FilePath::FromUTF8Unsafe(new_path); + if (ContainsReservedCharacters(relative_file_path)) + return ExtensionResource(); + ExtensionResource r(id(), path(), relative_file_path); + if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) { + r.set_follow_symlinks_anywhere(); + } + return r; +} + +ExtensionResource Extension::GetResource( + const base::FilePath& relative_file_path) const { + if (ContainsReservedCharacters(relative_file_path)) + return ExtensionResource(); + ExtensionResource r(id(), path(), relative_file_path); + if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) { + r.set_follow_symlinks_anywhere(); + } + return r; +} + +// TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a +// util class in base: +// http://code.google.com/p/chromium/issues/detail?id=13572 +// static +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); +} + +// static +bool Extension::ProducePEM(const std::string& input, std::string* output) { + DCHECK(output); + return (input.length() == 0) ? false : base::Base64Encode(input, output); +} + +// static +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 +GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) { + return GURL(std::string(extensions::kExtensionScheme) + + content::kStandardSchemeSeparator + extension_id + "/"); +} + +bool Extension::HasAPIPermission(APIPermission::ID permission) const { + return PermissionsData::HasAPIPermission(this, permission); +} + +bool Extension::HasAPIPermission(const std::string& permission_name) const { + return PermissionsData::HasAPIPermission(this, permission_name); +} + +scoped_refptr<const PermissionSet> Extension::GetActivePermissions() const { + return PermissionsData::GetActivePermissions(this); +} + +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() != Manifest::COMPONENT; +} + +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); +} + +bool Extension::RequiresSortOrdinal() const { + return is_app() && (display_in_launcher_ || display_in_new_tab_page_); +} + +bool Extension::ShouldDisplayInAppLauncher() const { + // Only apps should be displayed in the launcher. + return is_app() && display_in_launcher_; +} + +bool Extension::ShouldDisplayInNewTabPage() const { + // Only apps should be displayed on the NTP. + return is_app() && display_in_new_tab_page_; +} + +bool Extension::ShouldDisplayInExtensionSettings() const { + // Don't show for themes since the settings UI isn't really useful for them. + if (is_theme()) + return false; + + // Don't show component extensions and invisible apps. + if (ShouldNotBeVisible()) + return false; + + // Always show unpacked extensions and apps. + if (Manifest::IsUnpackedLocation(location())) + return true; + + // Unless they are unpacked, never show hosted apps. Note: We intentionally + // show packaged apps and platform apps because there are some pieces of + // functionality that are only available in chrome://extensions/ but which + // are needed for packaged and platform apps. For example, inspecting + // background pages. See http://crbug.com/116134. + if (is_hosted_app()) + return false; + + return true; +} + +bool Extension::ShouldNotBeVisible() const { + // Don't show component extensions because they are only extensions as an + // implementation detail of Chrome. + if (location() == Manifest::COMPONENT && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kShowComponentExtensionOptions)) { + return true; + } + + // Always show unpacked extensions and apps. + if (Manifest::IsUnpackedLocation(location())) + return false; + + // Don't show apps that aren't visible in either launcher or ntp. + if (is_app() && !ShouldDisplayInAppLauncher() && !ShouldDisplayInNewTabPage()) + return true; + + return false; +} + +Extension::ManifestData* Extension::GetManifestData(const std::string& key) + const { + DCHECK(finished_parsing_manifest_ || thread_checker_.CalledOnValidThread()); + ManifestDataMap::const_iterator iter = manifest_data_.find(key); + if (iter != manifest_data_.end()) + return iter->second.get(); + return NULL; +} + +void Extension::SetManifestData(const std::string& key, + Extension::ManifestData* data) { + DCHECK(!finished_parsing_manifest_ && thread_checker_.CalledOnValidThread()); + manifest_data_[key] = linked_ptr<ManifestData>(data); +} + +Manifest::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(); +} + +void Extension::AddInstallWarning(const InstallWarning& new_warning) { + install_warnings_.push_back(new_warning); +} + +void Extension::AddInstallWarnings( + const std::vector<InstallWarning>& new_warnings) { + install_warnings_.insert(install_warnings_.end(), + new_warnings.begin(), new_warnings.end()); +} + +bool Extension::is_app() const { + return manifest_->is_app(); +} + +bool Extension::is_platform_app() const { + return manifest_->is_platform_app(); +} + +bool Extension::is_hosted_app() const { + return manifest()->is_hosted_app(); +} + +bool Extension::is_legacy_packaged_app() const { + return manifest()->is_legacy_packaged_app(); +} + +bool Extension::is_extension() const { + return manifest()->is_extension(); +} + +bool Extension::can_be_incognito_enabled() const { + // Only component platform apps are supported in incognito. + return !is_platform_app() || location() == Manifest::COMPONENT; +} + +bool Extension::force_incognito_enabled() const { + return PermissionsData::HasAPIPermission(this, APIPermission::kProxy); +} + +void Extension::AddWebExtentPattern(const URLPattern& pattern) { + extent_.AddPattern(pattern); +} + +bool Extension::is_theme() const { + return manifest()->is_theme(); +} + +// static +bool Extension::InitExtensionID(extensions::Manifest* manifest, + const base::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; + if (!manifest->GetString(keys::kPublicKey, &public_key) || + !ParsePEMKeyBytes(public_key, &public_key_bytes)) { + *error = ASCIIToUTF16(errors::kInvalidKey); + return false; + } + std::string extension_id = id_util::GenerateId(public_key_bytes); + 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 = id_util::GenerateIdForPath(path); + if (extension_id.empty()) { + NOTREACHED() << "Could not create ID from path."; + return false; + } + manifest->set_extension_id(extension_id); + return true; + } +} + +Extension::Extension(const base::FilePath& path, + scoped_ptr<extensions::Manifest> manifest) + : manifest_version_(0), + converted_from_user_script_(false), + manifest_(manifest.release()), + finished_parsing_manifest_(false), + display_in_launcher_(true), + display_in_new_tab_page_(true), + wants_file_access_(false), + creation_flags_(0) { + DCHECK(path.empty() || path.IsAbsolute()); + path_ = id_util::MaybeNormalizePath(path); +} + +Extension::~Extension() { +} + +bool Extension::InitFromValue(int flags, string16* error) { + DCHECK(error); + + creation_flags_ = flags; + + // Important to load manifest version first because many other features + // depend on its value. + if (!LoadManifestVersion(error)) + return false; + + if (!LoadRequiredFeatures(error)) + return false; + + // We don't need 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; + + permissions_data_.reset(new PermissionsData); + if (!permissions_data_->ParsePermissions(this, error)) + return false; + + if (manifest_->HasKey(keys::kConvertedFromUserScript)) { + manifest_->GetBoolean(keys::kConvertedFromUserScript, + &converted_from_user_script_); + } + + if (!LoadSharedFeatures(error)) + return false; + + finished_parsing_manifest_ = true; + + permissions_data_->InitializeManifestPermissions(this); + permissions_data_->FinalizePermissions(this); + + 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; + } + non_localized_name_ = UTF16ToUTF8(localized_name); + base::i18n::AdjustStringForLocaleDirection(&localized_name); + name_ = UTF16ToUTF8(localized_name); + 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(new Version(version_str)); + if (!version_->IsValid() || version_->components().size() > 4) { + *error = ASCIIToUTF16(errors::kInvalidVersion); + return false; + } + return true; +} + +bool Extension::LoadAppFeatures(string16* error) { + if (!LoadExtent(keys::kWebURLs, &extent_, + errors::kInvalidWebURLs, errors::kInvalidWebURL, error)) { + return false; + } + if (manifest_->HasKey(keys::kDisplayInLauncher) && + !manifest_->GetBoolean(keys::kDisplayInLauncher, &display_in_launcher_)) { + *error = ASCIIToUTF16(errors::kInvalidDisplayInLauncher); + return false; + } + if (manifest_->HasKey(keys::kDisplayInNewTabPage)) { + if (!manifest_->GetBoolean(keys::kDisplayInNewTabPage, + &display_in_new_tab_page_)) { + *error = ASCIIToUTF16(errors::kInvalidDisplayInNewTabPage); + return false; + } + } else { + // Inherit default from display_in_launcher property. + display_in_new_tab_page_ = display_in_launcher_; + } + return true; +} + +bool Extension::LoadExtent(const char* key, + URLPatternSet* extent, + const char* list_error, + const char* value_error, + string16* error) { + const base::Value* temp_pattern_value = NULL; + if (!manifest_->Get(key, &temp_pattern_value)) + return true; + + const base::ListValue* pattern_list = NULL; + if (!temp_pattern_value->GetAsList(&pattern_list)) { + *error = ASCIIToUTF16(list_error); + return false; + } + + for (size_t i = 0; i < pattern_list->GetSize(); ++i) { + std::string pattern_string; + if (!pattern_list->GetString(i, &pattern_string)) { + *error = ErrorUtils::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 = ErrorUtils::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 = ErrorUtils::FormatErrorMessageUTF16( + value_error, + base::UintToString(i), + errors::kCannotClaimAllURLsInExtent); + return false; + } + + // Do not allow authors to claim "*" for host. + if (pattern.host().empty()) { + *error = ErrorUtils::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 = ErrorUtils::FormatErrorMessageUTF16( + value_error, + base::UintToString(i), + errors::kNoWildCardsInPaths); + return false; + } + pattern.SetPath(pattern.path() + '*'); + + extent->AddPattern(pattern); + } + + return true; +} + +bool Extension::LoadSharedFeatures(string16* error) { + if (!LoadDescription(error) || + !ManifestHandler::ParseExtension(this, error) || + !LoadShortName(error)) + return false; + + 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::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 (manifest_version_ < kModernManifestVersion && + ((creation_flags_ & REQUIRE_MODERN_MANIFEST_VERSION && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAllowLegacyExtensionManifests)) || + GetType() == Manifest::TYPE_PLATFORM_APP)) { + *error = ErrorUtils::FormatErrorMessageUTF16( + errors::kInvalidManifestVersionOld, + base::IntToString(kModernManifestVersion), + is_platform_app() ? "apps" : "extensions"); + return false; + } + + return true; +} + +bool Extension::LoadShortName(string16* error) { + if (manifest_->HasKey(keys::kShortName)) { + string16 localized_short_name; + if (!manifest_->GetString(keys::kShortName, &localized_short_name) || + localized_short_name.empty()) { + *error = ASCIIToUTF16(errors::kInvalidShortName); + return false; + } + + base::i18n::AdjustStringForLocaleDirection(&localized_short_name); + short_name_ = UTF16ToUTF8(localized_short_name); + } else { + short_name_ = name_; + } + return true; +} + +ExtensionInfo::ExtensionInfo(const base::DictionaryValue* manifest, + const std::string& id, + const base::FilePath& path, + Manifest::Location location) + : extension_id(id), + extension_path(path), + extension_location(location) { + if (manifest) + extension_manifest.reset(manifest->DeepCopy()); +} + +ExtensionInfo::~ExtensionInfo() {} + +InstalledExtensionInfo::InstalledExtensionInfo( + const Extension* extension, + bool is_update, + const std::string& old_name) + : extension(extension), + is_update(is_update), + old_name(old_name) {} + +UnloadedExtensionInfo::UnloadedExtensionInfo( + const Extension* extension, + UnloadedExtensionInfo::Reason reason) + : reason(reason), + extension(extension) {} + +UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo( + const Extension* extension, + const PermissionSet* permissions, + Reason reason) + : reason(reason), + extension(extension), + permissions(permissions) {} + +} // namespace extensions diff --git a/extensions/common/extension.h b/extensions/common/extension.h new file mode 100644 index 0000000..642efb2 --- /dev/null +++ b/extensions/common/extension.h @@ -0,0 +1,535 @@ +// Copyright (c) 2013 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. + +#ifndef EXTENSIONS_COMMON_EXTENSION_H_ +#define EXTENSIONS_COMMON_EXTENSION_H_ + +#include <algorithm> +#include <iosfwd> +#include <map> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/install_warning.h" +#include "extensions/common/manifest.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/url_pattern.h" +#include "extensions/common/url_pattern_set.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/gfx/size.h" +#include "url/gurl.h" + +class ExtensionAction; +class SkBitmap; + +namespace base { +class DictionaryValue; +class Version; +} + +namespace gfx { +class ImageSkia; +} + +namespace extensions { +class PermissionsData; +class APIPermissionSet; +class ManifestPermissionSet; +class PermissionSet; + +// Represents a Chrome extension. +// Once created, an Extension object is immutable, with the exception of its +// RuntimeData. This makes it safe to use on any thread, since access to the +// RuntimeData is protected by a lock. +class Extension : public base::RefCountedThreadSafe<Extension> { + public: + struct ManifestData; + + typedef std::map<const std::string, linked_ptr<ManifestData> > + ManifestDataMap; + + enum State { + DISABLED = 0, + ENABLED, + // An external extension that the user uninstalled. We should not reinstall + // such extensions on startup. + EXTERNAL_EXTENSION_UNINSTALLED, + // Special state for component extensions, since they are always loaded by + // the component loader, and should never be auto-installed on startup. + ENABLED_COMPONENT, + NUM_STATES + }; + + // Used to record the reason an extension was disabled. + enum DeprecatedDisableReason { + DEPRECATED_DISABLE_UNKNOWN, + DEPRECATED_DISABLE_USER_ACTION, + DEPRECATED_DISABLE_PERMISSIONS_INCREASE, + DEPRECATED_DISABLE_RELOAD, + DEPRECATED_DISABLE_LAST, // Not used. + }; + + enum DisableReason { + DISABLE_NONE = 0, + DISABLE_USER_ACTION = 1 << 0, + DISABLE_PERMISSIONS_INCREASE = 1 << 1, + DISABLE_RELOAD = 1 << 2, + DISABLE_UNSUPPORTED_REQUIREMENT = 1 << 3, + DISABLE_SIDELOAD_WIPEOUT = 1 << 4, + DISABLE_UNKNOWN_FROM_SYNC = 1 << 5, + DISABLE_PERMISSIONS_CONSENT = 1 << 6, // Unused - abandoned experiment. + DISABLE_KNOWN_DISABLED = 1 << 7, + + // Disabled because we could not verify the install. + DISABLE_NOT_VERIFIED = 1 << 8, + }; + + enum InstallType { + INSTALL_ERROR, + DOWNGRADE, + REINSTALL, + UPGRADE, + NEW_INSTALL + }; + + // A base class for parsed manifest data that APIs want to store on + // the extension. Related to base::SupportsUserData, but with an immutable + // thread-safe interface to match Extension. + struct ManifestData { + virtual ~ManifestData() {} + }; + + enum InitFromValueFlags { + NO_FLAGS = 0, + + // Usually, the id of an extension is generated by the "key" property of + // its manifest, but if |REQUIRE_KEY| is not set, a temporary ID will be + // generated based on the path. + REQUIRE_KEY = 1 << 0, + + // Requires the extension to have an up-to-date manifest version. + // Typically, we'll support multiple manifest versions during a version + // transition. This flag signals that we want to require the most modern + // manifest version that Chrome understands. + REQUIRE_MODERN_MANIFEST_VERSION = 1 << 1, + + // |ALLOW_FILE_ACCESS| indicates that the user is allowing this extension + // to have file access. If it's not present, then permissions and content + // scripts that match file:/// URLs will be filtered out. + ALLOW_FILE_ACCESS = 1 << 2, + + // |FROM_WEBSTORE| indicates that the extension was installed from the + // Chrome Web Store. + FROM_WEBSTORE = 1 << 3, + + // |FROM_BOOKMARK| indicates the extension was created using a mock App + // created from a bookmark. + FROM_BOOKMARK = 1 << 4, + + // |FOLLOW_SYMLINKS_ANYWHERE| means that resources can be symlinks to + // anywhere in the filesystem, rather than being restricted to the + // extension directory. + FOLLOW_SYMLINKS_ANYWHERE = 1 << 5, + + // |ERROR_ON_PRIVATE_KEY| means that private keys inside an + // extension should be errors rather than warnings. + ERROR_ON_PRIVATE_KEY = 1 << 6, + + // |WAS_INSTALLED_BY_DEFAULT| installed by default when the profile was + // created. + WAS_INSTALLED_BY_DEFAULT = 1 << 7, + + // Unused - was part of an abandoned experiment. + REQUIRE_PERMISSIONS_CONSENT = 1 << 8, + }; + + static scoped_refptr<Extension> Create(const base::FilePath& path, + Manifest::Location location, + const base::DictionaryValue& value, + int flags, + std::string* error); + + // In a few special circumstances, we want to create an Extension and give it + // an explicit id. Most consumers should just use the other Create() method. + static scoped_refptr<Extension> Create(const base::FilePath& path, + Manifest::Location location, + const base::DictionaryValue& value, + int flags, + const std::string& explicit_id, + std::string* error); + + // Valid schemes for web extent URLPatterns. + static const int kValidWebExtentSchemes; + + // Valid schemes for host permission URLPatterns. + static const int kValidHostPermissionSchemes; + + // The mimetype used for extensions. + static const char kMimeType[]; + + // Checks to see if the extension has a valid ID. + static bool IdIsValid(const std::string& id); + + // See Type definition in Manifest. + Manifest::Type GetType() const; + + // Returns an absolute url to a resource inside of an extension. The + // |extension_url| argument should be the url() from an Extension object. The + // |relative_path| can be untrusted user input. The returned URL will either + // be invalid() or a child of |extension_url|. + // NOTE: Static so that it can be used from multiple threads. + static GURL GetResourceURL(const GURL& extension_url, + const std::string& relative_path); + GURL GetResourceURL(const std::string& relative_path) const { + return GetResourceURL(url(), relative_path); + } + + // Returns true if the resource matches a pattern in the pattern_set. + bool ResourceMatches(const URLPatternSet& pattern_set, + const std::string& resource) const; + + // Returns an extension resource object. |relative_path| should be UTF8 + // encoded. + ExtensionResource GetResource(const std::string& relative_path) const; + + // As above, but with |relative_path| following the file system's encoding. + ExtensionResource GetResource(const base::FilePath& relative_path) const; + + // |input| is expected to be the text of an rsa public or private key. It + // tolerates the presence or absence of bracking header/footer like this: + // -----(BEGIN|END) [RSA PUBLIC/PRIVATE] KEY----- + // and may contain newlines. + static bool ParsePEMKeyBytes(const std::string& input, std::string* output); + + // Does a simple base64 encoding of |input| into |output|. + static bool ProducePEM(const std::string& input, std::string* output); + + // Expects base64 encoded |input| and formats into |output| including + // the appropriate header & footer. + static bool FormatPEMForFileOutput(const std::string& input, + std::string* output, + bool is_public); + + // Returns the base extension url for a given |extension_id|. + static GURL GetBaseURLFromExtensionId(const std::string& extension_id); + + // DEPRECATED: These methods have been moved to PermissionsData. + // TODO(rdevlin.cronin): remove these once all calls have been updated. + bool HasAPIPermission(APIPermission::ID permission) const; + bool HasAPIPermission(const std::string& permission_name) const; + scoped_refptr<const PermissionSet> GetActivePermissions() const; + + // Whether context menu should be shown for page and browser actions. + bool ShowConfigureContextMenus() const; + + // Returns true if this extension or app includes areas within |origin|. + bool OverlapsWithOrigin(const GURL& origin) const; + + // Returns true if the extension requires a valid ordinal for sorting, e.g., + // for displaying in a launcher or new tab page. + bool RequiresSortOrdinal() const; + + // Returns true if the extension should be displayed in the app launcher. + bool ShouldDisplayInAppLauncher() const; + + // Returns true if the extension should be displayed in the browser NTP. + bool ShouldDisplayInNewTabPage() const; + + // Returns true if the extension should be displayed in the extension + // settings page (i.e. chrome://extensions). + bool ShouldDisplayInExtensionSettings() const; + + // Returns true if the extension should not be shown anywhere. This is + // mostly the same as the extension being a component extension, but also + // includes non-component apps that are hidden from the app launcher and ntp. + bool ShouldNotBeVisible() const; + + // Get the manifest data associated with the key, or NULL if there is none. + // Can only be called after InitValue is finished. + ManifestData* GetManifestData(const std::string& key) const; + + // Sets |data| to be associated with the key. Takes ownership of |data|. + // Can only be called before InitValue is finished. Not thread-safe; + // all SetManifestData calls should be on only one thread. + void SetManifestData(const std::string& key, ManifestData* data); + + // Accessors: + + const base::FilePath& path() const { return path_; } + const GURL& url() const { return extension_url_; } + Manifest::Location location() const; + const std::string& id() const; + const base::Version* version() const { return version_.get(); } + const std::string VersionString() const; + const std::string& name() const { return name_; } + const std::string& short_name() const { return short_name_; } + const std::string& non_localized_name() const { return non_localized_name_; } + // Base64-encoded version of the key used to sign this extension. + // In pseudocode, returns + // base::Base64Encode(RSAPrivateKey(pem_file).ExportPublicKey()). + const std::string& public_key() const { return public_key_; } + const std::string& description() const { return description_; } + int manifest_version() const { return manifest_version_; } + bool converted_from_user_script() const { + return converted_from_user_script_; + } + PermissionsData* permissions_data() { return permissions_data_.get(); } + const PermissionsData* permissions_data() const { + return permissions_data_.get(); + } + + // Appends |new_warning[s]| to install_warnings_. + void AddInstallWarning(const InstallWarning& new_warning); + void AddInstallWarnings(const std::vector<InstallWarning>& new_warnings); + const std::vector<InstallWarning>& install_warnings() const { + return install_warnings_; + } + const extensions::Manifest* manifest() const { + return manifest_.get(); + } + bool wants_file_access() const { return wants_file_access_; } + // TODO(rdevlin.cronin): This is needed for ContentScriptsHandler, and should + // be moved out as part of crbug.com/159265. This should not be used anywhere + // else. + void set_wants_file_access(bool wants_file_access) { + wants_file_access_ = wants_file_access; + } + int creation_flags() const { return creation_flags_; } + bool from_webstore() const { return (creation_flags_ & FROM_WEBSTORE) != 0; } + bool from_bookmark() const { return (creation_flags_ & FROM_BOOKMARK) != 0; } + bool was_installed_by_default() const { + return (creation_flags_ & WAS_INSTALLED_BY_DEFAULT) != 0; + } + + // App-related. + bool is_app() const; + bool is_platform_app() const; + bool is_hosted_app() const; + bool is_legacy_packaged_app() const; + bool is_extension() const; + bool can_be_incognito_enabled() const; + bool force_incognito_enabled() const; + + void AddWebExtentPattern(const URLPattern& pattern); + const URLPatternSet& web_extent() const { return extent_; } + + // Theme-related. + bool is_theme() const; + + private: + friend class base::RefCountedThreadSafe<Extension>; + + // Chooses the extension ID for an extension based on a variety of criteria. + // The chosen ID will be set in |manifest|. + static bool InitExtensionID(extensions::Manifest* manifest, + const base::FilePath& path, + const std::string& explicit_id, + int creation_flags, + string16* error); + + Extension(const base::FilePath& path, + scoped_ptr<extensions::Manifest> manifest); + virtual ~Extension(); + + // Initialize the extension from a parsed manifest. + // TODO(aa): Rename to just Init()? There's no Value here anymore. + // TODO(aa): It is really weird the way this class essentially contains a copy + // of the underlying DictionaryValue in its members. We should decide to + // either wrap the DictionaryValue and go with that only, or we should parse + // into strong types and discard the value. But doing both is bad. + bool InitFromValue(int flags, string16* error); + + // The following are helpers for InitFromValue to load various features of the + // extension from the manifest. + + bool LoadRequiredFeatures(string16* error); + bool LoadName(string16* error); + bool LoadVersion(string16* error); + + bool LoadAppFeatures(string16* error); + bool LoadExtent(const char* key, + URLPatternSet* extent, + const char* list_error, + const char* value_error, + string16* error); + + bool LoadSharedFeatures(string16* error); + bool LoadDescription(string16* error); + bool LoadManifestVersion(string16* error); + bool LoadShortName(string16* error); + + bool CheckMinimumChromeVersion(string16* error) const; + + // The extension's human-readable name. Name is used for display purpose. It + // might be wrapped with unicode bidi control characters so that it is + // displayed correctly in RTL context. + // NOTE: Name is UTF-8 and may contain non-ascii characters. + std::string name_; + + // A non-localized version of the extension's name. This is useful for + // debug output. + std::string non_localized_name_; + + // A short version of the extension's name. This can be used as an alternative + // to the name where there is insufficient space to display the full name. If + // an extension has not explicitly specified a short name, the value of this + // member variable will be the full name rather than an empty string. + std::string short_name_; + + // The version of this extension's manifest. We increase the manifest + // version when making breaking changes to the extension system. + // Version 1 was the first manifest version (implied by a lack of a + // manifest_version attribute in the extension's manifest). We initialize + // this member variable to 0 to distinguish the "uninitialized" case from + // the case when we know the manifest version actually is 1. + int manifest_version_; + + // The absolute path to the directory the extension is stored in. + base::FilePath path_; + + // Defines the set of URLs in the extension's web content. + URLPatternSet extent_; + + scoped_ptr<PermissionsData> permissions_data_; + + // Any warnings that occurred when trying to create/parse the extension. + std::vector<InstallWarning> install_warnings_; + + // The base extension url for the extension. + GURL extension_url_; + + // The extension's version. + scoped_ptr<base::Version> version_; + + // An optional longer description of the extension. + std::string description_; + + // True if the extension was generated from a user script. (We show slightly + // different UI if so). + bool converted_from_user_script_; + + // The public key used to sign the contents of the crx package. + std::string public_key_; + + // The manifest from which this extension was created. + scoped_ptr<Manifest> manifest_; + + // Stored parsed manifest data. + ManifestDataMap manifest_data_; + + // Set to true at the end of InitValue when initialization is finished. + bool finished_parsing_manifest_; + + // Ensures that any call to GetManifestData() prior to finishing + // initialization happens from the same thread (this can happen when certain + // parts of the initialization process need information from previous parts). + base::ThreadChecker thread_checker_; + + // Should this app be shown in the app launcher. + bool display_in_launcher_; + + // Should this app be shown in the browser New Tab Page. + bool display_in_new_tab_page_; + + // Whether the extension has host permissions or user script patterns that + // imply access to file:/// scheme URLs (the user may not have actually + // granted it that access). + bool wants_file_access_; + + // The flags that were passed to InitFromValue. + int creation_flags_; + + DISALLOW_COPY_AND_ASSIGN(Extension); +}; + +typedef std::vector<scoped_refptr<const Extension> > ExtensionList; +typedef std::set<std::string> ExtensionIdSet; +typedef std::vector<std::string> ExtensionIdList; + +// Handy struct to pass core extension info around. +struct ExtensionInfo { + ExtensionInfo(const base::DictionaryValue* manifest, + const std::string& id, + const base::FilePath& path, + Manifest::Location location); + ~ExtensionInfo(); + + scoped_ptr<base::DictionaryValue> extension_manifest; + std::string extension_id; + base::FilePath extension_path; + Manifest::Location extension_location; + + private: + DISALLOW_COPY_AND_ASSIGN(ExtensionInfo); +}; + +struct InstalledExtensionInfo { + // The extension being installed - this should always be non-NULL. + const Extension* extension; + + // True if the extension is being updated; false if it is being installed. + bool is_update; + + // The name of the extension prior to this update. Will be empty if + // |is_update| is false. + std::string old_name; + + InstalledExtensionInfo(const Extension* extension, + bool is_update, + const std::string& old_name); +}; + +struct UnloadedExtensionInfo { + enum Reason { + REASON_DISABLE, // Extension is being disabled. + REASON_UPDATE, // Extension is being updated to a newer version. + REASON_UNINSTALL, // Extension is being uninstalled. + REASON_TERMINATE, // Extension has terminated. + REASON_BLACKLIST, // Extension has been blacklisted. + }; + + Reason reason; + + // The extension being unloaded - this should always be non-NULL. + const Extension* extension; + + UnloadedExtensionInfo(const Extension* extension, Reason reason); +}; + +// The details sent for EXTENSION_PERMISSIONS_UPDATED notifications. +struct UpdatedExtensionPermissionsInfo { + enum Reason { + ADDED, // The permissions were added to the extension. + REMOVED, // The permissions were removed from the extension. + }; + + Reason reason; + + // The extension who's permissions have changed. + const Extension* extension; + + // The permissions that have changed. For Reason::ADDED, this would contain + // only the permissions that have added, and for Reason::REMOVED, this would + // only contain the removed permissions. + const PermissionSet* permissions; + + UpdatedExtensionPermissionsInfo( + const Extension* extension, + const PermissionSet* permissions, + Reason reason); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EXTENSION_H_ diff --git a/extensions/common/extension_api.cc b/extensions/common/extension_api.cc index a8b2fa0..dd68ffd 100644 --- a/extensions/common/extension_api.cc +++ b/extensions/common/extension_api.cc @@ -17,11 +17,11 @@ #include "base/strings/string_util.h" #include "base/values.h" #include "chrome/common/extensions/api/generated_schemas.h" -#include "chrome/common/extensions/extension.h" -#include "chrome/common/extensions/permissions/permissions_data.h" +#include "extensions/common/extension.h" #include "extensions/common/features/feature.h" #include "extensions/common/features/feature_provider.h" #include "extensions/common/permissions/permission_set.h" +#include "extensions/common/permissions/permissions_data.h" #include "grit/common_resources.h" #include "grit/extensions_api_resources.h" #include "ui/base/resource/resource_bundle.h" diff --git a/extensions/common/extension_builder.cc b/extensions/common/extension_builder.cc index ef5a14f..f993e94 100644 --- a/extensions/common/extension_builder.cc +++ b/extensions/common/extension_builder.cc @@ -4,7 +4,7 @@ #include "extensions/common/extension_builder.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" namespace extensions { diff --git a/extensions/common/manifest_handler.cc b/extensions/common/manifest_handler.cc index 944389d..4546f32 100644 --- a/extensions/common/manifest_handler.cc +++ b/extensions/common/manifest_handler.cc @@ -8,7 +8,7 @@ #include "base/logging.h" #include "base/stl_util.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/permissions/manifest_permission.h" #include "extensions/common/permissions/manifest_permission_set.h" diff --git a/extensions/common/manifest_handler_unittest.cc b/extensions/common/manifest_handler_unittest.cc index 620f0d6..9a3a292 100644 --- a/extensions/common/manifest_handler_unittest.cc +++ b/extensions/common/manifest_handler_unittest.cc @@ -9,7 +9,7 @@ #include "base/memory/scoped_ptr.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "extensions/common/install_warning.h" #include "extensions/common/manifest_handler.h" diff --git a/extensions/common/manifest_handlers/background_info.cc b/extensions/common/manifest_handlers/background_info.cc index f32cc4b..1ec404e 100644 --- a/extensions/common/manifest_handlers/background_info.cc +++ b/extensions/common/manifest_handlers/background_info.cc @@ -10,12 +10,12 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/common/extensions/permissions/permissions_data.h" #include "extensions/common/constants.h" #include "extensions/common/error_utils.h" #include "extensions/common/file_util.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/permissions/api_permission_set.h" +#include "extensions/common/permissions/permissions_data.h" #include "extensions/common/switches.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" diff --git a/extensions/common/manifest_handlers/background_info.h b/extensions/common/manifest_handlers/background_info.h index 40d4920..113c341 100644 --- a/extensions/common/manifest_handlers/background_info.h +++ b/extensions/common/manifest_handlers/background_info.h @@ -9,7 +9,7 @@ #include <vector> #include "base/values.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_handler.h" #include "url/gurl.h" diff --git a/extensions/common/manifest_handlers/incognito_info.cc b/extensions/common/manifest_handlers/incognito_info.cc index 686c84b..b57a8a6 100644 --- a/extensions/common/manifest_handlers/incognito_info.cc +++ b/extensions/common/manifest_handlers/incognito_info.cc @@ -7,7 +7,7 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_constants.h" namespace extensions { diff --git a/extensions/common/manifest_handlers/incognito_info.h b/extensions/common/manifest_handlers/incognito_info.h index b275761..cd44934 100644 --- a/extensions/common/manifest_handlers/incognito_info.h +++ b/extensions/common/manifest_handlers/incognito_info.h @@ -6,7 +6,7 @@ #define EXTENSIONS_COMMON_MANIFEST_HANDLERS_INCOGNITO_INFO_H_ #include "base/strings/string16.h" -#include "chrome/common/extensions/extension.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_handler.h" namespace extensions { diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc new file mode 100644 index 0000000..f43d1d7 --- /dev/null +++ b/extensions/common/permissions/permissions_data.cc @@ -0,0 +1,626 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/permissions/permissions_data.h" + +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "content/public/common/url_constants.h" +#include "extensions/common/constants.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/extension.h" +#include "extensions/common/extensions_client.h" +#include "extensions/common/features/feature.h" +#include "extensions/common/features/feature_provider.h" +#include "extensions/common/manifest.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/manifest_handler.h" +#include "extensions/common/permissions/api_permission_set.h" +#include "extensions/common/permissions/permission_message_provider.h" +#include "extensions/common/permissions/permission_set.h" +#include "extensions/common/permissions/permissions_info.h" +#include "extensions/common/switches.h" +#include "extensions/common/url_pattern_set.h" +#include "extensions/common/user_script.h" +#include "url/gurl.h" + +namespace extensions { + +namespace keys = manifest_keys; +namespace errors = manifest_errors; + +namespace { + +PermissionsData::PolicyDelegate* g_policy_delegate = NULL; + +// Custom checks for the experimental permission that can't be expressed in +// _permission_features.json. +bool CanSpecifyExperimentalPermission(const Extension* extension) { + if (extension->location() == Manifest::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 (extension->from_webstore()) + return true; + + return false; +} + +// Checks whether the host |pattern| is allowed for the given |extension|, +// given API permissions |permissions|. +bool CanSpecifyHostPermission(const Extension* extension, + const URLPattern& pattern, + const APIPermissionSet& permissions) { + if (!pattern.match_all_urls() && + pattern.MatchesScheme(chrome::kChromeUIScheme)) { + URLPatternSet chrome_scheme_hosts = ExtensionsClient::Get()-> + GetPermittedChromeSchemeHosts(extension, permissions); + if (chrome_scheme_hosts.ContainsPattern(pattern)) + return true; + + // Component extensions can have access to all of chrome://*. + if (PermissionsData::CanExecuteScriptEverywhere(extension)) + return true; + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kExtensionsOnChromeURLs)) { + return true; + } + + // TODO(aboxhall): return from_webstore() when webstore handles blocking + // extensions which request chrome:// urls + return false; + } + + // Otherwise, the valid schemes were handled by URLPattern. + return true; +} + +// Parses the host and api permissions from the specified permission |key| +// from |extension|'s manifest. +bool ParseHelper(Extension* extension, + const char* key, + APIPermissionSet* api_permissions, + URLPatternSet* host_permissions, + string16* error) { + if (!extension->manifest()->HasKey(key)) + return true; + + const base::ListValue* permissions = NULL; + if (!extension->manifest()->GetList(key, &permissions)) { + *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidPermissions, + std::string()); + return false; + } + + // NOTE: We need to get the APIPermission before we check if features + // associated with them are available because the feature system does not + // know about aliases. + + std::vector<std::string> host_data; + if (!APIPermissionSet::ParseFromJSON( + permissions, APIPermissionSet::kDisallowInternalPermissions, + api_permissions, error, &host_data)) { + return false; + } + + // Verify feature availability of permissions. + std::vector<APIPermission::ID> to_remove; + FeatureProvider* permission_features = + FeatureProvider::GetPermissionFeatures(); + for (APIPermissionSet::const_iterator iter = api_permissions->begin(); + iter != api_permissions->end(); ++iter) { + Feature* feature = permission_features->GetFeature(iter->name()); + + // The feature should exist since we just got an APIPermission for it. The + // two systems should be updated together whenever a permission is added. + DCHECK(feature); + // http://crbug.com/176381 + if (!feature) { + to_remove.push_back(iter->id()); + continue; + } + + Feature::Availability availability = feature->IsAvailableToManifest( + extension->id(), + extension->GetType(), + Feature::ConvertLocation(extension->location()), + extension->manifest_version()); + + if (!availability.is_available()) { + // Don't fail, but warn the developer that the manifest contains + // unrecognized permissions. This may happen legitimately if the + // extensions requests platform- or channel-specific permissions. + extension->AddInstallWarning(InstallWarning(availability.message(), + feature->name())); + to_remove.push_back(iter->id()); + continue; + } + + if (iter->id() == APIPermission::kExperimental) { + if (!CanSpecifyExperimentalPermission(extension)) { + *error = ASCIIToUTF16(errors::kExperimentalFlagRequired); + return false; + } + } + } + + api_permissions->AddImpliedPermissions(); + + // Remove permissions that are not available to this extension. + for (std::vector<APIPermission::ID>::const_iterator iter = to_remove.begin(); + iter != to_remove.end(); ++iter) { + api_permissions->erase(*iter); + } + + // Parse host pattern permissions. + const int kAllowedSchemes = + PermissionsData::CanExecuteScriptEverywhere(extension) ? + URLPattern::SCHEME_ALL : Extension::kValidHostPermissionSchemes; + + for (std::vector<std::string>::const_iterator iter = host_data.begin(); + iter != host_data.end(); ++iter) { + const std::string& permission_str = *iter; + + // Check if it's a host pattern permission. + URLPattern pattern = URLPattern(kAllowedSchemes); + URLPattern::ParseResult parse_result = pattern.Parse(permission_str); + if (parse_result == URLPattern::PARSE_SUCCESS) { + // The path component is not used for host permissions, so we force it + // to match all paths. + pattern.SetPath("/*"); + int valid_schemes = pattern.valid_schemes(); + if (pattern.MatchesScheme(chrome::kFileScheme) && + !PermissionsData::CanExecuteScriptEverywhere(extension)) { + extension->set_wants_file_access(true); + if (!(extension->creation_flags() & Extension::ALLOW_FILE_ACCESS)) + valid_schemes &= ~URLPattern::SCHEME_FILE; + } + + if (pattern.scheme() != chrome::kChromeUIScheme && + !PermissionsData::CanExecuteScriptEverywhere(extension)) { + // Keep chrome:// in allowed schemes only if it's explicitly requested + // or CanExecuteScriptEverywhere is true. If the + // extensions_on_chrome_urls flag is not set, CanSpecifyHostPermission + // will fail, so don't check the flag here. + valid_schemes &= ~URLPattern::SCHEME_CHROMEUI; + } + pattern.SetValidSchemes(valid_schemes); + + if (!CanSpecifyHostPermission(extension, pattern, *api_permissions)) { + // TODO(aboxhall): make a warning (see pattern.match_all_urls() block + // below). + extension->AddInstallWarning(InstallWarning( + ErrorUtils::FormatErrorMessage( + errors::kInvalidPermissionScheme, permission_str), + key, + permission_str)); + continue; + } + + host_permissions->AddPattern(pattern); + // We need to make sure all_urls matches chrome://favicon and (maybe) + // chrome://thumbnail, so add them back in to host_permissions separately. + if (pattern.match_all_urls()) { + host_permissions->AddPatterns( + ExtensionsClient::Get()->GetPermittedChromeSchemeHosts( + extension, *api_permissions)); + } + continue; + } + + // It's probably an unknown API permission. Do not throw an error so + // extensions can retain backwards compatability (http://crbug.com/42742). + extension->AddInstallWarning(InstallWarning( + ErrorUtils::FormatErrorMessage( + manifest_errors::kPermissionUnknownOrMalformed, + permission_str), + key, + permission_str)); + } + + return true; +} + +// Returns true if this extension id is from a trusted provider. +bool IsTrustedId(const std::string& extension_id) { + // See http://b/4946060 for more details. + return extension_id == std::string("nckgahadagoaajjgafhacjanaoiihapd"); +} + +} // namespace + +struct PermissionsData::InitialPermissions { + APIPermissionSet api_permissions; + ManifestPermissionSet manifest_permissions; + URLPatternSet host_permissions; + URLPatternSet scriptable_hosts; +}; + +PermissionsData::PermissionsData() { +} + +PermissionsData::~PermissionsData() { +} + +// static +void PermissionsData::SetPolicyDelegate(PolicyDelegate* delegate) { + g_policy_delegate = delegate; +} + +// static +const PermissionSet* PermissionsData::GetOptionalPermissions( + const Extension* extension) { + return extension->permissions_data()->optional_permission_set_.get(); +} + +// static +const PermissionSet* PermissionsData::GetRequiredPermissions( + const Extension* extension) { + return extension->permissions_data()->required_permission_set_.get(); +} + +// static +const APIPermissionSet* PermissionsData::GetInitialAPIPermissions( + const Extension* extension) { + return &extension->permissions_data()-> + initial_required_permissions_->api_permissions; +} + +// static +APIPermissionSet* PermissionsData::GetInitialAPIPermissions( + Extension* extension) { + return &extension->permissions_data()-> + initial_required_permissions_->api_permissions; +} + +// static +void PermissionsData::SetInitialScriptableHosts( + Extension* extension, const URLPatternSet& scriptable_hosts) { + extension->permissions_data()-> + initial_required_permissions_->scriptable_hosts = scriptable_hosts; +} + +// static +void PermissionsData::SetActivePermissions(const Extension* extension, + const PermissionSet* permissions) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + extension->permissions_data()->active_permissions_ = permissions; +} + +// static +scoped_refptr<const PermissionSet> PermissionsData::GetActivePermissions( + const Extension* extension) { + return extension->permissions_data()->active_permissions_; +} + +// static +scoped_refptr<const PermissionSet> PermissionsData::GetTabSpecificPermissions( + const Extension* extension, + int tab_id) { + CHECK_GE(tab_id, 0); + TabPermissionsMap::const_iterator iter = + extension->permissions_data()->tab_specific_permissions_.find(tab_id); + return + (iter != extension->permissions_data()->tab_specific_permissions_.end()) + ? iter->second + : NULL; +} + +// static +void PermissionsData::UpdateTabSpecificPermissions( + const Extension* extension, + int tab_id, + scoped_refptr<const PermissionSet> permissions) { + CHECK_GE(tab_id, 0); + TabPermissionsMap* tab_permissions = + &extension->permissions_data()->tab_specific_permissions_; + if (tab_permissions->count(tab_id)) { + (*tab_permissions)[tab_id] = PermissionSet::CreateUnion( + (*tab_permissions)[tab_id].get(), permissions.get()); + } else { + (*tab_permissions)[tab_id] = permissions; + } +} + +// static +void PermissionsData::ClearTabSpecificPermissions( + const Extension* extension, + int tab_id) { + CHECK_GE(tab_id, 0); + extension->permissions_data()->tab_specific_permissions_.erase(tab_id); +} + +// static +bool PermissionsData::HasAPIPermission(const Extension* extension, + APIPermission::ID permission) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + return GetActivePermissions(extension)->HasAPIPermission(permission); +} + +// static +bool PermissionsData::HasAPIPermission( + const Extension* extension, + const std::string& permission_name) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + return GetActivePermissions(extension)->HasAPIPermission(permission_name); +} + +// static +bool PermissionsData::HasAPIPermissionForTab( + const Extension* extension, + int tab_id, + APIPermission::ID permission) { + if (HasAPIPermission(extension, permission)) + return true; + + // Place autolock below the HasAPIPermission() check, since HasAPIPermission + // also acquires the lock. + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + scoped_refptr<const PermissionSet> tab_permissions = + GetTabSpecificPermissions(extension, tab_id); + return tab_permissions.get() && tab_permissions->HasAPIPermission(permission); +} + +// static +bool PermissionsData::CheckAPIPermissionWithParam( + const Extension* extension, + APIPermission::ID permission, + const APIPermission::CheckParam* param) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + return GetActivePermissions(extension)->CheckAPIPermissionWithParam( + permission, param); +} + +// static +const URLPatternSet& PermissionsData::GetEffectiveHostPermissions( + const Extension* extension) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + return GetActivePermissions(extension)->effective_hosts(); +} + +// static +bool PermissionsData::CanSilentlyIncreasePermissions( + const Extension* extension) { + return extension->location() != Manifest::INTERNAL; +} + +// static +bool PermissionsData::ShouldSkipPermissionWarnings(const Extension* extension) { + return IsTrustedId(extension->id()); +} + +// static +bool PermissionsData::HasHostPermission(const Extension* extension, + const GURL& url) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + return GetActivePermissions(extension)->HasExplicitAccessToOrigin(url); +} + +// static +bool PermissionsData::HasEffectiveAccessToAllHosts(const Extension* extension) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + return GetActivePermissions(extension)->HasEffectiveAccessToAllHosts(); +} + +// static +PermissionMessages PermissionsData::GetPermissionMessages( + const Extension* extension) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + if (ShouldSkipPermissionWarnings(extension)) { + return PermissionMessages(); + } else { + return PermissionMessageProvider::Get()->GetPermissionMessages( + GetActivePermissions(extension), extension->GetType()); + } +} + +// static +std::vector<string16> PermissionsData::GetPermissionMessageStrings( + const Extension* extension) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + if (ShouldSkipPermissionWarnings(extension)) { + return std::vector<string16>(); + } else { + return PermissionMessageProvider::Get()->GetWarningMessages( + GetActivePermissions(extension), extension->GetType()); + } +} + +// static +std::vector<string16> PermissionsData::GetPermissionMessageDetailsStrings( + const Extension* extension) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + if (ShouldSkipPermissionWarnings(extension)) { + return std::vector<string16>(); + } else { + return PermissionMessageProvider::Get()->GetWarningMessagesDetails( + GetActivePermissions(extension), extension->GetType()); + } +} + +// static +bool PermissionsData::CanExecuteScriptOnPage(const Extension* extension, + const GURL& document_url, + const GURL& top_frame_url, + int tab_id, + const UserScript* script, + int process_id, + std::string* error) { + base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + bool can_execute_everywhere = CanExecuteScriptEverywhere(extension); + + if (g_policy_delegate && + !g_policy_delegate->CanExecuteScriptOnPage( + extension, document_url, top_frame_url, tab_id, + script, process_id, error)) + return false; + + if (!can_execute_everywhere && + !ExtensionsClient::Get()->IsScriptableURL(document_url, error)) { + return false; + } + + if (!command_line->HasSwitch(switches::kExtensionsOnChromeURLs)) { + if (document_url.SchemeIs(chrome::kChromeUIScheme) && + !can_execute_everywhere) { + if (error) + *error = errors::kCannotAccessChromeUrl; + return false; + } + } + + if (top_frame_url.SchemeIs(extensions::kExtensionScheme) && + top_frame_url.GetOrigin() != + Extension::GetBaseURLFromExtensionId(extension->id()).GetOrigin() && + !can_execute_everywhere) { + if (error) + *error = errors::kCannotAccessExtensionUrl; + return false; + } + + // If a tab ID is specified, try the tab-specific permissions. + if (tab_id >= 0) { + scoped_refptr<const PermissionSet> tab_permissions = + GetTabSpecificPermissions(extension, tab_id); + if (tab_permissions.get() && + tab_permissions->explicit_hosts().MatchesSecurityOrigin(document_url)) { + return true; + } + } + + bool can_access = false; + + if (script) { + // If a script is specified, use its matches. + can_access = script->MatchesURL(document_url); + } else { + // Otherwise, see if this extension has permission to execute script + // programmatically on pages. + can_access = GetActivePermissions(extension)-> + HasExplicitAccessToOrigin(document_url); + } + + if (!can_access && error) { + *error = ErrorUtils::FormatErrorMessage(errors::kCannotAccessPage, + document_url.spec()); + } + + return can_access; +} + +// static +bool PermissionsData::CanExecuteScriptEverywhere(const Extension* extension) { + if (extension->location() == Manifest::COMPONENT) + return true; + + const ExtensionsClient::ScriptingWhitelist& whitelist = + ExtensionsClient::Get()->GetScriptingWhitelist(); + + return std::find(whitelist.begin(), whitelist.end(), extension->id()) != + whitelist.end(); +} + +// static +bool PermissionsData::CanCaptureVisiblePage(const Extension* extension, + const GURL& page_url, + int tab_id, + std::string* error) { + if (tab_id >= 0) { + scoped_refptr<const PermissionSet> tab_permissions = + GetTabSpecificPermissions(extension, tab_id); + if (tab_permissions.get() && + tab_permissions->explicit_hosts().MatchesSecurityOrigin(page_url)) { + return true; + } + } + + if (HasHostPermission(extension, page_url) || + page_url.GetOrigin() == extension->url()) { + return true; + } + + if (error) { + *error = ErrorUtils::FormatErrorMessage(errors::kCannotAccessPage, + page_url.spec()); + } + return false; +} + +bool PermissionsData::ParsePermissions(Extension* extension, string16* error) { + initial_required_permissions_.reset(new InitialPermissions); + if (!ParseHelper(extension, + keys::kPermissions, + &initial_required_permissions_->api_permissions, + &initial_required_permissions_->host_permissions, + error)) { + return false; + } + + // TODO(jeremya/kalman) do this via the features system by exposing the + // app.window API to platform apps, with no dependency on any permissions. + // See http://crbug.com/120069. + if (extension->is_platform_app()) { + initial_required_permissions_->api_permissions.insert( + APIPermission::kAppCurrentWindowInternal); + initial_required_permissions_->api_permissions.insert( + APIPermission::kAppRuntime); + initial_required_permissions_->api_permissions.insert( + APIPermission::kAppWindow); + } + + initial_optional_permissions_.reset(new InitialPermissions); + if (!ParseHelper(extension, + keys::kOptionalPermissions, + &initial_optional_permissions_->api_permissions, + &initial_optional_permissions_->host_permissions, + error)) { + return false; + } + + return true; +} + +void PermissionsData::InitializeManifestPermissions(Extension* extension) { + ManifestHandler::AddExtensionInitialRequiredPermissions( + extension, &initial_required_permissions_->manifest_permissions); +} + +void PermissionsData::FinalizePermissions(Extension* extension) { + active_permissions_ = new PermissionSet( + initial_required_permissions_->api_permissions, + initial_required_permissions_->manifest_permissions, + initial_required_permissions_->host_permissions, + initial_required_permissions_->scriptable_hosts); + + required_permission_set_ = new PermissionSet( + initial_required_permissions_->api_permissions, + initial_required_permissions_->manifest_permissions, + initial_required_permissions_->host_permissions, + initial_required_permissions_->scriptable_hosts); + + optional_permission_set_ = new PermissionSet( + initial_optional_permissions_->api_permissions, + initial_optional_permissions_->manifest_permissions, + initial_optional_permissions_->host_permissions, + URLPatternSet()); + + initial_required_permissions_.reset(); + initial_optional_permissions_.reset(); +} + +} // namespace extensions diff --git a/extensions/common/permissions/permissions_data.h b/extensions/common/permissions/permissions_data.h new file mode 100644 index 0000000..72b9d24 --- /dev/null +++ b/extensions/common/permissions/permissions_data.h @@ -0,0 +1,218 @@ +// Copyright (c) 2013 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. + +#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_DATA_H_ +#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_DATA_H_ + +#include <map> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/synchronization/lock.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/permissions/permission_message.h" + +class GURL; + +namespace extensions { + +class PermissionSet; +class APIPermissionSet; +class Extension; +class ManifestPermissionSet; +class URLPatternSet; +class UserScript; + +// A container for the permissions data of the extension; also responsible for +// parsing the "permissions" and "optional_permissions" manifest keys. This +// class also contains the active (runtime) permissions for the extension. +class PermissionsData { + public: + PermissionsData(); + ~PermissionsData(); + + // Delegate class to allow different contexts (e.g. browser vs renderer) to + // have control over policy decisions. + class PolicyDelegate { + public: + virtual ~PolicyDelegate() {} + + // Returns false if script access should be blocked on this page. + // Otherwise, default policy should decide. + virtual bool CanExecuteScriptOnPage(const Extension* extension, + const GURL& document_url, + const GURL& top_document_url, + int tab_id, + const UserScript* script, + int process_id, + std::string* error) = 0; + }; + + static void SetPolicyDelegate(PolicyDelegate* delegate); + + // Return the optional or required permission set for the given |extension|. + static const PermissionSet* GetOptionalPermissions( + const Extension* extension); + static const PermissionSet* GetRequiredPermissions( + const Extension* extension); + + // Return the temporary API permission set which is used during extension + // initialization. Once initialization completes, this is NULL. + static const APIPermissionSet* GetInitialAPIPermissions( + const Extension* extension); + static APIPermissionSet* GetInitialAPIPermissions(Extension* extension); + + // Set the scriptable hosts for the given |extension| during initialization. + static void SetInitialScriptableHosts(Extension* extension, + const URLPatternSet& scriptable_hosts); + + // Return the active (runtime) permissions for the given |extension|. + static scoped_refptr<const PermissionSet> GetActivePermissions( + const Extension* extension); + // Sets the runtime permissions of the given |extension| to |permissions|. + static void SetActivePermissions(const Extension* extension, + const PermissionSet* active); + + // Gets the tab-specific host permissions of |tab_id|, or NULL if there + // aren't any. + static scoped_refptr<const PermissionSet> GetTabSpecificPermissions( + const Extension* extension, + int tab_id); + // Updates the tab-specific permissions of |tab_id| to include those from + // |permissions|. + static void UpdateTabSpecificPermissions( + const Extension* extension, + int tab_id, + scoped_refptr<const PermissionSet> permissions); + // Clears the tab-specific permissions of |tab_id|. + static void ClearTabSpecificPermissions(const Extension* extension, + int tab_id); + + // Returns true if the |extension| has the given |permission|. Prefer + // IsExtensionWithPermissionOrSuggestInConsole when developers may be using an + // api that requires a permission they didn't know about, e.g. open web apis. + // Note this does not include APIs with no corresponding permission, like + // "runtime" or "browserAction". + // TODO(mpcomplete): drop the "API" from these names, it's confusing. + static bool HasAPIPermission(const Extension* extension, + APIPermission::ID permission); + static bool HasAPIPermission(const Extension* extension, + const std::string& permission_name); + static bool HasAPIPermissionForTab(const Extension* extension, + int tab_id, + APIPermission::ID permission); + + static bool CheckAPIPermissionWithParam( + const Extension* extension, + APIPermission::ID permission, + const APIPermission::CheckParam* param); + + static const URLPatternSet& GetEffectiveHostPermissions( + const Extension* extension); + + // Returns true if the |extension| can silently increase its permission level. + // Users must approve permissions for unpacked and packed extensions in the + // following situations: + // - when installing or upgrading packed extensions + // - when installing unpacked extensions that have NPAPI plugins + // - when either type of extension requests optional permissions + static bool CanSilentlyIncreasePermissions(const Extension* extension); + + // Returns true if the extension does not require permission warnings + // to be displayed at install time. + static bool ShouldSkipPermissionWarnings(const Extension* extension); + + // Whether the |extension| has access to the given |url|. + static bool HasHostPermission(const Extension* extension, const GURL& url); + + // Whether the |extension| has effective access to all hosts. This is true if + // there is a content script that matches all hosts, if there is a host + // permission grants access to all hosts (like <all_urls>) or an api + // permission that effectively grants access to all hosts (e.g. proxy, + // network, etc.) + static bool HasEffectiveAccessToAllHosts(const Extension* extension); + + // Returns the full list of permission messages that the given |extension| + // should display at install time. + static PermissionMessages GetPermissionMessages(const Extension* extension); + // Returns the full list of permission messages that the given |extension| + // should display at install time. The messages are returned as strings + // for convenience. + static std::vector<string16> GetPermissionMessageStrings( + const Extension* extension); + + // Returns the full list of permission details for messages that the given + // |extension| should display at install time. The messages are returned as + // strings for convenience. + static std::vector<string16> GetPermissionMessageDetailsStrings( + const Extension* extension); + + // Returns true if the given |extension| can execute script on a page. If a + // UserScript object is passed, permission to run that specific script is + // checked (using its matches list). Otherwise, permission to execute script + // programmatically is checked (using the extension's host permission). + // + // This method is also aware of certain special pages that extensions are + // usually not allowed to run script on. + static bool CanExecuteScriptOnPage(const Extension* extension, + const GURL& document_url, + const GURL& top_document_url, + int tab_id, + const UserScript* script, + int process_id, + std::string* error); + + // Returns true if the given |extension| is a COMPONENT extension, or if it is + // on the whitelist of extensions that can script all pages. + static bool CanExecuteScriptEverywhere(const Extension* extension); + + // Returns true if the |extension| is allowed to obtain the contents of a + // page as an image. Since a page may contain sensitive information, this + // is restricted to the extension's host permissions as well as the + // extension page itself. + static bool CanCaptureVisiblePage(const Extension* extension, + const GURL& page_url, + int tab_id, + std::string* error); + + // Parse the permissions of a given extension in the initialization process. + bool ParsePermissions(Extension* extension, string16* error); + + // Ensure manifest handlers provide their custom manifest permissions. + void InitializeManifestPermissions(Extension* extension); + + // Finalize permissions after the initialization process completes. + void FinalizePermissions(Extension* extension); + + private: + struct InitialPermissions; + typedef std::map<int, scoped_refptr<const PermissionSet> > TabPermissionsMap; + + // Temporary permissions during the initialization process; NULL after + // initialization completes. + scoped_ptr<InitialPermissions> initial_required_permissions_; + scoped_ptr<InitialPermissions> initial_optional_permissions_; + + // The set of permissions the extension can request at runtime. + scoped_refptr<const PermissionSet> optional_permission_set_; + + // The extension's required / default set of permissions. + scoped_refptr<const PermissionSet> required_permission_set_; + + mutable base::Lock runtime_lock_; + + // The permission's which are currently active on the extension during + // runtime. + mutable scoped_refptr<const PermissionSet> active_permissions_; + + mutable TabPermissionsMap tab_specific_permissions_; + + DISALLOW_COPY_AND_ASSIGN(PermissionsData); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_DATA_H_ diff --git a/extensions/common/permissions/permissions_data_unittest.cc b/extensions/common/permissions/permissions_data_unittest.cc new file mode 100644 index 0000000..3ea4856 --- /dev/null +++ b/extensions/common/permissions/permissions_data_unittest.cc @@ -0,0 +1,620 @@ +// Copyright (c) 2013 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 <vector> + +#include "base/command_line.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/chrome_version_info.h" +#include "chrome/common/extensions/extension_test_util.h" +#include "chrome/common/extensions/features/feature_channel.h" +#include "chrome/common/extensions/permissions/socket_permission.h" +#include "content/public/common/socket_permission_request.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/extension.h" +#include "extensions/common/id_util.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/permissions/permission_set.h" +#include "extensions/common/permissions/permissions_data.h" +#include "extensions/common/switches.h" +#include "extensions/common/url_pattern_set.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::SocketPermissionRequest; +using extension_test_util::LoadManifest; +using extension_test_util::LoadManifestUnchecked; +using extension_test_util::LoadManifestStrict; + +namespace extensions { + +namespace { + +bool CheckSocketPermission( + scoped_refptr<Extension> extension, + SocketPermissionRequest::OperationType type, + const char* host, + int port) { + SocketPermission::CheckParam param(type, host, port); + return PermissionsData::CheckAPIPermissionWithParam( + extension.get(), APIPermission::kSocket, ¶m); +} + +} // namespace + +TEST(ExtensionPermissionsTest, EffectiveHostPermissions) { + scoped_refptr<Extension> extension; + URLPatternSet hosts; + + extension = LoadManifest("effective_host_permissions", "empty.json"); + EXPECT_EQ(0u, + PermissionsData::GetEffectiveHostPermissions(extension.get()) + .patterns().size()); + EXPECT_FALSE(hosts.MatchesURL(GURL("http://www.google.com"))); + EXPECT_FALSE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", "one_host.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com"))); + EXPECT_FALSE(hosts.MatchesURL(GURL("https://www.google.com"))); + EXPECT_FALSE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", + "one_host_wildcard.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://foo.google.com"))); + EXPECT_FALSE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", "two_hosts.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com"))); + EXPECT_FALSE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", + "https_not_considered.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("https://google.com"))); + EXPECT_FALSE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", + "two_content_scripts.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com"))); + EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL( + GURL("http://www.reddit.com"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://news.ycombinator.com"))); + EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL( + GURL("http://news.ycombinator.com"))); + EXPECT_FALSE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", "all_hosts.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://test/"))); + EXPECT_FALSE(hosts.MatchesURL(GURL("https://test/"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com"))); + EXPECT_TRUE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", "all_hosts2.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://test/"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com"))); + EXPECT_TRUE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); + + extension = LoadManifest("effective_host_permissions", "all_hosts3.json"); + hosts = PermissionsData::GetEffectiveHostPermissions(extension.get()); + EXPECT_FALSE(hosts.MatchesURL(GURL("http://test/"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("https://test/"))); + EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com"))); + EXPECT_TRUE(PermissionsData::HasEffectiveAccessToAllHosts(extension.get())); +} + +TEST(ExtensionPermissionsTest, SocketPermissions) { + // Set feature current channel to appropriate value. + ScopedCurrentChannel scoped_channel(chrome::VersionInfo::CHANNEL_DEV); + scoped_refptr<Extension> extension; + std::string error; + + extension = LoadManifest("socket_permissions", "empty.json"); + EXPECT_FALSE(CheckSocketPermission(extension, + SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)); + + extension = LoadManifestUnchecked("socket_permissions", + "socket1.json", + Manifest::INTERNAL, Extension::NO_FLAGS, + &error); + EXPECT_TRUE(extension.get() == NULL); + ASSERT_EQ(ErrorUtils::FormatErrorMessage( + manifest_errors::kInvalidPermission, "socket"), + error); + + extension = LoadManifest("socket_permissions", "socket2.json"); + EXPECT_TRUE(CheckSocketPermission(extension, + SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)); + EXPECT_FALSE(CheckSocketPermission( + extension, SocketPermissionRequest::UDP_BIND, "", 80)); + EXPECT_TRUE(CheckSocketPermission( + extension, SocketPermissionRequest::UDP_BIND, "", 8888)); + + EXPECT_FALSE(CheckSocketPermission( + extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900)); + EXPECT_TRUE(CheckSocketPermission( + extension, + SocketPermissionRequest::UDP_SEND_TO, + "239.255.255.250", 1900)); +} + +TEST(ExtensionPermissionsTest, GetPermissionMessages_ManyAPIPermissions) { + scoped_refptr<Extension> extension; + extension = LoadManifest("permissions", "many-apis.json"); + std::vector<string16> warnings = + PermissionsData::GetPermissionMessageStrings(extension.get()); + ASSERT_EQ(6u, warnings.size()); + EXPECT_EQ("Access your data on api.flickr.com", + UTF16ToUTF8(warnings[0])); + EXPECT_EQ("Read and modify your bookmarks", UTF16ToUTF8(warnings[1])); + EXPECT_EQ("Detect your physical location", UTF16ToUTF8(warnings[2])); + EXPECT_EQ("Read and modify your browsing history", UTF16ToUTF8(warnings[3])); + EXPECT_EQ("Access your tabs and browsing activity", UTF16ToUTF8(warnings[4])); + EXPECT_EQ("Manage your apps, extensions, and themes", + UTF16ToUTF8(warnings[5])); +} + +TEST(ExtensionPermissionsTest, GetPermissionMessages_ManyHostsPermissions) { + scoped_refptr<Extension> extension; + extension = LoadManifest("permissions", "more-than-3-hosts.json"); + std::vector<string16> warnings = + PermissionsData::GetPermissionMessageStrings(extension.get()); + std::vector<string16> warnings_details = + PermissionsData::GetPermissionMessageDetailsStrings(extension.get()); + ASSERT_EQ(1u, warnings.size()); + ASSERT_EQ(1u, warnings_details.size()); + EXPECT_EQ("Access your data on 5 websites", UTF16ToUTF8(warnings[0])); + EXPECT_EQ("- www.a.com\n- www.b.com\n- www.c.com\n- www.d.com\n- www.e.com", + UTF16ToUTF8(warnings_details[0])); +} + +TEST(ExtensionPermissionsTest, GetPermissionMessages_LocationApiPermission) { + scoped_refptr<Extension> extension; + extension = LoadManifest("permissions", + "location-api.json", + Manifest::COMPONENT, + Extension::NO_FLAGS); + std::vector<string16> warnings = + PermissionsData::GetPermissionMessageStrings(extension.get()); + ASSERT_EQ(1u, warnings.size()); + EXPECT_EQ("Detect your physical location", UTF16ToUTF8(warnings[0])); +} + +TEST(ExtensionPermissionsTest, GetPermissionMessages_ManyHosts) { + scoped_refptr<Extension> extension; + extension = LoadManifest("permissions", "many-hosts.json"); + std::vector<string16> warnings = + PermissionsData::GetPermissionMessageStrings(extension.get()); + ASSERT_EQ(1u, warnings.size()); + EXPECT_EQ("Access your data on encrypted.google.com and www.google.com", + UTF16ToUTF8(warnings[0])); +} + +TEST(ExtensionPermissionsTest, GetPermissionMessages_Plugins) { + scoped_refptr<Extension> extension; + extension = LoadManifest("permissions", "plugins.json"); + std::vector<string16> warnings = + PermissionsData::GetPermissionMessageStrings(extension.get()); +// We don't parse the plugins key on Chrome OS, so it should not ask for any +// permissions. +#if defined(OS_CHROMEOS) + ASSERT_EQ(0u, warnings.size()); +#else + ASSERT_EQ(1u, warnings.size()); + EXPECT_EQ("Access all data on your computer and the websites you visit", + UTF16ToUTF8(warnings[0])); +#endif +} + +// Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage +// methods of Extension for extensions with various permissions. +class ExtensionScriptAndCaptureVisibleTest : public testing::Test { + protected: + ExtensionScriptAndCaptureVisibleTest() + : http_url("http://www.google.com"), + http_url_with_path("http://www.google.com/index.html"), + https_url("https://www.google.com"), + file_url("file:///foo/bar"), + favicon_url("chrome://favicon/http://www.google.com"), + extension_url("chrome-extension://" + + id_util::GenerateIdForPath( + base::FilePath(FILE_PATH_LITERAL("foo")))), + settings_url("chrome://settings"), + about_url("about:flags") { + urls_.insert(http_url); + urls_.insert(http_url_with_path); + urls_.insert(https_url); + urls_.insert(file_url); + urls_.insert(favicon_url); + urls_.insert(extension_url); + urls_.insert(settings_url); + urls_.insert(about_url); + // Ignore the policy delegate for this test. + PermissionsData::SetPolicyDelegate(NULL); + } + + bool AllowedScript(const Extension* extension, const GURL& url, + const GURL& top_url) { + return PermissionsData::CanExecuteScriptOnPage( + extension, url, top_url, -1, NULL, -1, NULL); + } + + bool BlockedScript(const Extension* extension, const GURL& url, + const GURL& top_url) { + return !PermissionsData::CanExecuteScriptOnPage( + extension, url, top_url, -1, NULL, -1, NULL); + } + + bool Allowed(const Extension* extension, const GURL& url) { + return Allowed(extension, url, -1); + } + + bool Allowed(const Extension* extension, const GURL& url, int tab_id) { + return (PermissionsData::CanExecuteScriptOnPage( + extension, url, url, tab_id, NULL, -1, NULL) && + PermissionsData::CanCaptureVisiblePage( + extension, url, tab_id, NULL)); + } + + bool CaptureOnly(const Extension* extension, const GURL& url) { + return CaptureOnly(extension, url, -1); + } + + bool CaptureOnly(const Extension* extension, const GURL& url, int tab_id) { + return !PermissionsData::CanExecuteScriptOnPage( + extension, url, url, tab_id, NULL, -1, NULL) && + PermissionsData::CanCaptureVisiblePage(extension, url, tab_id, NULL); + } + + bool Blocked(const Extension* extension, const GURL& url) { + return Blocked(extension, url, -1); + } + + bool Blocked(const Extension* extension, const GURL& url, int tab_id) { + return !(PermissionsData::CanExecuteScriptOnPage( + extension, url, url, tab_id, NULL, -1, NULL) || + PermissionsData::CanCaptureVisiblePage( + extension, url, tab_id, NULL)); + } + + bool AllowedExclusivelyOnTab( + const Extension* extension, + const std::set<GURL>& allowed_urls, + int tab_id) { + bool result = true; + for (std::set<GURL>::iterator it = urls_.begin(); it != urls_.end(); ++it) { + const GURL& url = *it; + if (allowed_urls.count(url)) + result &= Allowed(extension, url, tab_id); + else + result &= Blocked(extension, url, tab_id); + } + return result; + } + + // URLs that are "safe" to provide scripting and capture visible tab access + // to if the permissions allow it. + const GURL http_url; + const GURL http_url_with_path; + const GURL https_url; + const GURL file_url; + + // We should allow host permission but not scripting permission for favicon + // urls. + const GURL favicon_url; + + // URLs that regular extensions should never get access to. + const GURL extension_url; + const GURL settings_url; + const GURL about_url; + + private: + // The set of all URLs above. + std::set<GURL> urls_; +}; + +TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) { + // Test <all_urls> for regular extensions. + scoped_refptr<Extension> extension = LoadManifestStrict("script_and_capture", + "extension_regular_all.json"); + + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Allowed(extension.get(), https_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(CaptureOnly(extension.get(), favicon_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), extension_url)); + + // Test access to iframed content. + GURL within_extension_url = extension->GetResourceURL("page.html"); + EXPECT_TRUE(AllowedScript(extension.get(), http_url, http_url_with_path)); + EXPECT_TRUE(AllowedScript(extension.get(), https_url, http_url_with_path)); + EXPECT_TRUE(AllowedScript(extension.get(), http_url, within_extension_url)); + EXPECT_TRUE(AllowedScript(extension.get(), https_url, within_extension_url)); + EXPECT_TRUE(BlockedScript(extension.get(), http_url, extension_url)); + EXPECT_TRUE(BlockedScript(extension.get(), https_url, extension_url)); + + EXPECT_FALSE( + PermissionsData::HasHostPermission(extension.get(), settings_url)); + EXPECT_FALSE(PermissionsData::HasHostPermission(extension.get(), about_url)); + EXPECT_TRUE(PermissionsData::HasHostPermission(extension.get(), favicon_url)); + + // Test * for scheme, which implies just the http/https schemes. + extension = LoadManifestStrict("script_and_capture", + "extension_wildcard.json"); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Allowed(extension.get(), https_url)); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + extension = + LoadManifest("script_and_capture", "extension_wildcard_settings.json"); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + + // Having chrome://*/ should not work for regular extensions. Note that + // for favicon access, we require the explicit pattern chrome://favicon/*. + std::string error; + extension = LoadManifestUnchecked("script_and_capture", + "extension_wildcard_chrome.json", + Manifest::INTERNAL, Extension::NO_FLAGS, + &error); + std::vector<InstallWarning> warnings = extension->install_warnings(); + EXPECT_FALSE(warnings.empty()); + EXPECT_EQ(ErrorUtils::FormatErrorMessage( + manifest_errors::kInvalidPermissionScheme, + "chrome://*/"), + warnings[0].message); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + + // Having chrome://favicon/* should not give you chrome://* + extension = LoadManifestStrict("script_and_capture", + "extension_chrome_favicon_wildcard.json"); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(CaptureOnly(extension.get(), favicon_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(PermissionsData::HasHostPermission(extension.get(), favicon_url)); + + // Having http://favicon should not give you chrome://favicon + extension = LoadManifestStrict("script_and_capture", + "extension_http_favicon.json"); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + + // Component extensions with <all_urls> should get everything. + extension = LoadManifest("script_and_capture", "extension_component_all.json", + Manifest::COMPONENT, Extension::NO_FLAGS); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Allowed(extension.get(), https_url)); + EXPECT_TRUE(Allowed(extension.get(), settings_url)); + EXPECT_TRUE(Allowed(extension.get(), about_url)); + EXPECT_TRUE(Allowed(extension.get(), favicon_url)); + EXPECT_TRUE(PermissionsData::HasHostPermission(extension.get(), favicon_url)); + + // Component extensions should only get access to what they ask for. + extension = LoadManifest("script_and_capture", + "extension_component_google.json", Manifest::COMPONENT, + Extension::NO_FLAGS); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Blocked(extension.get(), https_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), extension_url)); + EXPECT_FALSE( + PermissionsData::HasHostPermission(extension.get(), settings_url)); +} + +TEST_F(ExtensionScriptAndCaptureVisibleTest, PermissionsWithChromeURLsEnabled) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kExtensionsOnChromeURLs); + + scoped_refptr<Extension> extension; + + // Test <all_urls> for regular extensions. + extension = LoadManifestStrict("script_and_capture", + "extension_regular_all.json"); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Allowed(extension.get(), https_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Allowed(extension.get(), favicon_url)); // chrome:// requested + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), extension_url)); + + // Test access to iframed content. + GURL within_extension_url = extension->GetResourceURL("page.html"); + EXPECT_TRUE(AllowedScript(extension.get(), http_url, http_url_with_path)); + EXPECT_TRUE(AllowedScript(extension.get(), https_url, http_url_with_path)); + EXPECT_TRUE(AllowedScript(extension.get(), http_url, within_extension_url)); + EXPECT_TRUE(AllowedScript(extension.get(), https_url, within_extension_url)); + EXPECT_TRUE(BlockedScript(extension.get(), http_url, extension_url)); + EXPECT_TRUE(BlockedScript(extension.get(), https_url, extension_url)); + + EXPECT_FALSE( + PermissionsData::HasHostPermission(extension.get(), settings_url)); + EXPECT_FALSE(PermissionsData::HasHostPermission(extension.get(), about_url)); + EXPECT_TRUE(PermissionsData::HasHostPermission(extension.get(), favicon_url)); + + // Test * for scheme, which implies just the http/https schemes. + extension = LoadManifestStrict("script_and_capture", + "extension_wildcard.json"); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Allowed(extension.get(), https_url)); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + extension = + LoadManifest("script_and_capture", "extension_wildcard_settings.json"); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + + // Having chrome://*/ should work for regular extensions with the flag + // enabled. + std::string error; + extension = LoadManifestUnchecked("script_and_capture", + "extension_wildcard_chrome.json", + Manifest::INTERNAL, Extension::NO_FLAGS, + &error); + EXPECT_FALSE(extension.get() == NULL); + EXPECT_TRUE(Blocked(extension.get(), http_url)); + EXPECT_TRUE(Blocked(extension.get(), https_url)); + EXPECT_TRUE(Allowed(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Allowed(extension.get(), favicon_url)); // chrome:// requested + + // Having chrome://favicon/* should not give you chrome://* + extension = LoadManifestStrict("script_and_capture", + "extension_chrome_favicon_wildcard.json"); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Allowed(extension.get(), favicon_url)); // chrome:// requested + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(PermissionsData::HasHostPermission(extension.get(), favicon_url)); + + // Having http://favicon should not give you chrome://favicon + extension = LoadManifestStrict("script_and_capture", + "extension_http_favicon.json"); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + + // Component extensions with <all_urls> should get everything. + extension = LoadManifest("script_and_capture", "extension_component_all.json", + Manifest::COMPONENT, Extension::NO_FLAGS); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Allowed(extension.get(), https_url)); + EXPECT_TRUE(Allowed(extension.get(), settings_url)); + EXPECT_TRUE(Allowed(extension.get(), about_url)); + EXPECT_TRUE(Allowed(extension.get(), favicon_url)); + EXPECT_TRUE(PermissionsData::HasHostPermission(extension.get(), favicon_url)); + + // Component extensions should only get access to what they ask for. + extension = LoadManifest("script_and_capture", + "extension_component_google.json", Manifest::COMPONENT, + Extension::NO_FLAGS); + EXPECT_TRUE(Allowed(extension.get(), http_url)); + EXPECT_TRUE(Blocked(extension.get(), https_url)); + EXPECT_TRUE(Blocked(extension.get(), file_url)); + EXPECT_TRUE(Blocked(extension.get(), settings_url)); + EXPECT_TRUE(Blocked(extension.get(), favicon_url)); + EXPECT_TRUE(Blocked(extension.get(), about_url)); + EXPECT_TRUE(Blocked(extension.get(), extension_url)); + EXPECT_FALSE( + PermissionsData::HasHostPermission(extension.get(), settings_url)); +} + +TEST_F(ExtensionScriptAndCaptureVisibleTest, TabSpecific) { + scoped_refptr<Extension> extension = + LoadManifestStrict("script_and_capture", "tab_specific.json"); + + EXPECT_FALSE(PermissionsData::GetTabSpecificPermissions(extension.get(), 0) + .get()); + EXPECT_FALSE(PermissionsData::GetTabSpecificPermissions(extension.get(), 1) + .get()); + EXPECT_FALSE(PermissionsData::GetTabSpecificPermissions(extension.get(), 2) + .get()); + + std::set<GURL> no_urls; + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 2)); + + URLPatternSet allowed_hosts; + allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL, + http_url.spec())); + std::set<GURL> allowed_urls; + allowed_urls.insert(http_url); + // http_url_with_path() will also be allowed, because Extension should be + // considering the security origin of the URL not the URL itself, and + // http_url is in allowed_hosts. + allowed_urls.insert(http_url_with_path); + + { + scoped_refptr<PermissionSet> permissions( + new PermissionSet(APIPermissionSet(), ManifestPermissionSet(), + allowed_hosts, URLPatternSet())); + PermissionsData::UpdateTabSpecificPermissions( + extension.get(), 0, permissions); + EXPECT_EQ(permissions->explicit_hosts(), + PermissionsData::GetTabSpecificPermissions(extension.get(), 0) + ->explicit_hosts()); + } + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), allowed_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 2)); + + PermissionsData::ClearTabSpecificPermissions(extension.get(), 0); + EXPECT_FALSE(PermissionsData::GetTabSpecificPermissions(extension.get(), 0) + .get()); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 2)); + + std::set<GURL> more_allowed_urls = allowed_urls; + more_allowed_urls.insert(https_url); + URLPatternSet more_allowed_hosts = allowed_hosts; + more_allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL, + https_url.spec())); + + { + scoped_refptr<PermissionSet> permissions( + new PermissionSet(APIPermissionSet(), ManifestPermissionSet(), + allowed_hosts, URLPatternSet())); + PermissionsData::UpdateTabSpecificPermissions( + extension.get(), 0, permissions); + EXPECT_EQ(permissions->explicit_hosts(), + PermissionsData::GetTabSpecificPermissions(extension.get(), 0) + ->explicit_hosts()); + + permissions = new PermissionSet(APIPermissionSet(), + ManifestPermissionSet(), + more_allowed_hosts, + URLPatternSet()); + PermissionsData::UpdateTabSpecificPermissions( + extension.get(), 1, permissions); + EXPECT_EQ(permissions->explicit_hosts(), + PermissionsData::GetTabSpecificPermissions(extension.get(), 1) + ->explicit_hosts()); + } + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), allowed_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), more_allowed_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 2)); + + PermissionsData::ClearTabSpecificPermissions(extension.get(), 0); + EXPECT_FALSE(PermissionsData::GetTabSpecificPermissions(extension.get(), 0) + .get()); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), more_allowed_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 2)); + + PermissionsData::ClearTabSpecificPermissions(extension.get(), 1); + EXPECT_FALSE(PermissionsData::GetTabSpecificPermissions(extension.get(), 1) + .get()); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension.get(), no_urls, 2)); +} + +} // namespace extensions diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index d673ac1..16d55b5 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -36,6 +36,8 @@ 'common/event_filtering_info.h', 'common/event_matcher.cc', 'common/event_matcher.h', + 'common/extension.cc', + 'common/extension.h', 'common/extension_api.cc', 'common/extension_api.h', 'common/extension_api_stub.cc', @@ -100,6 +102,8 @@ 'common/permissions/permission_message_provider.h', 'common/permissions/permission_set.cc', 'common/permissions/permission_set.h', + 'common/permissions/permissions_data.cc', + 'common/permissions/permissions_data.h', 'common/permissions/permissions_info.cc', 'common/permissions/permissions_info.h', 'common/permissions/permissions_provider.h', |