blob: ffac35c119d4da421c1128be9136c8b4edb743ba [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/rules_monitor_service.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/declarative_net_request/composite_matcher.h"
#include "extensions/browser/api/declarative_net_request/file_sequence_helper.h"
#include "extensions/browser/api/declarative_net_request/ruleset_manager.h"
#include "extensions/browser/api/declarative_net_request/ruleset_matcher.h"
#include "extensions/browser/api/declarative_net_request/ruleset_source.h"
#include "extensions/browser/api/web_request/permission_helper.h"
#include "extensions/browser/api/web_request/web_request_api.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/warning_service.h"
#include "extensions/browser/warning_service_factory.h"
#include "extensions/browser/warning_set.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/dnr_manifest_data.h"
#include "extensions/common/api/declarative_net_request/utils.h"
#include "extensions/common/extension_id.h"
namespace extensions {
namespace declarative_net_request {
namespace {
namespace dnr_api = api::declarative_net_request;
static base::LazyInstance<
BrowserContextKeyedAPIFactory<RulesMonitorService>>::Leaky g_factory =
LAZY_INSTANCE_INITIALIZER;
} // namespace
// Helper to bridge tasks to FileSequenceHelper. Lives on the UI thread.
class RulesMonitorService::FileSequenceBridge {
public:
FileSequenceBridge()
: file_task_runner_(GetExtensionFileTaskRunner()),
file_sequence_helper_(std::make_unique<FileSequenceHelper>()) {}
~FileSequenceBridge() {
file_task_runner_->DeleteSoon(FROM_HERE, std::move(file_sequence_helper_));
}
void LoadRulesets(
LoadRequestData load_data,
FileSequenceHelper::LoadRulesetsUICallback ui_callback) const {
// base::Unretained is safe here because we trigger the destruction of
// |file_sequence_helper_| on |file_task_runner_| from our destructor. Hence
// it is guaranteed to be alive when |load_ruleset_task| is run.
base::OnceClosure load_ruleset_task =
base::BindOnce(&FileSequenceHelper::LoadRulesets,
base::Unretained(file_sequence_helper_.get()),
std::move(load_data), std::move(ui_callback));
file_task_runner_->PostTask(FROM_HERE, std::move(load_ruleset_task));
}
void UpdateDynamicRules(
LoadRequestData load_data,
std::vector<dnr_api::Rule> rules,
DynamicRuleUpdateAction action,
FileSequenceHelper::UpdateDynamicRulesUICallback ui_callback) const {
// base::Unretained is safe here because we trigger the destruction of
// |file_sequence_state_| on |file_task_runner_| from our destructor. Hence
// it is guaranteed to be alive when |update_dynamic_rules_task| is run.
base::OnceClosure update_dynamic_rules_task = base::BindOnce(
&FileSequenceHelper::UpdateDynamicRules,
base::Unretained(file_sequence_helper_.get()), std::move(load_data),
std::move(rules), action, std::move(ui_callback));
file_task_runner_->PostTask(FROM_HERE,
std::move(update_dynamic_rules_task));
}
private:
scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
// Created on the UI thread. Accessed and destroyed on |file_task_runner_|.
// Maintains state needed on |file_task_runner_|.
std::unique_ptr<FileSequenceHelper> file_sequence_helper_;
DISALLOW_COPY_AND_ASSIGN(FileSequenceBridge);
};
// static
BrowserContextKeyedAPIFactory<RulesMonitorService>*
RulesMonitorService::GetFactoryInstance() {
return g_factory.Pointer();
}
// static
RulesMonitorService* RulesMonitorService::Get(
content::BrowserContext* browser_context) {
return BrowserContextKeyedAPIFactory<RulesMonitorService>::Get(
browser_context);
}
bool RulesMonitorService::HasAnyRegisteredRulesets() const {
return !extensions_with_rulesets_.empty();
}
bool RulesMonitorService::HasRegisteredRuleset(
const ExtensionId& extension_id) const {
return extensions_with_rulesets_.find(extension_id) !=
extensions_with_rulesets_.end();
}
void RulesMonitorService::UpdateDynamicRules(
const Extension& extension,
std::vector<api::declarative_net_request::Rule> rules,
DynamicRuleUpdateAction action,
DynamicRuleUpdateUICallback callback) {
DCHECK(HasRegisteredRuleset(extension.id()));
LoadRequestData data(extension.id());
// We are updating the indexed ruleset. Don't set the expected checksum since
// it'll change.
data.rulesets.emplace_back(RulesetSource::CreateDynamic(context_, extension));
auto update_rules_callback =
base::BindOnce(&RulesMonitorService::OnDynamicRulesUpdated,
weak_factory_.GetWeakPtr(), std::move(callback));
file_sequence_bridge_->UpdateDynamicRules(std::move(data), std::move(rules),
action,
std::move(update_rules_callback));
}
RulesMonitorService::RulesMonitorService(
content::BrowserContext* browser_context)
: registry_observer_(this),
file_sequence_bridge_(std::make_unique<FileSequenceBridge>()),
prefs_(ExtensionPrefs::Get(browser_context)),
extension_registry_(ExtensionRegistry::Get(browser_context)),
warning_service_(WarningService::Get(browser_context)),
context_(browser_context),
ruleset_manager_(browser_context) {
registry_observer_.Add(extension_registry_);
}
RulesMonitorService::~RulesMonitorService() = default;
/* Description of thread hops for various scenarios:
On ruleset load success:
- UI -> File -> UI.
- The File sequence might reindex the ruleset while parsing JSON OOP.
On ruleset load failure:
- UI -> File -> UI.
- The File sequence might reindex the ruleset while parsing JSON OOP.
On ruleset unload:
- UI.
On dynamic rules update.
- UI -> File -> UI -> IPC to extension
*/
void RulesMonitorService::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
DCHECK_EQ(context_, browser_context);
if (!declarative_net_request::DNRManifestData::HasRuleset(*extension))
return;
DCHECK(IsAPIAvailable());
LoadRequestData load_data(extension->id());
int expected_ruleset_checksum;
// Static ruleset.
{
bool has_checksum = prefs_->GetDNRRulesetChecksum(
extension->id(), &expected_ruleset_checksum);
DCHECK(has_checksum);
RulesetInfo static_ruleset(RulesetSource::CreateStatic(*extension));
static_ruleset.set_expected_checksum(expected_ruleset_checksum);
load_data.rulesets.push_back(std::move(static_ruleset));
}
// Dynamic ruleset
if (prefs_->GetDNRDynamicRulesetChecksum(extension->id(),
&expected_ruleset_checksum)) {
RulesetInfo dynamic_ruleset(
RulesetSource::CreateDynamic(browser_context, *extension));
dynamic_ruleset.set_expected_checksum(expected_ruleset_checksum);
load_data.rulesets.push_back(std::move(dynamic_ruleset));
}
auto load_ruleset_callback = base::BindOnce(
&RulesMonitorService::OnRulesetLoaded, weak_factory_.GetWeakPtr());
file_sequence_bridge_->LoadRulesets(std::move(load_data),
std::move(load_ruleset_callback));
}
void RulesMonitorService::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
DCHECK_EQ(context_, browser_context);
// Return early if the extension does not have an active indexed ruleset.
if (!extensions_with_rulesets_.erase(extension->id()))
return;
DCHECK(IsAPIAvailable());
UnloadRuleset(extension->id());
}
void RulesMonitorService::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const Extension* extension,
UninstallReason reason) {
DCHECK_EQ(context_, browser_context);
// Skip if the extension will be reinstalled soon.
if (reason == UNINSTALL_REASON_REINSTALL)
return;
// Skip if the extension doesn't have a dynamic ruleset.
int dynamic_checksum;
if (!prefs_->GetDNRDynamicRulesetChecksum(extension->id(), &dynamic_checksum))
return;
// Cleanup the dynamic rules directory for the extension.
// TODO(karandeepb): It's possible that this task fails, e.g. during shutdown.
// Make this more robust.
RulesetSource source =
RulesetSource::CreateDynamic(browser_context, *extension);
DCHECK_EQ(source.json_path().DirName(), source.indexed_path().DirName());
GetExtensionFileTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&base::DeleteFile),
source.json_path().DirName(), false /* recursive */));
}
void RulesMonitorService::OnRulesetLoaded(LoadRequestData load_data) {
// Currently we only support a single static and an optional dynamic ruleset
// per extension.
DCHECK(load_data.rulesets.size() == 1u || load_data.rulesets.size() == 2u);
RulesetInfo& static_ruleset = load_data.rulesets[0];
DCHECK_EQ(static_ruleset.source().id(), RulesetSource::kStaticRulesetID)
<< static_ruleset.source().id();
RulesetInfo* dynamic_ruleset =
load_data.rulesets.size() == 2 ? &load_data.rulesets[1] : nullptr;
DCHECK(!dynamic_ruleset ||
dynamic_ruleset->source().id() == RulesetSource::kDynamicRulesetID)
<< dynamic_ruleset->source().id();
// Update the ruleset checksums if needed.
if (static_ruleset.new_checksum()) {
prefs_->SetDNRRulesetChecksum(load_data.extension_id,
*(static_ruleset.new_checksum()));
}
if (dynamic_ruleset && dynamic_ruleset->new_checksum()) {
prefs_->SetDNRRulesetChecksum(load_data.extension_id,
*(dynamic_ruleset->new_checksum()));
}
// It's possible that the extension has been disabled since the initial load
// ruleset request. If it's disabled, do nothing.
if (!extension_registry_->enabled_extensions().Contains(
load_data.extension_id))
return;
CompositeMatcher::MatcherList matchers;
if (static_ruleset.did_load_successfully()) {
matchers.push_back(static_ruleset.TakeMatcher());
}
if (dynamic_ruleset && dynamic_ruleset->did_load_successfully()) {
matchers.push_back(dynamic_ruleset->TakeMatcher());
}
// A ruleset failed to load. Notify the user.
if (matchers.size() < load_data.rulesets.size()) {
warning_service_->AddWarnings(
{Warning::CreateRulesetFailedToLoadWarning(load_data.extension_id)});
}
if (matchers.empty())
return;
extensions_with_rulesets_.insert(load_data.extension_id);
LoadRuleset(load_data.extension_id,
std::make_unique<CompositeMatcher>(std::move(matchers)),
prefs_->GetDNRAllowedPages(load_data.extension_id));
}
void RulesMonitorService::OnDynamicRulesUpdated(
DynamicRuleUpdateUICallback callback,
LoadRequestData load_data,
base::Optional<std::string> error) {
DCHECK_EQ(1u, load_data.rulesets.size());
RulesetInfo& dynamic_ruleset = load_data.rulesets[0];
DCHECK_EQ(dynamic_ruleset.did_load_successfully(), !error.has_value());
// Update the ruleset checksums if needed. Note it's possible that
// new_checksum() is valid while did_load_successfully() returns false below.
// This should be rare but can happen when updating the rulesets succeeds but
// we fail to create a RulesetMatcher from the indexed ruleset file (e.g. due
// to a file read error). We still update the prefs checksum to ensure the
// next ruleset load succeeds.
if (dynamic_ruleset.new_checksum()) {
prefs_->SetDNRDynamicRulesetChecksum(load_data.extension_id,
*dynamic_ruleset.new_checksum());
}
// Respond to the extension.
std::move(callback).Run(std::move(error));
if (!dynamic_ruleset.did_load_successfully())
return;
DCHECK(dynamic_ruleset.new_checksum());
// It's possible that the extension has been disabled since the initial update
// rule request. If it's disabled, do nothing.
if (!extension_registry_->enabled_extensions().Contains(
load_data.extension_id)) {
return;
}
// Update the dynamic ruleset.
UpdateRuleset(load_data.extension_id, dynamic_ruleset.TakeMatcher());
}
void RulesMonitorService::UnloadRuleset(const ExtensionId& extension_id) {
bool had_extra_headers_matcher = ruleset_manager_.HasAnyExtraHeadersMatcher();
ruleset_manager_.RemoveRuleset(extension_id);
if (had_extra_headers_matcher &&
!ruleset_manager_.HasAnyExtraHeadersMatcher()) {
ExtensionWebRequestEventRouter::GetInstance()
->DecrementExtraHeadersListenerCount(context_);
}
}
void RulesMonitorService::LoadRuleset(const ExtensionId& extension_id,
std::unique_ptr<CompositeMatcher> matcher,
URLPatternSet allowed_pages) {
bool increment_extra_headers =
!ruleset_manager_.HasAnyExtraHeadersMatcher() &&
matcher->HasAnyExtraHeadersMatcher();
ruleset_manager_.AddRuleset(extension_id, std::move(matcher),
prefs_->GetDNRAllowedPages(extension_id));
if (increment_extra_headers) {
ExtensionWebRequestEventRouter::GetInstance()
->IncrementExtraHeadersListenerCount(context_);
}
}
void RulesMonitorService::UpdateRuleset(
const ExtensionId& extension_id,
std::unique_ptr<RulesetMatcher> ruleset_matcher) {
bool had_extra_headers_matcher = ruleset_manager_.HasAnyExtraHeadersMatcher();
CompositeMatcher* matcher =
ruleset_manager_.GetMatcherForExtension(extension_id);
DCHECK(matcher);
matcher->AddOrUpdateRuleset(std::move(ruleset_matcher));
bool has_extra_headers_matcher = ruleset_manager_.HasAnyExtraHeadersMatcher();
if (had_extra_headers_matcher == has_extra_headers_matcher)
return;
if (has_extra_headers_matcher) {
ExtensionWebRequestEventRouter::GetInstance()
->IncrementExtraHeadersListenerCount(context_);
} else {
ExtensionWebRequestEventRouter::GetInstance()
->DecrementExtraHeadersListenerCount(context_);
}
}
} // namespace declarative_net_request
template <>
void BrowserContextKeyedAPIFactory<
declarative_net_request::RulesMonitorService>::
DeclareFactoryDependencies() {
DependsOn(ExtensionRegistryFactory::GetInstance());
DependsOn(ExtensionPrefsFactory::GetInstance());
DependsOn(WarningServiceFactory::GetInstance());
DependsOn(PermissionHelper::GetFactoryInstance());
}
} // namespace extensions