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