| // Copyright (c) 2012 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/common/url_pattern_set.h" |
| |
| #include <stddef.h> |
| |
| #include <sstream> |
| |
| #include "base/containers/contains.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| void AddPattern(URLPatternSet* set, const std::string& pattern) { |
| int schemes = URLPattern::SCHEME_ALL; |
| set->AddPattern(URLPattern(schemes, pattern)); |
| } |
| |
| } // namespace |
| |
| TEST(URLPatternSetTest, Empty) { |
| URLPatternSet set; |
| EXPECT_FALSE(set.MatchesURL(GURL("http://www.foo.com/bar"))); |
| EXPECT_FALSE(set.MatchesURL(GURL())); |
| EXPECT_FALSE(set.MatchesURL(GURL("invalid"))); |
| } |
| |
| TEST(URLPatternSetTest, One) { |
| URLPatternSet set; |
| AddPattern(&set, "http://www.google.com/*"); |
| |
| EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/"))); |
| EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey"))); |
| EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/"))); |
| EXPECT_FALSE(set.MatchesURL(GURL("https://www.microsoft.com/"))); |
| } |
| |
| TEST(URLPatternSetTest, Two) { |
| URLPatternSet set; |
| AddPattern(&set, "http://www.google.com/*"); |
| AddPattern(&set, "http://www.yahoo.com/*"); |
| |
| EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey"))); |
| EXPECT_TRUE(set.MatchesURL(GURL("http://www.yahoo.com/monkey"))); |
| EXPECT_FALSE(set.MatchesURL(GURL("https://www.apple.com/monkey"))); |
| } |
| |
| TEST(URLPatternSetTest, StreamOperatorEmpty) { |
| URLPatternSet set; |
| |
| std::ostringstream stream; |
| stream << set; |
| EXPECT_EQ("{ }", stream.str()); |
| } |
| |
| TEST(URLPatternSetTest, StreamOperatorOne) { |
| URLPatternSet set; |
| AddPattern(&set, "http://www.google.com/*"); |
| |
| std::ostringstream stream; |
| stream << set; |
| EXPECT_EQ("{ \"http://www.google.com/*\" }", stream.str()); |
| } |
| |
| TEST(URLPatternSetTest, StreamOperatorTwo) { |
| URLPatternSet set; |
| AddPattern(&set, "http://www.google.com/*"); |
| AddPattern(&set, "http://www.yahoo.com/*"); |
| |
| std::ostringstream stream; |
| stream << set; |
| EXPECT_EQ("{ \"http://www.google.com/*\", \"http://www.yahoo.com/*\" }", |
| stream.str()); |
| } |
| |
| TEST(URLPatternSetTest, OverlapsWith) { |
| URLPatternSet set1; |
| AddPattern(&set1, "http://www.google.com/f*"); |
| AddPattern(&set1, "http://www.yahoo.com/b*"); |
| |
| URLPatternSet set2; |
| AddPattern(&set2, "http://www.reddit.com/f*"); |
| AddPattern(&set2, "http://www.yahoo.com/z*"); |
| |
| URLPatternSet set3; |
| AddPattern(&set3, "http://www.google.com/q/*"); |
| AddPattern(&set3, "http://www.yahoo.com/b/*"); |
| |
| EXPECT_FALSE(set1.OverlapsWith(set2)); |
| EXPECT_FALSE(set2.OverlapsWith(set1)); |
| |
| EXPECT_TRUE(set1.OverlapsWith(set3)); |
| EXPECT_TRUE(set3.OverlapsWith(set1)); |
| } |
| |
| TEST(URLPatternSetTest, CreateDifference) { |
| URLPatternSet expected; |
| URLPatternSet set1; |
| URLPatternSet set2; |
| AddPattern(&set1, "http://www.google.com/f*"); |
| AddPattern(&set1, "http://www.yahoo.com/b*"); |
| |
| // Subtract an empty set. |
| URLPatternSet result = URLPatternSet::CreateDifference(set1, set2); |
| EXPECT_EQ(set1, result); |
| |
| // Subtract a real set. |
| AddPattern(&set2, "http://www.reddit.com/f*"); |
| AddPattern(&set2, "http://www.yahoo.com/z*"); |
| AddPattern(&set2, "http://www.google.com/f*"); |
| |
| AddPattern(&expected, "http://www.yahoo.com/b*"); |
| |
| result = URLPatternSet::CreateDifference(set1, set2); |
| EXPECT_EQ(expected, result); |
| EXPECT_FALSE(result.is_empty()); |
| EXPECT_TRUE(set1.Contains(result)); |
| EXPECT_FALSE(result.Contains(set2)); |
| EXPECT_FALSE(set2.Contains(result)); |
| |
| URLPatternSet intersection = URLPatternSet::CreateIntersection( |
| result, set2, URLPatternSet::IntersectionBehavior::kStringComparison); |
| EXPECT_TRUE(intersection.is_empty()); |
| } |
| |
| TEST(URLPatternSetTest, CreateIntersection_StringComparison) { |
| URLPatternSet empty_set; |
| URLPatternSet expected; |
| URLPatternSet set1; |
| AddPattern(&set1, "http://www.google.com/f*"); |
| AddPattern(&set1, "http://www.yahoo.com/b*"); |
| |
| // Intersection with an empty set. |
| URLPatternSet result = URLPatternSet::CreateIntersection( |
| set1, empty_set, URLPatternSet::IntersectionBehavior::kStringComparison); |
| EXPECT_EQ(expected, result); |
| EXPECT_TRUE(result.is_empty()); |
| EXPECT_TRUE(empty_set.Contains(result)); |
| EXPECT_TRUE(result.Contains(empty_set)); |
| EXPECT_TRUE(set1.Contains(result)); |
| |
| // Intersection with a real set. |
| URLPatternSet set2; |
| AddPattern(&set2, "http://www.reddit.com/f*"); |
| AddPattern(&set2, "http://www.yahoo.com/z*"); |
| AddPattern(&set2, "http://www.google.com/f*"); |
| |
| AddPattern(&expected, "http://www.google.com/f*"); |
| |
| result = URLPatternSet::CreateIntersection( |
| set1, set2, URLPatternSet::IntersectionBehavior::kStringComparison); |
| EXPECT_EQ(expected, result); |
| EXPECT_FALSE(result.is_empty()); |
| EXPECT_TRUE(set1.Contains(result)); |
| EXPECT_TRUE(set2.Contains(result)); |
| } |
| |
| TEST(URLPatternSetTest, CreateIntersection_PatternsContainedByBoth) { |
| { |
| URLPatternSet set1; |
| AddPattern(&set1, "http://*.google.com/*"); |
| AddPattern(&set1, "http://*.yahoo.com/*"); |
| |
| URLPatternSet set2; |
| AddPattern(&set2, "http://google.com/*"); |
| |
| // The semantic intersection should contain only those patterns that are in |
| // both set 1 and set 2, or "http://google.com/*". |
| URLPatternSet intersection = URLPatternSet::CreateIntersection( |
| set1, set2, |
| URLPatternSet::IntersectionBehavior::kPatternsContainedByBoth); |
| ASSERT_EQ(1u, intersection.size()); |
| EXPECT_EQ("http://google.com/*", intersection.begin()->GetAsString()); |
| } |
| |
| { |
| // IntersectionBehavior::kPatternsContainedByBoth doesn't handle funny |
| // intersections, where the resultant pattern is neither of the two |
| // compared patterns. So the intersection of these two is not |
| // http://www.google.com/*, but rather nothing. |
| URLPatternSet set1; |
| AddPattern(&set1, "http://*/*"); |
| URLPatternSet set2; |
| AddPattern(&set2, "*://www.google.com/*"); |
| EXPECT_TRUE( |
| URLPatternSet::CreateIntersection( |
| set1, set2, |
| URLPatternSet::IntersectionBehavior::kPatternsContainedByBoth) |
| .is_empty()); |
| } |
| } |
| |
| TEST(URLPatternSetTest, CreateIntersection_Detailed) { |
| struct { |
| std::vector<std::string> set1; |
| std::vector<std::string> set2; |
| std::vector<std::string> expected_intersection; |
| } test_cases[] = { |
| {{"https://*.google.com/*", "https://chromium.org/*"}, |
| {"*://maps.google.com/*", "*://chromium.org/foo"}, |
| {"https://maps.google.com/*", "https://chromium.org/foo"}}, |
| {{"https://*/*", "http://*/*"}, |
| {"*://google.com/*", "*://chromium.org/*"}, |
| {"https://google.com/*", "http://google.com/*", "https://chromium.org/*", |
| "http://chromium.org/*"}}, |
| {{"<all_urls>"}, |
| {"https://chromium.org/*", "*://google.com/*"}, |
| {"https://chromium.org/*", "*://google.com/*"}}, |
| {{"*://*/maps", "*://*.example.com/*"}, |
| {"https://*.google.com/*", "https://www.example.com/*"}, |
| {"https://*.google.com/maps", "https://www.example.com/*"}}, |
| {{"https://*/maps", "https://*.google.com/*"}, |
| {"https://*.google.com/*"}, |
| {"https://*.google.com/*"}}, |
| {{"http://*/*"}, {"https://*/*"}, {}}, |
| {{"https://*.google.com/*", "https://maps.google.com/*"}, |
| {"https://*.google.com/*", "https://*/*"}, |
| // NOTE: We don't currently do any additional "collapsing" step after |
| // finding the intersection. We potentially could, to reduce the number |
| // of patterns we need to store. |
| {"https://*.google.com/*", "https://maps.google.com/*"}}, |
| }; |
| |
| constexpr int kValidSchemes = URLPattern::SCHEME_ALL; |
| constexpr char kTestCaseDescriptionTemplate[] = |
| "Running Test Case:\n" |
| " Set1: %s\n" |
| " Set2: %s\n" |
| " Expected Result: %s"; |
| for (const auto& test_case : test_cases) { |
| SCOPED_TRACE(base::StringPrintf( |
| kTestCaseDescriptionTemplate, |
| base::JoinString(test_case.set1, ", ").c_str(), |
| base::JoinString(test_case.set2, ", ").c_str(), |
| base::JoinString(test_case.expected_intersection, ", ").c_str())); |
| |
| URLPatternSet set1; |
| for (const auto& pattern_str : test_case.set1) { |
| URLPattern pattern(kValidSchemes); |
| ASSERT_EQ(URLPattern::ParseResult::kSuccess, pattern.Parse(pattern_str)) |
| << "Failed to parse: " << pattern_str; |
| set1.AddPattern(pattern); |
| } |
| |
| URLPatternSet set2; |
| for (const auto& pattern_str : test_case.set2) { |
| URLPattern pattern(kValidSchemes); |
| ASSERT_EQ(URLPattern::ParseResult::kSuccess, pattern.Parse(pattern_str)) |
| << "Failed to parse: " << pattern_str; |
| set2.AddPattern(pattern); |
| } |
| |
| URLPatternSet intersection1 = URLPatternSet::CreateIntersection( |
| set1, set2, URLPatternSet::IntersectionBehavior::kDetailed); |
| URLPatternSet intersection2 = URLPatternSet::CreateIntersection( |
| set2, set1, URLPatternSet::IntersectionBehavior::kDetailed); |
| |
| EXPECT_THAT( |
| *intersection1.ToStringVector(), |
| testing::UnorderedElementsAreArray(test_case.expected_intersection)); |
| EXPECT_THAT( |
| *intersection2.ToStringVector(), |
| testing::UnorderedElementsAreArray(test_case.expected_intersection)); |
| } |
| } |
| |
| TEST(URLPatternSetTest, CreateUnion) { |
| URLPatternSet empty_set; |
| |
| URLPatternSet set1; |
| AddPattern(&set1, "http://www.google.com/f*"); |
| AddPattern(&set1, "http://www.yahoo.com/b*"); |
| |
| URLPatternSet expected; |
| AddPattern(&expected, "http://www.google.com/f*"); |
| AddPattern(&expected, "http://www.yahoo.com/b*"); |
| |
| // Union with an empty set. |
| URLPatternSet result = URLPatternSet::CreateUnion(set1, empty_set); |
| EXPECT_EQ(expected, result); |
| |
| // Union with a real set. |
| URLPatternSet set2; |
| AddPattern(&set2, "http://www.reddit.com/f*"); |
| AddPattern(&set2, "http://www.yahoo.com/z*"); |
| AddPattern(&set2, "http://www.google.com/f*"); |
| |
| AddPattern(&expected, "http://www.reddit.com/f*"); |
| AddPattern(&expected, "http://www.yahoo.com/z*"); |
| |
| result = URLPatternSet::CreateUnion(set1, set2); |
| EXPECT_EQ(expected, result); |
| } |
| |
| TEST(URLPatternSetTest, Contains) { |
| URLPatternSet set1; |
| URLPatternSet set2; |
| URLPatternSet empty_set; |
| |
| AddPattern(&set1, "http://www.google.com/*"); |
| AddPattern(&set1, "http://www.yahoo.com/*"); |
| |
| AddPattern(&set2, "http://www.reddit.com/*"); |
| |
| EXPECT_FALSE(set1.Contains(set2)); |
| EXPECT_TRUE(set1.Contains(empty_set)); |
| EXPECT_FALSE(empty_set.Contains(set1)); |
| |
| AddPattern(&set2, "http://www.yahoo.com/*"); |
| |
| EXPECT_FALSE(set1.Contains(set2)); |
| EXPECT_FALSE(set2.Contains(set1)); |
| |
| AddPattern(&set2, "http://www.google.com/*"); |
| |
| EXPECT_FALSE(set1.Contains(set2)); |
| EXPECT_TRUE(set2.Contains(set1)); |
| |
| // Note that this checks if individual patterns contain other patterns, not |
| // just equality. For example: |
| AddPattern(&set1, "http://*.reddit.com/*"); |
| EXPECT_TRUE(set1.Contains(set2)); |
| EXPECT_FALSE(set2.Contains(set1)); |
| } |
| |
| TEST(URLPatternSetTest, Duplicates) { |
| URLPatternSet set1; |
| URLPatternSet set2; |
| |
| AddPattern(&set1, "http://www.google.com/*"); |
| AddPattern(&set2, "http://www.google.com/*"); |
| |
| AddPattern(&set1, "http://www.google.com/*"); |
| |
| // The sets should still be equal after adding a duplicate. |
| EXPECT_EQ(set2, set1); |
| } |
| |
| TEST(URLPatternSetTest, ToValueAndPopulate) { |
| URLPatternSet set1; |
| URLPatternSet set2; |
| |
| std::vector<std::string> patterns; |
| patterns.push_back("http://www.google.com/*"); |
| patterns.push_back("http://www.yahoo.com/*"); |
| |
| for (size_t i = 0; i < patterns.size(); ++i) |
| AddPattern(&set1, patterns[i]); |
| |
| std::string error; |
| bool allow_file_access = false; |
| std::unique_ptr<base::ListValue> value(set1.ToValue()); |
| set2.Populate(*value, URLPattern::SCHEME_ALL, allow_file_access, &error); |
| EXPECT_EQ(set1, set2); |
| |
| set2.ClearPatterns(); |
| set2.Populate(patterns, URLPattern::SCHEME_ALL, allow_file_access, &error); |
| EXPECT_EQ(set1, set2); |
| } |
| |
| TEST(URLPatternSetTest, AddOrigin) { |
| URLPatternSet set; |
| EXPECT_TRUE(set.AddOrigin( |
| URLPattern::SCHEME_ALL, GURL("https://www.google.com/"))); |
| EXPECT_TRUE(set.MatchesURL(GURL("https://www.google.com/foo/bar"))); |
| EXPECT_FALSE(set.MatchesURL(GURL("http://www.google.com/foo/bar"))); |
| EXPECT_FALSE(set.MatchesURL(GURL("https://en.google.com/foo/bar"))); |
| set.ClearPatterns(); |
| |
| EXPECT_TRUE(set.AddOrigin( |
| URLPattern::SCHEME_ALL, GURL("https://google.com/"))); |
| EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/foo/bar"))); |
| EXPECT_TRUE(set.MatchesURL(GURL("https://google.com/foo/bar"))); |
| |
| EXPECT_FALSE(set.AddOrigin( |
| URLPattern::SCHEME_HTTP, GURL("https://google.com/"))); |
| } |
| |
| TEST(URLPatternSet, AddOriginIPv6) { |
| { |
| URLPatternSet set; |
| EXPECT_TRUE(set.AddOrigin(URLPattern::SCHEME_HTTP, |
| GURL("http://[2607:f8b0:4005:805::200e]/*"))); |
| } |
| { |
| URLPatternSet set; |
| EXPECT_TRUE(set.AddOrigin(URLPattern::SCHEME_HTTP, |
| GURL("http://[2607:f8b0:4005:805::200e]/"))); |
| } |
| } |
| |
| TEST(URLPatternSetTest, ToStringVector) { |
| URLPatternSet set; |
| AddPattern(&set, "https://google.com/"); |
| AddPattern(&set, "https://google.com/"); |
| AddPattern(&set, "https://yahoo.com/"); |
| |
| std::unique_ptr<std::vector<std::string>> string_vector(set.ToStringVector()); |
| |
| EXPECT_EQ(2UL, string_vector->size()); |
| |
| EXPECT_TRUE(base::Contains(*string_vector, "https://google.com/")); |
| EXPECT_TRUE(base::Contains(*string_vector, "https://yahoo.com/")); |
| } |
| |
| } // namespace extensions |