diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-23 19:02:52 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-23 19:02:52 +0000 |
commit | 0d3e4a22b373147c6144b57fefdf4012823e9150 (patch) | |
tree | 6388b5763a9c434ba6c79daff407b0e4e11d3572 /chrome/common/extensions | |
parent | e31440ac4b5ff347768ade5e2cd6e42720234ca0 (diff) | |
download | chromium_src-0d3e4a22b373147c6144b57fefdf4012823e9150.zip chromium_src-0d3e4a22b373147c6144b57fefdf4012823e9150.tar.gz chromium_src-0d3e4a22b373147c6144b57fefdf4012823e9150.tar.bz2 |
Start refractoring extension permissions into ExtensionPermissionSet.
BUG=84507
TEST=*Extension*
Review URL: http://codereview.chromium.org/7003098
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@90244 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/extensions')
-rw-r--r-- | chrome/common/extensions/extension.cc | 598 | ||||
-rw-r--r-- | chrome/common/extensions/extension.h | 210 | ||||
-rw-r--r-- | chrome/common/extensions/extension_manifests_unittest.cc | 14 | ||||
-rw-r--r-- | chrome/common/extensions/extension_permission_set.cc | 778 | ||||
-rw-r--r-- | chrome/common/extensions/extension_permission_set.h | 389 | ||||
-rw-r--r-- | chrome/common/extensions/extension_permission_set_unittest.cc | 941 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unittest.cc | 407 | ||||
-rw-r--r-- | chrome/common/extensions/url_pattern_set.cc | 16 | ||||
-rw-r--r-- | chrome/common/extensions/url_pattern_set.h | 6 | ||||
-rw-r--r-- | chrome/common/extensions/url_pattern_set_unittest.cc | 53 |
10 files changed, 2264 insertions, 1148 deletions
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 3a47efe..e98c5f0 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -98,50 +98,6 @@ bool IsBaseCrxKey(const std::string& key) { return false; } -// Constant used to represent an undefined l10n message id. -const int kUndefinedMessageId = -1; - -// Names of API modules that do not require a permission. -const char kBrowserActionModuleName[] = "browserAction"; -const char kBrowserActionsModuleName[] = "browserActions"; -const char kDevToolsModuleName[] = "devtools"; -const char kExtensionModuleName[] = "extension"; -const char kI18NModuleName[] = "i18n"; -const char kOmniboxModuleName[] = "omnibox"; -const char kPageActionModuleName[] = "pageAction"; -const char kPageActionsModuleName[] = "pageActions"; -const char kTestModuleName[] = "test"; -const char kTypesModuleName[] = "types"; - -// Names of modules that can be used without listing it in the permissions -// section of the manifest. -const char* kNonPermissionModuleNames[] = { - kBrowserActionModuleName, - kBrowserActionsModuleName, - kDevToolsModuleName, - kExtensionModuleName, - kI18NModuleName, - kOmniboxModuleName, - kPageActionModuleName, - kPageActionsModuleName, - kTestModuleName, - kTypesModuleName -}; -const size_t kNumNonPermissionModuleNames = - arraysize(kNonPermissionModuleNames); - -// Names of functions (within modules requiring permissions) that can be used -// without asking for the module permission. In other words, functions you can -// use with no permissions specified. -const char* kNonPermissionFunctionNames[] = { - "tabs.create", - "tabs.onRemoved", - "tabs.remove", - "tabs.update", -}; -const size_t kNumNonPermissionFunctionNames = - arraysize(kNonPermissionFunctionNames); - // A singleton object containing global data needed by the extension objects. class ExtensionConfig { public: @@ -149,25 +105,14 @@ class ExtensionConfig { return Singleton<ExtensionConfig>::get(); } - Extension::PermissionMessage::MessageId GetPermissionMessageId( - const std::string& permission) { - return Extension::kPermissions[permission_map_[permission]].message_id; - } - Extension::ScriptingWhitelist* whitelist() { return &scripting_whitelist_; } private: friend struct DefaultSingletonTraits<ExtensionConfig>; - ExtensionConfig() { - for (size_t i = 0; i < Extension::kNumPermissions; ++i) - permission_map_[Extension::kPermissions[i].name] = i; - }; - + ExtensionConfig() { } ~ExtensionConfig() { } - std::map<const std::string, size_t> permission_map_; - // A whitelist of extensions that can script anywhere. Do not add to this // list (except in tests) without consulting the Extensions team first. // Note: Component extensions have this right implicitly and do not need to be @@ -175,10 +120,6 @@ class ExtensionConfig { Extension::ScriptingWhitelist scripting_whitelist_; }; -// Aliased to kTabPermission for purposes of API checks, but not allowed -// in the permissions field of the manifest. -static const char kWindowPermission[] = "windows"; - // Rank extension locations in a way that allows // Extension::GetHigherPriorityLocation() to compare locations. // An extension installed from two locations will have the location @@ -264,90 +205,6 @@ const int Extension::kPageActionIconMaxSize = 19; const int Extension::kBrowserActionIconMaxSize = 19; const int Extension::kSidebarIconMaxSize = 16; -// Explicit permissions -- permission declaration required. -const char Extension::kBackgroundPermission[] = "background"; -const char Extension::kBookmarkPermission[] = "bookmarks"; -const char Extension::kClipboardReadPermission[] = "clipboardRead"; -const char Extension::kClipboardWritePermission[] = "clipboardWrite"; -const char Extension::kContextMenusPermission[] = "contextMenus"; -const char Extension::kContentSettingsPermission[] = "contentSettings"; -const char Extension::kCookiePermission[] = "cookies"; -const char Extension::kChromePrivatePermission[] = "chromePrivate"; -const char Extension::kChromeosInfoPrivatePermission[] = "chromeosInfoPrivate"; -const char Extension::kDebuggerPermission[] = "debugger"; -const char Extension::kExperimentalPermission[] = "experimental"; -const char Extension::kFileBrowserHandlerPermission[] = "fileBrowserHandler"; -const char Extension::kFileBrowserPrivatePermission[] = "fileBrowserPrivate"; -const char Extension::kGeolocationPermission[] = "geolocation"; -const char Extension::kHistoryPermission[] = "history"; -const char Extension::kIdlePermission[] = "idle"; -const char Extension::kManagementPermission[] = "management"; -const char Extension::kMediaPlayerPrivatePermission[] = "mediaPlayerPrivate"; -const char Extension::kNotificationPermission[] = "notifications"; -const char Extension::kProxyPermission[] = "proxy"; -const char Extension::kTabPermission[] = "tabs"; -const char Extension::kUnlimitedStoragePermission[] = "unlimitedStorage"; -const char Extension::kWebstorePrivatePermission[] = "webstorePrivate"; -const char Extension::kWebSocketProxyPrivatePermission[] = - "webSocketProxyPrivate"; - -// In general, all permissions should have an install message. -// See ExtensionsTest.PermissionMessages for an explanation of each -// exception. -const Extension::Permission Extension::kPermissions[] = { - { kBackgroundPermission, PermissionMessage::ID_NONE }, - { kBookmarkPermission, PermissionMessage::ID_BOOKMARKS }, - { kChromePrivatePermission, PermissionMessage::ID_NONE }, - { kChromeosInfoPrivatePermission, PermissionMessage::ID_NONE }, - { kClipboardReadPermission, PermissionMessage::ID_CLIPBOARD }, - { kClipboardWritePermission, PermissionMessage::ID_NONE }, - { kContentSettingsPermission, PermissionMessage::ID_NONE }, - { kContextMenusPermission, PermissionMessage::ID_NONE }, - { kCookiePermission, PermissionMessage::ID_NONE }, - { kDebuggerPermission, PermissionMessage::ID_DEBUGGER }, - { kExperimentalPermission, PermissionMessage::ID_NONE }, - { kFileBrowserHandlerPermission, PermissionMessage::ID_NONE }, - { kFileBrowserPrivatePermission, PermissionMessage::ID_NONE }, - { kGeolocationPermission, PermissionMessage::ID_GEOLOCATION }, - { kHistoryPermission, PermissionMessage::ID_BROWSING_HISTORY }, - { kIdlePermission, PermissionMessage::ID_NONE }, - { kManagementPermission, PermissionMessage::ID_MANAGEMENT }, - { kMediaPlayerPrivatePermission, PermissionMessage::ID_NONE }, - { kNotificationPermission, PermissionMessage::ID_NONE }, - { kProxyPermission, PermissionMessage::ID_NONE }, - { kTabPermission, PermissionMessage::ID_TABS }, - { kUnlimitedStoragePermission, PermissionMessage::ID_NONE }, - { kWebSocketProxyPrivatePermission, PermissionMessage::ID_NONE }, - { kWebstorePrivatePermission, PermissionMessage::ID_NONE }, -}; -const size_t Extension::kNumPermissions = arraysize(Extension::kPermissions); - -const char* const Extension::kHostedAppPermissionNames[] = { - Extension::kBackgroundPermission, - Extension::kChromePrivatePermission, - Extension::kClipboardReadPermission, - Extension::kClipboardWritePermission, - Extension::kExperimentalPermission, - Extension::kGeolocationPermission, - Extension::kNotificationPermission, - Extension::kUnlimitedStoragePermission, - Extension::kWebstorePrivatePermission, -}; -const size_t Extension::kNumHostedAppPermissions = - arraysize(Extension::kHostedAppPermissionNames); - -const char* const Extension::kComponentPrivatePermissionNames[] = { - Extension::kFileBrowserPrivatePermission, - Extension::kWebstorePrivatePermission, - Extension::kMediaPlayerPrivatePermission, - Extension::kChromeosInfoPrivatePermission, -}; -const size_t Extension::kNumComponentPrivatePermissions = - arraysize(Extension::kComponentPrivatePermissionNames); - -// We purposefully don't put this into kPermissionNames. -const char Extension::kOldUnlimitedStoragePermission[] = "unlimited_storage"; - const int Extension::kValidWebExtentSchemes = URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS; @@ -364,84 +221,6 @@ Extension::InputComponentInfo::InputComponentInfo() Extension::InputComponentInfo::~InputComponentInfo() {} // -// PermissionMessage -// - -// static -Extension::PermissionMessage Extension::PermissionMessage::CreateFromMessageId( - Extension::PermissionMessage::MessageId message_id) { - DCHECK_GT(PermissionMessage::ID_NONE, PermissionMessage::ID_UNKNOWN); - if (message_id <= ID_NONE) - return PermissionMessage(message_id, string16()); - - string16 message = l10n_util::GetStringUTF16(kMessageIds[message_id]); - return PermissionMessage(message_id, message); -} - -// static -Extension::PermissionMessage Extension::PermissionMessage::CreateFromHostList( - const std::vector<std::string>& hosts) { - CHECK(hosts.size() > 0); - - MessageId message_id; - string16 message; - switch (hosts.size()) { - case 1: - message_id = ID_HOSTS_1; - message = l10n_util::GetStringFUTF16(kMessageIds[message_id], - UTF8ToUTF16(hosts[0])); - break; - case 2: - message_id = ID_HOSTS_2; - message = l10n_util::GetStringFUTF16(kMessageIds[message_id], - UTF8ToUTF16(hosts[0]), - UTF8ToUTF16(hosts[1])); - break; - case 3: - message_id = ID_HOSTS_3; - message = l10n_util::GetStringFUTF16(kMessageIds[message_id], - UTF8ToUTF16(hosts[0]), - UTF8ToUTF16(hosts[1]), - UTF8ToUTF16(hosts[2])); - break; - default: - message_id = ID_HOSTS_4_OR_MORE; - message = l10n_util::GetStringFUTF16( - kMessageIds[message_id], - UTF8ToUTF16(hosts[0]), - UTF8ToUTF16(hosts[1]), - base::IntToString16(hosts.size() - 2)); - break; - } - - return PermissionMessage(message_id, message); -} - -Extension::PermissionMessage::PermissionMessage( - Extension::PermissionMessage::MessageId message_id, string16 message) - : message_id_(message_id), - message_(message) { -} - -const int Extension::PermissionMessage::kMessageIds[] = { - kUndefinedMessageId, // "unknown" - kUndefinedMessageId, // "none" - IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS, - IDS_EXTENSION_PROMPT_WARNING_GEOLOCATION, - IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY, - IDS_EXTENSION_PROMPT_WARNING_TABS, - IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT, - IDS_EXTENSION_PROMPT_WARNING_DEBUGGER, - IDS_EXTENSION_PROMPT_WARNING_1_HOST, - IDS_EXTENSION_PROMPT_WARNING_2_HOSTS, - IDS_EXTENSION_PROMPT_WARNING_3_HOSTS, - IDS_EXTENSION_PROMPT_WARNING_4_OR_MORE_HOSTS, - IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS, - IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS, - IDS_EXTENSION_PROMPT_WARNING_CLIPBOARD -}; - -// // Extension // @@ -491,145 +270,12 @@ Extension::Location Extension::GetHigherPriorityLocation( return (loc1_rank > loc2_rank ? loc1 : loc2 ); } -// static -Extension::PermissionMessage::MessageId Extension::GetPermissionMessageId( - const std::string& permission) { - return ExtensionConfig::GetInstance()->GetPermissionMessageId(permission); -} - -Extension::PermissionMessages Extension::GetPermissionMessages() const { - PermissionMessages messages; - if (!plugins().empty()) { - messages.push_back(PermissionMessage::CreateFromMessageId( - PermissionMessage::ID_FULL_ACCESS)); - return messages; - } - - if (HasEffectiveAccessToAllHosts()) { - messages.push_back(PermissionMessage::CreateFromMessageId( - PermissionMessage::ID_HOSTS_ALL)); - } else { - std::vector<std::string> hosts = GetDistinctHostsForDisplay( - GetEffectiveHostPermissions().patterns()); - if (!hosts.empty()) - messages.push_back(PermissionMessage::CreateFromHostList(hosts)); - } - - std::set<PermissionMessage> simple_msgs = GetSimplePermissionMessages(); - messages.insert(messages.end(), simple_msgs.begin(), simple_msgs.end()); - - return messages; +ExtensionPermissionMessages Extension::GetPermissionMessages() const { + return permission_set_->GetPermissionMessages(); } std::vector<string16> Extension::GetPermissionMessageStrings() const { - std::vector<string16> messages; - PermissionMessages permissions = GetPermissionMessages(); - for (PermissionMessages::const_iterator i = permissions.begin(); - i != permissions.end(); ++i) - messages.push_back(i->message()); - return messages; -} - -std::set<Extension::PermissionMessage> - Extension::GetSimplePermissionMessages() const { - std::set<PermissionMessage> messages; - std::set<std::string>::const_iterator i; - for (i = api_permissions().begin(); i != api_permissions().end(); ++i) { - PermissionMessage::MessageId message_id = GetPermissionMessageId(*i); - DCHECK_GT(PermissionMessage::ID_NONE, PermissionMessage::ID_UNKNOWN); - if (message_id > PermissionMessage::ID_NONE) - messages.insert(PermissionMessage::CreateFromMessageId(message_id)); - } - return messages; -} - -// static -std::vector<std::string> Extension::GetDistinctHostsForDisplay( - const URLPatternList& list) { - return GetDistinctHosts(list, true); -} - -// static -bool Extension::IsElevatedHostList( - const URLPatternList& old_list, const URLPatternList& new_list) { - // TODO(jstritar): This is overly conservative with respect to subdomains. - // For example, going from *.google.com to www.google.com will be - // considered an elevation, even though it is not (http://crbug.com/65337). - - std::vector<std::string> new_hosts = GetDistinctHosts(new_list, false); - std::vector<std::string> old_hosts = GetDistinctHosts(old_list, false); - - std::set<std::string> old_hosts_set(old_hosts.begin(), old_hosts.end()); - std::set<std::string> new_hosts_set(new_hosts.begin(), new_hosts.end()); - std::set<std::string> new_hosts_only; - - std::set_difference(new_hosts_set.begin(), new_hosts_set.end(), - old_hosts_set.begin(), old_hosts_set.end(), - std::inserter(new_hosts_only, new_hosts_only.begin())); - - return !new_hosts_only.empty(); -} - -// Helper for GetDistinctHosts(): com > net > org > everything else. -static bool RcdBetterThan(const std::string& a, const std::string& b) { - if (a == b) - return false; - if (a == "com") - return true; - if (a == "net") - return b != "com"; - if (a == "org") - return b != "com" && b != "net"; - return false; -} - -// static -std::vector<std::string> Extension::GetDistinctHosts( - const URLPatternList& host_patterns, - bool include_rcd) { - // Use a vector to preserve order (also faster than a map on small sets). - // Each item is a host split into two parts: host without RCDs and - // current best RCD. - typedef std::vector<std::pair<std::string, std::string> > HostVector; - HostVector hosts_best_rcd; - for (size_t i = 0; i < host_patterns.size(); ++i) { - std::string host = host_patterns[i].host(); - - // Add the subdomain wildcard back to the host, if necessary. - if (host_patterns[i].match_subdomains()) - host = "*." + host; - - // If the host has an RCD, split it off so we can detect duplicates. - std::string rcd; - size_t reg_len = net::RegistryControlledDomainService::GetRegistryLength( - host, false); - if (reg_len && reg_len != std::string::npos) { - if (include_rcd) // else leave rcd empty - rcd = host.substr(host.size() - reg_len); - host = host.substr(0, host.size() - reg_len); - } - - // Check if we've already seen this host. - HostVector::iterator it = hosts_best_rcd.begin(); - for (; it != hosts_best_rcd.end(); ++it) { - if (it->first == host) - break; - } - // If this host was found, replace the RCD if this one is better. - if (it != hosts_best_rcd.end()) { - if (include_rcd && RcdBetterThan(rcd, it->second)) - it->second = rcd; - } else { // Previously unseen host, append it. - hosts_best_rcd.push_back(std::make_pair(host, rcd)); - } - } - - // Build up the final vector by concatenating hosts and RCDs. - std::vector<std::string> distinct_hosts; - for (HostVector::iterator it = hosts_best_rcd.begin(); - it != hosts_best_rcd.end(); ++it) - distinct_hosts.push_back(it->first + it->second); - return distinct_hosts; + return permission_set_->GetWarningMessages(); } FilePath Extension::MaybeNormalizePath(const FilePath& path) { @@ -648,16 +294,6 @@ FilePath Extension::MaybeNormalizePath(const FilePath& path) { #endif } -// static -bool Extension::IsHostedAppPermission(const std::string& str) { - for (size_t i = 0; i < Extension::kNumHostedAppPermissions; ++i) { - if (str == Extension::kHostedAppPermissionNames[i]) { - return true; - } - } - return false; -} - const std::string Extension::VersionString() const { return version()->GetString(); } @@ -1624,55 +1260,6 @@ bool Extension::FormatPEMForFileOutput(const std::string& input, } // static -bool Extension::IsPrivilegeIncrease(const bool granted_full_access, - const std::set<std::string>& granted_apis, - const URLPatternSet& granted_extent, - const Extension* new_extension) { - // If the extension had native code access, we don't need to go any further. - // Things can't get any worse. - if (granted_full_access) - return false; - - // Otherwise, if the new extension has a plugin, it's a privilege increase. - if (new_extension->HasFullPermissions()) - return true; - - // If the extension hadn't been granted access to all hosts in the past, then - // see if the extension requires more host permissions. - if (!HasEffectiveAccessToAllHosts(granted_extent, granted_apis)) { - if (new_extension->HasEffectiveAccessToAllHosts()) - return true; - - const URLPatternSet new_extent = - new_extension->GetEffectiveHostPermissions(); - - if (IsElevatedHostList(granted_extent.patterns(), new_extent.patterns())) - return true; - } - - std::set<std::string> new_apis = new_extension->api_permissions(); - std::set<std::string> new_apis_only; - std::set_difference(new_apis.begin(), new_apis.end(), - granted_apis.begin(), granted_apis.end(), - std::inserter(new_apis_only, new_apis_only.begin())); - - // Ignore API permissions that don't require user approval when deciding if - // an extension has increased its privileges. - size_t new_api_count = 0; - for (std::set<std::string>::iterator i = new_apis_only.begin(); - i != new_apis_only.end(); ++i) { - DCHECK_GT(PermissionMessage::ID_NONE, PermissionMessage::ID_UNKNOWN); - if (GetPermissionMessageId(*i) > PermissionMessage::ID_NONE) - new_api_count++; - } - - if (new_api_count) - return true; - - return false; -} - -// static void Extension::DecodeIcon(const Extension* extension, Icons icon_size, scoped_ptr<SkBitmap>* result) { @@ -1739,6 +1326,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, (flags & STRICT_ERROR_CHECKS ? URLPattern::PARSE_STRICT : URLPattern::PARSE_LENIENT); + // Initialize permissions with an empty, default permission set. + permission_set_.reset(new ExtensionPermissionSet()); + if (source.HasKey(keys::kPublicKey)) { std::string public_key_bytes; if (!source.GetString(keys::kPublicKey, @@ -2257,6 +1847,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } } + ExtensionAPIPermissionSet api_permissions; + URLPatternSet host_permissions; + // Initialize the permissions (optional). if (source.HasKey(keys::kPermissions)) { ListValue* permissions = NULL; @@ -2274,10 +1867,13 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, return false; } + ExtensionAPIPermission* permission = + ExtensionPermissionsInfo::GetInstance()->GetByName(permission_str); + // Only COMPONENT extensions can use private APIs. // TODO(asargent) - We want a more general purpose mechanism for this, // and better error messages. (http://crbug.com/54013) - if (!IsComponentOnlyPermission(permission_str) + if (!IsComponentOnlyPermission(permission) #ifndef NDEBUG && !CommandLine::ForCurrentProcess()->HasSwitch( switches::kExposePrivateExtensionApi) @@ -2286,31 +1882,27 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, continue; } - // Remap the old unlimited storage permission name. - if (permission_str == kOldUnlimitedStoragePermission) - permission_str = kUnlimitedStoragePermission; - if (web_extent().is_empty() || location() == Extension::COMPONENT) { // Check if it's a module permission. If so, enable that permission. - if (IsAPIPermission(permission_str)) { + if (permission != NULL) { // Only allow the experimental API permission if the command line // flag is present, or if the extension is a component of Chrome. - if (IsDisallowedExperimentalPermission(permission_str) && + if (IsDisallowedExperimentalPermission(permission->id()) && location() != Extension::COMPONENT) { *error = errors::kExperimentalFlagRequired; return false; } - api_permissions_.insert(permission_str); + api_permissions.insert(permission->id()); continue; } } else { // Hosted apps only get access to a subset of the valid permissions. - if (IsHostedAppPermission(permission_str)) { - if (IsDisallowedExperimentalPermission(permission_str)) { + if (permission != NULL && permission->is_hosted_app()) { + if (IsDisallowedExperimentalPermission(permission->id())) { *error = errors::kExperimentalFlagRequired; return false; } - api_permissions_.insert(permission_str); + api_permissions.insert(permission->id()); continue; } } @@ -2340,7 +1932,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, pattern.valid_schemes() & ~URLPattern::SCHEME_FILE); } - host_permissions_.push_back(pattern); + host_permissions.AddPattern(pattern); } // If it's not a host permission, then it's probably an unknown API @@ -2365,8 +1957,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, if (is_hosted_app()) { // Make sure "background" permission is set. - if (api_permissions_.find(kBackgroundPermission) == - api_permissions_.end()) { + if (!api_permissions.count(ExtensionAPIPermission::kBackground)) { *error = errors::kBackgroundPermissionNeeded; return false; } @@ -2605,7 +2196,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, *error = errors::kInvalidDevToolsPage; return false; } - if (!HasApiPermission(Extension::kExperimentalPermission)) { + if (!api_permissions.count(ExtensionAPIPermission::kExperimental)) { *error = errors::kDevToolsExperimental; return false; } @@ -2619,7 +2210,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, *error = errors::kInvalidSidebar; return false; } - if (!HasApiPermission(Extension::kExperimentalPermission)) { + if (!api_permissions.count(ExtensionAPIPermission::kExperimental)) { *error = errors::kSidebarExperimental; return false; } @@ -2705,7 +2296,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, return false; } - InitEffectiveHostPermissions(); + permission_set_.reset( + new ExtensionPermissionSet(this, api_permissions, host_permissions)); // Although |source| is passed in as a const, it's still possible to modify // it. This is dangerous since the utility process re-uses |source| after @@ -2896,91 +2488,37 @@ bool Extension::CanSpecifyHostPermission(const URLPattern& pattern) const { return true; } -// static -bool Extension::HasApiPermission( - const std::set<std::string>& api_permissions, - const std::string& function_name) { - std::string permission_name = function_name; - - for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) { - if (permission_name == kNonPermissionFunctionNames[i]) - return true; - } - - // See if this is a function or event name first and strip out the package. - // Functions will be of the form package.function - // Events will be of the form package/id or package.optional.stuff - size_t separator = function_name.find_first_of("./"); - if (separator != std::string::npos) - permission_name = function_name.substr(0, separator); - - // windows and tabs are the same permission. - if (permission_name == kWindowPermission) - permission_name = Extension::kTabPermission; - - if (api_permissions.count(permission_name)) - return true; - - for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i) { - if (permission_name == kNonPermissionModuleNames[i]) { - return true; - } - } - - return false; +bool Extension::HasAPIPermission( + ExtensionAPIPermission::ID permission) const { + return permission_set()->HasAPIPermission(permission); } -bool Extension::HasHostPermission(const GURL& url) const { - for (URLPatternList::const_iterator host = host_permissions().begin(); - host != host_permissions().end(); ++host) { - // Non-component extensions can only access chrome://favicon and no other - // chrome:// scheme urls. - if (url.SchemeIs(chrome::kChromeUIScheme) && - url.host() != chrome::kChromeUIFaviconHost && - location() != Extension::COMPONENT) - return false; - - if (host->MatchesURL(url)) - return true; - } - return false; +bool Extension::HasAPIPermission( + const std::string& function_name) const { + return permission_set()->HasAccessToFunction(function_name); } -void Extension::InitEffectiveHostPermissions() { - // Some APIs effectively grant access to every site. New ones should be - // added here. (I'm looking at you, network API) - if (HasApiPermission(api_permissions_, kProxyPermission) || - !devtools_url_.is_empty()) { - URLPattern all_urls(URLPattern::SCHEME_ALL); - all_urls.set_match_all_urls(true); - effective_host_permissions_.AddPattern(all_urls); - return; - } - - for (URLPatternList::const_iterator host = host_permissions().begin(); - host != host_permissions().end(); ++host) - effective_host_permissions_.AddPattern(*host); +const URLPatternSet& Extension::GetEffectiveHostPermissions() const { + return permission_set()->effective_hosts(); +} - for (UserScriptList::const_iterator content_script = - content_scripts().begin(); - content_script != content_scripts().end(); ++content_script) { - URLPatternList::const_iterator pattern = - content_script->url_patterns().begin(); - for (; pattern != content_script->url_patterns().end(); ++pattern) - effective_host_permissions_.AddPattern(*pattern); - } +bool Extension::HasHostPermission(const GURL& url) const { + if (url.SchemeIs(chrome::kChromeUIScheme) && + url.host() != chrome::kChromeUIFaviconHost && + location() != Extension::COMPONENT) + return false; + return permission_set()->HasExplicitAccessToOrigin(url); } -bool Extension::IsComponentOnlyPermission(const std::string& permission) const { +bool Extension::IsComponentOnlyPermission( + const ExtensionAPIPermission* api) const { if (location() == Extension::COMPONENT) return true; - // Non-component extensions are not allowed to access private apis. - for (size_t i = 0; i < Extension::kNumComponentPrivatePermissions; ++i) { - if (permission == Extension::kComponentPrivatePermissionNames[i]) - return false; - } - return true; + if (api == NULL) + return true; + + return !api->is_component_only(); } bool Extension::HasMultipleUISurfaces() const { @@ -3025,10 +2563,8 @@ bool Extension::CanExecuteScriptOnPage(const GURL& page_url, // Otherwise, see if this extension has permission to execute script // programmatically on pages. - for (size_t i = 0; i < host_permissions_.size(); ++i) { - if (host_permissions_[i].MatchesURL(page_url)) - return true; - } + if (permission_set()->HasExplicitAccessToOrigin(page_url)) + return true; if (error) { *error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage, @@ -3038,28 +2574,12 @@ bool Extension::CanExecuteScriptOnPage(const GURL& page_url, return false; } -// static -bool Extension::HasEffectiveAccessToAllHosts( - const URLPatternSet& effective_host_permissions, - const std::set<std::string>& api_permissions) { - const URLPatternList patterns = effective_host_permissions.patterns(); - for (URLPatternList::const_iterator host = patterns.begin(); - host != patterns.end(); ++host) { - if (host->match_all_urls() || - (host->match_subdomains() && host->host().empty())) - return true; - } - - return false; -} - bool Extension::HasEffectiveAccessToAllHosts() const { - return HasEffectiveAccessToAllHosts(GetEffectiveHostPermissions(), - api_permissions()); + return permission_set_->HasEffectiveAccessToAllHosts(); } bool Extension::HasFullPermissions() const { - return !plugins().empty(); + return permission_set_->HasEffectiveFullAccess(); } bool Extension::ShowConfigureContextMenus() const { @@ -3071,21 +2591,12 @@ bool Extension::ShowConfigureContextMenus() const { } bool Extension::IsDisallowedExperimentalPermission( - const std::string& permission_str) const { - return permission_str == Extension::kExperimentalPermission && + ExtensionAPIPermission::ID permission) const { + return permission == ExtensionAPIPermission::kExperimental && !CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableExperimentalExtensionApis); } -bool Extension::IsAPIPermission(const std::string& str) const { - for (size_t i = 0; i < Extension::kNumPermissions; ++i) { - if (str == Extension::kPermissions[i].name) { - return true; - } - } - return false; -} - bool Extension::CanExecuteScriptEverywhere() const { if (location() == Extension::COMPONENT #ifndef NDEBUG @@ -3161,7 +2672,8 @@ ExtensionInfo::~ExtensionInfo() {} UninstalledExtensionInfo::UninstalledExtensionInfo( const Extension& extension) : extension_id(extension.id()), - extension_api_permissions(extension.api_permissions()), + extension_api_permissions( + extension.permission_set()->GetAPIsAsStrings()), extension_type(extension.GetType()), update_url(extension.update_url()) {} diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 6624268..7e584d7 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -18,6 +18,7 @@ #include "base/memory/scoped_ptr.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_icon_set.h" +#include "chrome/common/extensions/extension_permission_set.h" #include "chrome/common/extensions/user_script.h" #include "chrome/common/extensions/url_pattern.h" #include "chrome/common/extensions/url_pattern_set.h" @@ -142,80 +143,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { std::string gender; }; - // When prompting the user to install or approve permissions, we display - // messages describing the effects of the permissions and not the permissions - // themselves. Each PermissionMessage represents one of the messages that is - // shown to the user. - class PermissionMessage { - public: - // Do not reorder or add new enumerations in this list. If you need to add a - // new enum, add it just prior to ID_ENUM_BOUNDARY and enter its l10n - // message in kMessageIds. - enum MessageId { - ID_UNKNOWN, - ID_NONE, - ID_BOOKMARKS, - ID_GEOLOCATION, - ID_BROWSING_HISTORY, - ID_TABS, - ID_MANAGEMENT, - ID_DEBUGGER, - ID_HOSTS_1, - ID_HOSTS_2, - ID_HOSTS_3, - ID_HOSTS_4_OR_MORE, - ID_HOSTS_ALL, - ID_FULL_ACCESS, - ID_CLIPBOARD, - ID_ENUM_BOUNDARY - }; - - // Creates a permission message with the given |message_id| and initializes - // its message to the appropriate value. - static PermissionMessage CreateFromMessageId(MessageId message_id); - - // Creates the corresponding permission message for a list of hosts. This - // method exists because the hosts are presented as one message that depends - // on what and how many hosts there are. - static PermissionMessage CreateFromHostList( - const std::vector<std::string>& hosts); - - // Gets the id of the permission message, which can be used in UMA - // histograms. - MessageId message_id() const { return message_id_; } - - // Gets a localized message describing this permission. Please note that - // the message will be empty for message types TYPE_NONE and TYPE_UNKNOWN. - const string16& message() const { return message_; } - - // Comparator to work with std::set. - bool operator<(const PermissionMessage& that) const { - return message_id_ < that.message_id_; - } - - private: - PermissionMessage(MessageId message_id, string16 message_); - - // The index of the id in the array is its enum value. The first two values - // are non-existent message ids to act as placeholders for "unknown" and - // "none". - // Note: Do not change the order of the items in this list since they - // are used in a histogram. The order must match the MessageId order. - static const int kMessageIds[]; - - MessageId message_id_; - string16 message_; - }; - - typedef std::vector<PermissionMessage> PermissionMessages; - - // A permission is defined by its |name| (what is used in the manifest), - // and the |message_id| that's used by install/update UI. - struct Permission { - const char* const name; - const PermissionMessage::MessageId message_id; - }; - enum InitFromValueFlags { NO_FLAGS = 0, @@ -256,38 +183,15 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // its install source should be set to GetHigherPriorityLocation(A, B). static Location GetHigherPriorityLocation(Location loc1, Location loc2); - // Get's the install message id for |permission|. Returns - // MessageId::TYPE_NONE if none exists. - static PermissionMessage::MessageId GetPermissionMessageId( - const std::string& permission); - // Returns the full list of permission messages that this extension // should display at install time. - PermissionMessages GetPermissionMessages() const; + ExtensionPermissionMessages GetPermissionMessages() const; // Returns the full list of permission messages that this extension // should display at install time. The messages are returned as strings // for convenience. std::vector<string16> GetPermissionMessageStrings() const; - // Returns the distinct hosts that should be displayed in the install UI - // for the URL patterns |list|. This discards some of the detail that is - // present in the manifest to make it as easy as possible to process by - // users. In particular we disregard the scheme and path components of - // URLPatterns and de-dupe the result, which includes filtering out common - // hosts with differing RCDs (aka Registry Controlled Domains, most of which - // are Top Level Domains but also include exceptions like co.uk). - // NOTE: when de-duping hosts the preferred RCD will be returned, given this - // order of preference: .com, .net, .org, first in list. - static std::vector<std::string> GetDistinctHostsForDisplay( - const URLPatternList& list); - - // Compares two URLPatternLists for security equality by returning whether - // the URL patterns in |new_list| contain additional distinct hosts compared - // to |old_list|. - static bool IsElevatedHostList( - const URLPatternList& old_list, const URLPatternList& new_list); - // Icon sizes used by the extension system. static const int kIconSizes[]; @@ -296,56 +200,12 @@ class Extension : public base::RefCountedThreadSafe<Extension> { static const int kBrowserActionIconMaxSize; static const int kSidebarIconMaxSize; - // Each permission is a module that the extension is permitted to use. - // - // NOTE: To add a new permission, define it here, and add an entry to - // Extension::kPermissions. - static const char kBackgroundPermission[]; - static const char kBookmarkPermission[]; - static const char kClipboardReadPermission[]; - static const char kClipboardWritePermission[]; - static const char kContentSettingsPermission[]; - static const char kContextMenusPermission[]; - static const char kCookiePermission[]; - static const char kChromePrivatePermission[]; - static const char kChromeosInfoPrivatePermission[]; - static const char kDebuggerPermission[]; - static const char kExperimentalPermission[]; - static const char kFileBrowserHandlerPermission[]; - static const char kFileBrowserPrivatePermission[]; - static const char kGeolocationPermission[]; - static const char kHistoryPermission[]; - static const char kIdlePermission[]; - static const char kManagementPermission[]; - static const char kMediaPlayerPrivatePermission[]; - static const char kNotificationPermission[]; - static const char kProxyPermission[]; - static const char kTabPermission[]; - static const char kUnlimitedStoragePermission[]; - static const char kWebstorePrivatePermission[]; - static const char kWebSocketProxyPrivatePermission[]; - - static const Permission kPermissions[]; - static const size_t kNumPermissions; - static const char* const kHostedAppPermissionNames[]; - static const size_t kNumHostedAppPermissions; - static const char* const kComponentPrivatePermissionNames[]; - static const size_t kNumComponentPrivatePermissions; - - // The old name for the unlimited storage permission, which is deprecated but - // still accepted as meaning the same thing as kUnlimitedStoragePermission. - static const char kOldUnlimitedStoragePermission[]; - // Valid schemes for web extent URLPatterns. static const int kValidWebExtentSchemes; // Valid schemes for host permission URLPatterns. static const int kValidHostPermissionSchemes; - // Returns true if the string is one of the known hosted app permissions (see - // kHostedAppPermissionNames). - static bool IsHostedAppPermission(const std::string& permission); - // The name of the manifest inside an extension. static const FilePath::CharType kManifestFilename[]; @@ -455,14 +315,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { std::string* output, bool is_public); - // Determine whether |new_extension| has increased privileges compared to - // its previously granted permissions, specified by |granted_apis|, - // |granted_extent| and |granted_full_access|. - static bool IsPrivilegeIncrease(const bool granted_full_access, - const std::set<std::string>& granted_apis, - const URLPatternSet& granted_extent, - const Extension* new_extension); - // Given an extension and icon size, read it if present and decode it into // result. In the browser process, this will DCHECK if not called on the // file thread. To easily load extension images on the UI thread, see @@ -497,24 +349,10 @@ class Extension : public base::RefCountedThreadSafe<Extension> { static void SetScriptingWhitelist(const ScriptingWhitelist& whitelist); static const ScriptingWhitelist* GetScriptingWhitelist(); - // Returns true if the extension has the specified API permission. - static bool HasApiPermission(const std::set<std::string>& api_permissions, - const std::string& function_name); - - // Whether the |effective_host_permissions| and |api_permissions| include - // effective access to all hosts. See the non-static version of the method - // for more details. - static bool HasEffectiveAccessToAllHosts( - const URLPatternSet& effective_host_permissions, - const std::set<std::string>& api_permissions); + bool HasAPIPermission(ExtensionAPIPermission::ID permission) const; + bool HasAPIPermission(const std::string& function_name) const; - bool HasApiPermission(const std::string& function_name) const { - return HasApiPermission(this->api_permissions(), function_name); - } - - const URLPatternSet& GetEffectiveHostPermissions() const { - return effective_host_permissions_; - } + const URLPatternSet& GetEffectiveHostPermissions() const; // Whether or not the extension is allowed permission for a URL pattern from // the manifest. http, https, and chrome://favicon/ is allowed for all @@ -630,10 +468,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> { const GURL& options_url() const { return options_url_; } const GURL& devtools_url() const { return devtools_url_; } const std::vector<GURL>& toolstrips() const { return toolstrips_; } - const std::set<std::string>& api_permissions() const { - return api_permissions_; + const ExtensionPermissionSet* permission_set() const { + return permission_set_.get(); } - const URLPatternList& host_permissions() const { return host_permissions_; } const GURL& update_url() const { return update_url_; } const ExtensionIconSet& icons() const { return icons_; } const DictionaryValue* manifest_value() const { @@ -766,10 +603,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { ExtensionSidebarDefaults* LoadExtensionSidebarDefaults( const DictionaryValue* sidebar, std::string* error); - // Calculates the effective host permissions from the permissions and content - // script petterns. - void InitEffectiveHostPermissions(); - // Returns true if the extension has more than one "UI surface". For example, // an extension that has a browser action and a page action. bool HasMultipleUISurfaces() const; @@ -780,21 +613,12 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // Only allow the experimental API permission if the command line // flag is present. - bool IsDisallowedExperimentalPermission(const std::string& permission) const; - - // Returns true if the string is one of the known api permissions (see - // kPermissions). - bool IsAPIPermission(const std::string& permission) const; + bool IsDisallowedExperimentalPermission( + ExtensionAPIPermission::ID permission) const; // Returns true if this is a component, or we are not attempting to access a // component-private permission. - bool IsComponentOnlyPermission(const std::string& permission) const; - - // The set of unique API install messages that the extension has. - // NOTE: This only includes messages related to permissions declared in the - // "permissions" key in the manifest. Permissions implied from other features - // of the manifest, like plugins and content scripts are not included. - std::set<PermissionMessage> GetSimplePermissionMessages() const; + bool IsComponentOnlyPermission(const ExtensionAPIPermission* api) const; // Cached images for this extension. This should only be touched on the UI // thread. @@ -825,15 +649,8 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // Defines the set of URLs in the extension's web content. URLPatternSet extent_; - // The set of host permissions that the extension effectively has access to, - // which is a merge of host_permissions_ and all of the match patterns in - // any content scripts the extension has. This is used to determine which - // URLs have the ability to load an extension's resources via embedded - // chrome-extension: URLs (see extension_protocols.cc). - URLPatternSet effective_host_permissions_; - - // The set of module-level APIs this extension can use. - std::set<std::string> api_permissions_; + // The set of permissions that the extension effectively has access to. + scoped_ptr<ExtensionPermissionSet> permission_set_; // The icons for the extension. ExtensionIconSet icons_; @@ -909,9 +726,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // Whether the extension is a theme. bool is_theme_; - // The sites this extension has permission to talk to (using XHR, etc). - URLPatternList host_permissions_; - // The homepage for this extension. Useful if it is not hosted by Google and // therefore does not have a Gallery URL. GURL homepage_url_; diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc index e2a2d76..7a7122a 100644 --- a/chrome/common/extensions/extension_manifests_unittest.cc +++ b/chrome/common/extensions/extension_manifests_unittest.cc @@ -296,8 +296,8 @@ TEST_F(ExtensionManifestTest, UpdateUrls) { TEST_F(ExtensionManifestTest, OldUnlimitedStoragePermission) { scoped_refptr<Extension> extension = LoadStrictAndExpectSuccess( "old_unlimited_storage.json"); - EXPECT_TRUE(extension->HasApiPermission( - Extension::kUnlimitedStoragePermission)); + EXPECT_TRUE(extension->HasAPIPermission( + ExtensionAPIPermission::kUnlimitedStorage)); } TEST_F(ExtensionManifestTest, ValidApp) { @@ -620,14 +620,18 @@ TEST_F(ExtensionManifestTest, AllowUnrecognizedPermissions) { ListValue *permissions = new ListValue(); manifest->Set(keys::kPermissions, permissions); - for (size_t i = 0; i < Extension::kNumPermissions; i++) { - const char* name = Extension::kPermissions[i].name; + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + ExtensionAPIPermissionSet api_perms = info->GetAll(); + for (ExtensionAPIPermissionSet::iterator i = api_perms.begin(); + i != api_perms.end(); ++i) { + ExtensionAPIPermission* permission = info->GetByID(*i); + const char* name = permission->name(); StringValue* p = new StringValue(name); permissions->Clear(); permissions->Append(p); std::string message_name = base::StringPrintf("permission-%s", name); - if (name == Extension::kExperimentalPermission) { + if (*i == ExtensionAPIPermission::kExperimental) { // Experimental permission is allowed, but requires this switch. CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExperimentalExtensionApis); diff --git a/chrome/common/extensions/extension_permission_set.cc b/chrome/common/extensions/extension_permission_set.cc new file mode 100644 index 0000000..d87e9e3 --- /dev/null +++ b/chrome/common/extensions/extension_permission_set.cc @@ -0,0 +1,778 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/extension_permission_set.h" + +#include <algorithm> +#include <string> + +#include "base/memory/singleton.h" +#include "base/values.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_l10n_util.h" +#include "chrome/common/extensions/url_pattern.h" +#include "chrome/common/extensions/url_pattern_set.h" +#include "grit/generated_resources.h" +#include "net/base/registry_controlled_domain.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// Helper for GetDistinctHosts(): com > net > org > everything else. +bool RcdBetterThan(std::string a, std::string b) { + if (a == b) + return false; + if (a == "com") + return true; + if (a == "net") + return b != "com"; + if (a == "org") + return b != "com" && b != "net"; + return false; +} + +// Names of API modules that do not require a permission. +const char kBrowserActionModuleName[] = "browserAction"; +const char kBrowserActionsModuleName[] = "browserActions"; +const char kDevToolsModuleName[] = "devtools"; +const char kExtensionModuleName[] = "extension"; +const char kI18NModuleName[] = "i18n"; +const char kOmniboxModuleName[] = "omnibox"; +const char kPageActionModuleName[] = "pageAction"; +const char kPageActionsModuleName[] = "pageActions"; +const char kTestModuleName[] = "test"; +const char kTypesModuleName[] = "types"; + +// Names of modules that can be used without listing it in the permissions +// section of the manifest. +const char* kNonPermissionModuleNames[] = { + kBrowserActionModuleName, + kBrowserActionsModuleName, + kDevToolsModuleName, + kExtensionModuleName, + kI18NModuleName, + kOmniboxModuleName, + kPageActionModuleName, + kPageActionsModuleName, + kTestModuleName, + kTypesModuleName +}; +const size_t kNumNonPermissionModuleNames = + arraysize(kNonPermissionModuleNames); + +// Names of functions (within modules requiring permissions) that can be used +// without asking for the module permission. In other words, functions you can +// use with no permissions specified. +const char* kNonPermissionFunctionNames[] = { + "tabs.create", + "tabs.onRemoved", + "tabs.remove", + "tabs.update", +}; +const size_t kNumNonPermissionFunctionNames = + arraysize(kNonPermissionFunctionNames); + +const char kOldUnlimitedStoragePermission[] = "unlimited_storage"; +const char kWindowsPermission[] = "windows"; + +void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) { + CHECK(out); + const URLPatternList& patterns = set.patterns(); + for (size_t i = 0; i < patterns.size(); ++i) { + URLPattern p = patterns.at(i); + p.SetPath("/*"); + out->AddPattern(p); + } +} + +} // namespace + +// +// PermissionMessage +// + +// static +ExtensionPermissionMessage ExtensionPermissionMessage::CreateFromHostList( + const std::vector<std::string>& hosts) { + CHECK(hosts.size() > 0); + ID message_id; + string16 message; + switch (hosts.size()) { + case 1: + message_id = kHosts1; + message = l10n_util::GetStringFUTF16(IDS_EXTENSION_PROMPT_WARNING_1_HOST, + UTF8ToUTF16(hosts[0])); + break; + case 2: + message_id = kHosts2; + message = l10n_util::GetStringFUTF16(IDS_EXTENSION_PROMPT_WARNING_2_HOSTS, + UTF8ToUTF16(hosts[0]), + UTF8ToUTF16(hosts[1])); + break; + case 3: + message_id = kHosts3; + message = l10n_util::GetStringFUTF16(IDS_EXTENSION_PROMPT_WARNING_3_HOSTS, + UTF8ToUTF16(hosts[0]), + UTF8ToUTF16(hosts[1]), + UTF8ToUTF16(hosts[2])); + break; + default: + message_id = kHosts4OrMore; + message = l10n_util::GetStringFUTF16( + IDS_EXTENSION_PROMPT_WARNING_4_OR_MORE_HOSTS, + UTF8ToUTF16(hosts[0]), + UTF8ToUTF16(hosts[1]), + base::IntToString16(hosts.size() - 2)); + break; + } + + return ExtensionPermissionMessage(message_id, message); +} + +ExtensionPermissionMessage::ExtensionPermissionMessage( + ExtensionPermissionMessage::ID id, const string16& message) + : id_(id), message_(message) { +} + +ExtensionPermissionMessage::~ExtensionPermissionMessage() { +} + +// +// ExtensionPermission +// + +ExtensionPermissionMessage ExtensionAPIPermission::GetMessage() const { + return ExtensionPermissionMessage( + message_id_, l10n_util::GetStringUTF16(l10n_message_id_)); +} + +ExtensionAPIPermission::ExtensionAPIPermission( + ID id, + const char* name, + bool is_hosted_app, + bool is_component_only, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id, + bool implies_full_access, + bool implies_full_url_access) + : id_(id), + name_(name), + implies_full_access_(implies_full_access), + implies_full_url_access_(implies_full_url_access), + is_hosted_app_(is_hosted_app), + is_component_only_(is_component_only), + l10n_message_id_(l10n_message_id), + message_id_(message_id) { +} + +ExtensionAPIPermission::~ExtensionAPIPermission() { +} + +// +// ExtensionPermissionsInfo +// + +// static +ExtensionPermissionsInfo* ExtensionPermissionsInfo::GetInstance() { + return Singleton<ExtensionPermissionsInfo>::get(); +} + +ExtensionAPIPermission* ExtensionPermissionsInfo::GetByID( + ExtensionAPIPermission::ID id) { + IDMap::iterator i = id_map_.find(id); + return (i == id_map_.end()) ? NULL : i->second; +} + +ExtensionAPIPermission* ExtensionPermissionsInfo::GetByName(std::string name) { + NameMap::iterator i = name_map_.find(name); + return (i == name_map_.end()) ? NULL : i->second; +} + +ExtensionAPIPermissionSet ExtensionPermissionsInfo::GetAll() { + ExtensionAPIPermissionSet permissions; + for (IDMap::const_iterator i = id_map_.begin(); i != id_map_.end(); ++i) + permissions.insert(i->second->id()); + return permissions; +} + +ExtensionAPIPermissionSet ExtensionPermissionsInfo::GetAllByName( + const std::set<std::string>& permission_names) { + ExtensionAPIPermissionSet permissions; + for (std::set<std::string>::const_iterator i = permission_names.begin(); + i != permission_names.end(); ++i) { + ExtensionAPIPermission* permission = GetByName(*i); + if (permission) + permissions.insert(permission->id()); + } + return permissions; +} + +ExtensionPermissionsInfo::~ExtensionPermissionsInfo() { + for (IDMap::iterator i = id_map_.begin(); i != id_map_.end(); ++i) + delete i->second; +} + +ExtensionPermissionsInfo::ExtensionPermissionsInfo() + : hosted_app_permission_count_(0), + permission_count_(0) { + // Hosted app permissions + RegisterHostedAppPermission( + ExtensionAPIPermission::kBackground, "background", 0, + ExtensionPermissionMessage::kNone); + RegisterHostedAppPermission( + ExtensionAPIPermission::kClipboardRead, "clipboardRead", + IDS_EXTENSION_PROMPT_WARNING_CLIPBOARD, + ExtensionPermissionMessage::kClipboard); + RegisterHostedAppPermission( + ExtensionAPIPermission::kClipboardWrite, "clipboardWrite", 0, + ExtensionPermissionMessage::kNone); + RegisterHostedAppPermission( + ExtensionAPIPermission::kChromePrivate, "chromePrivate", 0, + ExtensionPermissionMessage::kNone); + RegisterHostedAppPermission( + ExtensionAPIPermission::kExperimental, "experimental", 0, + ExtensionPermissionMessage::kNone); + RegisterHostedAppPermission( + ExtensionAPIPermission::kGeolocation, "geolocation", + IDS_EXTENSION_PROMPT_WARNING_GEOLOCATION, + ExtensionPermissionMessage::kGeolocation); + RegisterHostedAppPermission( + ExtensionAPIPermission::kNotification, "notifications", 0, + ExtensionPermissionMessage::kNone); + RegisterHostedAppPermission( + ExtensionAPIPermission::kUnlimitedStorage, "unlimitedStorage", 0, + ExtensionPermissionMessage::kNone); + + // Hosted app and private permissions. + RegisterPermission( + ExtensionAPIPermission::kWebstorePrivate, "webstorePrivate", 0, + ExtensionPermissionMessage::kNone, + true, true, false, false); + + // Extension permissions. + RegisterExtensionPermission( + ExtensionAPIPermission::kBookmark, "bookmarks", + IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS, + ExtensionPermissionMessage::kBookmarks); + RegisterExtensionPermission( + ExtensionAPIPermission::kContentSettings, "contentSettings", 0, + ExtensionPermissionMessage::kNone); + RegisterExtensionPermission( + ExtensionAPIPermission::kContextMenus, "contextMenus", 0, + ExtensionPermissionMessage::kNone); + RegisterExtensionPermission( + ExtensionAPIPermission::kCookie, "cookies", 0, + ExtensionPermissionMessage::kNone); + RegisterExtensionPermission( + ExtensionAPIPermission::kDebugger, "debugger", + IDS_EXTENSION_PROMPT_WARNING_DEBUGGER, + ExtensionPermissionMessage::kDebugger); + RegisterExtensionPermission( + ExtensionAPIPermission::kFileBrowserHandler, "fileBrowserHandler", 0, + ExtensionPermissionMessage::kNone); + RegisterExtensionPermission( + ExtensionAPIPermission::kHistory, "history", + IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY, + ExtensionPermissionMessage::kBrowsingHistory); + RegisterExtensionPermission( + ExtensionAPIPermission::kIdle, "idle", 0, + ExtensionPermissionMessage::kNone); + RegisterExtensionPermission( + ExtensionAPIPermission::kManagement, "management", + IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT, + ExtensionPermissionMessage::kManagement); + RegisterExtensionPermission( + ExtensionAPIPermission::kTab, "tabs", + IDS_EXTENSION_PROMPT_WARNING_TABS, + ExtensionPermissionMessage::kTabs); + RegisterExtensionPermission( + ExtensionAPIPermission::kWebSocketProxyPrivate, + "webSocketProxyPrivate", 0, + ExtensionPermissionMessage::kNone); + + // Private permissions + RegisterPrivatePermission( + ExtensionAPIPermission::kChromeosInfoPrivate, "chromeosInfoPrivate"); + RegisterPrivatePermission( + ExtensionAPIPermission::kFileBrowserPrivate, "fileBrowserPrivate"); + RegisterPrivatePermission( + ExtensionAPIPermission::kMediaPlayerPrivate, "mediaPlayerPrivate"); + + // Full url access permissions. + RegisterPermission( + ExtensionAPIPermission::kProxy, "proxy", 0, + ExtensionPermissionMessage::kNone, false, false, false, true); + + RegisterPermission( + ExtensionAPIPermission::kDevtools, "devtools", 0, + ExtensionPermissionMessage::kNone, false, false, false, true); + + RegisterPermission( + ExtensionAPIPermission::kPlugin, "plugin", + IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS, + ExtensionPermissionMessage::kFullAccess, false, false, true, true); + + RegisterPermission( + ExtensionAPIPermission::kDefault, "default", 0, + ExtensionPermissionMessage::kNone, false, false, false, false); + + // Register Aliases + RegisterAlias("unlimitedStorage", kOldUnlimitedStoragePermission); + RegisterAlias("tabs", kWindowsPermission); +} + +void ExtensionPermissionsInfo::RegisterAlias( + const char* name, const char* alias) { + CHECK(name_map_.find(name) != name_map_.end()); + CHECK(name_map_.find(alias) == name_map_.end()); + name_map_[alias] = name_map_[name]; +} + +void ExtensionPermissionsInfo::RegisterExtensionPermission( + ExtensionAPIPermission::ID id, + const char* name, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id) { + RegisterPermission(id, name, l10n_message_id, message_id, + false, false, false, false); +} + +void ExtensionPermissionsInfo::RegisterHostedAppPermission( + ExtensionAPIPermission::ID id, + const char* name, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id) { + RegisterPermission(id, name, l10n_message_id, message_id, + true, false, false, false); +} + +void ExtensionPermissionsInfo::RegisterPrivatePermission( + ExtensionAPIPermission::ID id, const char* name) { + RegisterPermission(id, name, 0, ExtensionPermissionMessage::kNone, + false, true, false, false); +} + +void ExtensionPermissionsInfo::RegisterPermission( + ExtensionAPIPermission::ID id, + const char* name, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id, + bool is_hosted_app, + bool is_component_only, + bool implies_full_access, + bool implies_full_url_access) { + CHECK(id_map_.find(id) == id_map_.end()); + CHECK(name_map_.find(name) == name_map_.end()); + + ExtensionAPIPermission* permission = + new ExtensionAPIPermission(id, + name, + is_hosted_app, + is_component_only, + l10n_message_id, + message_id, + implies_full_access, + implies_full_url_access); + id_map_[id] = permission; + name_map_[name] = permission; + + permission_count_++; + if (permission->is_hosted_app()) + hosted_app_permission_count_++; +} + +// +// ExtensionPermissionSet +// + +ExtensionPermissionSet::ExtensionPermissionSet() { +} + +ExtensionPermissionSet::ExtensionPermissionSet( + const Extension* extension, + const ExtensionAPIPermissionSet& apis, + const URLPatternSet& explicit_hosts) + : apis_(apis) { + CHECK(extension); + AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_); + InitImplicitExtensionPermissions(extension); + InitEffectiveHosts(); +} + +ExtensionPermissionSet::ExtensionPermissionSet( + const ExtensionAPIPermissionSet& apis, + const URLPatternSet& explicit_hosts, + const URLPatternSet& scriptable_hosts) + : apis_(apis), + scriptable_hosts_(scriptable_hosts) { + AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_); + InitEffectiveHosts(); +} + +ExtensionPermissionSet::~ExtensionPermissionSet() { +} + +// static +ExtensionPermissionSet* ExtensionPermissionSet::CreateUnion( + const ExtensionPermissionSet* set1, + const ExtensionPermissionSet* set2) { + ExtensionPermissionSet empty; + const ExtensionPermissionSet* set1_safe = (set1 == NULL) ? &empty : set1; + const ExtensionPermissionSet* set2_safe = (set2 == NULL) ? &empty : set2; + + ExtensionAPIPermissionSet apis; + std::set_union(set1_safe->apis().begin(), set1_safe->apis().end(), + set2_safe->apis().begin(), set2_safe->apis().end(), + std::insert_iterator<ExtensionAPIPermissionSet>( + apis, apis.begin())); + + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + URLPatternSet::CreateUnion(set1_safe->explicit_hosts(), + set2_safe->explicit_hosts(), + &explicit_hosts); + URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(), + set2_safe->scriptable_hosts(), + &scriptable_hosts); + + return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); +} + +std::set<std::string> ExtensionPermissionSet::GetAPIsAsStrings() const { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + std::set<std::string> apis_str; + for (ExtensionAPIPermissionSet::const_iterator i = apis_.begin(); + i != apis_.end(); ++i) { + ExtensionAPIPermission* permission = info->GetByID(*i); + if (permission) + apis_str.insert(permission->name()); + } + return apis_str; +} + +std::vector<std::string> + ExtensionPermissionSet::GetDistinctHostsForDisplay() const { + return GetDistinctHosts(effective_hosts_.patterns(), true); +} + +ExtensionPermissionMessages + ExtensionPermissionSet::GetPermissionMessages() const { + ExtensionPermissionMessages messages; + + if (HasEffectiveFullAccess()) { + messages.push_back(ExtensionPermissionMessage( + ExtensionPermissionMessage::kFullAccess, + l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS))); + return messages; + } + + if (HasEffectiveAccessToAllHosts()) { + messages.push_back(ExtensionPermissionMessage( + ExtensionPermissionMessage::kHostsAll, + l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS))); + } else { + std::vector<std::string> hosts = GetDistinctHostsForDisplay(); + if (!hosts.empty()) + messages.push_back(ExtensionPermissionMessage::CreateFromHostList(hosts)); + } + + std::set<ExtensionPermissionMessage> simple_msgs = + GetSimplePermissionMessages(); + messages.insert(messages.end(), simple_msgs.begin(), simple_msgs.end()); + + return messages; +} + +std::vector<string16> ExtensionPermissionSet::GetWarningMessages() const { + std::vector<string16> messages; + ExtensionPermissionMessages permissions = GetPermissionMessages(); + for (ExtensionPermissionMessages::const_iterator i = permissions.begin(); + i != permissions.end(); ++i) + messages.push_back(i->message()); + return messages; +} + +bool ExtensionPermissionSet::IsEmpty() const { + // Not default if any host permissions are present. + if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty())) + return false; + + // Or if it has no api permissions. + return apis().empty(); +} + +bool ExtensionPermissionSet::HasAPIPermission( + ExtensionAPIPermission::ID permission) const { + return apis().find(permission) != apis().end(); +} + +bool ExtensionPermissionSet::HasAccessToFunction( + const std::string& function_name) const { + // TODO(jstritar): Embed this information in each permission and add a method + // like GrantsAccess(function_name) to ExtensionAPIPermission. A "default" + // permission can then handle the modules and functions that everyone can + // access. + std::string permission_name = function_name; + + for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) { + if (permission_name == kNonPermissionFunctionNames[i]) + return true; + } + + // See if this is a function or event name first and strip out the package. + // Functions will be of the form package.function + // Events will be of the form package/id or package.optional.stuff + size_t separator = function_name.find_first_of("./"); + if (separator != std::string::npos) + permission_name = function_name.substr(0, separator); + + ExtensionAPIPermission* permission = + ExtensionPermissionsInfo::GetInstance()->GetByName(permission_name); + if (permission && apis_.count(permission->id())) + return true; + + for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i) { + if (permission_name == kNonPermissionModuleNames[i]) { + return true; + } + } + + return false; +} + +bool ExtensionPermissionSet::HasExplicitAccessToOrigin( + const GURL& origin) const { + return explicit_hosts().MatchesURL(origin); +} + +bool ExtensionPermissionSet::HasScriptableAccessToURL( + const GURL& origin) const { + // We only need to check our host list to verify access. The host list should + // already reflect any special rules (such as chrome://favicon, component + // all hosts access, etc.). + return scriptable_hosts().MatchesURL(origin); +} + +bool ExtensionPermissionSet::HasEffectiveAccessToAllHosts() const { + // There are two ways this set can have effective access to all hosts: + // 1) it has an <all_urls> URL pattern. + // 2) it has a named permission with implied full URL access. + const URLPatternList patterns = effective_hosts().patterns(); + for (URLPatternList::const_iterator host = patterns.begin(); + host != patterns.end(); ++host) { + if (host->match_all_urls() || + (host->match_subdomains() && host->host().empty())) + return true; + } + + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + for (ExtensionAPIPermissionSet::const_iterator i = apis().begin(); + i != apis().end(); ++i) { + ExtensionAPIPermission* permission = info->GetByID(*i); + if (permission->implies_full_url_access()) + return true; + } + return false; +} + +bool ExtensionPermissionSet::HasEffectiveAccessToURL( + const GURL& url) const { + return effective_hosts().MatchesURL(url); +} + +bool ExtensionPermissionSet::HasEffectiveFullAccess() const { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + for (ExtensionAPIPermissionSet::const_iterator i = apis().begin(); + i != apis().end(); ++i) { + ExtensionAPIPermission* permission = info->GetByID(*i); + if (permission->implies_full_access()) + return true; + } + return false; +} + +bool ExtensionPermissionSet::HasPrivatePermissions() const { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + for (ExtensionAPIPermissionSet::const_iterator i = apis_.begin(); + i != apis_.end(); ++i) { + ExtensionAPIPermission* permission = info->GetByID(*i); + if (permission && permission->is_component_only()) + return true; + } + return false; +} + +bool ExtensionPermissionSet::HasLessPrivilegesThan( + const ExtensionPermissionSet* permissions) const { + // Things can't get worse than native code access. + if (HasEffectiveFullAccess()) + return false; + + // Otherwise, it's a privilege increase if the new one has full access. + if (permissions->HasEffectiveFullAccess()) + return true; + + if (HasLessHostPrivilegesThan(permissions)) + return true; + + if (HasLessAPIPrivilegesThan(permissions)) + return true; + + return false; +} + +// static +std::vector<std::string> ExtensionPermissionSet::GetDistinctHosts( + const URLPatternList& host_patterns, bool include_rcd) { + // Use a vector to preserve order (also faster than a map on small sets). + // Each item is a host split into two parts: host without RCDs and + // current best RCD. + typedef std::vector<std::pair<std::string, std::string> > HostVector; + HostVector hosts_best_rcd; + for (size_t i = 0; i < host_patterns.size(); ++i) { + std::string host = host_patterns[i].host(); + + // Add the subdomain wildcard back to the host, if necessary. + if (host_patterns[i].match_subdomains()) + host = "*." + host; + + // If the host has an RCD, split it off so we can detect duplicates. + std::string rcd; + size_t reg_len = net::RegistryControlledDomainService::GetRegistryLength( + host, false); + if (reg_len && reg_len != std::string::npos) { + if (include_rcd) // else leave rcd empty + rcd = host.substr(host.size() - reg_len); + host = host.substr(0, host.size() - reg_len); + } + + // Check if we've already seen this host. + HostVector::iterator it = hosts_best_rcd.begin(); + for (; it != hosts_best_rcd.end(); ++it) { + if (it->first == host) + break; + } + // If this host was found, replace the RCD if this one is better. + if (it != hosts_best_rcd.end()) { + if (include_rcd && RcdBetterThan(rcd, it->second)) + it->second = rcd; + } else { // Previously unseen host, append it. + hosts_best_rcd.push_back(std::make_pair(host, rcd)); + } + } + + // Build up the final vector by concatenating hosts and RCDs. + std::vector<std::string> distinct_hosts; + for (HostVector::iterator it = hosts_best_rcd.begin(); + it != hosts_best_rcd.end(); ++it) + distinct_hosts.push_back(it->first + it->second); + return distinct_hosts; +} + +void ExtensionPermissionSet::InitEffectiveHosts() { + effective_hosts_.ClearPatterns(); + + if (HasEffectiveAccessToAllHosts()) { + URLPattern all_urls(URLPattern::SCHEME_ALL); + all_urls.set_match_all_urls(true); + effective_hosts_.AddPattern(all_urls); + return; + } + + URLPatternSet::CreateUnion( + explicit_hosts(), scriptable_hosts(), &effective_hosts_); +} + +void ExtensionPermissionSet::InitImplicitExtensionPermissions( + const Extension* extension) { + // Add the implied permissions. + if (!extension->plugins().empty()) + apis_.insert(ExtensionAPIPermission::kPlugin); + + if (!extension->devtools_url().is_empty()) + apis_.insert(ExtensionAPIPermission::kDevtools); + + // Add the scriptable hosts. + for (UserScriptList::const_iterator content_script = + extension->content_scripts().begin(); + content_script != extension->content_scripts().end(); ++content_script) { + URLPatternList::const_iterator pattern = + content_script->url_patterns().begin(); + for (; pattern != content_script->url_patterns().end(); ++pattern) + scriptable_hosts_.AddPattern(*pattern); + } +} + +std::set<ExtensionPermissionMessage> + ExtensionPermissionSet::GetSimplePermissionMessages() const { + std::set<ExtensionPermissionMessage> messages; + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + for (ExtensionAPIPermissionSet::const_iterator i = apis_.begin(); + i != apis_.end(); ++i) { + DCHECK_GT(ExtensionPermissionMessage::kNone, + ExtensionPermissionMessage::kUnknown); + ExtensionAPIPermission* perm = info->GetByID(*i); + if (perm && perm->message_id() > ExtensionPermissionMessage::kNone) + messages.insert(perm->GetMessage()); + } + return messages; +} + +bool ExtensionPermissionSet::HasLessAPIPrivilegesThan( + const ExtensionPermissionSet* permissions) const { + if (permissions == NULL) + return false; + + ExtensionAPIPermissionSet new_apis = permissions->apis_; + ExtensionAPIPermissionSet new_apis_only; + std::set_difference(new_apis.begin(), new_apis.end(), + apis_.begin(), apis_.end(), + std::inserter(new_apis_only, new_apis_only.begin())); + + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + + // Ignore API permissions that don't require user approval when deciding if + // an extension has increased its privileges. + for (ExtensionAPIPermissionSet::iterator i = new_apis_only.begin(); + i != new_apis_only.end(); ++i) { + ExtensionAPIPermission* perm = info->GetByID(*i); + if (perm && perm->message_id() > ExtensionPermissionMessage::kNone) + return true; + } + return false; +} + +bool ExtensionPermissionSet::HasLessHostPrivilegesThan( + const ExtensionPermissionSet* permissions) const { + // If this permission set can access any host, then it can't be elevated. + if (HasEffectiveAccessToAllHosts()) + return false; + + // Likewise, if the other permission set has full host access, then it must be + // a privilege increase. + if (permissions->HasEffectiveAccessToAllHosts()) + return true; + + const URLPatternList old_list = effective_hosts().patterns(); + const URLPatternList new_list = permissions->effective_hosts().patterns(); + + // TODO(jstritar): This is overly conservative with respect to subdomains. + // For example, going from *.google.com to www.google.com will be + // considered an elevation, even though it is not (http://crbug.com/65337). + std::vector<std::string> new_hosts = GetDistinctHosts(new_list, false); + std::vector<std::string> old_hosts = GetDistinctHosts(old_list, false); + + std::set<std::string> old_hosts_set(old_hosts.begin(), old_hosts.end()); + std::set<std::string> new_hosts_set(new_hosts.begin(), new_hosts.end()); + std::set<std::string> new_hosts_only; + + std::set_difference(new_hosts_set.begin(), new_hosts_set.end(), + old_hosts_set.begin(), old_hosts_set.end(), + std::inserter(new_hosts_only, new_hosts_only.begin())); + + return !new_hosts_only.empty(); +} diff --git a/chrome/common/extensions/extension_permission_set.h b/chrome/common/extensions/extension_permission_set.h new file mode 100644 index 0000000..14f58a90 --- /dev/null +++ b/chrome/common/extensions/extension_permission_set.h @@ -0,0 +1,389 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_PERMISSION_SET_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_PERMISSION_SET_H_ +#pragma once + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/memory/singleton.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/common/extensions/url_pattern_set.h" + +class DictionaryValue; +class Extension; +class ExtensionPrefs; +class ListValue; + +// When prompting the user to install or approve permissions, we display +// messages describing the effects of the permissions rather than listing the +// permissions themselves. Each ExtensionPermissionMessage represents one of the +// messages shown to the user. +class ExtensionPermissionMessage { + public: + // Do not reorder this enumeration. If you need to add a new enum, add it just + // prior to kEnumBoundary. + enum ID { + kUnknown, + kNone, + kBookmarks, + kGeolocation, + kBrowsingHistory, + kTabs, + kManagement, + kDebugger, + kHosts1, + kHosts2, + kHosts3, + kHosts4OrMore, + kHostsAll, + kFullAccess, + kClipboard, + kEnumBoundary + }; + + // Creates the corresponding permission message for a list of hosts. This is + // simply a convenience method around the constructor, since the messages + // change depending on what hosts are present. + static ExtensionPermissionMessage CreateFromHostList( + const std::vector<std::string>& hosts); + + // Creates the corresponding permission message. + ExtensionPermissionMessage(ID id, const string16& message); + ~ExtensionPermissionMessage(); + + // Gets the id of the permission message, which can be used in UMA + // histograms. + ID id() const { return id_; } + + // Gets a localized message describing this permission. Please note that + // the message will be empty for message types TYPE_NONE and TYPE_UNKNOWN. + const string16& message() const { return message_; } + + // Comparator to work with std::set. + bool operator<(const ExtensionPermissionMessage& that) const { + return id_ < that.id_; + } + + private: + ID id_; + string16 message_; +}; + +typedef std::vector<ExtensionPermissionMessage> ExtensionPermissionMessages; + +// The ExtensionAPIPermission is an immutable class that describes a single +// named permission (API permission). +class ExtensionAPIPermission { + public: + enum ID { + // Error codes. + kInvalid = -2, + kUnknown = -1, + + // Default permission that every extension has implicity. + kDefault, + + // Real permissions. + kBackground, + kBookmark, + kClipboardRead, + kClipboardWrite, + kContentSettings, + kContextMenus, + kCookie, + kChromePrivate, + kChromeosInfoPrivate, + kDebugger, + kExperimental, + kFileBrowserHandler, + kFileBrowserPrivate, + kGeolocation, + kHistory, + kIdle, + kManagement, + kMediaPlayerPrivate, + kNotification, + kProxy, + kTab, + kUnlimitedStorage, + kWebSocketProxyPrivate, + kWebstorePrivate, + kDevtools, + kPlugin, + kEnumBoundary + }; + + typedef std::set<ID> IDSet; + + ~ExtensionAPIPermission(); + + // Returns the localized permission message associated with this api. + ExtensionPermissionMessage GetMessage() const; + + ID id() const { return id_; } + + // Returns the message id associated with this permission. + ExtensionPermissionMessage::ID message_id() const { + return message_id_; + } + + // Returns the name of this permission. + const char* name() const { return name_; } + + // Returns true if this permission implies full access (e.g., native code). + bool implies_full_access() const { return implies_full_access_; } + + // Returns true if this permission implies full URL access. + bool implies_full_url_access() const { return implies_full_url_access_; } + + // Returns true if this permission can be accessed by hosted apps. + bool is_hosted_app() const { return is_hosted_app_; } + + // Returns true if this permission can only be acquired by COMPONENT + // extensions. + bool is_component_only() const { return is_component_only_; } + + private: + // Instances should only be constructed from within ExtensionPermissionsInfo. + friend class ExtensionPermissionsInfo; + + explicit ExtensionAPIPermission( + ID id, + const char* name, + bool is_hosted_app, + bool is_component_only, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id, + bool implies_full_access, + bool implies_full_url_access); + + ID id_; + const char* name_; + bool implies_full_access_; + bool implies_full_url_access_; + bool is_hosted_app_; + bool is_component_only_; + int l10n_message_id_; + ExtensionPermissionMessage::ID message_id_; +}; + +typedef std::set<ExtensionAPIPermission::ID> ExtensionAPIPermissionSet; + +// Singleton that holds the extension permission instances and provides static +// methods for accessing them. +class ExtensionPermissionsInfo { + public: + // Returns a pointer to the singleton instance. + static ExtensionPermissionsInfo* GetInstance(); + + // Returns the permission with the given |id|, and NULL if it doesn't exist. + ExtensionAPIPermission* GetByID(ExtensionAPIPermission::ID id); + + // Returns the permission with the given |name|, and NULL if none + // exists. + ExtensionAPIPermission* GetByName(std::string name); + + // Returns a set containing all valid api permission ids. + ExtensionAPIPermissionSet GetAll(); + + // Converts all the permission names in |permission_names| to permission ids. + ExtensionAPIPermissionSet GetAllByName( + const std::set<std::string>& permission_names); + + // Gets the total number of API permissions available to hosted apps. + size_t get_hosted_app_permission_count() { + return hosted_app_permission_count_; + } + + // Gets the total number of API permissions. + size_t get_permission_count() { return permission_count_; } + + private: + ~ExtensionPermissionsInfo(); + ExtensionPermissionsInfo(); + + // Registers an |alias| for a given permission |name|. + void RegisterAlias(const char* name, const char* alias); + + // Registers a standard extension permission. + void RegisterExtensionPermission( + ExtensionAPIPermission::ID id, + const char* name, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id); + + // Registers a permission that can be accessed by hosted apps. + void RegisterHostedAppPermission( + ExtensionAPIPermission::ID id, + const char* name, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id); + + // Registers a permission accessible only by COMPONENT extensions. + void RegisterPrivatePermission( + ExtensionAPIPermission::ID id, + const char* name); + + // Registers a permission with a custom set of attributes not satisfied + // by the other registration functions. + void RegisterPermission( + ExtensionAPIPermission::ID id, + const char* name, + int l10n_message_id, + ExtensionPermissionMessage::ID message_id, + bool is_hosted_app, + bool is_component_only, + bool implies_full_access, + bool implies_full_url_access); + + // Maps permission ids to permissions. + typedef std::map<ExtensionAPIPermission::ID, ExtensionAPIPermission*> IDMap; + + // Maps names and aliases to permissions. + typedef std::map<std::string, ExtensionAPIPermission*> NameMap; + + IDMap id_map_; + NameMap name_map_; + + size_t hosted_app_permission_count_; + size_t permission_count_; + + friend struct DefaultSingletonTraits<ExtensionPermissionsInfo>; + DISALLOW_COPY_AND_ASSIGN(ExtensionPermissionsInfo); +}; + +// The ExtensionPermissionSet is an immutable class that encapsulates an +// extension's permissions. The class exposes set operations for combining and +// manipulating the permissions. +class ExtensionPermissionSet { + public: + // Creates an empty permission set (e.g. default permissions). + ExtensionPermissionSet(); + + // Creates a new permission set based on the |extension| manifest data, and + // the api and host permissions (|apis| and |hosts|). The effective hosts + // of the newly created permission set will be inferred from the |extension| + // manifest, |apis| and |hosts|. + ExtensionPermissionSet(const Extension* extension, + const ExtensionAPIPermissionSet& apis, + const URLPatternSet& explicit_hosts); + + // Creates a new permission set based on the specified data. + ExtensionPermissionSet(const ExtensionAPIPermissionSet& apis, + const URLPatternSet& explicit_hosts, + const URLPatternSet& scriptable_hosts); + + ~ExtensionPermissionSet(); + + // Creates a new permission set equal to the union of |set1| and |set2|. + // Passes ownership of the new set to the caller. + static ExtensionPermissionSet* CreateUnion( + const ExtensionPermissionSet* set1, const ExtensionPermissionSet* set2); + + // Gets the API permissions in this set as a set of strings. + std::set<std::string> GetAPIsAsStrings() const; + + // Gets a list of the distinct hosts for displaying to the user. + // NOTE: do not use this for comparing permissions, since this disgards some + // information. + std::vector<std::string> GetDistinctHostsForDisplay() const; + + // Gets the localized permission messages that represent this set. + ExtensionPermissionMessages GetPermissionMessages() const; + + // Gets the localized permission messages that represent this set (represented + // as strings). + std::vector<string16> GetWarningMessages() const; + + // Returns true if this is an empty set (e.g., the default permission set). + bool IsEmpty() const; + + // Returns true if the set has the specified API permission. + bool HasAPIPermission(ExtensionAPIPermission::ID permission) const; + + // Returns true if the permissions in this set grant access to the specified + // |function_name|. + bool HasAccessToFunction(const std::string& function_name) const; + + // Returns true if this includes permission to access |origin|. + bool HasExplicitAccessToOrigin(const GURL& origin) const; + + // Returns true if this permission set includes access to script |url|. + bool HasScriptableAccessToURL(const GURL& url) const; + + // Returns true if this permission set includes effective access to all + // origins. + bool HasEffectiveAccessToAllHosts() const; + + // Returns true if this permission set includes effective access to |url|. + bool HasEffectiveAccessToURL(const GURL& url) const; + + // Returns ture if this permission set effectively represents full access + // (e.g. native code). + bool HasEffectiveFullAccess() const; + + // Returns true if this permission set includes permissions that are + // restricted to internal extensions. + bool HasPrivatePermissions() const; + + // Returns true if |permissions| has a greater privilege level than this + // permission set (e.g., this permission set has less permissions). + bool HasLessPrivilegesThan(const ExtensionPermissionSet* permissions) const; + + const ExtensionAPIPermissionSet& apis() const { return apis_; } + + const URLPatternSet& effective_hosts() const { return effective_hosts_; } + + const URLPatternSet& explicit_hosts() const { return explicit_hosts_; } + + const URLPatternSet& scriptable_hosts() const { return scriptable_hosts_; } + + private: + FRIEND_TEST_ALL_PREFIXES(ExtensionPermissionSetTest, + HasLessHostPrivilegesThan); + + static std::vector<std::string> GetDistinctHosts( + const URLPatternList& host_patterns, bool include_rcd); + + // Initializes the set based on |extension|'s manifest data. + void InitImplicitExtensionPermissions(const Extension* extension); + + // Initializes the effective host permission based on the data in this set. + void InitEffectiveHosts(); + + // Gets the permission messages for the API permissions. + std::set<ExtensionPermissionMessage> GetSimplePermissionMessages() const; + + // Returns true if |permissions| has an elevated API privilege level than + // this set. + bool HasLessAPIPrivilegesThan( + const ExtensionPermissionSet* permissions) const; + + // Returns true if |permissions| has more host permissions compared to this + // set. + bool HasLessHostPrivilegesThan( + const ExtensionPermissionSet* permissions) const; + + // The api list is used when deciding if an extension can access certain + // extension APIs and features. + ExtensionAPIPermissionSet apis_; + + // The list of hosts that can be accessed directly from the extension. + URLPatternSet explicit_hosts_; + + // The list of hosts that can be scripted by content scripts. + URLPatternSet scriptable_hosts_; + + // The list of hosts this effectively grants access to. + URLPatternSet effective_hosts_; +}; + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_PERMISSION_SET_H_ diff --git a/chrome/common/extensions/extension_permission_set_unittest.cc b/chrome/common/extensions/extension_permission_set_unittest.cc new file mode 100644 index 0000000..9cfa698 --- /dev/null +++ b/chrome/common/extensions/extension_permission_set_unittest.cc @@ -0,0 +1,941 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/extension_permission_set.h" + +#include "base/logging.h" +#include "base/path_service.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension.h" +#include "content/common/json_value_serializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static scoped_refptr<Extension> LoadManifest(const std::string& dir, + const std::string& test_file, + int extra_flags) { + FilePath path; + PathService::Get(chrome::DIR_TEST_DATA, &path); + path = path.AppendASCII("extensions") + .AppendASCII(dir) + .AppendASCII(test_file); + + JSONFileValueSerializer serializer(path); + std::string error; + scoped_ptr<Value> result(serializer.Deserialize(NULL, &error)); + if (!result.get()) { + EXPECT_EQ("", error); + return NULL; + } + + scoped_refptr<Extension> extension = Extension::Create( + path.DirName(), Extension::INVALID, + *static_cast<DictionaryValue*>(result.get()), + Extension::STRICT_ERROR_CHECKS | extra_flags, &error); + EXPECT_TRUE(extension) << error; + return extension; +} + +static scoped_refptr<Extension> LoadManifest(const std::string& dir, + const std::string& test_file) { + return LoadManifest(dir, test_file, Extension::NO_FLAGS); +} + +void CompareLists(const std::vector<std::string>& expected, + const std::vector<std::string>& actual) { + ASSERT_EQ(expected.size(), actual.size()); + + for (size_t i = 0; i < expected.size(); ++i) { + EXPECT_EQ(expected[i], actual[i]); + } +} + +static void AddPattern(URLPatternSet* extent, const std::string& pattern) { + int schemes = URLPattern::SCHEME_ALL; + extent->AddPattern(URLPattern(schemes, pattern)); +} + +static void AssertEqualExtents(const URLPatternSet& extent1, + const URLPatternSet& extent2) { + URLPatternList patterns1 = extent1.patterns(); + URLPatternList patterns2 = extent2.patterns(); + std::set<std::string> strings1; + EXPECT_EQ(patterns1.size(), patterns2.size()); + + for (size_t i = 0; i < patterns1.size(); ++i) + strings1.insert(patterns1.at(i).GetAsString()); + + std::set<std::string> strings2; + for (size_t i = 0; i < patterns2.size(); ++i) + strings2.insert(patterns2.at(i).GetAsString()); + + EXPECT_EQ(strings1, strings2); +} + +} // namespace + +class ExtensionAPIPermissionTest : public testing::Test { +}; + +class ExtensionPermissionSetTest : public testing::Test { +}; + + +// Tests GetByID. +TEST(ExtensionPermissionsInfoTest, GetByID) { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + ExtensionAPIPermissionSet ids = info->GetAll(); + for (ExtensionAPIPermissionSet::iterator i = ids.begin(); + i != ids.end(); ++i) { + EXPECT_EQ(*i, info->GetByID(*i)->id()); + } +} + +// Tests that GetByName works with normal permission names and aliases. +TEST(ExtensionPermissionsInfoTest, GetByName) { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + EXPECT_EQ(ExtensionAPIPermission::kTab, info->GetByName("tabs")->id()); + EXPECT_EQ(ExtensionAPIPermission::kManagement, + info->GetByName("management")->id()); + EXPECT_FALSE(info->GetByName("alsdkfjasldkfj")); +} + +TEST(ExtensionPermissionsInfoTest, GetAll) { + size_t count = 0; + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + ExtensionAPIPermissionSet apis = info->GetAll(); + for (ExtensionAPIPermissionSet::iterator api = apis.begin(); + api != apis.end(); ++api) { + // Make sure only the valid permission IDs get returned. + EXPECT_NE(ExtensionAPIPermission::kInvalid, *api); + EXPECT_NE(ExtensionAPIPermission::kUnknown, *api); + count++; + } + EXPECT_EQ(count, info->get_permission_count()); +} + +TEST(ExtensionPermissionInfoTest, GetAllByName) { + std::set<std::string> names; + names.insert("background"); + names.insert("management"); + + // This is an alias of kTab + names.insert("windows"); + + // This unknown name should get dropped. + names.insert("sdlkfjasdlkfj"); + + ExtensionAPIPermissionSet expected; + expected.insert(ExtensionAPIPermission::kBackground); + expected.insert(ExtensionAPIPermission::kManagement); + expected.insert(ExtensionAPIPermission::kTab); + + EXPECT_EQ(expected, + ExtensionPermissionsInfo::GetInstance()->GetAllByName(names)); +} + +// Tests that the aliases are properly mapped. +TEST(ExtensionAPIPermissionTest, Aliases) { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + // tabs: tabs, windows + std::string tabs_name = "tabs"; + EXPECT_EQ(tabs_name, info->GetByID(ExtensionAPIPermission::kTab)->name()); + EXPECT_EQ(ExtensionAPIPermission::kTab, info->GetByName("tabs")->id()); + EXPECT_EQ(ExtensionAPIPermission::kTab, info->GetByName("windows")->id()); + + // unlimitedStorage: unlimitedStorage, unlimited_storage + std::string storage_name = "unlimitedStorage"; + EXPECT_EQ(storage_name, info->GetByID( + ExtensionAPIPermission::kUnlimitedStorage)->name()); + EXPECT_EQ(ExtensionAPIPermission::kUnlimitedStorage, + info->GetByName("unlimitedStorage")->id()); + EXPECT_EQ(ExtensionAPIPermission::kUnlimitedStorage, + info->GetByName("unlimited_storage")->id()); +} + +TEST(ExtensionAPIPermissionTest, HostedAppPermissions) { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + ExtensionAPIPermissionSet hosted_perms; + hosted_perms.insert(ExtensionAPIPermission::kBackground); + hosted_perms.insert(ExtensionAPIPermission::kClipboardRead); + hosted_perms.insert(ExtensionAPIPermission::kClipboardWrite); + hosted_perms.insert(ExtensionAPIPermission::kChromePrivate); + hosted_perms.insert(ExtensionAPIPermission::kExperimental); + hosted_perms.insert(ExtensionAPIPermission::kGeolocation); + hosted_perms.insert(ExtensionAPIPermission::kNotification); + hosted_perms.insert(ExtensionAPIPermission::kUnlimitedStorage); + hosted_perms.insert(ExtensionAPIPermission::kWebstorePrivate); + + ExtensionAPIPermissionSet perms = info->GetAll(); + size_t count = 0; + for (ExtensionAPIPermissionSet::iterator i = perms.begin(); + i != perms.end(); ++i) { + count += hosted_perms.count(*i); + EXPECT_EQ(hosted_perms.count(*i) > 0, info->GetByID(*i)->is_hosted_app()); + } + + EXPECT_EQ(9u, count); + EXPECT_EQ(9u, info->get_hosted_app_permission_count()); +} + +TEST(ExtensionAPIPermissionTest, ComponentOnlyPermissions) { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + ExtensionAPIPermissionSet private_perms; + private_perms.insert(ExtensionAPIPermission::kChromeosInfoPrivate); + private_perms.insert(ExtensionAPIPermission::kFileBrowserPrivate); + private_perms.insert(ExtensionAPIPermission::kMediaPlayerPrivate); + private_perms.insert(ExtensionAPIPermission::kWebstorePrivate); + + ExtensionAPIPermissionSet perms = info->GetAll(); + int count = 0; + for (ExtensionAPIPermissionSet::iterator i = perms.begin(); + i != perms.end(); ++i) { + count += private_perms.count(*i); + EXPECT_EQ(private_perms.count(*i) > 0, + info->GetByID(*i)->is_component_only()); + } + + EXPECT_EQ(4, count); +} + +TEST(ExtensionPermissionSetTest, EffectiveHostPermissions) { + scoped_refptr<Extension> extension; + const ExtensionPermissionSet* permissions = NULL; + + extension = LoadManifest("effective_host_permissions", "empty.json"); + permissions = extension->permission_set(); + EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size()); + EXPECT_FALSE(permissions->HasEffectiveAccessToURL( + GURL("http://www.google.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", "one_host.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL( + GURL("http://www.google.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToURL( + GURL("https://www.google.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", + "one_host_wildcard.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL( + GURL("http://foo.google.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", "two_hosts.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL( + GURL("http://www.google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL( + GURL("http://www.reddit.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", + "https_not_considered.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("https://google.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", + "two_content_scripts.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL( + GURL("http://www.reddit.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL( + GURL("http://news.ycombinator.com"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", "all_hosts.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://test/"))); + EXPECT_FALSE(permissions->HasEffectiveAccessToURL(GURL("https://test/"))); + EXPECT_TRUE( + permissions->HasEffectiveAccessToURL(GURL("http://www.google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", "all_hosts2.json"); + permissions = extension->permission_set(); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://test/"))); + EXPECT_TRUE( + permissions->HasEffectiveAccessToURL(GURL("http://www.google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts()); + + extension = LoadManifest("effective_host_permissions", "all_hosts3.json"); + permissions = extension->permission_set(); + EXPECT_FALSE(permissions->HasEffectiveAccessToURL(GURL("http://test/"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("https://test/"))); + EXPECT_TRUE( + permissions->HasEffectiveAccessToURL(GURL("http://www.google.com"))); + EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts()); +} + +TEST(ExtensionPermissionSetTest, ExplicitAccessToOrigin) { + ExtensionAPIPermissionSet apis; + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + + AddPattern(&explicit_hosts, "http://*.google.com/*"); + // The explicit host paths should get set to /*. + AddPattern(&explicit_hosts, "http://www.example.com/a/particular/path/*"); + + ExtensionPermissionSet perm_set(apis, explicit_hosts, scriptable_hosts); + ASSERT_TRUE(perm_set.HasExplicitAccessToOrigin( + GURL("http://www.google.com/"))); + ASSERT_TRUE(perm_set.HasExplicitAccessToOrigin( + GURL("http://test.google.com/"))); + ASSERT_TRUE(perm_set.HasExplicitAccessToOrigin( + GURL("http://www.example.com"))); + ASSERT_TRUE(perm_set.HasEffectiveAccessToURL( + GURL("http://www.example.com"))); + ASSERT_FALSE(perm_set.HasExplicitAccessToOrigin( + GURL("http://test.example.com"))); +} + +TEST(ExtensionPermissionSetTest, CreateUnion) { + ExtensionAPIPermissionSet apis1; + ExtensionAPIPermissionSet apis2; + ExtensionAPIPermissionSet expected_apis; + + URLPatternSet explicit_hosts1; + URLPatternSet explicit_hosts2; + URLPatternSet expected_explicit_hosts; + + URLPatternSet scriptable_hosts1; + URLPatternSet scriptable_hosts2; + URLPatternSet expected_scriptable_hosts; + + URLPatternSet effective_hosts; + + scoped_ptr<ExtensionPermissionSet> set1; + scoped_ptr<ExtensionPermissionSet> set2; + scoped_ptr<ExtensionPermissionSet> union_set; + + // Union with an empty set. + apis1.insert(ExtensionAPIPermission::kTab); + apis1.insert(ExtensionAPIPermission::kBackground); + expected_apis.insert(ExtensionAPIPermission::kTab); + expected_apis.insert(ExtensionAPIPermission::kBackground); + + AddPattern(&explicit_hosts1, "http://*.google.com/*"); + AddPattern(&expected_explicit_hosts, "http://*.google.com/*"); + AddPattern(&effective_hosts, "http://*.google.com/*"); + + set1.reset(new ExtensionPermissionSet( + apis1, explicit_hosts1, scriptable_hosts1)); + set2.reset(new ExtensionPermissionSet( + apis2, explicit_hosts2, scriptable_hosts2)); + union_set.reset(ExtensionPermissionSet::CreateUnion(set1.get(), set2.get())); + + EXPECT_FALSE(union_set->HasEffectiveFullAccess()); + EXPECT_EQ(expected_apis, union_set->apis()); + AssertEqualExtents(expected_explicit_hosts, union_set->explicit_hosts()); + AssertEqualExtents(expected_scriptable_hosts, union_set->scriptable_hosts()); + AssertEqualExtents(expected_explicit_hosts, union_set->effective_hosts()); + + // Now use a real second set. + apis2.insert(ExtensionAPIPermission::kTab); + apis2.insert(ExtensionAPIPermission::kProxy); + apis2.insert(ExtensionAPIPermission::kClipboardWrite); + apis2.insert(ExtensionAPIPermission::kPlugin); + expected_apis.insert(ExtensionAPIPermission::kTab); + expected_apis.insert(ExtensionAPIPermission::kProxy); + expected_apis.insert(ExtensionAPIPermission::kClipboardWrite); + expected_apis.insert(ExtensionAPIPermission::kPlugin); + + AddPattern(&explicit_hosts2, "http://*.example.com/*"); + AddPattern(&scriptable_hosts2, "http://*.google.com/*"); + AddPattern(&expected_explicit_hosts, "http://*.example.com/*"); + AddPattern(&expected_scriptable_hosts, "http://*.google.com/*"); + + effective_hosts.ClearPatterns(); + AddPattern(&effective_hosts, "<all_urls>"); + + set2.reset(new ExtensionPermissionSet( + apis2, explicit_hosts2, scriptable_hosts2)); + union_set.reset(ExtensionPermissionSet::CreateUnion(set1.get(), set2.get())); + EXPECT_TRUE(union_set->HasEffectiveFullAccess()); + EXPECT_TRUE(union_set->HasEffectiveAccessToAllHosts()); + EXPECT_EQ(expected_apis, union_set->apis()); + AssertEqualExtents(expected_explicit_hosts, union_set->explicit_hosts()); + AssertEqualExtents(expected_scriptable_hosts, union_set->scriptable_hosts()); + AssertEqualExtents(effective_hosts, union_set->effective_hosts()); +} + +TEST(ExtensionPermissionSetTest, HasLessPrivilegesThan) { + const struct { + const char* base_name; + // Increase these sizes if you have more than 10. + const char* granted_apis[10]; + const char* granted_hosts[10]; + bool full_access; + bool expect_increase; + } kTests[] = { + { "allhosts1", {NULL}, {"http://*/", NULL}, false, + false }, // all -> all + { "allhosts2", {NULL}, {"http://*/", NULL}, false, + false }, // all -> one + { "allhosts3", {NULL}, {NULL}, false, true }, // one -> all + { "hosts1", {NULL}, + {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, + false }, // http://a,http://b -> http://a,http://b + { "hosts2", {NULL}, + {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, + true }, // http://a,http://b -> https://a,http://*.b + { "hosts3", {NULL}, + {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, + false }, // http://a,http://b -> http://a + { "hosts4", {NULL}, + {"http://www.google.com/", NULL}, false, + true }, // http://a -> http://a,http://b + { "hosts5", {"tabs", "notifications", NULL}, + {"http://*.example.com/", "http://*.example.com/*", + "http://*.example.co.uk/*", "http://*.example.com.au/*", + NULL}, false, + false }, // http://a,b,c -> http://a,b,c + https://a,b,c + { "hosts6", {"tabs", "notifications", NULL}, + {"http://*.example.com/", "http://*.example.com/*", NULL}, false, + false }, // http://a.com -> http://a.com + http://a.co.uk + { "permissions1", {"tabs", NULL}, + {NULL}, false, false }, // tabs -> tabs + { "permissions2", {"tabs", NULL}, + {NULL}, false, true }, // tabs -> tabs,bookmarks + { "permissions3", {NULL}, + {"http://*/*", NULL}, + false, true }, // http://a -> http://a,tabs + { "permissions5", {"bookmarks", NULL}, + {NULL}, false, true }, // bookmarks -> bookmarks,history +#if !defined(OS_CHROMEOS) // plugins aren't allowed in ChromeOS + { "permissions4", {NULL}, + {NULL}, true, false }, // plugin -> plugin,tabs + { "plugin1", {NULL}, + {NULL}, true, false }, // plugin -> plugin + { "plugin2", {NULL}, + {NULL}, true, false }, // plugin -> none + { "plugin3", {NULL}, + {NULL}, false, true }, // none -> plugin +#endif + { "storage", {NULL}, + {NULL}, false, false }, // none -> storage + { "notifications", {NULL}, + {NULL}, false, false } // none -> notifications + }; + + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { + scoped_refptr<Extension> old_extension( + LoadManifest("allow_silent_upgrade", + std::string(kTests[i].base_name) + "_old.json")); + scoped_refptr<Extension> new_extension( + LoadManifest("allow_silent_upgrade", + std::string(kTests[i].base_name) + "_new.json")); + + ExtensionAPIPermissionSet granted_apis; + for (size_t j = 0; kTests[i].granted_apis[j] != NULL; ++j) { + granted_apis.insert(info->GetByName(kTests[i].granted_apis[j])->id()); + } + + URLPatternSet granted_hosts; + for (size_t j = 0; kTests[i].granted_hosts[j] != NULL; ++j) + AddPattern(&granted_hosts, kTests[i].granted_hosts[j]); + + EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json"; + if (!new_extension.get()) + continue; + + const ExtensionPermissionSet* old_p = old_extension->permission_set(); + const ExtensionPermissionSet* new_p = new_extension->permission_set(); + + EXPECT_EQ(kTests[i].expect_increase, old_p->HasLessPrivilegesThan(new_p)) + << kTests[i].base_name; + } +} + +TEST(ExtensionPermissionSetTest, PermissionMessages) { + // Ensure that all permissions that needs to show install UI actually have + // strings associated with them. + ExtensionAPIPermissionSet skip; + + skip.insert(ExtensionAPIPermission::kDefault); + + // These are considered "nuisance" or "trivial" permissions that don't need + // a prompt. + skip.insert(ExtensionAPIPermission::kContextMenus); + skip.insert(ExtensionAPIPermission::kIdle); + skip.insert(ExtensionAPIPermission::kNotification); + skip.insert(ExtensionAPIPermission::kUnlimitedStorage); + skip.insert(ExtensionAPIPermission::kContentSettings); + + // TODO(erikkay) add a string for this permission. + skip.insert(ExtensionAPIPermission::kBackground); + + skip.insert(ExtensionAPIPermission::kClipboardWrite); + + // The cookie permission does nothing unless you have associated host + // permissions. + skip.insert(ExtensionAPIPermission::kCookie); + + // The proxy permission is warned as part of host permission checks. + skip.insert(ExtensionAPIPermission::kProxy); + + // This permission requires explicit user action (context menu handler) + // so we won't prompt for it for now. + skip.insert(ExtensionAPIPermission::kFileBrowserHandler); + + // If you've turned on the experimental command-line flag, we don't need + // to warn you further. + skip.insert(ExtensionAPIPermission::kExperimental); + + // These are private. + skip.insert(ExtensionAPIPermission::kWebstorePrivate); + skip.insert(ExtensionAPIPermission::kFileBrowserPrivate); + skip.insert(ExtensionAPIPermission::kMediaPlayerPrivate); + skip.insert(ExtensionAPIPermission::kChromePrivate); + skip.insert(ExtensionAPIPermission::kChromeosInfoPrivate); + skip.insert(ExtensionAPIPermission::kWebSocketProxyPrivate); + + // Warned as part of host permissions. + skip.insert(ExtensionAPIPermission::kDevtools); + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + ExtensionAPIPermissionSet permissions = info->GetAll(); + for (ExtensionAPIPermissionSet::const_iterator i = permissions.begin(); + i != permissions.end(); ++i) { + ExtensionAPIPermission* permission = info->GetByID(*i); + EXPECT_TRUE(permission); + if (skip.count(*i)) { + EXPECT_EQ(ExtensionPermissionMessage::kNone, permission->message_id()) + << "unexpected message_id for " << permission->name(); + } else { + EXPECT_NE(ExtensionPermissionMessage::kNone, permission->message_id()) + << "missing message_id for " << permission->name(); + } + } +} + +// Tests the default permissions (empty API permission set). +TEST(ExtensionPermissionSetTest, DefaultFunctionAccess) { + const struct { + const char* permission_name; + bool expect_success; + } kTests[] = { + // Negative test. + { "non_existing_permission", false }, + // Test default module/package permission. + { "browserAction", true }, + { "browserActions", true }, + { "devtools", true }, + { "extension", true }, + { "i18n", true }, + { "pageAction", true }, + { "pageActions", true }, + { "test", true }, + // Some negative tests. + { "bookmarks", false }, + { "cookies", false }, + { "history", false }, + { "tabs.onUpdated", false }, + // Make sure we find the module name after stripping '.' and '/'. + { "browserAction/abcd/onClick", true }, + { "browserAction.abcd.onClick", true }, + // Test Tabs functions. + { "tabs.create", true}, + { "tabs.update", true}, + { "tabs.getSelected", false}, + }; + + ExtensionPermissionSet permissions; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { + EXPECT_EQ(kTests[i].expect_success, + permissions.HasAccessToFunction(kTests[i].permission_name)); + } +} + +TEST(ExtensionPermissionSetTest, GetWarningMessages_ManyHosts) { + scoped_refptr<Extension> extension; + + extension = LoadManifest("permissions", "many-hosts.json"); + std::vector<string16> warnings = + extension->permission_set()->GetWarningMessages(); + ASSERT_EQ(1u, warnings.size()); + EXPECT_EQ("Your data on www.google.com and encrypted.google.com", + UTF16ToUTF8(warnings[0])); +} + +TEST(ExtensionPermissionSetTest, GetWarningMessages_Plugins) { + scoped_refptr<Extension> extension; + scoped_ptr<ExtensionPermissionSet> permissions; + + extension = LoadManifest("permissions", "plugins.json"); + std::vector<string16> warnings = + extension->permission_set()->GetWarningMessages(); + // 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("All data on your computer and the websites you visit", + UTF16ToUTF8(warnings[0])); +#endif +} + +TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { + scoped_ptr<ExtensionPermissionSet> perm_set; + ExtensionAPIPermissionSet empty_perms; + std::vector<std::string> expected; + expected.push_back("www.foo.com"); + expected.push_back("www.bar.com"); + expected.push_back("www.baz.com"); + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + + { + SCOPED_TRACE("no dupes"); + + // Simple list with no dupes. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("two dupes"); + + // Add some dupes. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("schemes differ"); + + // Add a pattern that differs only by scheme. This should be filtered out. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path")); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("paths differ"); + + // Add some dupes by path. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath")); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("subdomains differ"); + + // We don't do anything special for subdomains. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path")); + + expected.push_back("monkey.www.bar.com"); + expected.push_back("bar.com"); + + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("RCDs differ"); + + // Now test for RCD uniquing. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path")); + + // This is an unknown RCD, which shouldn't be uniqued out. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path")); + // But it should only occur once. + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path")); + + expected.push_back("www.foo.xyzzy"); + + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("wildcards"); + + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*")); + + expected.push_back("*.google.com"); + + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } + + { + SCOPED_TRACE("scriptable hosts"); + explicit_hosts.ClearPatterns(); + scriptable_hosts.ClearPatterns(); + expected.clear(); + + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*")); + scriptable_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://*.example.com/*")); + + expected.push_back("*.google.com"); + expected.push_back("*.example.com"); + + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); + } +} + +TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_ComIsBestRcd) { + scoped_ptr<ExtensionPermissionSet> perm_set; + ExtensionAPIPermissionSet empty_perms; + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); + + std::vector<std::string> expected; + expected.push_back("www.foo.com"); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); +} + +TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) { + scoped_ptr<ExtensionPermissionSet> perm_set; + ExtensionAPIPermissionSet empty_perms; + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); + // No http://www.foo.com/path + + std::vector<std::string> expected; + expected.push_back("www.foo.net"); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); +} + +TEST(ExtensionPermissionSetTest, + GetDistinctHostsForDisplay_OrgIs3rdBestRcd) { + scoped_ptr<ExtensionPermissionSet> perm_set; + ExtensionAPIPermissionSet empty_perms; + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); + // No http://www.foo.net/path + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); + // No http://www.foo.com/path + + std::vector<std::string> expected; + expected.push_back("www.foo.org"); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); +} + +TEST(ExtensionPermissionSetTest, + GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) { + scoped_ptr<ExtensionPermissionSet> perm_set; + ExtensionAPIPermissionSet empty_perms; + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); + // No http://www.foo.org/path + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); + // No http://www.foo.net/path + explicit_hosts.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); + // No http://www.foo.com/path + + std::vector<std::string> expected; + expected.push_back("www.foo.ca"); + perm_set.reset(new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts)); + CompareLists(expected, perm_set->GetDistinctHostsForDisplay()); +} + +TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { + URLPatternSet elist1; + URLPatternSet elist2; + URLPatternSet slist1; + URLPatternSet slist2; + scoped_ptr<ExtensionPermissionSet> set1; + scoped_ptr<ExtensionPermissionSet> set2; + ExtensionAPIPermissionSet empty_perms; + elist1.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); + elist1.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); + + // Test that the host order does not matter. + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); + + set1.reset(new ExtensionPermissionSet(empty_perms, elist1, slist1)); + set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + + EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get())); + EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); + + // Test that paths are ignored. + elist2.ClearPatterns(); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*")); + set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get())); + EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); + + // Test that RCDs are ignored. + elist2.ClearPatterns(); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*")); + set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get())); + EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); + + // Test that subdomain wildcards are handled properly. + elist2.ClearPatterns(); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*")); + set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get())); + //TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337 + //EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); + + // Test that different domains count as different hosts. + elist2.ClearPatterns(); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path")); + set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get())); + EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); + + // Test that different subdomains count as different hosts. + elist2.ClearPatterns(); + elist2.AddPattern( + URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*")); + set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get())); + EXPECT_TRUE(set2->HasLessHostPrivilegesThan(set1.get())); +} + +TEST(ExtensionPermissionSetTest, GetAPIsAsStrings) { + ExtensionAPIPermissionSet apis; + URLPatternSet empty_set; + + apis.insert(ExtensionAPIPermission::kProxy); + apis.insert(ExtensionAPIPermission::kBackground); + apis.insert(ExtensionAPIPermission::kNotification); + apis.insert(ExtensionAPIPermission::kTab); + + ExtensionPermissionSet perm_set(apis, empty_set, empty_set); + std::set<std::string> api_names = perm_set.GetAPIsAsStrings(); + + // The result is correct if it has the same number of elements + // and we can convert it back to the id set. + EXPECT_EQ(4u, api_names.size()); + EXPECT_EQ(apis, + ExtensionPermissionsInfo::GetInstance()->GetAllByName(api_names)); +} + +TEST(ExtensionPermissionSetTest, IsEmpty) { + ExtensionAPIPermissionSet empty_apis; + URLPatternSet empty_extent; + + ExtensionPermissionSet perm_set; + EXPECT_TRUE(perm_set.IsEmpty()); + + perm_set = ExtensionPermissionSet(empty_apis, empty_extent, empty_extent); + EXPECT_TRUE(perm_set.IsEmpty()); + + ExtensionAPIPermissionSet non_empty_apis; + non_empty_apis.insert(ExtensionAPIPermission::kBackground); + perm_set = ExtensionPermissionSet( + non_empty_apis, empty_extent, empty_extent); + EXPECT_FALSE(perm_set.IsEmpty()); + + // Try non standard host + URLPatternSet non_empty_extent; + AddPattern(&non_empty_extent, "http://www.google.com/*"); + + perm_set = ExtensionPermissionSet( + empty_apis, non_empty_extent, empty_extent); + EXPECT_FALSE(perm_set.IsEmpty()); + + perm_set = ExtensionPermissionSet( + empty_apis, empty_extent, non_empty_extent); + EXPECT_FALSE(perm_set.IsEmpty()); +} diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc index c74f6f8..aa8555c 100644 --- a/chrome/common/extensions/extension_unittest.cc +++ b/chrome/common/extensions/extension_unittest.cc @@ -42,11 +42,6 @@ void CompareLists(const std::vector<std::string>& expected, } } -static void AddPattern(URLPatternSet* extent, const std::string& pattern) { - int schemes = URLPattern::SCHEME_ALL; - extent->AddPattern(URLPattern(schemes, pattern)); -} - static scoped_refptr<Extension> LoadManifestUnchecked( const std::string& dir, const std::string& test_file, @@ -394,7 +389,11 @@ TEST(ExtensionTest, EffectiveHostPermissions) { hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com"))); EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com"))); + EXPECT_TRUE(extension->permission_set()->HasEffectiveAccessToURL( + GURL("http://www.reddit.com"))); EXPECT_TRUE(hosts.MatchesURL(GURL("http://news.ycombinator.com"))); + EXPECT_TRUE(extension->permission_set()->HasEffectiveAccessToURL( + GURL("http://news.ycombinator.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts.json"); @@ -418,151 +417,6 @@ TEST(ExtensionTest, EffectiveHostPermissions) { EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts()); } -TEST(ExtensionTest, IsPrivilegeIncrease) { - const struct { - const char* base_name; - // Increase these sizes if you have more than 10. - const char* granted_apis[10]; - const char* granted_hosts[10]; - bool full_access; - bool expect_increase; - } kTests[] = { - { "allhosts1", {NULL}, {"http://*/", NULL}, false, - false }, // all -> all - { "allhosts2", {NULL}, {"http://*/", NULL}, false, - false }, // all -> one - { "allhosts3", {NULL}, {NULL}, false, true }, // one -> all - { "hosts1", {NULL}, - {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, - false }, // http://a,http://b -> http://a,http://b - { "hosts2", {NULL}, - {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, - true }, // http://a,http://b -> https://a,http://*.b - { "hosts3", {NULL}, - {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, - false }, // http://a,http://b -> http://a - { "hosts4", {NULL}, - {"http://www.google.com/", NULL}, false, - true }, // http://a -> http://a,http://b - { "hosts5", {"tabs", "notifications", NULL}, - {"http://*.example.com/", "http://*.example.com/*", - "http://*.example.co.uk/*", "http://*.example.com.au/*", - NULL}, false, - false }, // http://a,b,c -> http://a,b,c + https://a,b,c - { "hosts6", {"tabs", "notifications", NULL}, - {"http://*.example.com/", "http://*.example.com/*", NULL}, false, - false }, // http://a.com -> http://a.com + http://a.co.uk - { "permissions1", {"tabs", NULL}, - {NULL}, false, false }, // tabs -> tabs - { "permissions2", {"tabs", NULL}, - {NULL}, false, true }, // tabs -> tabs,bookmarks - { "permissions3", {NULL}, - {"http://*/*", NULL}, - false, true }, // http://a -> http://a,tabs - { "permissions5", {"bookmarks", NULL}, - {NULL}, false, true }, // bookmarks -> bookmarks,history -#if !defined(OS_CHROMEOS) // plugins aren't allowed in ChromeOS - { "permissions4", {NULL}, - {NULL}, true, false }, // plugin -> plugin,tabs - { "plugin1", {NULL}, - {NULL}, true, false }, // plugin -> plugin - { "plugin2", {NULL}, - {NULL}, true, false }, // plugin -> none - { "plugin3", {NULL}, - {NULL}, false, true }, // none -> plugin -#endif - { "storage", {NULL}, - {NULL}, false, false }, // none -> storage - { "notifications", {NULL}, - {NULL}, false, false } // none -> notifications - }; - - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { - scoped_refptr<Extension> old_extension( - LoadManifest("allow_silent_upgrade", - std::string(kTests[i].base_name) + "_old.json")); - scoped_refptr<Extension> new_extension( - LoadManifest("allow_silent_upgrade", - std::string(kTests[i].base_name) + "_new.json")); - - std::set<std::string> granted_apis; - for (size_t j = 0; kTests[i].granted_apis[j] != NULL; ++j) - granted_apis.insert(kTests[i].granted_apis[j]); - - URLPatternSet granted_hosts; - for (size_t j = 0; kTests[i].granted_hosts[j] != NULL; ++j) - AddPattern(&granted_hosts, kTests[i].granted_hosts[j]); - - EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json"; - if (!new_extension.get()) - continue; - - EXPECT_EQ(kTests[i].expect_increase, - Extension::IsPrivilegeIncrease(kTests[i].full_access, - granted_apis, - granted_hosts, - new_extension.get())) - << kTests[i].base_name; - } -} - -TEST(ExtensionTest, PermissionMessages) { - // Ensure that all permissions that needs to show install UI actually have - // strings associated with them. - - std::set<std::string> skip; - - // These are considered "nuisance" or "trivial" permissions that don't need - // a prompt. - skip.insert(Extension::kContextMenusPermission); - skip.insert(Extension::kIdlePermission); - skip.insert(Extension::kNotificationPermission); - skip.insert(Extension::kUnlimitedStoragePermission); - skip.insert(Extension::kContentSettingsPermission); - - // TODO(erikkay) add a string for this permission. - skip.insert(Extension::kBackgroundPermission); - - skip.insert(Extension::kClipboardWritePermission); - - // The cookie permission does nothing unless you have associated host - // permissions. - skip.insert(Extension::kCookiePermission); - - // The proxy permission is warned as part of host permission checks. - skip.insert(Extension::kProxyPermission); - - // This permission requires explicit user action (context menu handler) - // so we won't prompt for it for now. - skip.insert(Extension::kFileBrowserHandlerPermission); - - // If you've turned on the experimental command-line flag, we don't need - // to warn you further. - skip.insert(Extension::kExperimentalPermission); - - // These are private. - skip.insert(Extension::kWebstorePrivatePermission); - skip.insert(Extension::kFileBrowserPrivatePermission); - skip.insert(Extension::kMediaPlayerPrivatePermission); - skip.insert(Extension::kChromePrivatePermission); - skip.insert(Extension::kChromeosInfoPrivatePermission); - skip.insert(Extension::kWebSocketProxyPrivatePermission); - - const Extension::PermissionMessage::MessageId ID_NONE = - Extension::PermissionMessage::ID_NONE; - - for (size_t i = 0; i < Extension::kNumPermissions; ++i) { - Extension::Permission permission = Extension::kPermissions[i]; - if (skip.count(permission.name)) { - EXPECT_EQ(ID_NONE, permission.message_id) - << "unexpected message_id for " << permission.name; - } else { - EXPECT_NE(ID_NONE, permission.message_id) - << "missing message_id for " << permission.name; - } - } -} - // Returns a copy of |source| resized to |size| x |size|. static SkBitmap ResizedCopy(const SkBitmap& source, int size) { return skia::ImageOperations::Resize(source, @@ -687,7 +541,7 @@ TEST(ExtensionTest, ApiPermissions) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { EXPECT_EQ(kTests[i].expect_success, - extension->HasApiPermission(kTests[i].permission_name)) + extension->HasAPIPermission(kTests[i].permission_name)) << "Permission being tested: " << kTests[i].permission_name; } } @@ -904,257 +758,6 @@ TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) { EXPECT_FALSE(extension->HasHostPermission(settings_url)); } -TEST(ExtensionTest, GetDistinctHostsForDisplay) { - std::vector<std::string> expected; - expected.push_back("www.foo.com"); - expected.push_back("www.bar.com"); - expected.push_back("www.baz.com"); - URLPatternList actual; - - { - SCOPED_TRACE("no dupes"); - - // Simple list with no dupes. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } - - { - SCOPED_TRACE("two dupes"); - - // Add some dupes. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } - - { - SCOPED_TRACE("schemes differ"); - - // Add a pattern that differs only by scheme. This should be filtered out. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path")); - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } - - { - SCOPED_TRACE("paths differ"); - - // Add some dupes by path. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath")); - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } - - { - SCOPED_TRACE("subdomains differ"); - - // We don't do anything special for subdomains. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path")); - - expected.push_back("monkey.www.bar.com"); - expected.push_back("bar.com"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } - - { - SCOPED_TRACE("RCDs differ"); - - // Now test for RCD uniquing. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path")); - - // This is an unknown RCD, which shouldn't be uniqued out. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path")); - // But it should only occur once. - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path")); - - expected.push_back("www.foo.xyzzy"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } - - { - SCOPED_TRACE("wildcards"); - - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*")); - - expected.push_back("*.google.com"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); - } -} - -TEST(ExtensionTest, GetDistinctHostsForDisplay_ComIsBestRcd) { - URLPatternList actual; - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); - - std::vector<std::string> expected; - expected.push_back("www.foo.com"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); -} - -TEST(ExtensionTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) { - URLPatternList actual; - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); - // No http://www.foo.com/path - - std::vector<std::string> expected; - expected.push_back("www.foo.net"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); -} - -TEST(ExtensionTest, GetDistinctHostsForDisplay_OrgIs3rdBestRcd) { - URLPatternList actual; - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); - // No http://www.foo.net/path - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); - // No http://www.foo.com/path - - std::vector<std::string> expected; - expected.push_back("www.foo.org"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); -} - -TEST(ExtensionTest, GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) { - URLPatternList actual; - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); - // No http://www.foo.org/path - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); - // No http://www.foo.net/path - actual.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); - // No http://www.foo.com/path - - std::vector<std::string> expected; - expected.push_back("www.foo.ca"); - - CompareLists(expected, - Extension::GetDistinctHostsForDisplay(actual)); -} - -TEST(ExtensionTest, IsElevatedHostList) { - URLPatternList list1; - URLPatternList list2; - - list1.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); - list1.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); - - // Test that the host order does not matter. - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); - - EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2)); - EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); - - // Test that paths are ignored. - list2.clear(); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*")); - EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2)); - EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); - - // Test that RCDs are ignored. - list2.clear(); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*")); - EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2)); - EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); - - // Test that subdomain wildcards are handled properly. - list2.clear(); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*")); - EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2)); - //TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337 - //EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); - - // Test that different domains count as different hosts. - list2.clear(); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path")); - EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2)); - EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); - - // Test that different subdomains count as different hosts. - list2.clear(); - list2.push_back( - URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*")); - EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2)); - EXPECT_TRUE(Extension::IsElevatedHostList(list2, list1)); -} - TEST(ExtensionTest, GenerateId) { std::string result; EXPECT_TRUE(Extension::GenerateId("", &result)); diff --git a/chrome/common/extensions/url_pattern_set.cc b/chrome/common/extensions/url_pattern_set.cc index c8374ef..65f8635 100644 --- a/chrome/common/extensions/url_pattern_set.cc +++ b/chrome/common/extensions/url_pattern_set.cc @@ -7,6 +7,22 @@ #include "chrome/common/extensions/url_pattern.h" #include "googleurl/src/gurl.h" +// static +void URLPatternSet::CreateUnion(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out) { + const URLPatternList list1 = set1.patterns(); + const URLPatternList list2 = set2.patterns(); + + out->ClearPatterns(); + + for (size_t i = 0; i < list1.size(); ++i) + out->AddPattern(list1.at(i)); + + for (size_t i = 0; i < list2.size(); ++i) + out->AddPattern(list2.at(i)); +} + URLPatternSet::URLPatternSet() { } diff --git a/chrome/common/extensions/url_pattern_set.h b/chrome/common/extensions/url_pattern_set.h index ac50afc..2120e547 100644 --- a/chrome/common/extensions/url_pattern_set.h +++ b/chrome/common/extensions/url_pattern_set.h @@ -15,6 +15,12 @@ class GURL; // Represents the set of URLs an extension uses for web content. class URLPatternSet { public: + // Clears |out| and populates the set with the union of |set1| and |set2|. + // NOTE: this does not discard duplicates. + static void CreateUnion(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out); + URLPatternSet(); URLPatternSet(const URLPatternSet& rhs); ~URLPatternSet(); diff --git a/chrome/common/extensions/url_pattern_set_unittest.cc b/chrome/common/extensions/url_pattern_set_unittest.cc index 5336410..8c05f2c 100644 --- a/chrome/common/extensions/url_pattern_set_unittest.cc +++ b/chrome/common/extensions/url_pattern_set_unittest.cc @@ -7,6 +7,27 @@ #include "googleurl/src/gurl.h" #include "testing/gtest/include/gtest/gtest.h" +namespace { + +static void AssertEqualExtents(const URLPatternSet& extent1, + const URLPatternSet& extent2) { + URLPatternList patterns1 = extent1.patterns(); + URLPatternList patterns2 = extent2.patterns(); + std::set<std::string> strings1; + EXPECT_EQ(patterns1.size(), patterns2.size()); + + for (size_t i = 0; i < patterns1.size(); ++i) + strings1.insert(patterns1.at(i).GetAsString()); + + std::set<std::string> strings2; + for (size_t i = 0; i < patterns2.size(); ++i) + strings2.insert(patterns2.at(i).GetAsString()); + + EXPECT_EQ(strings1, strings2); +} + +} // namespace + static const int kAllSchemes = URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | @@ -60,3 +81,35 @@ TEST(URLPatternSetTest, OverlapsWith) { EXPECT_TRUE(extent1.OverlapsWith(extent3)); EXPECT_TRUE(extent3.OverlapsWith(extent1)); } + +TEST(URLPatternSetTest, CreateUnion) { + URLPatternSet empty_extent; + + URLPatternSet extent1; + extent1.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); + extent1.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/b*")); + + URLPatternSet expected; + expected.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); + expected.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/b*")); + + // Union with an empty set. + URLPatternSet result; + URLPatternSet::CreateUnion(extent1, empty_extent, &result); + AssertEqualExtents(expected, result); + + // Union with a real set (including a duplicate). + URLPatternSet extent2; + extent2.AddPattern(URLPattern(kAllSchemes, "http://www.reddit.com/f*")); + extent2.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/z*")); + extent2.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); + + expected.AddPattern(URLPattern(kAllSchemes, "http://www.reddit.com/f*")); + expected.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/z*")); + // CreateUnion does not filter out duplicates right now. + expected.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); + + result.ClearPatterns(); + URLPatternSet::CreateUnion(extent1, extent2, &result); + AssertEqualExtents(expected, result); +} |