| // 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/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 "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/rules_count_pair.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/test_utils.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/value_builder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace extensions { |
| namespace 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::RULE_ACTION_TYPE_BLOCK; |
| case RequestAction::Type::ALLOW: |
| return dnr_api::RULE_ACTION_TYPE_ALLOW; |
| case RequestAction::Type::REDIRECT: |
| return dnr_api::RULE_ACTION_TYPE_REDIRECT; |
| case RequestAction::Type::UPGRADE: |
| return dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME; |
| case RequestAction::Type::ALLOW_ALL_REQUESTS: |
| return dnr_api::RULE_ACTION_TYPE_ALLOWALLREQUESTS; |
| case RequestAction::Type::MODIFY_HEADERS: |
| return dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS; |
| } |
| }(); |
| 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 absl::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_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_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_SPECIFIED: |
| output << "ERROR_NO_HEADERS_SPECIFIED"; |
| break; |
| case ParseResult::ERROR_EMPTY_REQUEST_HEADERS_LIST: |
| output << "ERROR_EMPTY_REQUEST_HEADERS_LIST"; |
| break; |
| case ParseResult::ERROR_EMPTY_RESPONSE_HEADERS_LIST: |
| output << "ERROR_EMPTY_RESPONSE_HEADERS_LIST"; |
| break; |
| case ParseResult::ERROR_INVALID_HEADER_NAME: |
| output << "ERROR_INVALID_HEADER_NAME"; |
| break; |
| case ParseResult::ERROR_INVALID_HEADER_VALUE: |
| output << "ERROR_INVALID_HEADER_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_REQUEST_HEADER_UNSUPPORTED: |
| output << "ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED"; |
| 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; |
| } |
| 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 RulesCountPair& count) { |
| output << "\nRulesCountPair\n"; |
| output << "|rule_count| " << count.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); |
| |
| const ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context); |
| for (const auto& source : sources) { |
| if (prefs->ShouldIgnoreDNRRuleset(extension.id(), source.id())) |
| continue; |
| |
| int expected_checksum = -1; |
| if (!prefs->GetDNRStaticRulesetChecksum(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|. |
| ListBuilder builder; |
| for (const auto& rule : rules) |
| builder.Append(rule.ToValue()); |
| JSONFileValueSerializer(source.json_path()).Serialize(*builder.Build()); |
| |
| // 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, |
| absl::optional<std::string> value) { |
| dnr_api::ModifyHeaderInfo header_info; |
| |
| header_info.operation = operation; |
| header_info.header = header; |
| |
| if (value) |
| header_info.value = std::make_unique<std::string>(*value); |
| |
| 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; |
| } |
| |
| 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(); |
| } |
| |
| } // namespace declarative_net_request |
| } // namespace extensions |