| // 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 "components/url_pattern_index/url_pattern_index.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <memory> |
| #include <numeric> |
| #include <string> |
| #include <vector> |
| |
| #include "base/containers/flat_set.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/rand_util.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/string_piece.h" |
| #include "components/url_pattern_index/url_pattern.h" |
| #include "components/url_pattern_index/url_rule_test_support.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace url_pattern_index { |
| |
| using testing::kAnchorNone; |
| using testing::kAnyParty; |
| using testing::kBoundary; |
| using testing::kDocument; |
| using testing::kFont; |
| using testing::kImage; |
| using testing::kNoActivation; |
| using testing::kScript; |
| using testing::kSubdomain; |
| using testing::kSubstring; |
| using testing::kThirdParty; |
| using testing::MakeUrlRule; |
| using EmbedderConditionsMatcher = |
| UrlPatternIndexMatcher::EmbedderConditionsMatcher; |
| |
| class UrlPatternIndexTest : public ::testing::Test { |
| public: |
| UrlPatternIndexTest() { Reset(); } |
| |
| UrlPatternIndexTest(const UrlPatternIndexTest&) = delete; |
| UrlPatternIndexTest& operator=(const UrlPatternIndexTest&) = delete; |
| |
| protected: |
| bool AddUrlRule(const proto::UrlRule& rule) { |
| auto offset = SerializeUrlRule(rule, flat_builder_.get(), &domain_map_); |
| if (offset.o) { |
| indexed_rules_count_++; |
| index_builder_->IndexUrlRule(offset); |
| } |
| return !!offset.o; |
| } |
| |
| void AddSimpleUrlRule(const std::string& pattern, |
| uint32_t id, |
| uint32_t priority, |
| uint8_t options, |
| uint16_t element_types, |
| uint16_t request_methods_mask, |
| const std::vector<uint8_t>& embedder_conditions = {}) { |
| auto pattern_offset = flat_builder_->CreateString(pattern); |
| auto embedder_conditions_offset = |
| flat_builder_->CreateVector(embedder_conditions); |
| |
| flat::UrlRuleBuilder rule_builder(*flat_builder_); |
| rule_builder.add_options(options); |
| rule_builder.add_url_pattern(pattern_offset); |
| rule_builder.add_id(id); |
| rule_builder.add_priority(priority); |
| rule_builder.add_element_types(element_types); |
| rule_builder.add_request_methods(request_methods_mask); |
| rule_builder.add_embedder_conditions(embedder_conditions_offset); |
| |
| auto rule_offset = rule_builder.Finish(); |
| |
| index_builder_->IndexUrlRule(rule_offset); |
| |
| indexed_rules_count_++; |
| } |
| |
| void Finish() { |
| const auto index_offset = index_builder_->Finish(); |
| flat_builder_->Finish(index_offset); |
| |
| const flat::UrlPatternIndex* flat_index = |
| flat::GetUrlPatternIndex(flat_builder_->GetBufferPointer()); |
| index_matcher_ = std::make_unique<UrlPatternIndexMatcher>(flat_index); |
| |
| ASSERT_EQ(indexed_rules_count_, index_matcher_->GetRulesCount()); |
| } |
| |
| const flat::UrlRule* FindMatch( |
| base::StringPiece url_string, |
| base::StringPiece document_origin_string = base::StringPiece(), |
| proto::ElementType element_type = testing::kOther, |
| proto::ActivationType activation_type = kNoActivation, |
| bool disable_generic_rules = false, |
| const base::flat_set<int>& disabled_rule_ids = {}) const { |
| const GURL url(url_string); |
| const url::Origin document_origin = |
| testing::GetOrigin(document_origin_string); |
| return index_matcher_->FindMatch( |
| url, document_origin, element_type, activation_type, |
| testing::IsThirdParty(url, document_origin), disable_generic_rules, |
| UrlPatternIndexMatcher::EmbedderConditionsMatcher(), |
| UrlPatternIndexMatcher::FindRuleStrategy::kAny, disabled_rule_ids); |
| } |
| |
| const flat::UrlRule* FindMatch( |
| base::StringPiece url_string, |
| base::StringPiece document_origin_string, |
| flat::ElementType element_type, |
| flat::ActivationType activation_type, |
| flat::RequestMethod request_method, |
| bool disable_generic_rules, |
| const EmbedderConditionsMatcher& embedder_conditions_matcher = |
| EmbedderConditionsMatcher()) const { |
| const GURL url(url_string); |
| const url::Origin document_origin = |
| testing::GetOrigin(document_origin_string); |
| return index_matcher_->FindMatch( |
| url, document_origin, element_type, activation_type, request_method, |
| testing::IsThirdParty(url, document_origin), disable_generic_rules, |
| embedder_conditions_matcher, |
| UrlPatternIndexMatcher::FindRuleStrategy::kAny, |
| {} /* disabled_rule_ids */); |
| } |
| |
| std::vector<const flat::UrlRule*> FindAllMatches( |
| base::StringPiece url_string, |
| base::StringPiece document_origin_string, |
| proto::ElementType element_type, |
| proto::ActivationType activation_type, |
| bool disable_generic_rules, |
| const base::flat_set<int>& disabled_rule_ids = {}) const { |
| const GURL url(url_string); |
| const url::Origin document_origin = |
| testing::GetOrigin(document_origin_string); |
| return index_matcher_->FindAllMatches( |
| url, document_origin, element_type, activation_type, |
| testing::IsThirdParty(url, document_origin), disable_generic_rules, |
| UrlPatternIndexMatcher::EmbedderConditionsMatcher(), disabled_rule_ids); |
| } |
| |
| const flat::UrlRule* FindHighestPriorityMatch( |
| base::StringPiece url_string, |
| const base::flat_set<int>& disabled_rule_ids = {}) const { |
| return index_matcher_->FindMatch( |
| GURL(url_string), url::Origin(), testing::kOther /*element_type*/, |
| kNoActivation /*activation_type*/, true /*is_third_party*/, |
| false /*disable_generic_rules*/, |
| UrlPatternIndexMatcher::EmbedderConditionsMatcher(), |
| UrlPatternIndexMatcher::FindRuleStrategy::kHighestPriority /*strategy*/, |
| disabled_rule_ids); |
| } |
| |
| bool IsOutOfRange(const flat::UrlRule* rule) const { |
| if (!rule) |
| return false; |
| const auto* data = reinterpret_cast<const uint8_t*>(rule); |
| return data < flat_builder_->GetBufferPointer() || |
| data >= flat_builder_->GetBufferPointer() + flat_builder_->GetSize(); |
| } |
| |
| void Reset() { |
| index_matcher_.reset(); |
| index_builder_.reset(); |
| flat_builder_ = std::make_unique<flatbuffers::FlatBufferBuilder>(); |
| index_builder_ = |
| std::make_unique<UrlPatternIndexBuilder>(flat_builder_.get()); |
| domain_map_.clear(); |
| indexed_rules_count_ = 0; |
| } |
| |
| private: |
| size_t indexed_rules_count_ = 0; |
| std::unique_ptr<flatbuffers::FlatBufferBuilder> flat_builder_; |
| std::unique_ptr<UrlPatternIndexBuilder> index_builder_; |
| std::unique_ptr<UrlPatternIndexMatcher> index_matcher_; |
| |
| FlatDomainMap domain_map_; |
| }; |
| |
| TEST_F(UrlPatternIndexTest, EmptyIndex) { |
| Finish(); |
| EXPECT_FALSE(FindMatch(base::StringPiece() /* url */)); |
| EXPECT_FALSE(FindMatch("http://example.com")); |
| EXPECT_FALSE(FindMatch("http://another.example.com?param=val")); |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneSimpleRule) { |
| ASSERT_TRUE(AddUrlRule(MakeUrlRule(UrlPattern("?param=", kSubstring)))); |
| Finish(); |
| |
| EXPECT_FALSE(FindMatch("https://example.com")); |
| EXPECT_TRUE(FindMatch("http://example.org?param=image1")); |
| } |
| |
| TEST_F(UrlPatternIndexTest, NoRuleApplies) { |
| ASSERT_TRUE(AddUrlRule(MakeUrlRule(UrlPattern("?filter_out=", kSubstring)))); |
| ASSERT_TRUE(AddUrlRule(MakeUrlRule(UrlPattern("&filter_out=", kSubstring)))); |
| Finish(); |
| |
| EXPECT_FALSE(FindMatch("http://example.com")); |
| EXPECT_FALSE(FindMatch("http://example.com?filter_not")); |
| EXPECT_FALSE(FindMatch("http://example.com?k=v&filter_not")); |
| } |
| |
| TEST_F(UrlPatternIndexTest, ProtoCaseSensitivity) { |
| ASSERT_TRUE( |
| AddUrlRule(MakeUrlRule(UrlPattern("case-sensitive", kSubstring)))); |
| proto::UrlRule rule = MakeUrlRule(UrlPattern("case-INSENsitive")); |
| rule.set_match_case(false); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| // We don't currently read case sensitivity from proto rules. |
| EXPECT_FALSE(FindMatch("http://abc.com/type=CASE-insEnsitIVe")); |
| EXPECT_FALSE(FindMatch("http://abc.com/type=case-INSENSITIVE")); |
| EXPECT_FALSE(FindMatch("http://abc.com?type=CASE-sensitive")); |
| EXPECT_TRUE(FindMatch("http://abc.com?type=case-sensitive")); |
| } |
| |
| TEST_F(UrlPatternIndexTest, CaseSensitivity) { |
| uint8_t common_options = flat::OptionFlag_APPLIES_TO_FIRST_PARTY | |
| flat::OptionFlag_APPLIES_TO_THIRD_PARTY; |
| AddSimpleUrlRule("case-insensitive", 0 /* id */, 0 /* priority */, |
| common_options | flat::OptionFlag_IS_CASE_INSENSITIVE, |
| flat::ElementType_ANY, flat::RequestMethod_ANY); |
| AddSimpleUrlRule("case-sensitive", 0 /* id */, 0 /* priority */, |
| common_options, flat::ElementType_ANY, |
| flat::RequestMethod_ANY); |
| Finish(); |
| |
| EXPECT_TRUE(FindMatch("http://abc.com/type=CASE-insEnsitIVe")); |
| EXPECT_TRUE(FindMatch("http://abc.com/type=case-INSENSITIVE")); |
| EXPECT_FALSE(FindMatch("http://abc.com?type=CASE-sensitive")); |
| EXPECT_TRUE(FindMatch("http://abc.com?type=case-sensitive")); |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithoutMetaInfo) { |
| const struct { |
| UrlPattern url_pattern; |
| const char* url; |
| bool expect_match; |
| } kTestCases[] = { |
| // SUBSTRING |
| {{"abcd", kSubstring}, "http://ex.com/abcd", true}, |
| {{"abcd", kSubstring}, "http://ex.com/dcab", false}, |
| {{"42", kSubstring}, "http://ex.com/adcd/picture42.png", true}, |
| {{"&test", kSubstring}, |
| "http://ex.com/params?param1=false&test=true", |
| true}, |
| {{"-test-42.", kSubstring}, "http://ex.com/unit-test-42.1", true}, |
| {{"/abcdtest160x600.", kSubstring}, |
| "http://ex.com/abcdtest160x600.png", |
| true}, |
| |
| // WILDCARDED |
| {{"http://ex.com/abcd/picture*.png"}, |
| "http://ex.com/abcd/picture42.png", |
| true}, |
| {{"ex.com", kSubdomain, kAnchorNone}, "http://ex.com", true}, |
| {{"ex.com", kSubdomain, kAnchorNone}, "http://test.ex.com", true}, |
| {{"ex.com", kSubdomain, kAnchorNone}, "https://test.ex.com.com", true}, |
| {{"ex.com", kSubdomain, kAnchorNone}, "https://test.rest.ex.com", true}, |
| {{"ex.com", kSubdomain, kAnchorNone}, "https://test_ex.com", false}, |
| |
| {{"http://ex.com", kBoundary, kAnchorNone}, "http://ex.com/", true}, |
| {{"http://ex.com", kBoundary, kAnchorNone}, "http://ex.com/42", true}, |
| {{"http://ex.com", kBoundary, kAnchorNone}, |
| "http://ex.com/42/http://ex.com/", |
| true}, |
| {{"http://ex.com", kBoundary, kAnchorNone}, |
| "http://ex.com/42/http://ex.info/", |
| true}, |
| {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com", true}, |
| {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com/42", false}, |
| {{"http://ex.com/", kBoundary, kBoundary}, |
| "http://ex.info/42/http://ex.com/", |
| false}, |
| {{"http://ex.com/", kBoundary, kBoundary}, |
| "http://ex.info/42/http://ex.com/", |
| false}, |
| {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com/", true}, |
| {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com/42.swf", false}, |
| {{"http://ex.com/", kBoundary, kBoundary}, |
| "http://ex.info/redirect/http://ex.com/", |
| false}, |
| {{"pdf", kAnchorNone, kBoundary}, "http://ex.com/abcd.pdf", true}, |
| {{"pdf", kAnchorNone, kBoundary}, "http://ex.com/pdfium", false}, |
| {{"http://ex.com^"}, "http://ex.com/", true}, |
| {{"http://ex.com^"}, "http://ex.com:8000/", true}, |
| {{"http://ex.com^"}, "http://ex.com.ru", false}, |
| {{"^ex.com^"}, |
| "http://ex.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82", |
| true}, |
| {{"^42.loss^"}, |
| "http://ex.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82", |
| true}, |
| |
| // TODO(pkalinnikov): The '^' at the end should match end-of-string. |
| // |
| // {"^%D1%82%D0%B5%D1%81%D1%82^", |
| // "http://ex.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82", |
| // true}, |
| // {"/abcd/*/picture^", "http://ex.com/abcd/42/picture", true}, |
| |
| {{"/abcd/*/picture^"}, "http://ex.com/abcd/42/loss/picture?param", true}, |
| {{"/abcd/*/picture^"}, "http://ex.com/abcd//picture/42", true}, |
| {{"/abcd/*/picture^"}, "http://ex.com/abcd/picture", false}, |
| {{"/abcd/*/picture^"}, "http://ex.com/abcd/42/pictureraph", false}, |
| {{"/abcd/*/picture^"}, "http://ex.com/abcd/42/picture.swf", false}, |
| {{"test.ex.com^", kSubdomain, kAnchorNone}, |
| "http://test.ex.com/42.swf", |
| true}, |
| {{"test.ex.com^", kSubdomain, kAnchorNone}, |
| "http://server1.test.ex.com/42.swf", |
| true}, |
| {{"test.ex.com^", kSubdomain, kAnchorNone}, |
| "https://test.ex.com:8000/", |
| true}, |
| {{"test.ex.com^", kSubdomain, kAnchorNone}, |
| "http://test.ex.com.ua/42.swf", |
| false}, |
| {{"test.ex.com^", kSubdomain, kAnchorNone}, |
| "http://ex.com/redirect/http://test.ex.com/", |
| false}, |
| |
| {{"/abcd/*"}, "https://ex.com/abcd/", true}, |
| {{"/abcd/*"}, "http://ex.com/abcd/picture.jpeg", true}, |
| {{"/abcd/*"}, "https://ex.com/abcd", false}, |
| {{"/abcd/*"}, "http://abcd.ex.com", false}, |
| {{"*/abcd/"}, "https://ex.com/abcd/", true}, |
| {{"*/abcd/"}, "http://ex.com/abcd/picture.jpeg", true}, |
| {{"*/abcd/"}, "https://ex.com/test-abcd/", false}, |
| {{"*/abcd/"}, "http://abcd.ex.com", false}, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() << "UrlPattern: " << test_case.url_pattern |
| << "; URL: " << test_case.url); |
| |
| ASSERT_TRUE(AddUrlRule(MakeUrlRule(test_case.url_pattern))); |
| Finish(); |
| |
| EXPECT_EQ(test_case.expect_match, !!FindMatch(test_case.url)); |
| Reset(); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithThirdParty) { |
| const struct { |
| const char* url_pattern; |
| proto::SourceType source_type; |
| |
| const char* url; |
| const char* document_origin; |
| bool expect_match; |
| } kTestCases[] = { |
| {"ex.com", kThirdParty, "http://ex.com", "http://exmpl.org", true}, |
| {"ex.com", kThirdParty, "http://ex.com", "http://ex.com", false}, |
| {"ex.com", kThirdParty, "http://ex.com/path?k=v", "http://exmpl.org", |
| true}, |
| {"ex.com", kThirdParty, "http://ex.com/path?k=v", "http://ex.com", false}, |
| {"ex.com", testing::kFirstParty, "http://ex.com/path?k=v", |
| "http://ex.com", true}, |
| {"ex.com", testing::kFirstParty, "http://ex.com/path?k=v", |
| "http://exmpl.com", false}, |
| {"ex.com", kAnyParty, "http://ex.com/path?k=v", "http://ex.com", true}, |
| {"ex.com", kAnyParty, "http://ex.com/path?k=v", "http://exmpl.com", true}, |
| {"ex.com", kThirdParty, "http://subdomain.ex.com", "http://ex.com", |
| false}, |
| {"ex.com", kThirdParty, "http://ex.com", "", true}, |
| |
| // Public Suffix List tests. |
| {"ex.com", kThirdParty, "http://two.ex.com", "http://one.ex.com", false}, |
| {"ex.com", kThirdParty, "http://ex.com", "http://one.ex.com", false}, |
| {"ex.com", kThirdParty, "http://two.ex.com", "http://ex.com", false}, |
| {"ex.com", kThirdParty, "http://ex.com", "http://example.org", true}, |
| {"appspot.com", kThirdParty, "http://two.appspot.org", |
| "http://one.appspot.com", false}, |
| }; |
| |
| for (auto test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "UrlPattern: " << test_case.url_pattern |
| << "; SourceType: " << static_cast<int>(test_case.source_type) |
| << "; URL: " << test_case.url |
| << "; DocumentOrigin: " << test_case.document_origin); |
| |
| auto rule = MakeUrlRule(UrlPattern(test_case.url_pattern, kSubstring)); |
| rule.set_source_type(test_case.source_type); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_EQ(test_case.expect_match, |
| !!FindMatch(test_case.url, test_case.document_origin)); |
| Reset(); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithDomainList) { |
| const struct { |
| std::vector<std::string> domains; |
| base::StringPiece url_or_origin; |
| bool expect_match; |
| } kTestCases[] = { |
| {std::vector<std::string>(), "", true}, |
| {std::vector<std::string>(), "http://domain.com", true}, |
| |
| {{"domain.com"}, "", false}, |
| {{"domain.com"}, "http://domain.com", true}, |
| {{"ddomain.com"}, "http://domain.com", false}, |
| {{"domain.com"}, "http://ddomain.com", false}, |
| {{"domain.com"}, "http://sub.domain.com", true}, |
| {{"sub.domain.com"}, "http://domain.com", false}, |
| {{"sub.domain.com"}, "http://sub.domain.com", true}, |
| {{"sub.domain.com"}, "http://a.b.c.sub.domain.com", true}, |
| {{"sub.domain.com"}, "http://sub.domain.com.com", false}, |
| |
| // TODO(pkalinnikov): Probably need to canonicalize domain patterns to |
| // avoid subtleties like below. |
| {{"domain.com"}, "http://domain.com.", true}, |
| {{"domain.com"}, "http://.domain.com", true}, |
| {{"domain.com"}, "http://.domain.com.", true}, |
| {{".domain.com"}, "http://.domain.com", true}, |
| {{"domain.com."}, "http://domain.com", false}, |
| {{"domain.com."}, "http://domain.com.", true}, |
| |
| {{"domain..com"}, "http://domain.com", false}, |
| {{"domain.com"}, "http://domain..com", false}, |
| {{"domain..com"}, "http://domain..com", true}, |
| |
| {{"~domain.com"}, "", true}, |
| {{"~domain.com"}, "http://domain.com", false}, |
| {{"~ddomain.com"}, "http://domain.com", true}, |
| {{"~domain.com"}, "http://ddomain.com", true}, |
| {{"~domain.com"}, "http://sub.domain.com", false}, |
| {{"~sub.domain.com"}, "http://domain.com", true}, |
| {{"~sub.domain.com"}, "http://sub.domain.com", false}, |
| {{"~sub.domain.com"}, "http://a.b.c.sub.domain.com", false}, |
| {{"~sub.domain.com"}, "http://sub.domain.com.com", true}, |
| |
| {{"domain1.com", "domain2.com"}, "", false}, |
| {{"domain1.com", "domain2.com"}, "http://domain1.com", true}, |
| {{"domain1.com", "domain2.com"}, "http://domain2.com", true}, |
| {{"domain1.com", "domain2.com"}, "http://domain3.com", false}, |
| {{"domain1.com", "domain2.com"}, "http://not_domain1.com", false}, |
| {{"domain1.com", "domain2.com"}, "http://sub.domain1.com", true}, |
| {{"domain1.com", "domain2.com"}, "http://a.b.c.sub.domain2.com", true}, |
| |
| {{"~domain1.com", "~domain2.com"}, "http://domain1.com", false}, |
| {{"~domain1.com", "~domain2.com"}, "http://domain2.com", false}, |
| {{"~domain1.com", "~domain2.com"}, "http://domain3.com", true}, |
| |
| {{"domain.com", "~sub.domain.com"}, "http://domain.com", true}, |
| {{"domain.com", "~sub.domain.com"}, "http://sub.domain.com", false}, |
| {{"domain.com", "~sub.domain.com"}, "http://a.b.sub.domain.com", false}, |
| {{"domain.com", "~sub.domain.com"}, "http://ssub.domain.com", true}, |
| |
| {{"domain.com", "~a.domain.com", "~b.domain.com"}, |
| "http://domain.com", |
| true}, |
| {{"domain.com", "~a.domain.com", "~b.domain.com"}, |
| "http://a.domain.com", |
| false}, |
| {{"domain.com", "~a.domain.com", "~b.domain.com"}, |
| "http://b.domain.com", |
| false}, |
| |
| {{"domain.com", "~a.domain.com", "b.a.domain.com"}, |
| "http://domain.com", |
| true}, |
| {{"domain.com", "~a.domain.com", "b.a.domain.com"}, |
| "http://a.domain.com", |
| false}, |
| {{"domain.com", "~a.domain.com", "b.a.domain.com"}, |
| "http://b.a.domain.com", |
| true}, |
| {{"domain.com", "~a.domain.com", "b.a.domain.com"}, |
| "http://c.b.a.domain.com", |
| true}, |
| |
| // The following test addresses a former bug in domain list matcher. When |
| // "domain.com" was matched, the positive filters lookup stopped, and the |
| // next domain was considered as a negative. The initial character was |
| // skipped (supposing it's a '~') and the remainder was considered a |
| // domain. So "ddomain.com" would be matched and thus the whole rule would |
| // be classified as non-matching, which is not correct. |
| {{"domain.com", "ddomain.com", "~sub.domain.com"}, |
| "http://domain.com", |
| true}, |
| }; |
| |
| // Test initiator domain conditions. |
| constexpr const char* kUrl = "http://example.com"; |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "Initiator Domains: " |
| << ::testing::PrintToString(test_case.domains) |
| << "; DocumentOrigin: " << test_case.url_or_origin); |
| |
| auto rule = MakeUrlRule(UrlPattern(kUrl, kSubstring)); |
| testing::AddInitiatorDomains(test_case.domains, &rule); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_EQ(test_case.expect_match, |
| !!FindMatch(kUrl, test_case.url_or_origin)); |
| Reset(); |
| } |
| |
| // Test request domain conditions. |
| for (const auto& test_case : kTestCases) { |
| if (test_case.url_or_origin.empty()) |
| continue; |
| |
| SCOPED_TRACE(::testing::Message() |
| << "Request Domains: " |
| << ::testing::PrintToString(test_case.domains) |
| << "; Request URL: " << test_case.url_or_origin); |
| |
| auto rule = MakeUrlRule(UrlPattern(test_case.url_or_origin, kSubstring)); |
| testing::AddRequestDomains(test_case.domains, &rule); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_EQ(test_case.expect_match, !!FindMatch(test_case.url_or_origin)); |
| Reset(); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithInitiatorAndRequestDomainLists) { |
| const struct { |
| std::vector<std::string> initiator_domains; |
| std::vector<std::string> request_domains; |
| const char* request_url; |
| const char* document_origin; |
| bool expect_match; |
| } kTestCases[] = { |
| {{"initiator.com"}, |
| {"request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| true}, |
| {{"initiator.com"}, |
| {"other-request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| false}, |
| {{"other-initiator.com"}, |
| {"request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| false}, |
| {{"~initiator.com"}, |
| {"request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| false}, |
| {{"initiator.com"}, |
| {"~request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| false}, |
| {{"~initiator.com"}, |
| {"~request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| false}, |
| {{"~other-initiator.com"}, |
| {"request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| true}, |
| {{"initiator.com"}, |
| {"~other-request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| true}, |
| {{"~other-initiator.com"}, |
| {"~other-request.com"}, |
| "http://request.com/path", |
| "http://initiator.com", |
| true}, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "Initiator Domains: " |
| << ::testing::PrintToString(test_case.initiator_domains) |
| << "; Request Domains: " |
| << ::testing::PrintToString(test_case.request_domains) |
| << "; Request URL: " << test_case.request_url |
| << "; Request Origin: " << test_case.document_origin); |
| |
| auto rule = MakeUrlRule(UrlPattern(test_case.request_url, kSubstring)); |
| testing::AddInitiatorDomains(test_case.initiator_domains, &rule); |
| testing::AddRequestDomains(test_case.request_domains, &rule); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_EQ(test_case.expect_match, |
| !!FindMatch(test_case.request_url, test_case.document_origin)); |
| Reset(); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithLongDomainList) { |
| constexpr const char* kUrl = "http://example.com"; |
| constexpr size_t kDomains = 200; |
| |
| std::vector<std::string> domains; |
| for (size_t i = 0; i < kDomains; ++i) { |
| const std::string domain = "domain" + std::to_string(i) + ".com"; |
| domains.push_back(domain); |
| domains.push_back("~sub." + domain); |
| domains.push_back("a.sub." + domain); |
| domains.push_back("b.sub." + domain); |
| domains.push_back("c.sub." + domain); |
| domains.push_back("~aa.sub." + domain); |
| domains.push_back("~ab.sub." + domain); |
| domains.push_back("~ba.sub." + domain); |
| domains.push_back("~bb.sub." + domain); |
| domains.push_back("~sub.sub.c.sub." + domain); |
| } |
| |
| auto rule = MakeUrlRule(UrlPattern(kUrl, kSubstring)); |
| testing::AddInitiatorDomains(domains, &rule); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| for (size_t i = 0; i < kDomains; ++i) { |
| SCOPED_TRACE(::testing::Message() << "Iteration: " << i); |
| const std::string domain = "domain" + std::to_string(i) + ".com"; |
| |
| EXPECT_TRUE(FindMatch(kUrl, "http://" + domain)); |
| EXPECT_FALSE(FindMatch(kUrl, "http://sub." + domain)); |
| EXPECT_TRUE(FindMatch(kUrl, "http://a.sub." + domain)); |
| EXPECT_TRUE(FindMatch(kUrl, "http://b.sub." + domain)); |
| EXPECT_TRUE(FindMatch(kUrl, "http://c.sub." + domain)); |
| EXPECT_FALSE(FindMatch(kUrl, "http://aa.sub." + domain)); |
| EXPECT_FALSE(FindMatch(kUrl, "http://ab.sub." + domain)); |
| EXPECT_FALSE(FindMatch(kUrl, "http://ba.sub." + domain)); |
| EXPECT_FALSE(FindMatch(kUrl, "http://bb.sub." + domain)); |
| EXPECT_TRUE(FindMatch(kUrl, "http://sub.c.sub." + domain)); |
| EXPECT_FALSE(FindMatch(kUrl, "http://sub.sub.c.sub." + domain)); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithElementTypes) { |
| constexpr auto kAll = testing::kAllElementTypes; |
| const struct { |
| const char* url_pattern; |
| int32_t element_types; |
| |
| const char* url; |
| proto::ElementType element_type; |
| bool expect_match; |
| } kTestCases[] = { |
| {"ex.com", kAll, "http://ex.com/img.jpg", kImage, true}, |
| {"ex.com", kAll & ~testing::kPopup, "http://ex.com/img", testing::kPopup, |
| false}, |
| |
| {"ex.com", kImage, "http://ex.com/img.jpg", kImage, true}, |
| {"ex.com", kAll & ~kImage, "http://ex.com/img.jpg", kImage, false}, |
| {"ex.com", kScript, "http://ex.com/img.jpg", kImage, false}, |
| {"ex.com", kAll & ~kScript, "http://ex.com/img.jpg", kImage, true}, |
| |
| {"ex.com", kImage | kFont, "http://ex.com/font", kFont, true}, |
| {"ex.com", kImage | kFont, "http://ex.com/image", kImage, true}, |
| {"ex.com", kImage | kFont, "http://ex.com/video", |
| proto::ELEMENT_TYPE_MEDIA, false}, |
| {"ex.com", kAll & ~kFont & ~kScript, "http://ex.com/font", kFont, false}, |
| {"ex.com", kAll & ~kFont & ~kScript, "http://ex.com/scr", kScript, false}, |
| {"ex.com", kAll & ~kFont & ~kScript, "http://ex.com/img", kImage, true}, |
| |
| {"ex.com", kAll, "http://ex.com", proto::ELEMENT_TYPE_OTHER, true}, |
| {"ex.com", kAll, "http://ex.com", proto::ELEMENT_TYPE_UNSPECIFIED, false}, |
| {"ex.com", testing::kWebSocket, "ws://ex.com", |
| proto::ELEMENT_TYPE_WEBSOCKET, true}, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE( |
| ::testing::Message() |
| << "UrlPattern: " << test_case.url_pattern |
| << "; ElementTypes: " << static_cast<int>(test_case.element_types) |
| << "; URL: " << test_case.url |
| << "; ElementType: " << static_cast<int>(test_case.element_type)); |
| |
| auto rule = MakeUrlRule(UrlPattern(test_case.url_pattern, kSubstring)); |
| rule.set_element_types(test_case.element_types); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_EQ(test_case.expect_match, |
| !!FindMatch(test_case.url, "" /* document_origin_string */, |
| test_case.element_type)); |
| Reset(); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithActivationTypes) { |
| const struct { |
| const char* url_pattern; |
| int32_t activation_types; |
| |
| const char* document_url; |
| proto::ActivationType activation_type; |
| bool expect_match; |
| } kTestCases[] = { |
| {"example.com", kDocument, "http://example.com", kDocument, true}, |
| {"xample.com", kDocument, "http://example.com", kDocument, true}, |
| {"exampl.com", kDocument, "http://example.com", kDocument, false}, |
| |
| {"example.com", testing::kGenericBlock, "http://example.com", kDocument, |
| false}, |
| {"example.com", kDocument, "http://example.com", kNoActivation, false}, |
| {"example.com", testing::kGenericBlock, "http://example.com", |
| kNoActivation, false}, |
| |
| // Invalid GURL. |
| {"example.com", kDocument, "http;//example.com", kDocument, false}, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE( |
| ::testing::Message() |
| << "UrlPattern: " << test_case.url_pattern |
| << "; ActivationTypes: " << static_cast<int>(test_case.activation_types) |
| << "; DocumentURL: " << test_case.document_url |
| << "; ActivationType: " << static_cast<int>(test_case.activation_type)); |
| |
| auto rule = MakeUrlRule(UrlPattern(test_case.url_pattern, kSubstring)); |
| rule.set_semantics(proto::RULE_SEMANTICS_ALLOWLIST); |
| rule.clear_element_types(); |
| rule.set_activation_types(test_case.activation_types); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_EQ( |
| test_case.expect_match, |
| !!FindMatch(test_case.document_url, "" /* parent_document_origin */, |
| testing::kNoElement, test_case.activation_type)); |
| EXPECT_EQ(test_case.expect_match, |
| !!FindMatch(test_case.document_url, "http://example.com/", |
| testing::kNoElement, test_case.activation_type)); |
| EXPECT_EQ(test_case.expect_match, |
| !!FindMatch(test_case.document_url, "http://xmpl.com/", |
| testing::kNoElement, test_case.activation_type)); |
| Reset(); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, OneRuleWithElementAndActivationTypes) { |
| auto rule = MakeUrlRule(UrlPattern("allow.ex.com", kSubstring)); |
| rule.set_semantics(proto::RULE_SEMANTICS_ALLOWLIST); |
| rule.set_element_types(testing::kSubdocument); |
| rule.set_activation_types(kDocument); |
| ASSERT_TRUE(AddUrlRule(rule)); |
| Finish(); |
| |
| EXPECT_FALSE(FindMatch("http://allow.ex.com")); |
| EXPECT_TRUE(FindMatch("http://allow.ex.com", "" /*document_origin_string */, |
| testing::kSubdocument)); |
| |
| EXPECT_FALSE(FindMatch("http://allow.ex.com", "" /* document_origin_string */, |
| testing::kNoElement, testing::kGenericBlock)); |
| EXPECT_TRUE(FindMatch("http://allow.ex.com", "" /* document_origin_string */, |
| testing::kNoElement, kDocument)); |
| } |
| |
| // Test that FindAllMatches will return the correct number of UrlRule matches |
| // for incoming requests. |
| TEST_F(UrlPatternIndexTest, MultipleRuleMatches) { |
| const struct { |
| uint32_t id; |
| const char* url_pattern; |
| uint16_t element_types; |
| } kRules[] = {{0, "ex1", flat::ElementType_ANY}, |
| {1, "ex1", flat::ElementType_IMAGE}, |
| {2, "ex1", flat::ElementType_IMAGE | flat::ElementType_FONT}, |
| {3, "ex12", flat::ElementType_ANY}, |
| {4, "google", flat::ElementType_ANY}, |
| {5, "google", flat::ElementType_IMAGE}}; |
| |
| for (const auto& rule_data : kRules) { |
| AddSimpleUrlRule(rule_data.url_pattern, rule_data.id, 0 /* priority */, |
| flat::OptionFlag_APPLIES_TO_FIRST_PARTY | |
| flat::OptionFlag_APPLIES_TO_THIRD_PARTY, |
| rule_data.element_types, flat::RequestMethod_ANY); |
| } |
| Finish(); |
| |
| const struct { |
| const char* url; |
| proto::ElementType element_type; |
| std::vector<uint32_t> expected_matched_ids; |
| } kTestCases[] = {{"http://ex1.com", proto::ELEMENT_TYPE_OTHER, {0}}, |
| {"http://ex1.com/font", kFont, {0, 2}}, |
| {"http://ex1.com/img", kImage, {0, 1, 2}}, |
| {"http://ex12.com", proto::ELEMENT_TYPE_OTHER, {0, 3}}, |
| {"http://ex12.com/img", kImage, {0, 1, 2, 3}}, |
| {"http://google.com", proto::ELEMENT_TYPE_OTHER, {4}}, |
| {"http://google.com/img", kImage, {4, 5}}, |
| {"http://ex12google.com/img", kImage, {0, 1, 2, 3, 4, 5}}, |
| {"http://nomatch.com/img", kImage, {}}}; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "UrlPattern: " << test_case.url << "; ElementTypes: " |
| << static_cast<int>(test_case.element_type)); |
| |
| std::vector<uint32_t> actual_matched_ids; |
| std::vector<const flat::UrlRule*> matched_rules = FindAllMatches( |
| test_case.url, "" /* document_origin_string */, test_case.element_type, |
| kNoActivation, false /* disable_generic_rules */); |
| |
| for (const auto* rule : matched_rules) |
| actual_matched_ids.push_back(rule->id()); |
| |
| EXPECT_THAT(actual_matched_ids, ::testing::UnorderedElementsAreArray( |
| test_case.expected_matched_ids)); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, MatchWithDisableGenericRules) { |
| const struct { |
| const char* url_pattern; |
| std::vector<std::string> initiator_domains; |
| } kRules[] = { |
| // Generic rules. |
| {"some_text", std::vector<std::string>()}, |
| {"another_text", {"~example.com"}}, |
| {"final_text", {"~example1.com", "~example2.com"}}, |
| // Domain specific rules. |
| {"some_text", {"example1.com"}}, |
| {"more_text", {"example.com", "~exclude.example.com"}}, |
| {"last_text", {"example1.com", "sub.example2.com"}}, |
| }; |
| |
| for (const auto& rule_data : kRules) { |
| auto rule = MakeUrlRule(UrlPattern(rule_data.url_pattern, kSubstring)); |
| testing::AddInitiatorDomains(rule_data.initiator_domains, &rule); |
| ASSERT_TRUE(AddUrlRule(rule)) |
| << "UrlPattern: " << rule_data.url_pattern << "; Initiator Domains: " |
| << ::testing::PrintToString(rule_data.initiator_domains); |
| } |
| |
| // Note: Some of the rules have common domains (e.g., example1.com), which are |
| // ultimately shared by FlatBuffers' CreateSharedString. The test also makes |
| // sure that the data structure works properly with such optimization. |
| Finish(); |
| |
| const struct { |
| const char* url; |
| const char* document_origin; |
| bool expect_match_with_enable_all_rules; |
| bool expect_match_with_disable_generic_rules; |
| } kTestCases[] = { |
| {"http://ex.com/some_text", "http://example.com", true, false}, |
| {"http://ex.com/some_text", "http://example1.com", true, true}, |
| |
| {"http://ex.com/another_text", "http://example.com", false, false}, |
| {"http://ex.com/another_text", "http://example1.com", true, false}, |
| |
| {"http://ex.com/final_text", "http://example.com", true, false}, |
| {"http://ex.com/final_text", "http://example1.com", false, false}, |
| {"http://ex.com/final_text", "http://example2.com", false, false}, |
| |
| {"http://ex.com/more_text", "http://example.com", true, true}, |
| {"http://ex.com/more_text", "http://exclude.example.com", false, false}, |
| {"http://ex.com/more_text", "http://example1.com", false, false}, |
| |
| {"http://ex.com/last_text", "http://example.com", false, false}, |
| {"http://ex.com/last_text", "http://example1.com", true, true}, |
| {"http://ex.com/last_text", "http://example2.com", false, false}, |
| {"http://ex.com/last_text", "http://sub.example2.com", true, true}, |
| }; |
| |
| constexpr bool kDisableGenericRules = true; |
| constexpr bool kEnableAllRules = false; |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "UrlPattern: " << test_case.url |
| << "; DocumentOrigin: " << test_case.document_origin); |
| |
| EXPECT_EQ( |
| test_case.expect_match_with_disable_generic_rules, |
| !!FindMatch(test_case.url, test_case.document_origin, testing::kOther, |
| kNoActivation, kDisableGenericRules)); |
| EXPECT_EQ(test_case.expect_match_with_enable_all_rules, |
| !!FindMatch(test_case.url, test_case.document_origin, |
| testing::kOther, kNoActivation, kEnableAllRules)); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, RulesWithUnsupportedTypes) { |
| const struct { |
| int element_types; |
| int activation_types; |
| } kRules[] = { |
| {proto::ELEMENT_TYPE_MAX << 1, 0}, |
| {0, proto::ACTIVATION_TYPE_MAX << 1}, |
| {proto::ELEMENT_TYPE_MAX << 1, proto::ACTIVATION_TYPE_MAX << 1}, |
| |
| {testing::kPopup, 0}, |
| {0, proto::ACTIVATION_TYPE_ELEMHIDE}, |
| {0, proto::ACTIVATION_TYPE_GENERICHIDE}, |
| {0, proto::ACTIVATION_TYPE_ELEMHIDE | proto::ACTIVATION_TYPE_GENERICHIDE}, |
| {proto::ELEMENT_TYPE_POPUP, proto::ACTIVATION_TYPE_ELEMHIDE}, |
| }; |
| |
| for (const auto& rule_data : kRules) { |
| auto rule = MakeUrlRule(UrlPattern("example.com", kSubstring)); |
| rule.set_element_types(rule_data.element_types); |
| rule.set_activation_types(rule_data.activation_types); |
| EXPECT_FALSE(AddUrlRule(rule)) |
| << "ElementTypes: " << static_cast<int>(rule_data.element_types) |
| << "; ActivationTypes: " |
| << static_cast<int>(rule_data.activation_types); |
| } |
| ASSERT_TRUE(AddUrlRule(MakeUrlRule(UrlPattern("exmpl.com", kSubstring)))); |
| Finish(); |
| |
| EXPECT_FALSE(FindMatch("http://example.com/")); |
| EXPECT_TRUE(FindMatch("https://exmpl.com/")); |
| } |
| |
| TEST_F(UrlPatternIndexTest, RulesWithSupportedAndUnsupportedTypes) { |
| const struct { |
| int element_types; |
| int activation_types; |
| } kRules[] = { |
| {kImage | (proto::ELEMENT_TYPE_MAX << 1), 0}, |
| {kScript | testing::kPopup, 0}, |
| {0, kDocument | (proto::ACTIVATION_TYPE_MAX << 1)}, |
| }; |
| |
| for (const auto& rule_data : kRules) { |
| auto rule = MakeUrlRule(UrlPattern("example.com", kSubstring)); |
| rule.set_semantics(proto::RULE_SEMANTICS_ALLOWLIST); |
| rule.set_element_types(rule_data.element_types); |
| rule.set_activation_types(rule_data.activation_types); |
| EXPECT_TRUE(AddUrlRule(rule)) |
| << "ElementTypes: " << static_cast<int>(rule_data.element_types) |
| << "; ActivationTypes: " |
| << static_cast<int>(rule_data.activation_types); |
| } |
| Finish(); |
| |
| EXPECT_TRUE(FindMatch("http://example.com/", "", kImage)); |
| EXPECT_TRUE(FindMatch("http://example.com/", "", kScript)); |
| EXPECT_FALSE(FindMatch("http://example.com/", "", testing::kPopup)); |
| EXPECT_FALSE(FindMatch("http://example.com/")); |
| |
| EXPECT_TRUE( |
| FindMatch("http://example.com", "", testing::kNoElement, kDocument)); |
| EXPECT_FALSE(FindMatch("http://example.com", "", testing::kNoElement, |
| testing::kGenericBlock)); |
| } |
| |
| TEST_F(UrlPatternIndexTest, FindMatchReturnsCorrectRules) { |
| constexpr size_t kNumOfPatterns = 1024; |
| |
| std::vector<std::string> url_patterns(kNumOfPatterns); |
| for (size_t i = 0; i < kNumOfPatterns; ++i) { |
| url_patterns[i] = "http://example." + std::to_string(i) + ".com"; |
| ASSERT_TRUE( |
| AddUrlRule(MakeUrlRule(UrlPattern(url_patterns[i], kSubstring)))) |
| << "Rule #" << i; |
| } |
| Finish(); |
| |
| std::reverse(url_patterns.begin() + kNumOfPatterns / 2, url_patterns.end()); |
| for (const std::string& url_pattern : url_patterns) { |
| SCOPED_TRACE(::testing::Message() << "UrlPattern: " << url_pattern); |
| |
| const flat::UrlRule* rule = FindMatch(url_pattern); |
| ASSERT_TRUE(rule); |
| ASSERT_FALSE(IsOutOfRange(rule)); |
| |
| const flatbuffers::String* rule_pattern = rule->url_pattern(); |
| ASSERT_TRUE(rule_pattern); |
| EXPECT_EQ(url_pattern, |
| base::StringPiece(rule_pattern->data(), rule_pattern->size())); |
| } |
| |
| EXPECT_FALSE( |
| FindMatch("http://example." + std::to_string(kNumOfPatterns) + ".com")); |
| } |
| |
| // Tests UrlPatternIndexMatcher::FindMatch works with the kHighestPriority match |
| // strategy. |
| TEST_F(UrlPatternIndexTest, FindMatchHighestPriority) { |
| const size_t kNumPatternTypes = 15; |
| |
| int id = 1; |
| auto pattern_for_number = [](size_t num) { |
| return "http://" + std::to_string(num) + ".com"; |
| }; |
| |
| for (size_t i = 1; i <= kNumPatternTypes; i++) { |
| // For pattern type |i|, add |i| rules with priority from 1 to |i|. |
| std::string pattern = pattern_for_number(i); |
| |
| // Create a shuffled vector of priorities from 1 to |i|. |
| std::vector<uint32_t> priorities(i); |
| std::iota(priorities.begin(), priorities.end(), 1); |
| base::RandomShuffle(priorities.begin(), priorities.end()); |
| |
| for (size_t j = 0; j < i; j++) { |
| AddSimpleUrlRule(pattern, id, priorities[j], |
| flat::OptionFlag_APPLIES_TO_FIRST_PARTY | |
| flat::OptionFlag_APPLIES_TO_THIRD_PARTY, |
| flat::ElementType_ANY, flat::RequestMethod_ANY); |
| id++; |
| } |
| } |
| Finish(); |
| |
| for (size_t i = 1; i <= kNumPatternTypes; i++) { |
| std::string pattern = pattern_for_number(i); |
| SCOPED_TRACE(::testing::Message() << "UrlPattern: " << pattern); |
| |
| const flat::UrlRule* rule = FindHighestPriorityMatch(pattern); |
| ASSERT_TRUE(rule); |
| |
| EXPECT_EQ(i, rule->priority()); |
| } |
| |
| EXPECT_FALSE(FindHighestPriorityMatch(pattern_for_number(0))); |
| EXPECT_FALSE( |
| FindHighestPriorityMatch(pattern_for_number(kNumPatternTypes + 1))); |
| } |
| |
| TEST_F(UrlPatternIndexTest, LongUrl_NoMatch) { |
| std::string pattern = "http://example.com"; |
| ASSERT_TRUE(AddUrlRule(MakeUrlRule(UrlPattern(pattern, kSubstring)))); |
| Finish(); |
| |
| std::string url = "http://example.com/"; |
| url.append(url::kMaxURLChars - url.size(), 'x'); |
| EXPECT_EQ(url::kMaxURLChars, url.size()); |
| EXPECT_TRUE(FindMatch(url)); |
| |
| // Add a single extra character, which should push this over the max URL |
| // limit. At this point, URL pattern matching should just give up since the |
| // URL load will be disallowed elsewhere in the stack. |
| url += "x"; |
| EXPECT_GT(url.size(), url::kMaxURLChars); |
| EXPECT_FALSE(FindMatch(url)); |
| } |
| |
| TEST_F(UrlPatternIndexTest, RequestMethod) { |
| const flat::ElementType other_element = flat::ElementType_OTHER; |
| const flat::ActivationType no_activation = flat::ActivationType_NONE; |
| const std::string origin = "http://foo.com"; |
| |
| const struct { |
| std::string name; |
| flat::RequestMethod request_method; |
| } request_methods[] = {{"delete", flat::RequestMethod_DELETE}, |
| {"get", flat::RequestMethod_GET}, |
| {"head", flat::RequestMethod_HEAD}, |
| {"options", flat::RequestMethod_OPTIONS}, |
| {"patch", flat::RequestMethod_PATCH}, |
| {"post", flat::RequestMethod_POST}, |
| {"put", flat::RequestMethod_PUT}}; |
| |
| int next_rule_id = 0; |
| for (auto request_method : request_methods) { |
| AddSimpleUrlRule(request_method.name /* pattern */, next_rule_id++, |
| 0 /* priority */, |
| flat::OptionFlag_APPLIES_TO_FIRST_PARTY | |
| flat::OptionFlag_APPLIES_TO_THIRD_PARTY, |
| flat::ElementType_ANY, request_method.request_method); |
| } |
| |
| Finish(); |
| |
| for (size_t i = 0; i < std::size(request_methods); i++) { |
| SCOPED_TRACE(::testing::Message() |
| << "RequestMethod: " << request_methods[i].name); |
| std::string url = origin + "/" + request_methods[i].name; |
| EXPECT_TRUE(FindMatch(url, origin, other_element, no_activation, |
| flat::RequestMethod_ANY, false)); |
| EXPECT_TRUE(FindMatch(url, origin, other_element, no_activation, |
| flat::RequestMethod_NONE, false)); |
| for (size_t j = 0; j < std::size(request_methods); j++) { |
| EXPECT_EQ(i == j, !!FindMatch(url, origin, other_element, no_activation, |
| request_methods[j].request_method, false)); |
| } |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, EmbedderConditions) { |
| const std::vector<uint8_t> embedder_data_1 = {1, 2, 3}; |
| const std::string url_1 = "http://foo.com"; |
| AddSimpleUrlRule("foo", 1 /* id */, 0 /* priority */, flat::OptionFlag_ANY, |
| flat::ElementType_ANY, flat::RequestMethod_ANY, |
| embedder_data_1); |
| const std::vector<uint8_t> embedder_data_2 = {4, 5}; |
| const std::string url_2 = "http://bar.com"; |
| AddSimpleUrlRule("bar", 2 /* id */, 0 /* priority */, flat::OptionFlag_ANY, |
| flat::ElementType_ANY, flat::RequestMethod_ANY, |
| embedder_data_2); |
| Finish(); |
| |
| EmbedderConditionsMatcher match_first_element_one = |
| base::BindRepeating([](const flatbuffers::Vector<uint8_t>& conditions) { |
| return conditions.size() >= 1 && conditions[0] == 1; |
| }); |
| EmbedderConditionsMatcher match_first_element_three = |
| base::BindRepeating([](const flatbuffers::Vector<uint8_t>& conditions) { |
| return conditions.size() >= 1 && conditions[0] == 3; |
| }); |
| EmbedderConditionsMatcher match_has_evens = |
| base::BindRepeating([](const flatbuffers::Vector<uint8_t>& conditions) { |
| return base::ranges::any_of(conditions, |
| [](int i) { return i % 2 == 0; }); |
| }); |
| |
| struct { |
| const std::string url; |
| const EmbedderConditionsMatcher matcher; |
| const bool expect_match; |
| // Fields below are valid iff `expect_match` is true. |
| const uint32_t expected_id = 0; |
| const absl::optional<std::vector<uint8_t>> expected_embedder_data; |
| } cases[] = {{url_1, match_first_element_one, true, 1, embedder_data_1}, |
| {url_1, match_has_evens, true, 1, embedder_data_1}, |
| {url_1, match_first_element_three, false}, |
| {url_2, match_first_element_one, false}, |
| {url_2, match_has_evens, true, 2, embedder_data_2}, |
| {url_2, match_first_element_three, false}, |
| {"http://abc.com", match_first_element_one, false}}; |
| |
| for (size_t i = 0; i < std::size(cases); ++i) { |
| SCOPED_TRACE(::testing::Message() << "Testing case " << i); |
| bool disable_generic_rules = false; |
| const flat::UrlRule* rule = FindMatch( |
| cases[i].url, "", flat::ElementType_OTHER, flat::ActivationType_NONE, |
| flat::RequestMethod_GET, disable_generic_rules, cases[i].matcher); |
| EXPECT_EQ(cases[i].expect_match, !!rule); |
| if (cases[i].expect_match) { |
| EXPECT_EQ(cases[i].expected_id, rule->id()); |
| EXPECT_EQ(cases[i].expected_embedder_data, |
| std::vector<uint8_t>(rule->embedder_conditions()->begin(), |
| rule->embedder_conditions()->end())); |
| } |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, FindMatchWithDisabledRuleIds) { |
| const struct { |
| uint32_t id; |
| const char* url_pattern; |
| uint32_t priority; |
| } kRules[] = {{0, "ex1", 0}, {1, "ex11", 1}, {2, "ex111", 2}}; |
| |
| for (const auto& rule_data : kRules) { |
| AddSimpleUrlRule(rule_data.url_pattern, rule_data.id, rule_data.priority, |
| flat::OptionFlag_ANY, flat::ElementType_ANY, |
| flat::RequestMethod_ANY); |
| } |
| Finish(); |
| |
| const struct { |
| const char* url; |
| base::flat_set<int> disabled_rule_ids; |
| const bool expected_match; |
| // Fields below are valid only if `expected_match` is true. |
| const uint32_t expected_highest_priority_id = UINT32_MAX; |
| } kTestCases[] = {{"http://ex1.com", {}, true, 0}, |
| {"http://ex1.com", {}, true, 0}, |
| {"http://ex1.com", {0}, false}, |
| {"http://ex1.com", {1}, true, 0}, |
| {"http://ex1.com", {0, 1}, false}, |
| {"http://ex11.com", {}, true, 1}, |
| {"http://ex11.com", {}, true, 1}, |
| {"http://ex11.com", {0}, true, 1}, |
| {"http://ex11.com", {1}, true, 0}, |
| {"http://ex11.com", {0, 1}, false}, |
| {"http://ex11.com", {2}, true, 1}, |
| {"http://ex11.com", {0, 2}, true, 1}, |
| {"http://ex11.com", {1, 2}, true, 0}, |
| {"http://ex111.com", {}, true, 2}, |
| {"http://ex111.com", {}, true, 2}, |
| {"http://ex111.com", {0}, true, 2}, |
| {"http://ex111.com", {0, 1}, true, 2}, |
| {"http://ex111.com", {2}, true, 1}, |
| {"http://ex111.com", {0, 2}, true, 1}, |
| {"http://ex111.com", {1, 2}, true, 0}, |
| {"http://ex111.com", {0, 1, 2}, false}, |
| {"http://ex111.com", {3}, true, 2}, |
| {"http://ex111.com", {2, 3}, true, 1}}; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "UrlPattern: " << test_case.url << "; DisabledRuleIds: " |
| << ::testing::PrintToString(test_case.disabled_rule_ids)); |
| |
| const flat::UrlRule* rule = FindMatch( |
| test_case.url, base::StringPiece(), testing::kOther, kNoActivation, |
| false /* disable_generic_rules */, test_case.disabled_rule_ids); |
| |
| EXPECT_EQ(test_case.expected_match, !!rule); |
| |
| rule = FindHighestPriorityMatch(test_case.url, test_case.disabled_rule_ids); |
| EXPECT_EQ(test_case.expected_match, !!rule); |
| if (test_case.expected_match && rule) |
| EXPECT_EQ(test_case.expected_highest_priority_id, rule->id()); |
| } |
| } |
| |
| TEST_F(UrlPatternIndexTest, FindAllMatchesWithDisabledRuleIds) { |
| const struct { |
| uint32_t id; |
| const char* url_pattern; |
| } kRules[] = {{0, "ex1"}, {1, "ex11"}, {2, "ex111"}}; |
| |
| for (const auto& rule_data : kRules) { |
| AddSimpleUrlRule(rule_data.url_pattern, rule_data.id, 0 /* priority */, |
| flat::OptionFlag_ANY, flat::ElementType_ANY, |
| flat::RequestMethod_ANY); |
| } |
| Finish(); |
| |
| const struct { |
| const char* url; |
| base::flat_set<int> disabled_rule_ids; |
| std::vector<uint32_t> expected_matched_ids; |
| } kTestCases[] = {{"http://ex1.com", {}, {0}}, |
| {"http://ex1.com", {}, {0}}, |
| {"http://ex1.com", {0}, {}}, |
| {"http://ex1.com", {1}, {0}}, |
| {"http://ex1.com", {0, 1}, {}}, |
| {"http://ex11.com", {}, {0, 1}}, |
| {"http://ex11.com", {0}, {1}}, |
| {"http://ex11.com", {1}, {0}}, |
| {"http://ex11.com", {0, 1}, {}}, |
| {"http://ex11.com", {2}, {0, 1}}, |
| {"http://ex11.com", {0, 2}, {1}}, |
| {"http://ex111.com", {}, {0, 1, 2}}, |
| {"http://ex111.com", {}, {0, 1, 2}}, |
| {"http://ex111.com", {0}, {1, 2}}, |
| {"http://ex111.com", {1}, {0, 2}}, |
| {"http://ex111.com", {2}, {0, 1}}, |
| {"http://ex111.com", {0, 2}, {1}}, |
| {"http://ex111.com", {0, 1, 2}, {}}, |
| {"http://ex111.com", {3}, {0, 1, 2}}, |
| {"http://ex111.com", {1, 3}, {0, 2}}}; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() |
| << "UrlPattern: " << test_case.url << "; DisabledRuleIds: " |
| << ::testing::PrintToString(test_case.disabled_rule_ids)); |
| |
| std::vector<const flat::UrlRule*> matched_rules = FindAllMatches( |
| test_case.url, "" /* document_origin_string */, |
| proto::ELEMENT_TYPE_OTHER, kNoActivation, |
| false /* disable_generic_rules */, test_case.disabled_rule_ids); |
| |
| std::vector<uint32_t> actual_matched_ids; |
| for (const auto* rule : matched_rules) |
| actual_matched_ids.push_back(rule->id()); |
| |
| EXPECT_THAT(actual_matched_ids, ::testing::UnorderedElementsAreArray( |
| test_case.expected_matched_ids)); |
| } |
| } |
| |
| } // namespace url_pattern_index |