summaryrefslogtreecommitdiffstats
path: root/components/url_matcher/url_matcher_factory_unittest.cc
blob: 4718ca95b3ba886748e3a068610b524f58fb1fd7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
// Copyright 2013 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 "components/url_matcher/url_matcher_factory.h"

#include <stddef.h>

#include "base/format_macros.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "components/url_matcher/url_matcher_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace url_matcher {

namespace keys = url_matcher_constants;

TEST(URLMatcherFactoryTest, CreateFromURLFilterDictionary) {
  URLMatcher matcher;

  std::string error;
  scoped_refptr<URLMatcherConditionSet> result;

  // Invalid key: {"invalid": "foobar"}
  base::DictionaryValue invalid_condition;
  invalid_condition.SetString("invalid", "foobar");

  // Invalid value type: {"hostSuffix": []}
  base::DictionaryValue invalid_condition2;
  invalid_condition2.Set(keys::kHostSuffixKey, new base::ListValue);

  // Invalid regex value: {"urlMatches": "*"}
  base::DictionaryValue invalid_condition3;
  invalid_condition3.SetString(keys::kURLMatchesKey, "*");

  // Invalid regex value: {"originAndPathMatches": "*"}
  base::DictionaryValue invalid_condition4;
  invalid_condition4.SetString(keys::kOriginAndPathMatchesKey, "*");

  // Valid values:
  // {
  //   "port_range": [80, [1000, 1010]],
  //   "schemes": ["http"],
  //   "hostSuffix": "example.com"
  //   "hostPrefix": "www"
  // }

  // Port range: Allow 80;1000-1010.
  base::ListValue* port_range = new base::ListValue();
  port_range->Append(new base::FundamentalValue(1000));
  port_range->Append(new base::FundamentalValue(1010));
  base::ListValue* port_ranges = new base::ListValue();
  port_ranges->Append(new base::FundamentalValue(80));
  port_ranges->Append(port_range);

  base::ListValue* scheme_list = new base::ListValue();
  scheme_list->Append(new base::StringValue("http"));

  base::DictionaryValue valid_condition;
  valid_condition.SetString(keys::kHostSuffixKey, "example.com");
  valid_condition.SetString(keys::kHostPrefixKey, "www");
  valid_condition.Set(keys::kPortsKey, port_ranges);
  valid_condition.Set(keys::kSchemesKey, scheme_list);

  // Test wrong condition name passed.
  error.clear();
  result = URLMatcherFactory::CreateFromURLFilterDictionary(
      matcher.condition_factory(), &invalid_condition, 1, &error);
  EXPECT_FALSE(error.empty());
  EXPECT_FALSE(result.get());

  // Test wrong datatype in hostSuffix.
  error.clear();
  result = URLMatcherFactory::CreateFromURLFilterDictionary(
      matcher.condition_factory(), &invalid_condition2, 2, &error);
  EXPECT_FALSE(error.empty());
  EXPECT_FALSE(result.get());

  // Test invalid regex in urlMatches.
  error.clear();
  result = URLMatcherFactory::CreateFromURLFilterDictionary(
      matcher.condition_factory(), &invalid_condition3, 3, &error);
  EXPECT_FALSE(error.empty());
  EXPECT_FALSE(result.get());

  error.clear();
  result = URLMatcherFactory::CreateFromURLFilterDictionary(
      matcher.condition_factory(), &invalid_condition4, 4, &error);
  EXPECT_FALSE(error.empty());
  EXPECT_FALSE(result.get());

  // Test success.
  error.clear();
  result = URLMatcherFactory::CreateFromURLFilterDictionary(
      matcher.condition_factory(), &valid_condition, 100, &error);
  EXPECT_EQ("", error);
  ASSERT_TRUE(result.get());

  URLMatcherConditionSet::Vector conditions;
  conditions.push_back(result);
  matcher.AddConditionSets(conditions);

  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com")).size());
  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:80")).size());
  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:1000")).size());
  // Wrong scheme.
  EXPECT_EQ(0u, matcher.MatchURL(GURL("https://www.example.com:80")).size());
  // Wrong port.
  EXPECT_EQ(0u, matcher.MatchURL(GURL("http://www.example.com:81")).size());
  // Unfulfilled host prefix.
  EXPECT_EQ(0u, matcher.MatchURL(GURL("http://mail.example.com:81")).size());
}

