blob: ba3196665d7094b764147177c72dc66e156c16ef [file] [log] [blame]
// Copyright 2017 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/ruleset_manager.h"
#include <algorithm>
#include <tuple>
#include <utility>
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "components/web_cache/browser/web_cache_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
#include "extensions/browser/api/declarative_net_request/ruleset_matcher.h"
#include "extensions/browser/api/web_request/web_request_info.h"
#include "extensions/browser/info_map.h"
#include "extensions/common/api/declarative_net_request/utils.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
namespace extensions {
namespace declarative_net_request {
namespace {
namespace flat_rule = url_pattern_index::flat;
// Maps content::ResourceType to flat_rule::ElementType.
flat_rule::ElementType GetElementType(content::ResourceType type) {
switch (type) {
case content::RESOURCE_TYPE_LAST_TYPE:
case content::RESOURCE_TYPE_PREFETCH:
case content::RESOURCE_TYPE_SUB_RESOURCE:
// TODO(crbug.com/696822): Add support for main frame and csp report to
// url_pattern_index. These are supported by the Web Request API.
case content::RESOURCE_TYPE_MAIN_FRAME:
case content::RESOURCE_TYPE_CSP_REPORT:
return flat_rule::ElementType_OTHER;
case content::RESOURCE_TYPE_SCRIPT:
case content::RESOURCE_TYPE_WORKER:
case content::RESOURCE_TYPE_SHARED_WORKER:
case content::RESOURCE_TYPE_SERVICE_WORKER:
return flat_rule::ElementType_SCRIPT;
case content::RESOURCE_TYPE_IMAGE:
case content::RESOURCE_TYPE_FAVICON:
return flat_rule::ElementType_IMAGE;
case content::RESOURCE_TYPE_STYLESHEET:
return flat_rule::ElementType_STYLESHEET;
case content::RESOURCE_TYPE_OBJECT:
case content::RESOURCE_TYPE_PLUGIN_RESOURCE:
return flat_rule::ElementType_OBJECT;
case content::RESOURCE_TYPE_XHR:
return flat_rule::ElementType_XMLHTTPREQUEST;
case content::RESOURCE_TYPE_SUB_FRAME:
return flat_rule::ElementType_SUBDOCUMENT;
case content::RESOURCE_TYPE_PING:
return flat_rule::ElementType_PING;
case content::RESOURCE_TYPE_MEDIA:
return flat_rule::ElementType_MEDIA;
case content::RESOURCE_TYPE_FONT_RESOURCE:
return flat_rule::ElementType_FONT;
}
NOTREACHED();
return flat_rule::ElementType_OTHER;
}
// Returns the flat_rule::ElementType for the given |request|.
flat_rule::ElementType GetElementType(const WebRequestInfo& request) {
if (request.url.SchemeIsWSOrWSS())
return flat_rule::ElementType_WEBSOCKET;
return request.type.has_value() ? GetElementType(request.type.value())
: flat_rule::ElementType_OTHER;
}
// Returns whether the request to |url| is third party to its |document_origin|.
// TODO(crbug.com/696822): Look into caching this.
bool IsThirdPartyRequest(const GURL& url, const url::Origin& document_origin) {
if (document_origin.unique())
return true;
return !net::registry_controlled_domains::SameDomainOrHost(
url, document_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}
void ClearRendererCacheOnUI() {
web_cache::WebCacheManager::GetInstance()->ClearCacheOnNavigation();
}
// Helper to clear each renderer's in-memory cache the next time it navigates.
void ClearRendererCacheOnNavigation() {
if (content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
ClearRendererCacheOnUI();
} else {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::BindOnce(&ClearRendererCacheOnUI));
}
}
} // namespace
RulesetManager::RulesetManager(const InfoMap* info_map) : info_map_(info_map) {
DCHECK(info_map_);
// RulesetManager can be created on any sequence.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
RulesetManager::~RulesetManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void RulesetManager::AddRuleset(
const ExtensionId& extension_id,
std::unique_ptr<RulesetMatcher> ruleset_matcher) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsAPIAvailable());
bool inserted;
std::tie(std::ignore, inserted) =
rulesets_.emplace(extension_id, info_map_->GetInstallTime(extension_id),
std::move(ruleset_matcher));
DCHECK(inserted) << "AddRuleset called twice in succession for "
<< extension_id;
// Clear the renderers' cache so that they take the new rules into account.
ClearRendererCacheOnNavigation();
}
void RulesetManager::RemoveRuleset(const ExtensionId& extension_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsAPIAvailable());
auto compare_by_id =
[&extension_id](const ExtensionRulesetData& ruleset_data) {
return ruleset_data.extension_id == extension_id;
};
DCHECK(std::find_if(rulesets_.begin(), rulesets_.end(), compare_by_id) !=
rulesets_.end())
<< "RemoveRuleset called without a corresponding AddRuleset for "
<< extension_id;
base::EraseIf(rulesets_, compare_by_id);
// Clear the renderers' cache so that they take the removed rules into
// account.
ClearRendererCacheOnNavigation();
}
bool RulesetManager::ShouldBlockRequest(const WebRequestInfo& request,
bool is_incognito_context) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Return early if DNR is not enabled.
if (!IsAPIAvailable())
return false;
if (test_observer_)
test_observer_->OnShouldBlockRequest(request, is_incognito_context);
SCOPED_UMA_HISTOGRAM_TIMER(
"Extensions.DeclarativeNetRequest.ShouldBlockRequestTime.AllExtensions");
const url::Origin first_party_origin =
request.initiator.value_or(url::Origin());
const flat_rule::ElementType element_type = GetElementType(request);
const bool is_third_party =
IsThirdPartyRequest(request.url, first_party_origin);
for (const auto& ruleset_data : rulesets_) {
const bool evaluate_ruleset =
!is_incognito_context ||
info_map_->IsIncognitoEnabled(ruleset_data.extension_id);
// TODO(crbug.com/777714): Check host permissions etc.
if (evaluate_ruleset &&
ruleset_data.matcher->ShouldBlockRequest(
request.url, first_party_origin, element_type, is_third_party)) {
return true;
}
}
return false;
}
bool RulesetManager::ShouldRedirectRequest(const WebRequestInfo& request,
bool is_incognito_context,
GURL* redirect_url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(redirect_url);
// Return early if DNR is not enabled.
if (!IsAPIAvailable())
return false;
// Redirecting WebSocket handshake request is prohibited.
const flat_rule::ElementType element_type = GetElementType(request);
if (element_type == flat_rule::ElementType_WEBSOCKET)
return false;
SCOPED_UMA_HISTOGRAM_TIMER(
"Extensions.DeclarativeNetRequest.ShouldRedirectRequestTime."
"AllExtensions");
const GURL& url = request.url;
const url::Origin first_party_origin =
request.initiator.value_or(url::Origin());
const bool is_third_party = IsThirdPartyRequest(url, first_party_origin);
// This iterates in decreasing order of extension installation time. Hence
// more recently installed extensions get higher priority in choosing the
// redirect url.
for (const auto& ruleset_data : rulesets_) {
const bool evaluate_ruleset =
(!is_incognito_context ||
info_map_->IsIncognitoEnabled(ruleset_data.extension_id));
// TODO(crbug.com/777714): Check host permissions etc.
if (evaluate_ruleset && ruleset_data.matcher->ShouldRedirectRequest(
url, first_party_origin, element_type,
is_third_party, redirect_url)) {
return true;
}
}
return false;
}
void RulesetManager::SetObserverForTest(TestObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
test_observer_ = observer;
}
RulesetManager::ExtensionRulesetData::ExtensionRulesetData(
const ExtensionId& extension_id,
const base::Time& extension_install_time,
std::unique_ptr<RulesetMatcher> matcher)
: extension_id(extension_id),
extension_install_time(extension_install_time),
matcher(std::move(matcher)) {}
RulesetManager::ExtensionRulesetData::~ExtensionRulesetData() = default;
RulesetManager::ExtensionRulesetData::ExtensionRulesetData(
ExtensionRulesetData&& other) = default;
RulesetManager::ExtensionRulesetData& RulesetManager::ExtensionRulesetData::
operator=(ExtensionRulesetData&& other) = default;
bool RulesetManager::ExtensionRulesetData::operator<(
const ExtensionRulesetData& other) const {
// Sort based on descending installation time, using extension id to break
// ties.
return (extension_install_time != other.extension_install_time)
? (extension_install_time > other.extension_install_time)
: (extension_id < other.extension_id);
}
} // namespace declarative_net_request
} // namespace extensions