1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/url_matcher/url_matcher_factory.h"
6
7#include "base/basictypes.h"
8#include "base/format_macros.h"
9#include "base/strings/stringprintf.h"
10#include "base/values.h"
11#include "components/url_matcher/url_matcher_constants.h"
12#include "testing/gtest/include/gtest/gtest.h"
13#include "url/gurl.h"
14
15namespace url_matcher {
16
17namespace keys = url_matcher_constants;
18
19TEST(URLMatcherFactoryTest, CreateFromURLFilterDictionary) {
20  URLMatcher matcher;
21
22  std::string error;
23  scoped_refptr<URLMatcherConditionSet> result;
24
25  // Invalid key: {"invalid": "foobar"}
26  base::DictionaryValue invalid_condition;
27  invalid_condition.SetString("invalid", "foobar");
28
29  // Invalid value type: {"hostSuffix": []}
30  base::DictionaryValue invalid_condition2;
31  invalid_condition2.Set(keys::kHostSuffixKey, new base::ListValue);
32
33  // Invalid regex value: {"urlMatches": "*"}
34  base::DictionaryValue invalid_condition3;
35  invalid_condition3.SetString(keys::kURLMatchesKey, "*");
36
37  // Invalid regex value: {"originAndPathMatches": "*"}
38  base::DictionaryValue invalid_condition4;
39  invalid_condition4.SetString(keys::kOriginAndPathMatchesKey, "*");
40
41  // Valid values:
42  // {
43  //   "port_range": [80, [1000, 1010]],
44  //   "schemes": ["http"],
45  //   "hostSuffix": "example.com"
46  //   "hostPrefix": "www"
47  // }
48
49  // Port range: Allow 80;1000-1010.
50  base::ListValue* port_range = new base::ListValue();
51  port_range->Append(new base::FundamentalValue(1000));
52  port_range->Append(new base::FundamentalValue(1010));
53  base::ListValue* port_ranges = new base::ListValue();
54  port_ranges->Append(new base::FundamentalValue(80));
55  port_ranges->Append(port_range);
56
57  base::ListValue* scheme_list = new base::ListValue();
58  scheme_list->Append(new base::StringValue("http"));
59
60  base::DictionaryValue valid_condition;
61  valid_condition.SetString(keys::kHostSuffixKey, "example.com");
62  valid_condition.SetString(keys::kHostPrefixKey, "www");
63  valid_condition.Set(keys::kPortsKey, port_ranges);
64  valid_condition.Set(keys::kSchemesKey, scheme_list);
65
66  // Test wrong condition name passed.
67  error.clear();
68  result = URLMatcherFactory::CreateFromURLFilterDictionary(
69      matcher.condition_factory(), &invalid_condition, 1, &error);
70  EXPECT_FALSE(error.empty());
71  EXPECT_FALSE(result.get());
72
73  // Test wrong datatype in hostSuffix.
74  error.clear();
75  result = URLMatcherFactory::CreateFromURLFilterDictionary(
76      matcher.condition_factory(), &invalid_condition2, 2, &error);
77  EXPECT_FALSE(error.empty());
78  EXPECT_FALSE(result.get());
79
80  // Test invalid regex in urlMatches.
81  error.clear();
82  result = URLMatcherFactory::CreateFromURLFilterDictionary(
83      matcher.condition_factory(), &invalid_condition3, 3, &error);
84  EXPECT_FALSE(error.empty());
85  EXPECT_FALSE(result.get());
86
87  error.clear();
88  result = URLMatcherFactory::CreateFromURLFilterDictionary(
89      matcher.condition_factory(), &invalid_condition4, 4, &error);
90  EXPECT_FALSE(error.empty());
91  EXPECT_FALSE(result.get());
92
93  // Test success.
94  error.clear();
95  result = URLMatcherFactory::CreateFromURLFilterDictionary(
96      matcher.condition_factory(), &valid_condition, 100, &error);
97  EXPECT_EQ("", error);
98  ASSERT_TRUE(result.get());
99
100  URLMatcherConditionSet::Vector conditions;
101  conditions.push_back(result);
102  matcher.AddConditionSets(conditions);
103
104  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com")).size());
105  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:80")).size());
106  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:1000")).size());
107  // Wrong scheme.
108  EXPECT_EQ(0u, matcher.MatchURL(GURL("https://www.example.com:80")).size());
109  // Wrong port.
110  EXPECT_EQ(0u, matcher.MatchURL(GURL("http://www.example.com:81")).size());
111  // Unfulfilled host prefix.
112  EXPECT_EQ(0u, matcher.MatchURL(GURL("http://mail.example.com:81")).size());
113}
114
115// Using upper case letters for scheme and host values is currently an error.
116// See more context at http://crbug.com/160702#c6 .
117TEST(URLMatcherFactoryTest, UpperCase) {
118  URLMatcher matcher;
119  std::string error;
120  scoped_refptr<URLMatcherConditionSet> result;
121
122  // {"hostContains": "exaMple"}
123  base::DictionaryValue invalid_condition1;
124  invalid_condition1.SetString(keys::kHostContainsKey, "exaMple");
125
126  // {"hostSuffix": ".Com"}
127  base::DictionaryValue invalid_condition2;
128  invalid_condition2.SetString(keys::kHostSuffixKey, ".Com");
129
130  // {"hostPrefix": "WWw."}
131  base::DictionaryValue invalid_condition3;
132  invalid_condition3.SetString(keys::kHostPrefixKey, "WWw.");
133
134  // {"hostEquals": "WWW.example.Com"}
135  base::DictionaryValue invalid_condition4;
136  invalid_condition4.SetString(keys::kHostEqualsKey, "WWW.example.Com");
137
138  // {"scheme": ["HTTP"]}
139  base::ListValue* scheme_list = new base::ListValue();
140  scheme_list->Append(new base::StringValue("HTTP"));
141  base::DictionaryValue invalid_condition5;
142  invalid_condition5.Set(keys::kSchemesKey, scheme_list);
143
144  const base::DictionaryValue* invalid_conditions[] = {
145    &invalid_condition1,
146    &invalid_condition2,
147    &invalid_condition3,
148    &invalid_condition4,
149    &invalid_condition5
150  };
151
152  for (size_t i = 0; i < arraysize(invalid_conditions); ++i) {
153    error.clear();
154    result = URLMatcherFactory::CreateFromURLFilterDictionary(
155        matcher.condition_factory(), invalid_conditions[i], 1, &error);
156    EXPECT_FALSE(error.empty()) << "in iteration " << i;
157    EXPECT_FALSE(result.get()) << "in iteration " << i;
158  }
159}
160
161// This class wraps a case sensitivity test for a single UrlFilter condition.
162class UrlConditionCaseTest {
163 public:
164  // The condition is identified by the key |condition_key|. If that key is
165  // associated with string values, then |use_list_of_strings| should be false,
166  // if the key is associated with list-of-string values, then
167  // |use_list_of_strings| should be true. In |url| is the URL to test against.
168  UrlConditionCaseTest(const char* condition_key,
169                       bool use_list_of_strings,
170                       const std::string& expected_value,
171                       const std::string& incorrect_case_value,
172                       bool case_sensitive,
173                       bool lower_case_enforced,
174                       const GURL& url)
175      : condition_key_(condition_key),
176        use_list_of_strings_(use_list_of_strings),
177        expected_value_(expected_value),
178        incorrect_case_value_(incorrect_case_value),
179        expected_result_for_wrong_case_(ExpectedResult(case_sensitive,
180                                                       lower_case_enforced)),
181        url_(url) {}
182
183  ~UrlConditionCaseTest() {}
184
185  // Match the condition against |url_|. Checks via EXPECT_* macros that
186  // |expected_value_| matches always, and that |incorrect_case_value_| matches
187  // iff |case_sensitive_| is false.
188  void Test() const;
189
190 private:
191  enum ResultType { OK, NOT_FULFILLED, CREATE_FAILURE };
192
193  // What is the expected result of |CheckCondition| if a wrong-case |value|
194  // containing upper case letters is supplied.
195  static ResultType ExpectedResult(bool case_sensitive,
196                                   bool lower_case_enforced) {
197    if (lower_case_enforced)
198      return CREATE_FAILURE;
199    if (case_sensitive)
200      return NOT_FULFILLED;
201    return OK;
202  }
203
204  // Test the condition |condition_key_| = |value| against |url_|.
205  // Check, via EXPECT_* macros, that either the condition cannot be constructed
206  // at all, or that the condition is not fulfilled, or that it is fulfilled,
207  // depending on the value of |expected_result|.
208  void CheckCondition(const std::string& value,
209                      ResultType expected_result) const;
210
211  const char* condition_key_;
212  const bool use_list_of_strings_;
213  const std::string& expected_value_;
214  const std::string& incorrect_case_value_;
215  const ResultType expected_result_for_wrong_case_;
216  const GURL& url_;
217
218  // Allow implicit copy and assign, because a public copy constructor is
219  // needed, but never used (!), for the definition of arrays of this class.
220};
221
222void UrlConditionCaseTest::Test() const {
223  CheckCondition(expected_value_, OK);
224  CheckCondition(incorrect_case_value_, expected_result_for_wrong_case_);
225}
226
227void UrlConditionCaseTest::CheckCondition(
228    const std::string& value,
229    UrlConditionCaseTest::ResultType expected_result) const {
230  base::DictionaryValue condition;
231  if (use_list_of_strings_) {
232    base::ListValue* list = new base::ListValue();
233    list->Append(new base::StringValue(value));
234    condition.SetWithoutPathExpansion(condition_key_, list);
235  } else {
236    condition.SetStringWithoutPathExpansion(condition_key_, value);
237  }
238
239  URLMatcher matcher;
240  std::string error;
241  scoped_refptr<URLMatcherConditionSet> result;
242
243  result = URLMatcherFactory::CreateFromURLFilterDictionary(
244      matcher.condition_factory(), &condition, 1, &error);
245  if (expected_result == CREATE_FAILURE) {
246    EXPECT_FALSE(error.empty());
247    EXPECT_FALSE(result.get());
248    return;
249  }
250  EXPECT_EQ("", error);
251  ASSERT_TRUE(result.get());
252
253  URLMatcherConditionSet::Vector conditions;
254  conditions.push_back(result);
255  matcher.AddConditionSets(conditions);
256  EXPECT_EQ((expected_result == OK ? 1u : 0u), matcher.MatchURL(url_).size())
257      << "while matching condition " << condition_key_ << " with value "
258      << value  << " against url " << url_;
259}
260
261// This tests that the UrlFilter handles case sensitivity on various parts of
262// URLs correctly.
263TEST(URLMatcherFactoryTest, CaseSensitivity) {
264  const std::string kScheme("https");
265  const std::string kSchemeUpper("HTTPS");
266  const std::string kHost("www.example.com");
267  const std::string kHostUpper("WWW.EXAMPLE.COM");
268  const std::string kPath("/path");
269  const std::string kPathUpper("/PATH");
270  const std::string kQuery("?option=value&A=B");
271  const std::string kQueryUpper("?OPTION=VALUE&A=B");
272  const std::string kUrl(kScheme + "://" + kHost + ":1234" + kPath + kQuery);
273  const std::string kUrlUpper(
274      kSchemeUpper + "://" + kHostUpper + ":1234" + kPathUpper + kQueryUpper);
275  const GURL url(kUrl);
276  // Note: according to RFC 3986, and RFC 1034, schema and host, respectively
277  // should be case insensitive. See crbug.com/160702#6 for why we still
278  // require them to be case sensitive in UrlFilter, and enforce lower case.
279  const bool kIsSchemeLowerCaseEnforced = true;
280  const bool kIsHostLowerCaseEnforced = true;
281  const bool kIsPathLowerCaseEnforced = false;
282  const bool kIsQueryLowerCaseEnforced = false;
283  const bool kIsUrlLowerCaseEnforced = false;
284  const bool kIsSchemeCaseSensitive = true;
285  const bool kIsHostCaseSensitive = true;
286  const bool kIsPathCaseSensitive = true;
287  const bool kIsQueryCaseSensitive = true;
288  const bool kIsUrlCaseSensitive = kIsSchemeCaseSensitive ||
289                                   kIsHostCaseSensitive ||
290                                   kIsPathCaseSensitive ||
291                                   kIsQueryCaseSensitive;
292
293  const UrlConditionCaseTest case_tests[] = {
294    UrlConditionCaseTest(keys::kSchemesKey, true, kScheme, kSchemeUpper,
295                         kIsSchemeCaseSensitive, kIsSchemeLowerCaseEnforced,
296                         url),
297    UrlConditionCaseTest(keys::kHostContainsKey, false, kHost, kHostUpper,
298                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
299    UrlConditionCaseTest(keys::kHostEqualsKey, false, kHost, kHostUpper,
300                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
301    UrlConditionCaseTest(keys::kHostPrefixKey, false, kHost, kHostUpper,
302                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
303    UrlConditionCaseTest(keys::kHostSuffixKey, false, kHost, kHostUpper,
304                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
305    UrlConditionCaseTest(keys::kPathContainsKey, false, kPath, kPathUpper,
306                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
307    UrlConditionCaseTest(keys::kPathEqualsKey, false, kPath, kPathUpper,
308                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
309    UrlConditionCaseTest(keys::kPathPrefixKey, false, kPath, kPathUpper,
310                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
311    UrlConditionCaseTest(keys::kPathSuffixKey, false, kPath, kPathUpper,
312                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
313    UrlConditionCaseTest(keys::kQueryContainsKey, false, kQuery, kQueryUpper,
314                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
315    UrlConditionCaseTest(keys::kQueryEqualsKey, false, kQuery, kQueryUpper,
316                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
317    UrlConditionCaseTest(keys::kQueryPrefixKey, false, kQuery, kQueryUpper,
318                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
319    UrlConditionCaseTest(keys::kQuerySuffixKey, false, kQuery, kQueryUpper,
320                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
321    // Excluding kURLMatchesKey because case sensitivity can be specified in the
322    // RE2 expression.
323    UrlConditionCaseTest(keys::kURLContainsKey, false, kUrl, kUrlUpper,
324                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
325    UrlConditionCaseTest(keys::kURLEqualsKey, false, kUrl, kUrlUpper,
326                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
327    UrlConditionCaseTest(keys::kURLPrefixKey, false, kUrl, kUrlUpper,
328                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
329    UrlConditionCaseTest(keys::kURLSuffixKey, false, kUrl, kUrlUpper,
330                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
331  };
332
333  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(case_tests); ++i) {
334    SCOPED_TRACE(base::StringPrintf("Iteration: %" PRIuS, i));
335    case_tests[i].Test();
336  }
337}
338
339}  // namespace url_matcher
340