// Using upper case letters for scheme and host values is currently an error.
// See more context at http://crbug.com/160702#c6 .
TEST(URLMatcherFactoryTest, UpperCase) {
  URLMatcher matcher;
  std::string error;
  scoped_refptr<URLMatcherConditionSet> result;

  // {"hostContains": "exaMple"}
  base::DictionaryValue invalid_condition1;
  invalid_condition1.SetString(keys::kHostContainsKey, "exaMple");

  // {"hostSuffix": ".Com"}
  base::DictionaryValue invalid_condition2;
  invalid_condition2.SetString(keys::kHostSuffixKey, ".Com");

  // {"hostPrefix": "WWw."}
  base::DictionaryValue invalid_condition3;
  invalid_condition3.SetString(keys::kHostPrefixKey, "WWw.");

  // {"hostEquals": "WWW.example.Com"}
  base::DictionaryValue invalid_condition4;
  invalid_condition4.SetString(keys::kHostEqualsKey, "WWW.example.Com");

  // {"scheme": ["HTTP"]}
  base::ListValue* scheme_list = new base::ListValue();
  scheme_list->Append(new base::StringValue("HTTP"));
  base::DictionaryValue invalid_condition5;
  invalid_condition5.Set(keys::kSchemesKey, scheme_list);

  const base::DictionaryValue* invalid_conditions[] = {
    &invalid_condition1,
    &invalid_condition2,
    &invalid_condition3,
    &invalid_condition4,
    &invalid_condition5
  };

  for (size_t i = 0; i < arraysize(invalid_conditions); ++i) {
    error.clear();
    result = URLMatcherFactory::CreateFromURLFilterDictionary(
        matcher.condition_factory(), invalid_conditions[i], 1, &error);
    EXPECT_FALSE(error.empty()) << "in iteration " << i;
    EXPECT_FALSE(result.get()) << "in iteration " << i;
  }
}

// This class wraps a case sensitivity test for a single UrlFilter condition.
class UrlConditionCaseTest {
 public:
  // The condition is identified by the key |condition_key|. If that key is
  // associated with string values, then |use_list_of_strings| should be false,
  // if the key is associated with list-of-string values, then
  // |use_list_of_strings| should be true. In |url| is the URL to test against.
  UrlConditionCaseTest(const char* condition_key,
                       bool use_list_of_strings,
                       const std::string& expected_value,
                       const std::string& incorrect_case_value,
                       bool case_sensitive,
                       bool lower_case_enforced,
                       const GURL& url)
      : condition_key_(condition_key),
        use_list_of_strings_(use_list_of_strings),
        expected_value_(expected_value),
        incorrect_case_value_(incorrect_case_value),
        expected_result_for_wrong_case_(ExpectedResult(case_sensitive,
                                                       lower_case_enforced)),
        url_(url) {}

  ~UrlConditionCaseTest() {}

  // Match the condition against |url_|. Checks via EXPECT_* macros that
  // |expected_value_| matches always, and that |incorrect_case_value_| matches
  // iff |case_sensitive_| is false.
  void Test() const;

 private:
  enum ResultType { OK, NOT_FULFILLED, CREATE_FAILURE };

  // What is the expected result of |CheckCondition| if a wrong-case |value|
  // containing upper case letters is supplied.
  static ResultType ExpectedResult(bool case_sensitive,
                                   bool lower_case_enforced) {
    if (lower_case_enforced)
      return CREATE_FAILURE;
    if (case_sensitive)
      return NOT_FULFILLED;
    return OK;
  }

  // Test the condition |condition_key_| = |value| against |url_|.
  // Check, via EXPECT_* macros, that either the condition cannot be constructed
  // at all, or that the condition is not fulfilled, or that it is fulfilled,
  // depending on the value of |expected_result|.
  void CheckCondition(const std::string& value,
                      ResultType expected_result) const;

  const char* condition_key_;
  const bool use_list_of_strings_;
  const std::string& expected_value_;
  const std::string& incorrect_case_value_;
  const ResultType expected_result_for_wrong_case_;
  const GURL& url_;

  // Allow implicit copy and assign, because a public copy constructor is
  // needed, but never used (!), for the definition of arrays of this class.
};

void UrlConditionCaseTest::Test() const {
  CheckCondition(expected_value_, OK);
  CheckCondition(incorrect_case_value_, expected_result_for_wrong_case_);
}

