Index: chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc |
diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc |
index 4d825d9e154226af331e555bf84de0a77d9073df..c4170c131543fd6d33991185818efce7900e2a65 100644 |
--- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc |
+++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc |
@@ -5,8 +5,11 @@ |
#include "chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.h" |
#include <algorithm> |
+#include <set> |
+#include <string.h> |
#include "base/logging.h" |
+#include "base/string_util.h" |
#include "base/stringprintf.h" |
#include "base/values.h" |
#include "chrome/browser/extensions/api/declarative_webrequest/request_stage.h" |
@@ -18,6 +21,11 @@ |
#include "net/http/http_request_headers.h" |
#include "net/url_request/url_request.h" |
+using base::DictionaryValue; |
+using base::ListValue; |
+using base::StringValue; |
+using base::Value; |
+ |
namespace { |
// Error messages. |
const char kUnknownConditionAttribute[] = "Unknown matching condition: '*'"; |
@@ -43,7 +51,9 @@ bool WebRequestConditionAttribute::IsKnownType( |
const std::string& instance_type) { |
return |
WebRequestConditionAttributeResourceType::IsMatchingType(instance_type) || |
- WebRequestConditionAttributeContentType::IsMatchingType(instance_type); |
+ WebRequestConditionAttributeContentType::IsMatchingType(instance_type) || |
+ WebRequestConditionAttributeContainsHeaders::IsMatchingType( |
+ instance_type); |
} |
// static |
@@ -52,10 +62,15 @@ WebRequestConditionAttribute::Create( |
const std::string& name, |
const base::Value* value, |
std::string* error) { |
+ CHECK(value != NULL && error != NULL); |
if (WebRequestConditionAttributeResourceType::IsMatchingType(name)) { |
return WebRequestConditionAttributeResourceType::Create(name, value, error); |
} else if (WebRequestConditionAttributeContentType::IsMatchingType(name)) { |
return WebRequestConditionAttributeContentType::Create(name, value, error); |
+ } else if (WebRequestConditionAttributeContainsHeaders::IsMatchingType( |
+ name)) { |
+ return WebRequestConditionAttributeContainsHeaders::Create( |
+ name, value, error); |
} |
*error = ExtensionErrorUtils::FormatErrorMessage(kUnknownConditionAttribute, |
@@ -219,4 +234,255 @@ WebRequestConditionAttributeContentType::GetType() const { |
return CONDITION_CONTENT_TYPE; |
} |
+// |
+// WebRequestConditionAttributeContainsHeaders |
+// |
+ |
+WebRequestConditionAttributeContainsHeaders::TestGroup::TestGroup() {} |
+WebRequestConditionAttributeContainsHeaders::TestGroup::~TestGroup() {} |
+ |
+WebRequestConditionAttributeContainsHeaders:: |
+WebRequestConditionAttributeContainsHeaders(bool positive_test) |
+ : positive_test_(positive_test) {} |
+ |
+WebRequestConditionAttributeContainsHeaders:: |
+~WebRequestConditionAttributeContainsHeaders() {} |
+ |
+// static |
+bool WebRequestConditionAttributeContainsHeaders::IsMatchingType( |
+ const std::string& instance_type) { |
+ return instance_type == keys::kContainsHeadersKey || |
+ instance_type == keys::kDoesNotContainHeadersKey; |
+} |
+ |
+// static |
+scoped_ptr<WebRequestConditionAttribute> |
+WebRequestConditionAttributeContainsHeaders::Create( |
+ const std::string& name, |
+ const base::Value* value, |
+ std::string* error) { |
+ DCHECK(IsMatchingType(name)); |
+ |
+ enum { kList, kDictionary, kNone } obtained_value = kNone; |
+ |
+ const DictionaryValue* value_as_dictionary = NULL; |
+ const ListValue* value_as_list = NULL; |
+ if (value->GetAsList(&value_as_list)) |
+ obtained_value = kList; |
+ else if (value->GetAsDictionary(&value_as_dictionary)) |
+ obtained_value = kDictionary; |
+ |
+ scoped_ptr<WebRequestConditionAttributeContainsHeaders> result; |
+ if (obtained_value != kNone) { |
+ // Only construct the condition after we know we have a valid |value|. |
+ result.reset(new WebRequestConditionAttributeContainsHeaders( |
+ name == keys::kContainsHeadersKey)); |
+ } |
+ switch (obtained_value) { |
+ case kList: |
+ for (ListValue::const_iterator it = value_as_list->begin(); |
+ it != value_as_list->end(); ++it) { |
+ const DictionaryValue* tests = NULL; |
+ if (!(*it)->GetAsDictionary(&tests)) { |
+ *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, name); |
+ return scoped_ptr<WebRequestConditionAttribute>(NULL); |
+ } |
+ |
+ if (!result->PushMatchTests(tests, error)) { |
+ return scoped_ptr<WebRequestConditionAttribute>(NULL); |
+ } |
+ } |
+ break; |
+ case kDictionary: |
+ if (!result->PushMatchTests(value_as_dictionary, error)) { |
+ return scoped_ptr<WebRequestConditionAttribute>(NULL); |
+ } |
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
|
+ break; |
+ case kNone: |
+ *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, name); |
+ return scoped_ptr<WebRequestConditionAttribute>(NULL); |
+ } |
+ |
+ |
+ return result.PassAs<WebRequestConditionAttribute>(); |
+} |
+ |
+int WebRequestConditionAttributeContainsHeaders::GetStages() const { |
+ return ON_HEADERS_RECEIVED; |
+} |
+ |
+bool WebRequestConditionAttributeContainsHeaders::IsFulfilled( |
+ const WebRequestRule::RequestData& request_data) { |
+ if (!(request_data.stage & GetStages())) |
+ return false; |
+ |
+ const net::HttpResponseHeaders* |
+ 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.
|
+ if (headers == NULL) { |
+ // Each header of an empty set satisfies (the negation of) everything; |
+ // OTOH, there is no header to satisfy even the most permissive test. |
+ return !positive_test_; |
+ } |
+ |
+ // |success| == for some |i|, has there been a name-value header pair |
+ // satisfying all tests from |tests_[i]|? |
+ bool success = false; |
+ |
+ // First we get all header names. Unfortunately, this is unnecessarily |
+ // costly, because the values are copied along anyway. But we need to know |
+ // the header names so that we can call EnumerateHeader instead of |
+ // EnumerateHeaderLines. |
+ // TODO(vabr) -- check whether you could implement this header enumeration |
+ // directly in HttpResponseHeaders instead. |
+ std::set<std::string> header_names; |
+ std::string name; |
+ std::string value; |
+ for (void* iter = NULL; |
+ headers->EnumerateHeaderLines(&iter, &name, &value); |
+ /* No increment here, this is done inside EnumerateHeaderLines. */) { |
+ StringToLowerASCII(&name); // Header names are case-insensitive. |
+ header_names.insert(name); |
+ } |
+ |
+ for (std::set<std::string>::const_iterator it = header_names.begin(); |
+ !success && it != header_names.end(); |
+ ++it) { |
+ 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.
|
+ // |header_success| == have all tests for this header passed so far? |
+ bool header_success = true; |
+ for (size_t j = 0; header_success && j < tests_[i].name.size(); ++j) { |
+ header_success &= tests_[i].name[j].Matches(*it); |
+ } |
+ if (!header_success) |
+ continue; |
+ |
+ // Is there a value of this header such that all tests pass for it? |
+ bool found_value = false; |
+ for (void* iter = NULL; |
+ !found_value && headers->EnumerateHeader(&iter, *it, &value); |
+ /* No increment here, done inside EnumerateHeader. */) { |
+ // |value_success| == have all tests for this value passed so far? |
+ bool value_success = true; |
+ for (size_t j = 0; value_success && j < tests_[i].value.size(); ++j) { |
+ value_success &= tests_[i].value[j].Matches(value); |
+ } |
+ found_value |= value_success; |
+ } |
+ |
+ // Here we already know that header_success == true, just check values. |
+ success |= found_value; |
+ } |
+ } |
+ |
+ return (positive_test_ ? success : !success); |
+} |
+ |
+WebRequestConditionAttribute::Type |
+WebRequestConditionAttributeContainsHeaders::GetType() const { |
+ return CONDITION_CONTAINS_HEADERS; |
+} |
+ |
+bool WebRequestConditionAttributeContainsHeaders::MatchTest::Matches( |
+ const std::string& str) { |
+ switch (type_) { |
+ 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
|
+ if (data_.size() > str.size()) |
+ return false; |
+ return strncmp(data_.data(), str.data(), data_.size()) == 0; |
+ } |
+ case kSuffix: { |
+ if (data_.size() > str.size()) |
+ return false; |
+ const char* start = str.data() + str.size() - data_.size(); |
+ return strncmp(data_.data(), start, data_.size()) == 0; |
+ } |
+ case kEqual: { |
+ return data_ == str; |
+ } |
+ case kContains: { |
+ return str.find(data_) != std::string::npos; |
+ } |
+ } |
+ // We never get past the "switch", but the compiler worries about no return. |
+ NOTREACHED(); |
+ return false; |
+} |
+ |
+bool WebRequestConditionAttributeContainsHeaders::PushMatchTests( |
+ 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.
|
+ tests_.push_back(TestGroup()); |
+ |
+ for (DictionaryValue::key_iterator key = tests->begin_keys(); |
+ key != tests->end_keys(); |
+ ++key) { |
+ bool is_name = false; // Is this test for header name? |
+ MatchType match_type; |
+ if (*key == keys::kNamePrefixKey) { |
+ is_name = true; |
+ match_type = kPrefix; |
+ } else if (*key == keys::kNameSuffixKey) { |
+ is_name = true; |
+ match_type = kSuffix; |
+ } else if (*key == keys::kNameContainsKey) { |
+ is_name = true; |
+ match_type = kContains; |
+ } else if (*key == keys::kNameEqualsKey) { |
+ is_name = true; |
+ match_type = kEqual; |
+ } else if (*key == keys::kValuePrefixKey) { |
+ match_type = kPrefix; |
+ } else if (*key == keys::kValueSuffixKey) { |
+ match_type = kSuffix; |
+ } else if (*key == keys::kValueContainsKey) { |
+ match_type = kContains; |
+ } else if (*key == keys::kValueEqualsKey) { |
+ match_type = kEqual; |
+ } else { |
+ NOTREACHED(); // JSON schema type checking should prevent this. |
+ *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, *key); |
+ return false; |
+ } |
+ const Value* content; |
battre
2012/08/23 12:32:11
nit: = NULL
vabr (Chromium)
2012/08/24 14:48:59
Done.
|
+ 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.
|
+ switch (content->GetType()) { |
+ case Value::TYPE_LIST: { |
+ const ListValue* list = NULL; |
+ CHECK(content->GetAsList(&list)); |
+ for (ListValue::const_iterator it = list->begin(); |
+ it != list->end(); ++it) { |
+ PushOneMatchTest(*it, is_name, match_type); |
+ } |
+ break; |
+ } |
+ case Value::TYPE_STRING: { |
+ PushOneMatchTest(content, is_name, match_type); |
+ break; |
+ } |
+ default: { |
+ NOTREACHED(); // JSON schema type checking should prevent this. |
+ *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, *key); |
+ return false; |
+ } |
+ } |
+ } |
+ return true; |
+} |
+ |
+void WebRequestConditionAttributeContainsHeaders::PushOneMatchTest( |
+ const Value* content, bool is_name_test, MatchType match_type) { |
+ std::string* data = NULL; |
+ if (is_name_test) { |
+ std::vector<MatchTest>* current_name_tests = &(tests_.back().name); |
+ current_name_tests->push_back(MatchTest(match_type)); |
+ data = &(current_name_tests->back().data()); |
+ } else { |
+ std::vector<MatchTest>* current_value_tests = &(tests_.back().value); |
+ current_value_tests->push_back(MatchTest(match_type)); |
+ data = &(current_value_tests->back().data()); |
+ } |
+ CHECK(content->GetAsString(data)); |
+ if (is_name_test) // Header names are case-insensitive. |
+ StringToLowerASCII(data); |
+} |
+ |
} // namespace extensions |