blob: 560bf56223b8d430a9b46692a2e3cdc76621c81e [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/devtools/devtools_navigation_gating_rule_manager.h"
#include <optional>
#include <string>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/values.h"
#include "chrome/common/chrome_switches.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_utils.h"
namespace {
constexpr char kAllowlistKey[] = "allowlist";
constexpr char kBlocklistKey[] = "blocklist";
} // namespace
// static
DevToolsNavigationGatingRuleManager&
DevToolsNavigationGatingRuleManager::Get() {
static base::NoDestructor<DevToolsNavigationGatingRuleManager> instance(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kDevToolsNavigationGatingRules));
return *instance;
}
// static
DevToolsNavigationGatingRuleManager
DevToolsNavigationGatingRuleManager::CreateForTesting(
std::string_view rules_json) {
return DevToolsNavigationGatingRuleManager(rules_json);
}
DevToolsNavigationGatingRuleManager::DevToolsNavigationGatingRuleManager(
std::string_view rules_json)
: origin_gating_checker_(
*this,
origin_gating::OriginGatingConfiguration(
{
{origin_gating::CustomPredicate(
base::BindRepeating(
&DevToolsNavigationGatingRuleManager::EvaluateRules,
// Safe because `origin_gating_checker_` is owned by
// `this`, so the callback is guaranteed to be
// destroyed before `this` is.
base::Unretained(this)),
"DevToolsNavigationGatingRules"),
origin_gating::GateableEventSet::All()},
},
/*use_site_keyed_cache=*/false)) {
if (rules_json.empty()) {
return;
}
auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
rules_json, base::JSON_PARSE_RFC);
if (!parsed_json.has_value()) {
LOG(ERROR) << "Failed to parse DevTools navigation gating rules: "
<< parsed_json.error().message;
return;
}
const base::DictValue* dict = parsed_json->GetIfDict();
if (!dict) {
LOG(ERROR) << "DevTools navigation gating rules must be a JSON dictionary.";
return;
}
auto parse_and_add_rules = [&](std::string_view key, ContentSetting setting) {
const base::Value* val = dict->Find(key);
if (!val) {
return;
}
const base::ListValue* list = val->GetIfList();
if (!list) {
LOG(ERROR) << "DevTools navigation gating rules key '" << key
<< "' must be a list.";
return;
}
for (const auto& item : *list) {
if (!item.is_string()) {
LOG(ERROR) << "DevTools navigation gating list '" << key
<< "' contains a non-string element.";
continue;
}
ContentSettingsPattern pattern =
ContentSettingsPattern::FromString(item.GetString());
if (!pattern.IsValid()) {
LOG(ERROR) << "DevTools navigation gating list '" << key
<< "' contains an invalid pattern: '" << item.GetString()
<< "'";
continue;
}
rules_.SetValue(ContentSettingsPattern::Wildcard(), pattern,
content_settings::ContentSettingToValue(setting), {});
}
return;
};
has_allowlist_ = !!dict->FindList(kAllowlistKey);
parse_and_add_rules(kAllowlistKey, CONTENT_SETTING_ALLOW);
parse_and_add_rules(kBlocklistKey, CONTENT_SETTING_BLOCK);
}
DevToolsNavigationGatingRuleManager::~DevToolsNavigationGatingRuleManager() =
default;
void DevToolsNavigationGatingRuleManager::IsNavigationAllowed(
const GURL& url,
base::OnceCallback<void(bool)> callback) {
origin_gating_checker_.ComputeGatingDecision(
/*context=*/nullptr, origin_gating::GateableEvent::kNavigationRequest,
/*source=*/GURL(), /*destination=*/url,
base::BindOnce([](std::unique_ptr<origin_gating::GatingDecisionContext>
context,
origin_gating::GatingDecision decision) {
return decision.is_allowed;
}).Then(std::move(callback)));
}
bool DevToolsNavigationGatingRuleManager::MayBlockNavigation() const {
return !rules_.empty() || has_allowlist_;
}
void DevToolsNavigationGatingRuleManager::DoesOriginRequireUserConfirmation(
origin_gating::GatingDecisionContext* context,
origin_gating::GateableEvent event,
const GURL& source,
const GURL& destination,
DoesOriginRequireUserConfirmationCallback callback) const {
NOTREACHED();
}
void DevToolsNavigationGatingRuleManager::EvaluateEnterprisePolicy(
const GURL& destination,
EvaluateEnterprisePolicyCallback callback) const {
NOTREACHED();
}
void DevToolsNavigationGatingRuleManager::OnNoVerdict(
origin_gating::GatingDecisionContext* context,
origin_gating::GateableEvent event,
const GURL& source,
const GURL& destination,
bool requires_user_confirmation,
base::OnceCallback<void(NoVerdictResult)> callback) {
NOTREACHED();
}
origin_gating::Decision DevToolsNavigationGatingRuleManager::EvaluateRules(
const origin_gating::GatingDecisionContext* context,
const GURL& source,
const GURL& destination) const {
if (!MayBlockNavigation()) {
return origin_gating::Decision::kAllowed;
}
// GURL() is passed as primary_url to represent the wildcard source pattern.
const content_settings::RuleEntry* rule_entry =
rules_.Find(GURL(), destination);
if (!rule_entry) {
return has_allowlist_ ? origin_gating::Decision::kBlocked
: origin_gating::Decision::kAllowed;
}
std::optional<ContentSetting> setting =
content_settings::ParseContentSettingValue(rule_entry->second.value);
CHECK(setting.has_value());
switch (setting.value()) {
case CONTENT_SETTING_ALLOW:
return origin_gating::Decision::kAllowed;
case CONTENT_SETTING_BLOCK:
return origin_gating::Decision::kBlocked;
case CONTENT_SETTING_DEFAULT:
case CONTENT_SETTING_ASK:
case CONTENT_SETTING_SESSION_ONLY:
case CONTENT_SETTING_NUM_SETTINGS:
NOTREACHED();
}
NOTREACHED();
}