void UrlConditionCaseTest::CheckCondition(
    const std::string& value,
    UrlConditionCaseTest::ResultType expected_result) const {
  base::DictionaryValue condition;
  if (use_list_of_strings_) {
    base::ListValue* list = new base::ListValue();
    list->Append(new base::StringValue(value));
    condition.SetWithoutPathExpansion(condition_key_, list);
  } else {
    condition.SetStringWithoutPathExpansion(condition_key_, value);
  }

  URLMatcher matcher;
  std::string error;
  scoped_refptr<URLMatcherConditionSet> result;

  result = URLMatcherFactory::CreateFromURLFilterDictionary(
      matcher.condition_factory(), &condition, 1, &error);
  if (expected_result == CREATE_FAILURE) {
    EXPECT_FALSE(error.empty());
    EXPECT_FALSE(result.get());
    return;
  }
  EXPECT_EQ("", error);
  ASSERT_TRUE(result.get());

  URLMatcherConditionSet::Vector conditions;
  conditions.push_back(result);
  matcher.AddConditionSets(conditions);
  EXPECT_EQ((expected_result == OK ? 1u : 0u), matcher.MatchURL(url_).size())
      << "while matching condition " << condition_key_ << " with value "
      << value  << " against url " << url_;
}

// This tests that the UrlFilter handles case sensitivity on various parts of
// URLs correctly.
TEST(URLMatcherFactoryTest, CaseSensitivity) {
  const std::string kScheme("https");
  const std::string kSchemeUpper("HTTPS");
  const std::string kHost("www.example.com");
  const std::string kHostUpper("WWW.EXAMPLE.COM");
  const std::string kPath("/path");
  const std::string kPathUpper("/PATH");
  const std::string kQuery("?option=value&A=B");
  const std::string kQueryUpper("?OPTION=VALUE&A=B");
  const std::string kUrl(kScheme + "://" + kHost + ":1234" + kPath + kQuery);
  const std::string kUrlUpper(
      kSchemeUpper + "://" + kHostUpper + ":1234" + kPathUpper + kQueryUpper);
  const GURL url(kUrl);
  // Note: according to RFC 3986, and RFC 1034, schema and host, respectively
  // should be case insensitive. See crbug.com/160702#6 for why we still
  // require them to be case sensitive in UrlFilter, and enforce lower case.
  const bool kIsSchemeLowerCaseEnforced = true;
  const bool kIsHostLowerCaseEnforced = true;
  const bool kIsPathLowerCaseEnforced = false;
  const bool kIsQueryLowerCaseEnforced = false;
  const bool kIsUrlLowerCaseEnforced = false;
  const bool kIsSchemeCaseSensitive = true;
  const bool kIsHostCaseSensitive = true;
  const bool kIsPathCaseSensitive = true;
  const bool kIsQueryCaseSensitive = true;
  const bool kIsUrlCaseSensitive = kIsSchemeCaseSensitive ||
                                   kIsHostCaseSensitive ||
                                   kIsPathCaseSensitive ||
                                   kIsQueryCaseSensitive;

  const UrlConditionCaseTest case_tests[] = {
    UrlConditionCaseTest(keys::kSchemesKey, true, kScheme, kSchemeUpper,
                         kIsSchemeCaseSensitive, kIsSchemeLowerCaseEnforced,
                         url),
    UrlConditionCaseTest(keys::kHostContainsKey, false, kHost, kHostUpper,
                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kHostEqualsKey, false, kHost, kHostUpper,
                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kHostPrefixKey, false, kHost, kHostUpper,
                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kHostSuffixKey, false, kHost, kHostUpper,
                         kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kPathContainsKey, false, kPath, kPathUpper,
                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kPathEqualsKey, false, kPath, kPathUpper,
                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kPathPrefixKey, false, kPath, kPathUpper,
                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kPathSuffixKey, false, kPath, kPathUpper,
                         kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kQueryContainsKey, false, kQuery, kQueryUpper,
                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kQueryEqualsKey, false, kQuery, kQueryUpper,
                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kQueryPrefixKey, false, kQuery, kQueryUpper,
                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kQuerySuffixKey, false, kQuery, kQueryUpper,
                         kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url),
    // Excluding kURLMatchesKey because case sensitivity can be specified in the
    // RE2 expression.
    UrlConditionCaseTest(keys::kURLContainsKey, false, kUrl, kUrlUpper,
                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kURLEqualsKey, false, kUrl, kUrlUpper,
                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kURLPrefixKey, false, kUrl, kUrlUpper,
                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
    UrlConditionCaseTest(keys::kURLSuffixKey, false, kUrl, kUrlUpper,
                         kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url),
  };

  for (size_t i = 0; i < arraysize(case_tests); ++i) {
    SCOPED_TRACE(base::StringPrintf("Iteration: %" PRIuS, i));
    case_tests[i].Test();
  }
}

}  // namespace url_matcher