| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/stl_util.h" |
| #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_condition.h" |
| #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h" |
| #include "chrome/browser/extensions/api/web_request/web_request_api_helpers.h" |
| #include "chrome/browser/extensions/api/web_request/web_request_permissions.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "extensions/common/error_utils.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "net/url_request/url_request.h" |
| |
| namespace { |
| |
| const char kActionCannotBeExecuted[] = "The action '*' can never be executed " |
| "because there are is no time in the request life-cycle during which the " |
| "conditions can be checked and the action can possibly be executed."; |
| |
| const char kAllURLsPermissionNeeded[] = |
| "To execute the action '*', you need to request host permission for all " |
| "hosts."; |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| WebRequestRulesRegistry::WebRequestRulesRegistry( |
| Profile* profile, |
| RulesCacheDelegate* cache_delegate, |
| const WebViewKey& webview_key) |
| : RulesRegistry(profile, |
| declarative_webrequest_constants::kOnRequest, |
| content::BrowserThread::IO, |
| cache_delegate, |
| webview_key), |
| profile_id_(profile) { |
| if (profile) |
| extension_info_map_ = ExtensionSystem::Get(profile)->info_map(); |
| } |
| |
| std::set<const WebRequestRule*> WebRequestRulesRegistry::GetMatches( |
| const WebRequestData& request_data_without_ids) const { |
| RuleSet result; |
| |
| WebRequestDataWithMatchIds request_data(&request_data_without_ids); |
| request_data.url_match_ids = url_matcher_.MatchURL( |
| request_data.data->request->url()); |
| request_data.first_party_url_match_ids = url_matcher_.MatchURL( |
| request_data.data->request->first_party_for_cookies()); |
| |
| // 1st phase -- add all rules with some conditions without UrlFilter |
| // attributes. |
| for (RuleSet::const_iterator it = rules_with_untriggered_conditions_.begin(); |
| it != rules_with_untriggered_conditions_.end(); ++it) { |
| if ((*it)->conditions().IsFulfilled(-1, request_data)) |
| result.insert(*it); |
| } |
| |
| // 2nd phase -- add all rules with some conditions triggered by URL matches. |
| AddTriggeredRules(request_data.url_match_ids, request_data, &result); |
| AddTriggeredRules(request_data.first_party_url_match_ids, |
| request_data, &result); |
| |
| return result; |
| } |
| |
| std::list<LinkedPtrEventResponseDelta> WebRequestRulesRegistry::CreateDeltas( |
| const InfoMap* extension_info_map, |
| const WebRequestData& request_data, |
| bool crosses_incognito) { |
| if (webrequest_rules_.empty()) |
| return std::list<LinkedPtrEventResponseDelta>(); |
| |
| std::set<const WebRequestRule*> matches = GetMatches(request_data); |
| |
| // Sort all matching rules by their priority so that they can be processed |
| // in decreasing order. |
| typedef std::pair<WebRequestRule::Priority, WebRequestRule::GlobalRuleId> |
| PriorityRuleIdPair; |
| std::vector<PriorityRuleIdPair> ordered_matches; |
| ordered_matches.reserve(matches.size()); |
| for (std::set<const WebRequestRule*>::iterator i = matches.begin(); |
| i != matches.end(); ++i) { |
| ordered_matches.push_back(make_pair((*i)->priority(), (*i)->id())); |
| } |
| // Sort from rbegin to rend in order to get descending priority order. |
| std::sort(ordered_matches.rbegin(), ordered_matches.rend()); |
| |
| // Build a map that maps each extension id to the minimum required priority |
| // for rules of that extension. Initially, this priority is -infinite and |
| // will be increased when the rules are processed and raise the bar via |
| // WebRequestIgnoreRulesActions. |
| typedef std::string ExtensionId; |
| typedef std::map<ExtensionId, WebRequestRule::Priority> MinPriorities; |
| typedef std::map<ExtensionId, std::set<std::string> > IgnoreTags; |
| MinPriorities min_priorities; |
| IgnoreTags ignore_tags; |
| for (std::vector<PriorityRuleIdPair>::iterator i = ordered_matches.begin(); |
| i != ordered_matches.end(); ++i) { |
| const WebRequestRule::GlobalRuleId& rule_id = i->second; |
| const ExtensionId& extension_id = rule_id.first; |
| min_priorities[extension_id] = std::numeric_limits<int>::min(); |
| } |
| |
| // Create deltas until we have passed the minimum priority. |
| std::list<LinkedPtrEventResponseDelta> result; |
| for (std::vector<PriorityRuleIdPair>::iterator i = ordered_matches.begin(); |
| i != ordered_matches.end(); ++i) { |
| const WebRequestRule::Priority priority_of_rule = i->first; |
| const WebRequestRule::GlobalRuleId& rule_id = i->second; |
| const ExtensionId& extension_id = rule_id.first; |
| const WebRequestRule* rule = |
| webrequest_rules_[rule_id.first][rule_id.second].get(); |
| CHECK(rule); |
| |
| // Skip rule if a previous rule of this extension instructed to ignore |
| // all rules with a lower priority than min_priorities[extension_id]. |
| int current_min_priority = min_priorities[extension_id]; |
| if (priority_of_rule < current_min_priority) |
| continue; |
| |
| if (!rule->tags().empty() && !ignore_tags[extension_id].empty()) { |
| bool ignore_rule = false; |
| const WebRequestRule::Tags& tags = rule->tags(); |
| for (WebRequestRule::Tags::const_iterator i = tags.begin(); |
| !ignore_rule && i != tags.end(); |
| ++i) { |
| ignore_rule |= ContainsKey(ignore_tags[extension_id], *i); |
| } |
| if (ignore_rule) |
| continue; |
| } |
| |
| std::list<LinkedPtrEventResponseDelta> rule_result; |
| WebRequestAction::ApplyInfo apply_info = { |
| extension_info_map, request_data, crosses_incognito, &rule_result, |
| &ignore_tags[extension_id] |
| }; |
| rule->Apply(&apply_info); |
| result.splice(result.begin(), rule_result); |
| |
| min_priorities[extension_id] = std::max(current_min_priority, |
| rule->GetMinimumPriority()); |
| } |
| return result; |
| } |
| |
| std::string WebRequestRulesRegistry::AddRulesImpl( |
| const std::string& extension_id, |
| const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) { |
| typedef std::pair<WebRequestRule::RuleId, linked_ptr<WebRequestRule> > |
| IdRulePair; |
| typedef std::vector<IdRulePair> RulesVector; |
| |
| base::Time extension_installation_time = |
| GetExtensionInstallationTime(extension_id); |
| |
| std::string error; |
| RulesVector new_webrequest_rules; |
| new_webrequest_rules.reserve(rules.size()); |
| const Extension* extension = |
| extension_info_map_->extensions().GetByID(extension_id); |
| RulesMap& registered_rules = webrequest_rules_[extension_id]; |
| |
| for (std::vector<linked_ptr<RulesRegistry::Rule> >::const_iterator rule = |
| rules.begin(); rule != rules.end(); ++rule) { |
| const WebRequestRule::RuleId& rule_id(*(*rule)->id); |
| DCHECK(registered_rules.find(rule_id) == registered_rules.end()); |
| |
| scoped_ptr<WebRequestRule> webrequest_rule(WebRequestRule::Create( |
| url_matcher_.condition_factory(), |
| extension, extension_installation_time, *rule, |
| base::Bind(&Checker, base::Unretained(extension)), |
| &error)); |
| if (!error.empty()) { |
| // We don't return here, because we want to clear temporary |
| // condition sets in the url_matcher_. |
| break; |
| } |
| |
| new_webrequest_rules.push_back( |
| IdRulePair(rule_id, make_linked_ptr(webrequest_rule.release()))); |
| } |
| |
| if (!error.empty()) { |
| // Clean up temporary condition sets created during rule creation. |
| url_matcher_.ClearUnusedConditionSets(); |
| return error; |
| } |
| |
| // Wohoo, everything worked fine. |
| registered_rules.insert(new_webrequest_rules.begin(), |
| new_webrequest_rules.end()); |
| |
| // Create the triggers. |
| for (RulesVector::const_iterator i = new_webrequest_rules.begin(); |
| i != new_webrequest_rules.end(); ++i) { |
| URLMatcherConditionSet::Vector url_condition_sets; |
| const WebRequestConditionSet& conditions = i->second->conditions(); |
| conditions.GetURLMatcherConditionSets(&url_condition_sets); |
| for (URLMatcherConditionSet::Vector::iterator j = |
| url_condition_sets.begin(); j != url_condition_sets.end(); ++j) { |
| rule_triggers_[(*j)->id()] = i->second.get(); |
| } |
| } |
| |
| // Register url patterns in |url_matcher_| and |
| // |rules_with_untriggered_conditions_|. |
| URLMatcherConditionSet::Vector all_new_condition_sets; |
| for (RulesVector::const_iterator i = new_webrequest_rules.begin(); |
| i != new_webrequest_rules.end(); ++i) { |
| i->second->conditions().GetURLMatcherConditionSets(&all_new_condition_sets); |
| if (i->second->conditions().HasConditionsWithoutUrls()) |
| rules_with_untriggered_conditions_.insert(i->second.get()); |
| } |
| url_matcher_.AddConditionSets(all_new_condition_sets); |
| |
| ClearCacheOnNavigation(); |
| |
| if (profile_id_ && !registered_rules.empty()) { |
| content::BrowserThread::PostTask( |
| content::BrowserThread::UI, FROM_HERE, |
| base::Bind(&extension_web_request_api_helpers::NotifyWebRequestAPIUsed, |
| profile_id_, make_scoped_refptr(extension))); |
| } |
| |
| return std::string(); |
| } |
| |
| std::string WebRequestRulesRegistry::RemoveRulesImpl( |
| const std::string& extension_id, |
| const std::vector<std::string>& rule_identifiers) { |
| // URLMatcherConditionSet IDs that can be removed from URLMatcher. |
| std::vector<URLMatcherConditionSet::ID> remove_from_url_matcher; |
| RulesMap& registered_rules = webrequest_rules_[extension_id]; |
| |
| for (std::vector<std::string>::const_iterator i = rule_identifiers.begin(); |
| i != rule_identifiers.end(); ++i) { |
| // Skip unknown rules. |
| RulesMap::iterator webrequest_rules_entry = registered_rules.find(*i); |
| if (webrequest_rules_entry == registered_rules.end()) |
| continue; |
| |
| // Remove all triggers but collect their IDs. |
| CleanUpAfterRule(webrequest_rules_entry->second.get(), |
| &remove_from_url_matcher); |
| |
| // Removes the owning references to (and thus deletes) the rule. |
| registered_rules.erase(webrequest_rules_entry); |
| } |
| if (registered_rules.empty()) |
| webrequest_rules_.erase(extension_id); |
| |
| // Clear URLMatcher based on condition_set_ids that are not needed any more. |
| url_matcher_.RemoveConditionSets(remove_from_url_matcher); |
| |
| ClearCacheOnNavigation(); |
| |
| return std::string(); |
| } |
| |
| std::string WebRequestRulesRegistry::RemoveAllRulesImpl( |
| const std::string& extension_id) { |
| // First we get out all URLMatcherConditionSets and remove the rule references |
| // from |rules_with_untriggered_conditions_|. |
| std::vector<URLMatcherConditionSet::ID> remove_from_url_matcher; |
| for (RulesMap::const_iterator it = webrequest_rules_[extension_id].begin(); |
| it != webrequest_rules_[extension_id].end(); |
| ++it) { |
| CleanUpAfterRule(it->second.get(), &remove_from_url_matcher); |
| } |
| url_matcher_.RemoveConditionSets(remove_from_url_matcher); |
| |
| webrequest_rules_.erase(extension_id); |
| ClearCacheOnNavigation(); |
| return std::string(); |
| } |
| |
| void WebRequestRulesRegistry::CleanUpAfterRule( |
| const WebRequestRule* rule, |
| std::vector<URLMatcherConditionSet::ID>* remove_from_url_matcher) { |
| URLMatcherConditionSet::Vector condition_sets; |
| rule->conditions().GetURLMatcherConditionSets(&condition_sets); |
| for (URLMatcherConditionSet::Vector::iterator j = condition_sets.begin(); |
| j != condition_sets.end(); |
| ++j) { |
| remove_from_url_matcher->push_back((*j)->id()); |
| rule_triggers_.erase((*j)->id()); |
| } |
| rules_with_untriggered_conditions_.erase(rule); |
| } |
| |
| bool WebRequestRulesRegistry::IsEmpty() const { |
| // Easy first. |
| if (!rule_triggers_.empty() && url_matcher_.IsEmpty()) |
| return false; |
| |
| // Now all the registered rules for each extensions. |
| for (std::map<WebRequestRule::ExtensionId, RulesMap>::const_iterator it = |
| webrequest_rules_.begin(); |
| it != webrequest_rules_.end(); |
| ++it) { |
| if (!it->second.empty()) |
| return false; |
| } |
| return true; |
| } |
| |
| WebRequestRulesRegistry::~WebRequestRulesRegistry() {} |
| |
| base::Time WebRequestRulesRegistry::GetExtensionInstallationTime( |
| const std::string& extension_id) const { |
| return extension_info_map_->GetInstallTime(extension_id); |
| } |
| |
| void WebRequestRulesRegistry::ClearCacheOnNavigation() { |
| extension_web_request_api_helpers::ClearCacheOnNavigation(); |
| } |
| |
| // static |
| bool WebRequestRulesRegistry::Checker(const Extension* extension, |
| const WebRequestConditionSet* conditions, |
| const WebRequestActionSet* actions, |
| std::string* error) { |
| return (StageChecker(conditions, actions, error) && |
| HostPermissionsChecker(extension, actions, error)); |
| } |
| |
| // static |
| bool WebRequestRulesRegistry::HostPermissionsChecker( |
| const Extension* extension, |
| const WebRequestActionSet* actions, |
| std::string* error) { |
| if (PermissionsData::HasEffectiveAccessToAllHosts(extension)) |
| return true; |
| |
| // Without the permission for all URLs, actions with the STRATEGY_DEFAULT |
| // should not be registered, they would never be able to execute. |
| for (WebRequestActionSet::Actions::const_iterator action_iter = |
| actions->actions().begin(); |
| action_iter != actions->actions().end(); |
| ++action_iter) { |
| if ((*action_iter)->host_permissions_strategy() == |
| WebRequestAction::STRATEGY_DEFAULT) { |
| *error = ErrorUtils::FormatErrorMessage(kAllURLsPermissionNeeded, |
| (*action_iter)->GetName()); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // static |
| bool WebRequestRulesRegistry::StageChecker( |
| const WebRequestConditionSet* conditions, |
| const WebRequestActionSet* actions, |
| std::string* error) { |
| // Actions and conditions can be checked and executed in specific stages |
| // of each web request. A rule is inconsistent if there is an action that |
| // can only be triggered in stages in which no condition can be evaluated. |
| |
| // In which stages there are conditions to evaluate. |
| int condition_stages = 0; |
| for (WebRequestConditionSet::Conditions::const_iterator condition_iter = |
| conditions->conditions().begin(); |
| condition_iter != conditions->conditions().end(); |
| ++condition_iter) { |
| condition_stages |= (*condition_iter)->stages(); |
| } |
| |
| for (WebRequestActionSet::Actions::const_iterator action_iter = |
| actions->actions().begin(); |
| action_iter != actions->actions().end(); |
| ++action_iter) { |
| // Test the intersection of bit masks, this is intentionally & and not &&. |
| if ((*action_iter)->stages() & condition_stages) |
| continue; |
| // We only get here if no matching condition was found. |
| *error = ErrorUtils::FormatErrorMessage(kActionCannotBeExecuted, |
| (*action_iter)->GetName()); |
| return false; |
| } |
| return true; |
| } |
| void WebRequestRulesRegistry::AddTriggeredRules( |
| const URLMatches& url_matches, |
| const WebRequestCondition::MatchData& request_data, |
| RuleSet* result) const { |
| for (URLMatches::const_iterator url_match = url_matches.begin(); |
| url_match != url_matches.end(); ++url_match) { |
| RuleTriggers::const_iterator rule_trigger = rule_triggers_.find(*url_match); |
| CHECK(rule_trigger != rule_triggers_.end()); |
| if (!ContainsKey(*result, rule_trigger->second) && |
| rule_trigger->second->conditions().IsFulfilled(*url_match, |
| request_data)) |
| result->insert(rule_trigger->second); |
| } |
| } |
| |
| } // namespace extensions |