Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_condit ion_attribute.h" | 5 #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_condit ion_attribute.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <set> | |
| 9 #include <string.h> | |
| 8 | 10 |
| 9 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/string_util.h" | |
| 10 #include "base/stringprintf.h" | 13 #include "base/stringprintf.h" |
| 11 #include "base/values.h" | 14 #include "base/values.h" |
| 12 #include "chrome/browser/extensions/api/declarative_webrequest/request_stage.h" | 15 #include "chrome/browser/extensions/api/declarative_webrequest/request_stage.h" |
| 13 #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_consta nts.h" | 16 #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_consta nts.h" |
| 14 #include "chrome/browser/extensions/api/web_request/web_request_api_helpers.h" | 17 #include "chrome/browser/extensions/api/web_request/web_request_api_helpers.h" |
| 15 #include "chrome/common/extensions/extension_error_utils.h" | 18 #include "chrome/common/extensions/extension_error_utils.h" |
| 16 #include "content/public/browser/resource_request_info.h" | 19 #include "content/public/browser/resource_request_info.h" |
| 17 #include "net/http/http_util.h" | 20 #include "net/http/http_util.h" |
| 18 #include "net/http/http_request_headers.h" | 21 #include "net/http/http_request_headers.h" |
| 19 #include "net/url_request/url_request.h" | 22 #include "net/url_request/url_request.h" |
| 20 | 23 |
| 24 using base::DictionaryValue; | |
| 25 using base::ListValue; | |
| 26 using base::StringValue; | |
| 27 using base::Value; | |
| 28 | |
| 21 namespace { | 29 namespace { |
| 22 // Error messages. | 30 // Error messages. |
| 23 const char kUnknownConditionAttribute[] = "Unknown matching condition: '*'"; | 31 const char kUnknownConditionAttribute[] = "Unknown matching condition: '*'"; |
| 24 const char kInvalidValue[] = "Condition '*' has an invalid value"; | 32 const char kInvalidValue[] = "Condition '*' has an invalid value"; |
| 25 } | 33 } |
| 26 | 34 |
| 27 namespace helpers = extension_web_request_api_helpers; | 35 namespace helpers = extension_web_request_api_helpers; |
| 28 | 36 |
| 29 namespace extensions { | 37 namespace extensions { |
| 30 | 38 |
| 31 namespace keys = declarative_webrequest_constants; | 39 namespace keys = declarative_webrequest_constants; |
| 32 | 40 |
| 33 // | 41 // |
| 34 // WebRequestConditionAttribute | 42 // WebRequestConditionAttribute |
| 35 // | 43 // |
| 36 | 44 |
| 37 WebRequestConditionAttribute::WebRequestConditionAttribute() {} | 45 WebRequestConditionAttribute::WebRequestConditionAttribute() {} |
| 38 | 46 |
| 39 WebRequestConditionAttribute::~WebRequestConditionAttribute() {} | 47 WebRequestConditionAttribute::~WebRequestConditionAttribute() {} |
| 40 | 48 |
| 41 // static | 49 // static |
| 42 bool WebRequestConditionAttribute::IsKnownType( | 50 bool WebRequestConditionAttribute::IsKnownType( |
| 43 const std::string& instance_type) { | 51 const std::string& instance_type) { |
| 44 return | 52 return |
| 45 WebRequestConditionAttributeResourceType::IsMatchingType(instance_type) || | 53 WebRequestConditionAttributeResourceType::IsMatchingType(instance_type) || |
| 46 WebRequestConditionAttributeContentType::IsMatchingType(instance_type); | 54 WebRequestConditionAttributeContentType::IsMatchingType(instance_type) || |
| 55 WebRequestConditionAttributeContainsHeaders::IsMatchingType( | |
| 56 instance_type); | |
| 47 } | 57 } |
| 48 | 58 |
| 49 // static | 59 // static |
| 50 scoped_ptr<WebRequestConditionAttribute> | 60 scoped_ptr<WebRequestConditionAttribute> |
| 51 WebRequestConditionAttribute::Create( | 61 WebRequestConditionAttribute::Create( |
| 52 const std::string& name, | 62 const std::string& name, |
| 53 const base::Value* value, | 63 const base::Value* value, |
| 54 std::string* error) { | 64 std::string* error) { |
| 65 CHECK(value != NULL && error != NULL); | |
| 55 if (WebRequestConditionAttributeResourceType::IsMatchingType(name)) { | 66 if (WebRequestConditionAttributeResourceType::IsMatchingType(name)) { |
| 56 return WebRequestConditionAttributeResourceType::Create(name, value, error); | 67 return WebRequestConditionAttributeResourceType::Create(name, value, error); |
| 57 } else if (WebRequestConditionAttributeContentType::IsMatchingType(name)) { | 68 } else if (WebRequestConditionAttributeContentType::IsMatchingType(name)) { |
| 58 return WebRequestConditionAttributeContentType::Create(name, value, error); | 69 return WebRequestConditionAttributeContentType::Create(name, value, error); |
| 70 } else if (WebRequestConditionAttributeContainsHeaders::IsMatchingType( | |
| 71 name)) { | |
| 72 return WebRequestConditionAttributeContainsHeaders::Create( | |
| 73 name, value, error); | |
| 59 } | 74 } |
| 60 | 75 |
| 61 *error = ExtensionErrorUtils::FormatErrorMessage(kUnknownConditionAttribute, | 76 *error = ExtensionErrorUtils::FormatErrorMessage(kUnknownConditionAttribute, |
| 62 name); | 77 name); |
| 63 return scoped_ptr<WebRequestConditionAttribute>(NULL); | 78 return scoped_ptr<WebRequestConditionAttribute>(NULL); |
| 64 } | 79 } |
| 65 | 80 |
| 66 // | 81 // |
| 67 // WebRequestConditionAttributeResourceType | 82 // WebRequestConditionAttributeResourceType |
| 68 // | 83 // |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 212 return std::find(content_types_.begin(), content_types_.end(), | 227 return std::find(content_types_.begin(), content_types_.end(), |
| 213 mime_type) == content_types_.end(); | 228 mime_type) == content_types_.end(); |
| 214 } | 229 } |
| 215 } | 230 } |
| 216 | 231 |
| 217 WebRequestConditionAttribute::Type | 232 WebRequestConditionAttribute::Type |
| 218 WebRequestConditionAttributeContentType::GetType() const { | 233 WebRequestConditionAttributeContentType::GetType() const { |
| 219 return CONDITION_CONTENT_TYPE; | 234 return CONDITION_CONTENT_TYPE; |
| 220 } | 235 } |
| 221 | 236 |
| 237 // | |
| 238 // WebRequestConditionAttributeContainsHeaders | |
| 239 // | |
| 240 | |
| 241 WebRequestConditionAttributeContainsHeaders::TestGroup::TestGroup() {} | |
| 242 WebRequestConditionAttributeContainsHeaders::TestGroup::~TestGroup() {} | |
| 243 | |
| 244 WebRequestConditionAttributeContainsHeaders:: | |
| 245 WebRequestConditionAttributeContainsHeaders(bool positive_test) | |
| 246 : positive_test_(positive_test) {} | |
| 247 | |
| 248 WebRequestConditionAttributeContainsHeaders:: | |
| 249 ~WebRequestConditionAttributeContainsHeaders() {} | |
| 250 | |
| 251 // static | |
| 252 bool WebRequestConditionAttributeContainsHeaders::IsMatchingType( | |
| 253 const std::string& instance_type) { | |
| 254 return instance_type == keys::kContainsHeadersKey || | |
| 255 instance_type == keys::kDoesNotContainHeadersKey; | |
| 256 } | |
| 257 | |
| 258 // static | |
| 259 scoped_ptr<WebRequestConditionAttribute> | |
| 260 WebRequestConditionAttributeContainsHeaders::Create( | |
| 261 const std::string& name, | |
| 262 const base::Value* value, | |
| 263 std::string* error) { | |
| 264 DCHECK(IsMatchingType(name)); | |
| 265 | |
| 266 enum { kList, kDictionary, kNone } obtained_value = kNone; | |
| 267 | |
| 268 const DictionaryValue* value_as_dictionary = NULL; | |
| 269 const ListValue* value_as_list = NULL; | |
| 270 if (value->GetAsList(&value_as_list)) | |
| 271 obtained_value = kList; | |
| 272 else if (value->GetAsDictionary(&value_as_dictionary)) | |
| 273 obtained_value = kDictionary; | |
| 274 | |
| 275 scoped_ptr<WebRequestConditionAttributeContainsHeaders> result; | |
| 276 if (obtained_value != kNone) { | |
| 277 // Only construct the condition after we know we have a valid |value|. | |
| 278 result.reset(new WebRequestConditionAttributeContainsHeaders( | |
| 279 name == keys::kContainsHeadersKey)); | |
| 280 } | |
| 281 switch (obtained_value) { | |
| 282 case kList: | |
| 283 for (ListValue::const_iterator it = value_as_list->begin(); | |
| 284 it != value_as_list->end(); ++it) { | |
| 285 const DictionaryValue* tests = NULL; | |
| 286 if (!(*it)->GetAsDictionary(&tests)) { | |
| 287 *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, name); | |
| 288 return scoped_ptr<WebRequestConditionAttribute>(NULL); | |
| 289 } | |
| 290 | |
| 291 if (!result->PushMatchTests(tests, error)) { | |
| 292 return scoped_ptr<WebRequestConditionAttribute>(NULL); | |
| 293 } | |
| 294 } | |
| 295 break; | |
| 296 case kDictionary: | |
| 297 if (!result->PushMatchTests(value_as_dictionary, error)) { | |
| 298 return scoped_ptr<WebRequestConditionAttribute>(NULL); | |
| 299 } | |
|
battre
2012/08/23 12:32:11
nit: no {}, also in 291
vabr (Chromium)
2012/08/24 14:48:59
Done, and subsequently deleted altogether. Case kD
| |
| 300 break; | |
| 301 case kNone: | |
| 302 *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, name); | |
| 303 return scoped_ptr<WebRequestConditionAttribute>(NULL); | |
| 304 } | |
| 305 | |
| 306 | |
| 307 return result.PassAs<WebRequestConditionAttribute>(); | |
| 308 } | |
| 309 | |
| 310 int WebRequestConditionAttributeContainsHeaders::GetStages() const { | |
| 311 return ON_HEADERS_RECEIVED; | |
| 312 } | |
| 313 | |
| 314 bool WebRequestConditionAttributeContainsHeaders::IsFulfilled( | |
| 315 const WebRequestRule::RequestData& request_data) { | |
| 316 if (!(request_data.stage & GetStages())) | |
| 317 return false; | |
| 318 | |
| 319 const net::HttpResponseHeaders* | |
| 320 headers = request_data.original_response_headers; | |
|
battre
2012/08/23 12:32:11
"headers =" goes into previous line.
vabr (Chromium)
2012/08/24 14:48:59
Done.
| |
| 321 if (headers == NULL) { | |
| 322 // Each header of an empty set satisfies (the negation of) everything; | |
| 323 // OTOH, there is no header to satisfy even the most permissive test. | |
| 324 return !positive_test_; | |
| 325 } | |
| 326 | |
| 327 // |success| == for some |i|, has there been a name-value header pair | |
| 328 // satisfying all tests from |tests_[i]|? | |
| 329 bool success = false; | |
| 330 | |
| 331 // First we get all header names. Unfortunately, this is unnecessarily | |
| 332 // costly, because the values are copied along anyway. But we need to know | |
| 333 // the header names so that we can call EnumerateHeader instead of | |
| 334 // EnumerateHeaderLines. | |
| 335 // TODO(vabr) -- check whether you could implement this header enumeration | |
| 336 // directly in HttpResponseHeaders instead. | |
| 337 std::set<std::string> header_names; | |
| 338 std::string name; | |
| 339 std::string value; | |
| 340 for (void* iter = NULL; | |
| 341 headers->EnumerateHeaderLines(&iter, &name, &value); | |
| 342 /* No increment here, this is done inside EnumerateHeaderLines. */) { | |
| 343 StringToLowerASCII(&name); // Header names are case-insensitive. | |
| 344 header_names.insert(name); | |
| 345 } | |
| 346 | |
| 347 for (std::set<std::string>::const_iterator it = header_names.begin(); | |
| 348 !success && it != header_names.end(); | |
| 349 ++it) { | |
| 350 for (size_t i = 0; !success && i < tests_.size(); ++i) { | |
|
battre
2012/08/23 12:32:11
How about moving this loop to become the outer loo
vabr (Chromium)
2012/08/24 14:48:59
Done.
| |
| 351 // |header_success| == have all tests for this header passed so far? | |
| 352 bool header_success = true; | |
| 353 for (size_t j = 0; header_success && j < tests_[i].name.size(); ++j) { | |
| 354 header_success &= tests_[i].name[j].Matches(*it); | |
| 355 } | |
| 356 if (!header_success) | |
| 357 continue; | |
| 358 | |
| 359 // Is there a value of this header such that all tests pass for it? | |
| 360 bool found_value = false; | |
| 361 for (void* iter = NULL; | |
| 362 !found_value && headers->EnumerateHeader(&iter, *it, &value); | |
| 363 /* No increment here, done inside EnumerateHeader. */) { | |
| 364 // |value_success| == have all tests for this value passed so far? | |
| 365 bool value_success = true; | |
| 366 for (size_t j = 0; value_success && j < tests_[i].value.size(); ++j) { | |
| 367 value_success &= tests_[i].value[j].Matches(value); | |
| 368 } | |
| 369 found_value |= value_success; | |
| 370 } | |
| 371 | |
| 372 // Here we already know that header_success == true, just check values. | |
| 373 success |= found_value; | |
| 374 } | |
| 375 } | |
| 376 | |
| 377 return (positive_test_ ? success : !success); | |
| 378 } | |
| 379 | |
| 380 WebRequestConditionAttribute::Type | |
| 381 WebRequestConditionAttributeContainsHeaders::GetType() const { | |
| 382 return CONDITION_CONTAINS_HEADERS; | |
| 383 } | |
| 384 | |
| 385 bool WebRequestConditionAttributeContainsHeaders::MatchTest::Matches( | |
| 386 const std::string& str) { | |
| 387 switch (type_) { | |
| 388 case kPrefix: { | |
|
battre
2012/08/23 12:32:11
You can use StartsWithASCII and EndsWith for these
vabr (Chromium)
2012/08/24 14:48:59
Done. Thanks for making me aware of those function
| |
| 389 if (data_.size() > str.size()) | |
| 390 return false; | |
| 391 return strncmp(data_.data(), str.data(), data_.size()) == 0; | |
| 392 } | |
| 393 case kSuffix: { | |
| 394 if (data_.size() > str.size()) | |
| 395 return false; | |
| 396 const char* start = str.data() + str.size() - data_.size(); | |
| 397 return strncmp(data_.data(), start, data_.size()) == 0; | |
| 398 } | |
| 399 case kEqual: { | |
| 400 return data_ == str; | |
| 401 } | |
| 402 case kContains: { | |
| 403 return str.find(data_) != std::string::npos; | |
| 404 } | |
| 405 } | |
| 406 // We never get past the "switch", but the compiler worries about no return. | |
| 407 NOTREACHED(); | |
| 408 return false; | |
| 409 } | |
| 410 | |
| 411 bool WebRequestConditionAttributeContainsHeaders::PushMatchTests( | |
| 412 const DictionaryValue* tests, std::string* error) { | |
|
battre
2012/08/23 12:32:11
How about a functional style of programming? I.e.
vabr (Chromium)
2012/08/24 14:48:59
Done.
| |
| 413 tests_.push_back(TestGroup()); | |
| 414 | |
| 415 for (DictionaryValue::key_iterator key = tests->begin_keys(); | |
| 416 key != tests->end_keys(); | |
| 417 ++key) { | |
| 418 bool is_name = false; // Is this test for header name? | |
| 419 MatchType match_type; | |
| 420 if (*key == keys::kNamePrefixKey) { | |
| 421 is_name = true; | |
| 422 match_type = kPrefix; | |
| 423 } else if (*key == keys::kNameSuffixKey) { | |
| 424 is_name = true; | |
| 425 match_type = kSuffix; | |
| 426 } else if (*key == keys::kNameContainsKey) { | |
| 427 is_name = true; | |
| 428 match_type = kContains; | |
| 429 } else if (*key == keys::kNameEqualsKey) { | |
| 430 is_name = true; | |
| 431 match_type = kEqual; | |
| 432 } else if (*key == keys::kValuePrefixKey) { | |
| 433 match_type = kPrefix; | |
| 434 } else if (*key == keys::kValueSuffixKey) { | |
| 435 match_type = kSuffix; | |
| 436 } else if (*key == keys::kValueContainsKey) { | |
| 437 match_type = kContains; | |
| 438 } else if (*key == keys::kValueEqualsKey) { | |
| 439 match_type = kEqual; | |
| 440 } else { | |
| 441 NOTREACHED(); // JSON schema type checking should prevent this. | |
| 442 *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, *key); | |
| 443 return false; | |
| 444 } | |
| 445 const Value* content; | |
|
battre
2012/08/23 12:32:11
nit: = NULL
vabr (Chromium)
2012/08/24 14:48:59
Done.
| |
| 446 tests->Get(*key, &content); // We already checked that |key| is there. | |
|
battre
2012/08/23 12:32:11
CHECK?
vabr (Chromium)
2012/08/24 14:48:59
Done.
| |
| 447 switch (content->GetType()) { | |
| 448 case Value::TYPE_LIST: { | |
| 449 const ListValue* list = NULL; | |
| 450 CHECK(content->GetAsList(&list)); | |
| 451 for (ListValue::const_iterator it = list->begin(); | |
| 452 it != list->end(); ++it) { | |
| 453 PushOneMatchTest(*it, is_name, match_type); | |
| 454 } | |
| 455 break; | |
| 456 } | |
| 457 case Value::TYPE_STRING: { | |
| 458 PushOneMatchTest(content, is_name, match_type); | |
| 459 break; | |
| 460 } | |
| 461 default: { | |
| 462 NOTREACHED(); // JSON schema type checking should prevent this. | |
| 463 *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, *key); | |
| 464 return false; | |
| 465 } | |
| 466 } | |
| 467 } | |
| 468 return true; | |
| 469 } | |
| 470 | |
| 471 void WebRequestConditionAttributeContainsHeaders::PushOneMatchTest( | |
| 472 const Value* content, bool is_name_test, MatchType match_type) { | |
| 473 std::string* data = NULL; | |
| 474 if (is_name_test) { | |
| 475 std::vector<MatchTest>* current_name_tests = &(tests_.back().name); | |
| 476 current_name_tests->push_back(MatchTest(match_type)); | |
| 477 data = &(current_name_tests->back().data()); | |
| 478 } else { | |
| 479 std::vector<MatchTest>* current_value_tests = &(tests_.back().value); | |
| 480 current_value_tests->push_back(MatchTest(match_type)); | |
| 481 data = &(current_value_tests->back().data()); | |
| 482 } | |
| 483 CHECK(content->GetAsString(data)); | |
| 484 if (is_name_test) // Header names are case-insensitive. | |
| 485 StringToLowerASCII(data); | |
| 486 } | |
| 487 | |
| 222 } // namespace extensions | 488 } // namespace extensions |
| OLD | NEW |