blob: 1a1e635184d313f5cbf92ce6b37877d7405a94e7 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/declarative_net_request/composite_matcher.h"
#include <algorithm>
#include <set>
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "extensions/browser/api/declarative_net_request/flat/extension_ruleset_generated.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
namespace extensions {
namespace declarative_net_request {
namespace flat_rule = url_pattern_index::flat;
using PageAccess = PermissionsData::PageAccess;
using RedirectAction = CompositeMatcher::RedirectAction;
namespace {
bool AreIDsUnique(const CompositeMatcher::MatcherList& matchers) {
std::set<size_t> ids;
for (const auto& matcher : matchers) {
bool did_insert = ids.insert(matcher->id()).second;
if (!did_insert)
return false;
}
return true;
}
bool AreSortedPrioritiesUnique(const CompositeMatcher::MatcherList& matchers) {
base::Optional<size_t> previous_priority;
for (const auto& matcher : matchers) {
if (matcher->priority() == previous_priority)
return false;
previous_priority = matcher->priority();
}
return true;
}
bool HasMatchingAllowRule(const RulesetMatcher* matcher,
const RequestParams& params) {
if (!params.allow_rule_cache.contains(matcher))
params.allow_rule_cache[matcher] = matcher->HasMatchingAllowRule(params);
return params.allow_rule_cache[matcher];
}
// Upgrades the url's scheme to HTTPS.
GURL GetUpgradedUrl(const GURL& url) {
DCHECK(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kFtpScheme));
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kHttpsScheme);
return url.ReplaceComponents(replacements);
}
// Compares |redirect_rule| and |upgrade_rule| and determines the redirect URL
// based on the rule with the higher priority.
GURL GetUrlByRulePriority(const flat_rule::UrlRule* redirect_rule,
const flat_rule::UrlRule* upgrade_rule,
const GURL& request_url,
GURL redirect_rule_url) {
DCHECK(upgrade_rule || redirect_rule);
if (!upgrade_rule)
return redirect_rule_url;
if (!redirect_rule)
return GetUpgradedUrl(request_url);
return upgrade_rule->priority() > redirect_rule->priority()
? GetUpgradedUrl(request_url)
: redirect_rule_url;
}
} // namespace
RedirectAction::RedirectAction(base::Optional<GURL> redirect_url,
bool notify_request_withheld)
: redirect_url(std::move(redirect_url)),
notify_request_withheld(notify_request_withheld) {}
RedirectAction::~RedirectAction() = default;
RedirectAction::RedirectAction(RedirectAction&&) = default;
RedirectAction& RedirectAction::operator=(RedirectAction&& other) = default;
CompositeMatcher::CompositeMatcher(MatcherList matchers)
: matchers_(std::move(matchers)) {
SortMatchersByPriority();
DCHECK(AreIDsUnique(matchers_));
}
CompositeMatcher::~CompositeMatcher() = default;
void CompositeMatcher::AddOrUpdateRuleset(
std::unique_ptr<RulesetMatcher> new_matcher) {
// A linear search is ok since the number of rulesets per extension is
// expected to be quite small.
auto it = std::find_if(
matchers_.begin(), matchers_.end(),
[&new_matcher](const std::unique_ptr<RulesetMatcher>& matcher) {
return new_matcher->id() == matcher->id();
});
if (it == matchers_.end()) {
matchers_.push_back(std::move(new_matcher));
SortMatchersByPriority();
} else {
// Update the matcher. The priority for a given ID should remain the same.
DCHECK_EQ(new_matcher->priority(), (*it)->priority());
*it = std::move(new_matcher);
}
// Clear the renderers' cache so that they take the updated rules into
// account.
ClearRendererCacheOnNavigation();
has_any_extra_headers_matcher_.reset();
}
bool CompositeMatcher::ShouldBlockRequest(const RequestParams& params) const {
// TODO(karandeepb): change this to report time in micro-seconds.
SCOPED_UMA_HISTOGRAM_TIMER(
"Extensions.DeclarativeNetRequest.ShouldBlockRequestTime."
"SingleExtension");
for (const auto& matcher : matchers_) {
if (HasMatchingAllowRule(matcher.get(), params))
return false;
if (matcher->HasMatchingBlockRule(params))
return true;
}
return false;
}
RedirectAction CompositeMatcher::ShouldRedirectRequest(
const RequestParams& params,
PageAccess page_access) const {
// TODO(karandeepb): change this to report time in micro-seconds.
SCOPED_UMA_HISTOGRAM_TIMER(
"Extensions.DeclarativeNetRequest.ShouldRedirectRequestTime."
"SingleExtension");
bool notify_request_withheld = false;
for (const auto& matcher : matchers_) {
if (HasMatchingAllowRule(matcher.get(), params)) {
return RedirectAction(base::nullopt /* redirect_url */,
false /* notify_request_withheld */);
}
if (page_access == PageAccess::kAllowed) {
GURL redirect_rule_url;
const flat_rule::UrlRule* redirect_rule =
matcher->GetRedirectRule(params, &redirect_rule_url);
const flat_rule::UrlRule* upgrade_rule = matcher->GetUpgradeRule(params);
if (!upgrade_rule && !redirect_rule)
continue;
GURL redirect_url =
GetUrlByRulePriority(redirect_rule, upgrade_rule, *params.url,
std::move(redirect_rule_url));
return RedirectAction(std::move(redirect_url),
false /* notify_request_withheld */);
}
// If the extension has no host permissions for the request, it can still
// upgrade the request.
if (matcher->GetUpgradeRule(params)) {
return RedirectAction(GetUpgradedUrl(*params.url),
false /* notify_request_withheld */);
}
GURL redirect_url;
notify_request_withheld |=
(page_access == PageAccess::kWithheld &&
matcher->GetRedirectRule(params, &redirect_url));
}
return RedirectAction(base::nullopt /* redirect_url */,
notify_request_withheld);
}
uint8_t CompositeMatcher::GetRemoveHeadersMask(const RequestParams& params,
uint8_t current_mask) const {
uint8_t mask = current_mask;
for (const auto& matcher : matchers_) {
// The allow rule will override lower priority remove header rules.
if (HasMatchingAllowRule(matcher.get(), params))
return mask;
mask |= matcher->GetRemoveHeadersMask(params, mask);
}
return mask;
}
bool CompositeMatcher::HasAnyExtraHeadersMatcher() const {
if (!has_any_extra_headers_matcher_.has_value())
has_any_extra_headers_matcher_ = ComputeHasAnyExtraHeadersMatcher();
return has_any_extra_headers_matcher_.value();
}
bool CompositeMatcher::ComputeHasAnyExtraHeadersMatcher() const {
for (const auto& matcher : matchers_) {
if (matcher->IsExtraHeadersMatcher())
return true;
}
return false;
}
void CompositeMatcher::SortMatchersByPriority() {
std::sort(matchers_.begin(), matchers_.end(),
[](const std::unique_ptr<RulesetMatcher>& a,
const std::unique_ptr<RulesetMatcher>& b) {
return a->priority() > b->priority();
});
DCHECK(AreSortedPrioritiesUnique(matchers_));
}
} // namespace declarative_net_request
} // namespace extensions