blob: 8b2a191f7f90ae40a3dea1cc1e2cec6056215c13 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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/test_utils.h"
#include <string>
#include <tuple>
#include <utility>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/values.h"
#include "extensions/browser/api/declarative_net_request/composite_matcher.h"
#include "extensions/browser/api/declarative_net_request/file_backed_ruleset_source.h"
#include "extensions/browser/api/declarative_net_request/indexed_rule.h"
#include "extensions/browser/api/declarative_net_request/prefs_helper.h"
#include "extensions/browser/api/declarative_net_request/request_params.h"
#include "extensions/browser/api/declarative_net_request/rule_counts.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/web_request_info.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/test_utils.h"
#include "extensions/common/extension.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions::declarative_net_request {
namespace dnr_api = api::declarative_net_request;
RequestAction CreateRequestActionForTesting(RequestAction::Type type,
uint32_t rule_id,
uint32_t rule_priority,
RulesetID ruleset_id,
const ExtensionId& extension_id) {
dnr_api::RuleActionType action = [type] {
switch (type) {
case RequestAction::Type::BLOCK:
case RequestAction::Type::COLLAPSE:
return dnr_api::RuleActionType::kBlock;
case RequestAction::Type::ALLOW:
return dnr_api::RuleActionType::kAllow;
case RequestAction::Type::REDIRECT:
return dnr_api::RuleActionType::kRedirect;
case RequestAction::Type::UPGRADE:
return dnr_api::RuleActionType::kUpgradeScheme;
case RequestAction::Type::ALLOW_ALL_REQUESTS:
return dnr_api::RuleActionType::kAllowAllRequests;
case RequestAction::Type::MODIFY_HEADERS:
return dnr_api::RuleActionType::kModifyHeaders;
}
}();
return RequestAction(type, rule_id,
ComputeIndexedRulePriority(rule_priority, action),
ruleset_id, extension_id);
}
bool operator==(const RequestAction::HeaderInfo& lhs,
const RequestAction::HeaderInfo& rhs) {
return lhs.header == rhs.header && lhs.operation == rhs.operation;
}
std::ostream& operator<<(std::ostream& output,
const RequestAction::HeaderInfo& header_info) {
output << "\nRequestAction::HeaderInfo\n";
output << "\t|operation| " << dnr_api::ToString(header_info.operation)
<< "\n";
output << "\t|header| " << header_info.header << "\n";
output << "\t|value| "
<< (header_info.value ? *header_info.value : std::string("nullopt"));
return output;
}
// Note: This is not declared in the anonymous namespace so that we can use it
// with gtest. This reuses the logic used to test action equality in
// TestRequestAction in test_utils.h.
bool operator==(const RequestAction& lhs, const RequestAction& rhs) {
static_assert(flat::IndexType_count == 3,
"Modify this method to ensure it stays updated as new actions "
"are added.");
auto get_members_tuple = [](const RequestAction& action) {
return std::tie(action.type, action.redirect_url, action.rule_id,
action.index_priority, action.ruleset_id,
action.extension_id);
};
auto are_headers_equal = [](std::vector<RequestAction::HeaderInfo> a,
std::vector<RequestAction::HeaderInfo> b) {
auto header_info_comparator = [](const RequestAction::HeaderInfo& lhs,
const RequestAction::HeaderInfo& rhs) {
return std::make_tuple(lhs.header, lhs.operation, lhs.value) >
std::make_tuple(rhs.header, rhs.operation, rhs.value);
};
std::sort(a.begin(), a.end(), header_info_comparator);
std::sort(b.begin(), b.end(), header_info_comparator);
return a == b;
};
return get_members_tuple(lhs) == get_members_tuple(rhs) &&
are_headers_equal(lhs.request_headers_to_modify,
rhs.request_headers_to_modify) &&
are_headers_equal(lhs.response_headers_to_modify,
rhs.response_headers_to_modify);
}
std::ostream& operator<<(std::ostream& output, RequestAction::Type type) {
switch (type) {
case RequestAction::Type::BLOCK:
output << "BLOCK";
break;
case RequestAction::Type::COLLAPSE:
output << "COLLAPSE";
break;
case RequestAction::Type::ALLOW:
output << "ALLOW";
break;
case RequestAction::Type::REDIRECT:
output << "REDIRECT";
break;
case RequestAction::Type::UPGRADE:
output << "UPGRADE";
break;
case RequestAction::Type::ALLOW_ALL_REQUESTS:
output << "ALLOW_ALL_REQUESTS";
break;
case RequestAction::Type::MODIFY_HEADERS:
output << "MODIFY_HEADERS";
break;
}
return output;
}
std::ostream& operator<<(std::ostream& output, const RequestAction& action) {
output << "\nRequestAction\n";
output << "|type| " << action.type << "\n";
output << "|redirect_url| "
<< (action.redirect_url ? action.redirect_url->spec()
: std::string("nullopt"))
<< "\n";
output << "|rule_id| " << action.rule_id << "\n";
output << "|index_priority| " << action.index_priority << "\n";
output << "|ruleset_id| " << action.ruleset_id << "\n";
output << "|extension_id| " << action.extension_id << "\n";
output << "|request_headers_to_modify|"
<< ::testing::PrintToString(action.request_headers_to_modify) << "\n";
output << "|response_headers_to_modify|"
<< ::testing::PrintToString(action.response_headers_to_modify);
return output;
}
std::ostream& operator<<(std::ostream& output,
const std::optional<RequestAction>& action) {
if (!action) {
return output << "empty Optional<RequestAction>";
}
return output << *action;
}
std::ostream& operator<<(std::ostream& output, const ParseResult& result) {
switch (result) {
case ParseResult::NONE:
output << "NONE";
break;
case ParseResult::SUCCESS:
output << "SUCCESS";
break;
case ParseResult::ERROR_REQUEST_METHOD_DUPLICATED:
output << "ERROR_REQUEST_METHOD_DUPLICATED";
break;
case ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED:
output << "ERROR_RESOURCE_TYPE_DUPLICATED";
break;
case ParseResult::ERROR_INVALID_RULE_ID:
output << "ERROR_INVALID_RULE_ID";
break;
case ParseResult::ERROR_INVALID_RULE_PRIORITY:
output << "ERROR_INVALID_RULE_PRIORITY";
break;
case ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES:
output << "ERROR_NO_APPLICABLE_RESOURCE_TYPES";
break;
case ParseResult::ERROR_EMPTY_DOMAINS_LIST:
output << "ERROR_EMPTY_DOMAINS_LIST";
break;
case ParseResult::ERROR_EMPTY_INITIATOR_DOMAINS_LIST:
output << "ERROR_EMPTY_INITIATOR_DOMAINS_LIST";
break;
case ParseResult::ERROR_EMPTY_REQUEST_DOMAINS_LIST:
output << "ERROR_EMPTY_REQUEST_DOMAINS_LIST";
break;
case ParseResult::ERROR_EMPTY_TOP_DOMAINS_LIST:
output << "ERROR_EMPTY_TOP_DOMAINS_LIST";
break;
case ParseResult::ERROR_DOMAINS_AND_INITIATOR_DOMAINS_BOTH_SPECIFIED:
output << "ERROR_DOMAINS_AND_INITIATOR_DOMAINS_BOTH_SPECIFIED";
break;
case ParseResult::
ERROR_EXCLUDED_DOMAINS_AND_EXCLUDED_INITIATOR_DOMAINS_BOTH_SPECIFIED:
output << "ERROR_EXCLUDED_DOMAINS_AND_EXCLUDED_INITIATOR_DOMAINS_BOTH_"
"SPECIFIED";
break;
case ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST:
output << "ERROR_EMPTY_RESOURCE_TYPES_LIST";
break;
case ParseResult::ERROR_EMPTY_REQUEST_METHODS_LIST:
output << "ERROR_EMPTY_REQUEST_METHODS_LIST";
break;
case ParseResult::ERROR_EMPTY_URL_FILTER:
output << "ERROR_EMPTY_URL_FILTER";
break;
case ParseResult::ERROR_INVALID_REDIRECT_URL:
output << "ERROR_INVALID_REDIRECT_URL";
break;
case ParseResult::ERROR_DUPLICATE_IDS:
output << "ERROR_DUPLICATE_IDS";
break;
case ParseResult::ERROR_NON_ASCII_URL_FILTER:
output << "ERROR_NON_ASCII_URL_FILTER";
break;
case ParseResult::ERROR_NON_ASCII_DOMAIN:
output << "ERROR_NON_ASCII_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_EXCLUDED_DOMAIN:
output << "ERROR_NON_ASCII_EXCLUDED_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_INITIATOR_DOMAIN:
output << "ERROR_NON_ASCII_INITIATOR_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_EXCLUDED_INITIATOR_DOMAIN:
output << "ERROR_NON_ASCII_EXCLUDED_INITIATOR_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_REQUEST_DOMAIN:
output << "ERROR_NON_ASCII_REQUEST_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_EXCLUDED_REQUEST_DOMAIN:
output << "ERROR_NON_ASCII_EXCLUDED_REQUEST_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_TOP_DOMAIN:
output << "ERROR_NON_ASCII_TOP_DOMAIN";
break;
case ParseResult::ERROR_NON_ASCII_EXCLUDED_TOP_DOMAIN:
output << "ERROR_NON_ASCII_EXCLUDED_TOP_DOMAIN";
break;
case ParseResult::ERROR_INVALID_URL_FILTER:
output << "ERROR_INVALID_URL_FILTER";
break;
case ParseResult::ERROR_INVALID_REDIRECT:
output << "ERROR_INVALID_REDIRECT";
break;
case ParseResult::ERROR_INVALID_EXTENSION_PATH:
output << "ERROR_INVALID_EXTENSION_PATH";
break;
case ParseResult::ERROR_INVALID_TRANSFORM_SCHEME:
output << "ERROR_INVALID_TRANSFORM_SCHEME";
break;
case ParseResult::ERROR_INVALID_TRANSFORM_PORT:
output << "ERROR_INVALID_TRANSFORM_PORT";
break;
case ParseResult::ERROR_INVALID_TRANSFORM_QUERY:
output << "ERROR_INVALID_TRANSFORM_QUERY";
break;
case ParseResult::ERROR_INVALID_TRANSFORM_FRAGMENT:
output << "ERROR_INVALID_TRANSFORM_FRAGMENT";
break;
case ParseResult::ERROR_QUERY_AND_TRANSFORM_BOTH_SPECIFIED:
output << "ERROR_QUERY_AND_TRANSFORM_BOTH_SPECIFIED";
break;
case ParseResult::ERROR_JAVASCRIPT_REDIRECT:
output << "ERROR_JAVASCRIPT_REDIRECT";
break;
case ParseResult::ERROR_EMPTY_REGEX_FILTER:
output << "ERROR_EMPTY_REGEX_FILTER";
break;
case ParseResult::ERROR_NON_ASCII_REGEX_FILTER:
output << "ERROR_NON_ASCII_REGEX_FILTER";
break;
case ParseResult::ERROR_INVALID_REGEX_FILTER:
output << "ERROR_INVALID_REGEX_FILTER";
break;
case ParseResult::ERROR_REGEX_TOO_LARGE:
output << "ERROR_REGEX_TOO_LARGE";
break;
case ParseResult::ERROR_MULTIPLE_FILTERS_SPECIFIED:
output << "ERROR_MULTIPLE_FILTERS_SPECIFIED";
break;
case ParseResult::ERROR_REGEX_SUBSTITUTION_WITHOUT_FILTER:
output << "ERROR_REGEX_SUBSTITUTION_WITHOUT_FILTER";
break;
case ParseResult::ERROR_INVALID_REGEX_SUBSTITUTION:
output << "ERROR_INVALID_REGEX_SUBSTITUTION";
break;
case ParseResult::ERROR_INVALID_ALLOW_ALL_REQUESTS_RESOURCE_TYPE:
output << "ERROR_INVALID_ALLOW_ALL_REQUESTS_RESOURCE_TYPE";
break;
case ParseResult::ERROR_NO_HEADERS_TO_MODIFY_SPECIFIED:
output << "ERROR_NO_HEADERS_TO_MODIFY_SPECIFIED";
break;
case ParseResult::ERROR_EMPTY_MODIFY_REQUEST_HEADERS_LIST:
output << "ERROR_EMPTY_MODIFY_REQUEST_HEADERS_LIST";
break;
case ParseResult::ERROR_EMPTY_MODIFY_RESPONSE_HEADERS_LIST:
output << "ERROR_EMPTY_MODIFY_RESPONSE_HEADERS_LIST";
break;
case ParseResult::ERROR_INVALID_HEADER_TO_MODIFY_NAME:
output << "ERROR_INVALID_HEADER_TO_MODIFY_NAME";
break;
case ParseResult::ERROR_INVALID_HEADER_TO_MODIFY_VALUE:
output << "ERROR_INVALID_HEADER_TO_MODIFY_VALUE";
break;
case ParseResult::ERROR_HEADER_VALUE_NOT_SPECIFIED:
output << "ERROR_HEADER_VALUE_NOT_SPECIFIED";
break;
case ParseResult::ERROR_HEADER_VALUE_PRESENT:
output << "ERROR_HEADER_VALUE_PRESENT";
break;
case ParseResult::ERROR_APPEND_INVALID_REQUEST_HEADER:
output << "ERROR_APPEND_INVALID_REQUEST_HEADER";
break;
case ParseResult::ERROR_EMPTY_TAB_IDS_LIST:
output << "ERROR_EMPTY_TAB_IDS_LIST";
break;
case ParseResult::ERROR_TAB_IDS_ON_NON_SESSION_RULE:
output << "ERROR_TAB_IDS_ON_NON_SESSION_RULE";
break;
case ParseResult::ERROR_TAB_ID_DUPLICATED:
output << "ERROR_TAB_ID_DUPLICATED";
break;
case ParseResult::ERROR_EMPTY_RESPONSE_HEADER_MATCHING_LIST:
output << "ERROR_EMPTY_RESPONSE_HEADER_MATCHING_LIST";
break;
case ParseResult::ERROR_EMPTY_EXCLUDED_RESPONSE_HEADER_MATCHING_LIST:
output << "ERROR_EMPTY_EXCLUDED_RESPONSE_HEADER_MATCHING_LIST";
break;
case ParseResult::ERROR_INVALID_MATCHING_RESPONSE_HEADER_NAME:
output << "ERROR_INVALID_MATCHING_RESPONSE_HEADER_NAME";
break;
case ParseResult::ERROR_INVALID_MATCHING_EXCLUDED_RESPONSE_HEADER_NAME:
output << "ERROR_INVALID_MATCHING_EXCLUDED_RESPONSE_HEADER_NAME";
break;
case ParseResult::ERROR_INVALID_MATCHING_RESPONSE_HEADER_VALUE:
output << "ERROR_INVALID_MATCHING_RESPONSE_HEADER_VALUE";
break;
case ParseResult::ERROR_MATCHING_RESPONSE_HEADER_DUPLICATED:
output << "ERROR_MATCHING_RESPONSE_HEADER_DUPLICATED";
break;
case ParseResult::ERROR_RESPONSE_HEADER_RULE_CANNOT_MODIFY_REQUEST_HEADERS:
output << "ERROR_RESPONSE_HEADER_RULE_CANNOT_MODIFY_REQUEST_HEADERS";
break;
}
return output;
}
std::ostream& operator<<(std::ostream& output, LoadRulesetResult result) {
switch (result) {
case LoadRulesetResult::kSuccess:
output << "kSuccess";
break;
case LoadRulesetResult::kErrorInvalidPath:
output << "kErrorInvalidPath";
break;
case LoadRulesetResult::kErrorCannotReadFile:
output << "kErrorCannotReadFile";
break;
case LoadRulesetResult::kErrorChecksumMismatch:
output << "kErrorChecksumMismatch";
break;
case LoadRulesetResult::kErrorVersionMismatch:
output << "kErrorVersionMismatch";
break;
case LoadRulesetResult::kErrorChecksumNotFound:
output << "kErrorChecksumNotFound";
break;
}
return output;
}
std::ostream& operator<<(std::ostream& output, const RuleCounts& count) {
output << "\nRuleCounts\n";
output << "|rule_count| " << count.rule_count << "\n";
if (count.unsafe_rule_count.has_value()) {
output << "|unsafe_rule_count| " << *(count.unsafe_rule_count) << "\n";
}
output << "|regex_rule_count| " << count.regex_rule_count << "\n";
return output;
}
bool AreAllIndexedStaticRulesetsValid(
const Extension& extension,
content::BrowserContext* browser_context,
FileBackedRulesetSource::RulesetFilter ruleset_filter) {
std::vector<FileBackedRulesetSource> sources =
FileBackedRulesetSource::CreateStatic(extension, ruleset_filter);
ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context);
PrefsHelper helper(*prefs);
for (const auto& source : sources) {
if (helper.ShouldIgnoreRuleset(extension.id(), source.id())) {
continue;
}
int expected_checksum = -1;
if (!helper.GetStaticRulesetChecksum(extension.id(), source.id(),
expected_checksum)) {
return false;
}
std::unique_ptr<RulesetMatcher> matcher;
if (source.CreateVerifiedMatcher(expected_checksum, &matcher) !=
LoadRulesetResult::kSuccess) {
return false;
}
}
return true;
}
bool CreateVerifiedMatcher(const std::vector<TestRule>& rules,
const FileBackedRulesetSource& source,
std::unique_ptr<RulesetMatcher>* matcher,
int* expected_checksum) {
using IndexStatus = IndexAndPersistJSONRulesetResult::Status;
// Serialize |rules|.
base::Value::List builder;
for (const auto& rule : rules) {
builder.Append(rule.ToValue());
}
JSONFileValueSerializer(source.json_path()).Serialize(std::move(builder));
// Index ruleset.
auto parse_flags = FileBackedRulesetSource::kRaiseErrorOnInvalidRules |
FileBackedRulesetSource::kRaiseWarningOnLargeRegexRules;
IndexAndPersistJSONRulesetResult result =
source.IndexAndPersistJSONRulesetUnsafe(parse_flags);
if (result.status == IndexStatus::kError) {
DCHECK(result.error.empty()) << result.error;
return false;
}
if (!result.warnings.empty()) {
return false;
}
DCHECK_EQ(IndexStatus::kSuccess, result.status);
if (expected_checksum) {
*expected_checksum = result.ruleset_checksum;
}
LoadRulesetResult load_result =
source.CreateVerifiedMatcher(result.ruleset_checksum, matcher);
return load_result == LoadRulesetResult::kSuccess;
}
FileBackedRulesetSource CreateTemporarySource(RulesetID id,
size_t rule_count_limit,
ExtensionId extension_id) {
std::unique_ptr<FileBackedRulesetSource> source =
FileBackedRulesetSource::CreateTemporarySource(id, rule_count_limit,
std::move(extension_id));
CHECK(source);
return source->Clone();
}
dnr_api::ModifyHeaderInfo CreateModifyHeaderInfo(
dnr_api::HeaderOperation operation,
std::string header,
std::optional<std::string> value,
std::optional<std::string> regex_filter,
std::optional<std::string> regex_substitution,
std::optional<dnr_api::HeaderRegexOptions> regex_options) {
dnr_api::ModifyHeaderInfo header_info;
header_info.operation = std::move(operation);
header_info.header = std::move(header);
header_info.value = std::move(value);
header_info.regex_filter = std::move(regex_filter);
header_info.regex_substitution = std::move(regex_substitution);
header_info.regex_options = std::move(regex_options);
return header_info;
}
bool EqualsForTesting(const dnr_api::ModifyHeaderInfo& lhs,
const dnr_api::ModifyHeaderInfo& rhs) {
bool are_values_equal = lhs.value && rhs.value ? *lhs.value == *rhs.value
: lhs.value == rhs.value;
return lhs.operation == rhs.operation && lhs.header == rhs.header &&
are_values_equal;
}
dnr_api::HeaderInfo CreateHeaderInfo(
std::string header,
std::optional<std::vector<std::string>> values,
std::optional<std::vector<std::string>> excluded_values) {
dnr_api::HeaderInfo header_info;
header_info.header = std::move(header);
header_info.values = std::move(values);
header_info.excluded_values = std::move(excluded_values);
return header_info;
}
RulesetManagerObserver::RulesetManagerObserver(RulesetManager* manager)
: manager_(manager), current_count_(manager_->GetMatcherCountForTest()) {
manager_->SetObserverForTest(this);
}
RulesetManagerObserver::~RulesetManagerObserver() {
manager_->SetObserverForTest(nullptr);
}
std::vector<GURL> RulesetManagerObserver::GetAndResetRequestSeen() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<GURL> seen_requests;
std::swap(seen_requests, observed_requests_);
return seen_requests;
}
void RulesetManagerObserver::WaitForExtensionsWithRulesetsCount(size_t count) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ASSERT_FALSE(expected_count_);
if (current_count_ == count) {
return;
}
expected_count_ = count;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
void RulesetManagerObserver::OnRulesetCountChanged(size_t count) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
current_count_ = count;
if (expected_count_ != count) {
return;
}
ASSERT_TRUE(run_loop_.get());
run_loop_->Quit();
expected_count_.reset();
}
void RulesetManagerObserver::OnEvaluateRequest(const WebRequestInfo& request,
bool is_incognito_context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observed_requests_.push_back(request.url);
}
WarningServiceObserver::WarningServiceObserver(WarningService* warning_service,
const ExtensionId& extension_id)
: extension_id_(extension_id) {
observation_.Observe(warning_service);
}
WarningServiceObserver::~WarningServiceObserver() = default;
void WarningServiceObserver::WaitForWarning() {
run_loop_.Run();
}
void WarningServiceObserver::ExtensionWarningsChanged(
const ExtensionIdSet& affected_extensions) {
if (!base::Contains(affected_extensions, extension_id_)) {
return;
}
run_loop_.Quit();
}
base::flat_set<int> GetDisabledRuleIdsFromMatcherForTesting(
const RulesetManager& ruleset_manager,
const Extension& extension,
const std::string& ruleset_id_string) {
const DNRManifestData::ManifestIDToRulesetMap& public_id_map =
DNRManifestData::GetManifestIDToRulesetMap(extension);
auto it = public_id_map.find(ruleset_id_string);
CHECK(public_id_map.end() != it);
RulesetID ruleset_id = it->second->id;
const CompositeMatcher* composite_matcher =
ruleset_manager.GetMatcherForExtension(extension.id());
DCHECK(composite_matcher);
for (const auto& matcher : composite_matcher->matchers()) {
if (ruleset_id != matcher->id()) {
continue;
}
return matcher->GetDisabledRuleIdsForTesting();
}
return {};
}
RequestParams CreateRequestWithResponseHeaders(
const GURL& url,
const net::HttpResponseHeaders* headers) {
return RequestParams(url, url::Origin(), url::Origin(),
dnr_api::ResourceType::kSubFrame,
dnr_api::RequestMethod::kGet, -1, headers);
}
} // namespace extensions::declarative_net_request