diff options
author | vabr@chromium.org <vabr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-28 20:20:57 +0000 |
---|---|---|
committer | vabr@chromium.org <vabr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-28 20:20:57 +0000 |
commit | aae817b9e926345eb875b95db223e9897fc4f1dc (patch) | |
tree | fed37a0cdb978da17a49d1a4f8c7f459f91ef4e8 | |
parent | c51f2c7f2b44efd65df1418902cc5fa5e0a89951 (diff) | |
download | chromium_src-aae817b9e926345eb875b95db223e9897fc4f1dc.zip chromium_src-aae817b9e926345eb875b95db223e9897fc4f1dc.tar.gz chromium_src-aae817b9e926345eb875b95db223e9897fc4f1dc.tar.bz2 |
Adding condition attributes for response headers to Declarative WebRequest
BUG=143662,112155
TEST=Install the extension attached to BUG 143662. If this feature is working correctly it should cancel every navigation.
TBR=kathyw@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10874029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@153720 0039d316-1c4b-4281-b951-d872f2087c98
9 files changed, 1011 insertions, 42 deletions
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 4d825d9..bd95cec 100644 --- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc +++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.cc @@ -7,6 +7,7 @@ #include <algorithm> #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 +19,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 +49,9 @@ bool WebRequestConditionAttribute::IsKnownType( const std::string& instance_type) { return WebRequestConditionAttributeResourceType::IsMatchingType(instance_type) || - WebRequestConditionAttributeContentType::IsMatchingType(instance_type); + WebRequestConditionAttributeContentType::IsMatchingType(instance_type) || + WebRequestConditionAttributeResponseHeaders::IsMatchingType( + instance_type); } // static @@ -52,10 +60,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 (WebRequestConditionAttributeResponseHeaders::IsMatchingType( + name)) { + return WebRequestConditionAttributeResponseHeaders::Create( + name, value, error); } *error = ExtensionErrorUtils::FormatErrorMessage(kUnknownConditionAttribute, @@ -122,7 +135,7 @@ int WebRequestConditionAttributeResourceType::GetStages() const { } bool WebRequestConditionAttributeResourceType::IsFulfilled( - const WebRequestRule::RequestData& request_data) { + const WebRequestRule::RequestData& request_data) const { if (!(request_data.stage & GetStages())) return false; const content::ResourceRequestInfo* info = @@ -193,7 +206,7 @@ int WebRequestConditionAttributeContentType::GetStages() const { } bool WebRequestConditionAttributeContentType::IsFulfilled( - const WebRequestRule::RequestData& request_data) { + const WebRequestRule::RequestData& request_data) const { if (!(request_data.stage & GetStages())) return false; std::string content_type; @@ -219,4 +232,236 @@ WebRequestConditionAttributeContentType::GetType() const { return CONDITION_CONTENT_TYPE; } +// +// WebRequestConditionAttributeResponseHeaders +// + +WebRequestConditionAttributeResponseHeaders::StringMatchTest::StringMatchTest( + const std::string& data, + MatchType type) + : data_(data), + type_(type) {} + +WebRequestConditionAttributeResponseHeaders::StringMatchTest::~StringMatchTest() +{} + +WebRequestConditionAttributeResponseHeaders::HeaderMatchTest::HeaderMatchTest( + ScopedVector<const StringMatchTest>* name, + ScopedVector<const StringMatchTest>* value) + : name_(name->Pass()), + value_(value->Pass()) {} + +WebRequestConditionAttributeResponseHeaders::HeaderMatchTest::~HeaderMatchTest() +{} + +WebRequestConditionAttributeResponseHeaders:: +WebRequestConditionAttributeResponseHeaders( + bool positive_test, ScopedVector<const HeaderMatchTest>* tests) + : tests_(tests->Pass()), + positive_test_(positive_test) {} + +WebRequestConditionAttributeResponseHeaders:: +~WebRequestConditionAttributeResponseHeaders() {} + +// static +bool WebRequestConditionAttributeResponseHeaders::IsMatchingType( + const std::string& instance_type) { + return instance_type == keys::kResponseHeadersKey || + instance_type == keys::kExcludeResponseHeadersKey; +} + +// static +scoped_ptr<WebRequestConditionAttribute> +WebRequestConditionAttributeResponseHeaders::Create( + const std::string& name, + const base::Value* value, + std::string* error) { + DCHECK(IsMatchingType(name)); + + const ListValue* value_as_list = NULL; + if (!value->GetAsList(&value_as_list)) { + *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, name); + return scoped_ptr<WebRequestConditionAttribute>(NULL); + } + + ScopedVector<const HeaderMatchTest> header_tests; + 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); + } + + scoped_ptr<const HeaderMatchTest> header_test(CreateTests(tests, error)); + if (header_test.get() == NULL) + return scoped_ptr<WebRequestConditionAttribute>(NULL); + header_tests.push_back(header_test.release()); + } + + scoped_ptr<WebRequestConditionAttributeResponseHeaders> result; + result.reset(new WebRequestConditionAttributeResponseHeaders( + name == keys::kResponseHeadersKey, &header_tests)); + + return result.PassAs<WebRequestConditionAttribute>(); +} + +int WebRequestConditionAttributeResponseHeaders::GetStages() const { + return ON_HEADERS_RECEIVED; +} + +bool WebRequestConditionAttributeResponseHeaders::IsFulfilled( + const WebRequestRule::RequestData& request_data) const { + if (!(request_data.stage & GetStages())) + return false; + + const net::HttpResponseHeaders* headers = + request_data.original_response_headers; + 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_; + } + + // Has some header already passed some header test? + bool header_found = false; + + for (size_t i = 0; !header_found && i < tests_.size(); ++i) { + std::string name; + std::string value; + + void* iter = NULL; + while (!header_found && + headers->EnumerateHeaderLines(&iter, &name, &value)) { + StringToLowerASCII(&name); // Header names are case-insensitive. + header_found |= tests_[i]->Matches(name, value); + } + } + + return (positive_test_ ? header_found : !header_found); +} + +WebRequestConditionAttribute::Type +WebRequestConditionAttributeResponseHeaders::GetType() const { + return CONDITION_REQUEST_HEADERS; +} + +bool WebRequestConditionAttributeResponseHeaders::StringMatchTest::Matches( + const std::string& str) const { + switch (type_) { + case kPrefix: + return StartsWithASCII(str, data_, true /*case_sensitive*/); + case kSuffix: + return EndsWith(str, data_, true /*case_sensitive*/); + case kEquals: + 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 WebRequestConditionAttributeResponseHeaders::HeaderMatchTest::Matches( + const std::string& name, + const std::string& value) const { + for (size_t i = 0; i < name_.size(); ++i) { + if (!name_[i]->Matches(name)) + return false; + } + + for (size_t i = 0; i < value_.size(); ++i) { + if (!value_[i]->Matches(value)) + return false; + } + + return true; +} + + +// static +scoped_ptr<const WebRequestConditionAttributeResponseHeaders::HeaderMatchTest> +WebRequestConditionAttributeResponseHeaders::CreateTests( + const DictionaryValue* tests, + std::string* error) { + ScopedVector<const StringMatchTest> name; + ScopedVector<const StringMatchTest> value; + + 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 = kEquals; + } 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 = kEquals; + } else { + NOTREACHED(); // JSON schema type checking should prevent this. + *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, *key); + return scoped_ptr<const HeaderMatchTest>(NULL); + } + const Value* content = NULL; + // This should not fire, we already checked that |key| is there. + CHECK(tests->Get(*key, &content)); + + 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) { + ScopedVector<const StringMatchTest>* tests = is_name ? &name : &value; + tests->push_back(CreateMatchTest(*it, is_name, match_type).release()); + } + break; + } + case Value::TYPE_STRING: { + ScopedVector<const StringMatchTest>* tests = is_name ? &name : &value; + tests->push_back( + CreateMatchTest(content, is_name, match_type).release()); + break; + } + default: { + NOTREACHED(); // JSON schema type checking should prevent this. + *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidValue, *key); + return scoped_ptr<const HeaderMatchTest>(NULL); + } + } + } + + return scoped_ptr<const HeaderMatchTest>(new HeaderMatchTest(&name, &value)); +} + +// static +scoped_ptr<const WebRequestConditionAttributeResponseHeaders::StringMatchTest> +WebRequestConditionAttributeResponseHeaders::CreateMatchTest( + const Value* content, bool is_name_test, MatchType match_type) { + std::string str; + + CHECK(content->GetAsString(&str)); + if (is_name_test) // Header names are case-insensitive. + StringToLowerASCII(&str); + + return scoped_ptr<const StringMatchTest>( + new StringMatchTest(str, match_type)); +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.h b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.h index f9aa4e0..8ee69d5 100644 --- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.h +++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.h @@ -11,6 +11,7 @@ #include "base/basictypes.h" #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "chrome/browser/extensions/api/declarative_webrequest/request_stage.h" #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_rule.h" #include "chrome/common/extensions/api/events.h" @@ -32,7 +33,8 @@ class WebRequestConditionAttribute { public: enum Type { CONDITION_RESOURCE_TYPE, - CONDITION_CONTENT_TYPE + CONDITION_CONTENT_TYPE, + CONDITION_REQUEST_HEADERS }; WebRequestConditionAttribute(); @@ -53,7 +55,8 @@ class WebRequestConditionAttribute { virtual int GetStages() const = 0; // Returns whether the condition is fulfilled for this request. - virtual bool IsFulfilled(const WebRequestRule::RequestData& request_data) = 0; + virtual bool IsFulfilled( + const WebRequestRule::RequestData& request_data) const = 0; virtual Type GetType() const = 0; @@ -88,8 +91,8 @@ class WebRequestConditionAttributeResourceType // Implementation of WebRequestConditionAttribute: virtual int GetStages() const OVERRIDE; - virtual bool IsFulfilled(const WebRequestRule::RequestData& request_data) - OVERRIDE; + virtual bool IsFulfilled( + const WebRequestRule::RequestData& request_data) const OVERRIDE; virtual Type GetType() const OVERRIDE; private: @@ -118,8 +121,8 @@ class WebRequestConditionAttributeContentType // Implementation of WebRequestConditionAttribute: virtual int GetStages() const OVERRIDE; - virtual bool IsFulfilled(const WebRequestRule::RequestData& request_data) - OVERRIDE; + virtual bool IsFulfilled( + const WebRequestRule::RequestData& request_data) const OVERRIDE; virtual Type GetType() const OVERRIDE; private: @@ -133,6 +136,87 @@ class WebRequestConditionAttributeContentType DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeContentType); }; +// Condition that performs matches against response headers' names and values. +// In the comments below there is a distinction between when this condition is +// "satisfied" and when it is "fulfilled". See the comments at |tests_| for +// "satisfied" and the comment at |positive_test_| for "fulfilled". +class WebRequestConditionAttributeResponseHeaders + : public WebRequestConditionAttribute { + public: + virtual ~WebRequestConditionAttributeResponseHeaders(); + + static bool IsMatchingType(const std::string& instance_type); + + // Factory method, see WebRequestConditionAttribute::Create. + static scoped_ptr<WebRequestConditionAttribute> Create( + const std::string& name, + const base::Value* value, + std::string* error); + + // Implementation of WebRequestConditionAttribute: + virtual int GetStages() const OVERRIDE; + virtual bool IsFulfilled( + const WebRequestRule::RequestData& request_data) const OVERRIDE; + virtual Type GetType() const OVERRIDE; + + private: + enum MatchType { kPrefix, kSuffix, kEquals, kContains }; + + class StringMatchTest { + public: + StringMatchTest(const std::string& data, MatchType type); + ~StringMatchTest(); + + // Does |str| pass |*this| StringMatchTest? + bool Matches(const std::string& str) const; + + private: + const std::string data_; + const MatchType type_; + DISALLOW_COPY_AND_ASSIGN(StringMatchTest); + }; + + class HeaderMatchTest { + public: + // Takes ownership of the content of both |name| and |value|. + HeaderMatchTest(ScopedVector<const StringMatchTest>* name, + ScopedVector<const StringMatchTest>* value); + ~HeaderMatchTest(); + // Does the header |name|: |value| match all tests in this header test? + bool Matches(const std::string& name, const std::string& value) const; + + private: + // Tests to be passed by a header's name. + const ScopedVector<const StringMatchTest> name_; + // Tests to be passed by a header's value. + const ScopedVector<const StringMatchTest> value_; + DISALLOW_COPY_AND_ASSIGN(HeaderMatchTest); + }; + + WebRequestConditionAttributeResponseHeaders( + bool positive_test, ScopedVector<const HeaderMatchTest>* tests); + + // Gets the tests' description in |tests| and creates the corresponding + // HeaderMatchTest. Returns NULL on failure. + static scoped_ptr<const HeaderMatchTest> CreateTests( + const base::DictionaryValue* tests, + std::string* error); + // Helper to CreateTests. Never returns NULL, except for memory failures. + static scoped_ptr<const StringMatchTest> CreateMatchTest(const Value* content, + bool is_name_test, + MatchType match_type); + + // The condition is satisfied if there is a header and its value such that for + // some |i| the header passes all the tests from |tests_[i]|. + ScopedVector<const HeaderMatchTest> tests_; + + // True means that IsFulfilled() reports whether the condition is satisfied. + // False means that it reports whether the condition is NOT satisfied. + bool positive_test_; + + DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeResponseHeaders); +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_ATTRIBUTE_H_ diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc index c6027aa..d0c79b1 100644 --- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc +++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_condition_attribute.h" +#include "base/basictypes.h" #include "base/file_path.h" #include "base/message_loop.h" #include "base/values.h" @@ -14,6 +15,11 @@ #include "net/test/test_server.h" #include "testing/gtest/include/gtest/gtest.h" +using base::DictionaryValue; +using base::ListValue; +using base::StringValue; +using base::Value; + namespace { const char kUnknownConditionName[] = "unknownType"; } // namespace @@ -156,4 +162,261 @@ TEST(WebRequestConditionAttributeTest, ContentType) { url_request.response_headers()))); } +namespace { + +// Builds a vector of vectors of string pointers from an array of strings. +// |array| is in fact a sequence of arrays. The array |sizes| captures the sizes +// of all parts of |array|, and |size| is the length of |sizes| itself. +// Example (this is pseudo-code, not C++): +// array = { "a", "b", "c", "d", "e", "f" } +// sizes = { 2, 0, 4 } +// size = 3 +// results in out == { {&"a", &"b"}, {}, {&"c", &"d", &"e", &"f"} } +void GetArrayAsVector(const std::string array[], + const size_t sizes[], + const size_t size, + std::vector< std::vector<const std::string*> >* out) { + out->clear(); + size_t next = 0; + for (size_t i = 0; i < size; ++i) { + out->push_back(std::vector<const std::string*>()); + for (size_t j = next; j < next + sizes[i]; ++j) { + out->back().push_back(&(array[j])); + } + next += sizes[i]; + } +} + +// Builds a DictionaryValue from an array of the form {name1, value1, name2, +// value2, ...}. Values for the same key are grouped in a ListValue. +scoped_ptr<DictionaryValue> GetDictionaryFromArray( + const std::vector<const std::string*>& array) { + const size_t length = array.size(); + CHECK(length % 2 == 0); + + scoped_ptr<DictionaryValue> dictionary(new DictionaryValue); + for (size_t i = 0; i < length; i += 2) { + const std::string* name = array[i]; + const std::string* value = array[i+1]; + if (dictionary->HasKey(*name)) { + Value* entry = NULL; + ListValue* list = NULL; + if (!dictionary->GetWithoutPathExpansion(*name, &entry)) + return scoped_ptr<DictionaryValue>(NULL); + switch (entry->GetType()) { + case Value::TYPE_STRING: // Replace the present string with a list. + list = new ListValue; + // Ignoring return value, we already verified the entry is there. + dictionary->RemoveWithoutPathExpansion(*name, &entry); + list->Append(entry); + list->Append(Value::CreateStringValue(*value)); + dictionary->SetWithoutPathExpansion(*name, list); + break; + case Value::TYPE_LIST: // Just append to the list. + CHECK(entry->GetAsList(&list)); + list->Append(Value::CreateStringValue(*value)); + break; + default: + NOTREACHED(); // We never put other Values here. + return scoped_ptr<DictionaryValue>(NULL); + } + } else { + dictionary->SetString(*name, *value); + } + } + return dictionary.Pass(); +} + +// Returns whether the response headers from |url_request| satisfy the match +// criteria given in |tests|. For at least one |i| all tests from |tests[i]| +// must pass. If |positive_test| is true, the dictionary is interpreted as the +// containsHeaders property of a RequestMatcher, otherwise as +// doesNotContainHeaders. +void MatchAndCheck(const std::vector< std::vector<const std::string*> >& tests, + const std::string& key, + net::URLRequest* url_request, + bool* result) { + ListValue contains_headers; + for (size_t i = 0; i < tests.size(); ++i) { + scoped_ptr<DictionaryValue> temp(GetDictionaryFromArray(tests[i])); + ASSERT_TRUE(temp.get() != NULL); + contains_headers.Append(temp.release()); + } + + std::string error; + scoped_ptr<WebRequestConditionAttribute> attribute = + WebRequestConditionAttribute::Create(key, &contains_headers, &error); + ASSERT_EQ("", error); + ASSERT_TRUE(attribute.get() != NULL); + + *result = attribute->IsFulfilled(WebRequestRule::RequestData( + url_request, ON_HEADERS_RECEIVED, url_request->response_headers())); +} + +} // namespace + +// Here we test WebRequestConditionAttributeResponseHeaders for: +// 1. Correct implementation of prefix/suffix/contains/equals matching. +// 2. Performing logical disjunction (||) between multiple specifications. +// 3. Negating the match in case of 'doesNotContainHeaders'. +TEST(WebRequestConditionAttributeTest, Headers) { + // Necessary for TestURLRequest. + MessageLoop message_loop(MessageLoop::TYPE_IO); + + net::TestServer test_server( + net::TestServer::TYPE_HTTP, + net::TestServer::kLocalhost, + FilePath(FILE_PATH_LITERAL( + "chrome/test/data/extensions/api_test/webrequest/declarative"))); + ASSERT_TRUE(test_server.Start()); + + TestURLRequestContext context; + TestDelegate delegate; + TestURLRequest url_request(test_server.GetURL("files/headers.html"), + &delegate, &context); + url_request.Start(); + MessageLoop::current()->Run(); + + // In all the tests below we assume that the server includes the headers + // Custom-Header: custom/value + // Custom-Header-B: valueA + // Custom-Header-B: valueB + // Custom-Header-C: valueC, valueD + // Custom-Header-D: + // in the response, but does not include "Non-existing: void". + + std::vector< std::vector<const std::string*> > tests; + bool result; + + // 1.a. -- All these tests should pass. + const std::string kPassingCondition[] = { + keys::kNamePrefixKey, "Custom", + keys::kNameSuffixKey, "m-header", // Header names are case insensitive. + keys::kValueContainsKey, "alu", + keys::kValueEqualsKey, "custom/value" + }; + const size_t kPassingConditionSizes[] = { arraysize(kPassingCondition) }; + GetArrayAsVector(kPassingCondition, kPassingConditionSizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + + // 1.b. -- None of the following tests in the discjunction should pass. + const std::string kFailCondition[] = { + keys::kNamePrefixKey, " Custom", // Test 1. + keys::kNameContainsKey, " -", // Test 2. + keys::kValueSuffixKey, "alu", // Test 3. + keys::kValueEqualsKey, "custom" // Test 4. + }; + const size_t kFailConditionSizes[] = { 2u, 2u, 2u, 2u }; + GetArrayAsVector(kFailCondition, kFailConditionSizes, 4u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_FALSE(result); + + // 1.c. -- This should fail (mixing name and value from different headers) + const std::string kMixingCondition[] = { + keys::kNameSuffixKey, "Header-B", + keys::kValueEqualsKey, "custom/value" + }; + const size_t kMixingConditionSizes[] = { arraysize(kMixingCondition) }; + GetArrayAsVector(kMixingCondition, kMixingConditionSizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_FALSE(result); + + // 1.d. -- Test handling multiple values for one header (both should pass). + const std::string kMoreValues1[] = { + keys::kNameEqualsKey, "Custom-header-b", + keys::kValueEqualsKey, "valueA" + }; + const size_t kMoreValues1Sizes[] = { arraysize(kMoreValues1) }; + GetArrayAsVector(kMoreValues1, kMoreValues1Sizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + const std::string kMoreValues2[] = { + keys::kNameEqualsKey, "Custom-header-b", + keys::kValueEqualsKey, "valueB" + }; + const size_t kMoreValues2Sizes[] = { arraysize(kMoreValues2) }; + GetArrayAsVector(kMoreValues2, kMoreValues2Sizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + + // 1.e. -- This should fail as conjunction but pass as disjunction. + const std::string kConflict[] = { + keys::kNameSuffixKey, "Header", // True for some header. + keys::kNameContainsKey, "Header-B" // True for a different header. + }; + // First disjunction, no conflict. + const size_t kNoConflictSizes[] = { 2u, 2u }; + GetArrayAsVector(kConflict, kNoConflictSizes, 2u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + // Then conjunction, conflict. + const size_t kConflictSizes[] = { arraysize(kConflict) }; + GetArrayAsVector(kConflict, kConflictSizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_FALSE(result); + + // 1.f. -- This should pass, checking for correct treatment of ',' in values. + const std::string kComma[] = { + keys::kNameSuffixKey, "Header-C", + keys::kValueEqualsKey, "valueC, valueD" + }; + const size_t kCommaSizes[] = { arraysize(kComma) }; + GetArrayAsVector(kComma, kCommaSizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + + // 1.g. -- This should pass, empty values are values as well. + const std::string kEmpty[] = { + keys::kNameEqualsKey, "custom-header-d", + keys::kValueEqualsKey, "" + }; + const size_t kEmptySizes[] = { arraysize(kEmpty) }; + GetArrayAsVector(kEmpty, kEmptySizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + + // 1.h. -- Values are case-sensitive, this should fail + const std::string kLowercase[] = { + keys::kNameEqualsKey, "Custom-header-b", + keys::kValueEqualsKey, "valuea" // valuea != valueA + }; + const size_t kLowercaseSizes[] = { arraysize(kLowercase) }; + GetArrayAsVector(kLowercase, kLowercaseSizes, 1u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_FALSE(result); + + // 2.a. -- This should pass as disjunction, because one of the tests passes. + const std::string kDisjunction[] = { + keys::kNamePrefixKey, "Non-existing", // This one fails. + keys::kNameSuffixKey, "Non-existing", // This one fails. + keys::kValueEqualsKey, "void", // This one fails. + keys::kValueContainsKey, "alu" // This passes. + }; + const size_t kDisjunctionSizes[] = { 2u, 2u, 2u, 2u }; + GetArrayAsVector(kDisjunction, kDisjunctionSizes, 4u, &tests); + MatchAndCheck(tests, keys::kResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + + // 3.a. -- This should pass. + const std::string kNonExistent[] = { + keys::kNameEqualsKey, "Non-existing", + keys::kValueEqualsKey, "void" + }; + const size_t kNonExistentSizes[] = { arraysize(kNonExistent) }; + GetArrayAsVector(kNonExistent, kNonExistentSizes, 1u, &tests); + MatchAndCheck(tests, keys::kExcludeResponseHeadersKey, &url_request, &result); + EXPECT_TRUE(result); + + // 3.b. -- This should fail. + const std::string kExisting[] = { + keys::kNameEqualsKey, "custom-header-b", + keys::kValueEqualsKey, "valueB" + }; + const size_t kExistingSize[] = { arraysize(kExisting) }; + GetArrayAsVector(kExisting, kExistingSize, 1u, &tests); + MatchAndCheck(tests, keys::kExcludeResponseHeadersKey, &url_request, &result); + EXPECT_FALSE(result); +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.cc index 852fed4..c204807 100644 --- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.cc +++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.cc @@ -32,6 +32,16 @@ const char kSecureKey[] = "secure"; const char kToKey[] = "to"; const char kUrlKey[] = "url"; const char kValueKey[] = "value"; +const char kResponseHeadersKey[] = "responseHeaders"; +const char kExcludeResponseHeadersKey[] = "excludeResponseHeaders"; +const char kNamePrefixKey[] = "namePrefix"; +const char kNameSuffixKey[] = "nameSuffix"; +const char kNameContainsKey[] = "nameContains"; +const char kNameEqualsKey[] = "nameEquals"; +const char kValuePrefixKey[] = "valuePrefix"; +const char kValueSuffixKey[] = "valueSuffix"; +const char kValueContainsKey[] = "valueContains"; +const char kValueEqualsKey[] = "valueEquals"; // Values of dictionaries, in particular instance types const char kAddRequestCookieType[] = "declarativeWebRequest.AddRequestCookie"; diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h b/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h index 7eac594..77af30d 100644 --- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h +++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h @@ -35,6 +35,16 @@ extern const char kSecureKey[]; extern const char kToKey[]; extern const char kUrlKey[]; extern const char kValueKey[]; +extern const char kResponseHeadersKey[]; +extern const char kExcludeResponseHeadersKey[]; +extern const char kNamePrefixKey[]; +extern const char kNameSuffixKey[]; +extern const char kNameContainsKey[]; +extern const char kNameEqualsKey[]; +extern const char kValuePrefixKey[]; +extern const char kValueSuffixKey[]; +extern const char kValueContainsKey[]; +extern const char kValueEqualsKey[]; // Values of dictionaries, in particular instance types extern const char kAddRequestCookieType[]; diff --git a/chrome/common/extensions/api/declarative_web_request.json b/chrome/common/extensions/api/declarative_web_request.json index 3a8f715..5d5943b 100644 --- a/chrome/common/extensions/api/declarative_web_request.json +++ b/chrome/common/extensions/api/declarative_web_request.json @@ -8,6 +8,59 @@ "documentation_permissions_required": ["declarative", "declarativeWebRequest"], "types": [ { + "id": "HeaderFilter", + "type": "object", + "description": "Filters request headers for various criteria.", + "properties": { + "namePrefix": { + "description" : "Matches if the header name starts with the specified string.", + "type": "string", + "optional": true + }, + "nameSuffix": { + "type": "string", + "optional": true, + "description" : "Matches if the header name ends with the specified string." + }, + "nameContains": { + "choices": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "string"} + ], + "optional": true, + "description" : "Matches if the header name contains all of the specified strings." + }, + "nameEquals": { + "type": "string", + "optional": true, + "description" : "Matches if the header name is equal to the specified string." + }, + "valuePrefix": { + "type": "string", + "optional": true, + "description" : "Matches if the header value starts with the specified string." + }, + "valueSuffix": { + "type": "string", + "optional": true, + "description" : "Matches if the header value ends with the specified string." + }, + "valueContains": { + "choices": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "string"} + ], + "optional": true, + "description" : "Matches if the header value contains all of the specified strings." + }, + "valueEquals": { + "type": "string", + "optional": true, + "description" : "Matches if the header value is equal to the specified string." + } + } + }, + { "id": "RequestMatcher", "type": "object", "description": "Matches network events by various criteria.", @@ -35,6 +88,18 @@ "description": "Matches if the MIME media type of a response (from the HTTP Content-Type header) is <em>not</em> contained in the list.", "items": { "type": "string" } }, + "responseHeaders": { + "type": "array", + "optional": true, + "description": "Matches if some of the response headers is matched by one of the HeaderFilters.", + "items": { "$ref": "HeaderFilter" } + }, + "excludeResponseHeaders": { + "type": "array", + "optional": true, + "description": "Matches if none of the response headers is matched by one of the HeaderFilters.", + "items": { "$ref": "HeaderFilter" } + }, "instanceType": { "type": "string", "enum": ["declarativeWebRequest.RequestMatcher"], "nodoc": true diff --git a/chrome/common/extensions/docs/extensions/declarativeWebRequest.html b/chrome/common/extensions/docs/extensions/declarativeWebRequest.html index 5dc39c5..a93d624 100644 --- a/chrome/common/extensions/docs/extensions/declarativeWebRequest.html +++ b/chrome/common/extensions/docs/extensions/declarativeWebRequest.html @@ -230,7 +230,11 @@ <ol> </ol> </li><li> - <a href="#type-declarativeWebRequest.RequestMatcher">RequestMatcher</a> + <a href="#type-declarativeWebRequest.HeaderFilter">HeaderFilter</a> + <ol> + </ol> + </li><li> + <a href="#type-declarativeWebRequest.CancelRequest">CancelRequest</a> <ol> </ol> </li><li> @@ -262,7 +266,7 @@ <ol> </ol> </li><li> - <a href="#type-declarativeWebRequest.CancelRequest">CancelRequest</a> + <a href="#type-declarativeWebRequest.RequestMatcher">RequestMatcher</a> <ol> </ol> </li><li> @@ -698,8 +702,8 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <!-- FUNCTION PARAMETERS --> </div> </div><div class="apiItem"> - <a name="type-declarativeWebRequest.RequestMatcher"></a> - <h4>declarativeWebRequest.RequestMatcher</h4> + <a name="type-declarativeWebRequest.HeaderFilter"></a> + <h4>declarativeWebRequest.HeaderFilter</h4> <div> <dt> <em> @@ -715,14 +719,14 @@ very fast URL matching algorithm for hundreds of thousands of URLs. </div> </em> </dt> - <dd>Matches network events by various criteria.</dd> + <dd>Filters request headers for various criteria.</dd> <!-- OBJECT PROPERTIES --> <dd> <dl> <div> <div> <dt> - <var>url</var> + <var>namePrefix</var> <em> <!-- TYPE --> <div style="display:inline"> @@ -730,14 +734,14 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <span class="optional">optional</span> <span id="typeTemplate"> <span> - <a href="events.html#type-events.UrlFilter">events.UrlFilter</a> + <span>string</span> </span> </span> ) </div> </em> </dt> - <dd>Matches if the condition of the UrlFilter are fulfilled for the URL of the request.</dd> + <dd>Matches if the header name starts with the specified string.</dd> <!-- OBJECT PROPERTIES --> <!-- OBJECT METHODS --> <!-- OBJECT EVENT FIELDS --> @@ -746,7 +750,7 @@ very fast URL matching algorithm for hundreds of thousands of URLs. </div><div> <div> <dt> - <var>resourceType</var> + <var>nameSuffix</var> <em> <!-- TYPE --> <div style="display:inline"> @@ -754,21 +758,38 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <span class="optional">optional</span> <span id="typeTemplate"> <span> - <span> - array of <span><span> - <span> <span>string</span> - <span>["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"]</span> </span> - </span></span> - </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the header name ends with the specified string.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>nameContains</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>array of string or string</span> </span> </span> ) </div> </em> </dt> - <dd>Matches if the request type of a request is contained in the list. Requests that cannot match any of the types will be filtered out.</dd> + <dd>Matches if the header name contains all of the specified strings.</dd> <!-- OBJECT PROPERTIES --> <!-- OBJECT METHODS --> <!-- OBJECT EVENT FIELDS --> @@ -777,7 +798,7 @@ very fast URL matching algorithm for hundreds of thousands of URLs. </div><div> <div> <dt> - <var>contentType</var> + <var>nameEquals</var> <em> <!-- TYPE --> <div style="display:inline"> @@ -785,20 +806,62 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <span class="optional">optional</span> <span id="typeTemplate"> <span> - <span> - array of <span><span> + <span>string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the header name is equal to the specified string.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>valuePrefix</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> <span> <span>string</span> </span> - </span></span> - </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the header value starts with the specified string.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>valueSuffix</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>string</span> </span> </span> ) </div> </em> </dt> - <dd>Matches if the MIME media type of a response (from the HTTP Content-Type header) is contained in the list.</dd> + <dd>Matches if the header value ends with the specified string.</dd> <!-- OBJECT PROPERTIES --> <!-- OBJECT METHODS --> <!-- OBJECT EVENT FIELDS --> @@ -807,7 +870,7 @@ very fast URL matching algorithm for hundreds of thousands of URLs. </div><div> <div> <dt> - <var>excludeContentType</var> + <var>valueContains</var> <em> <!-- TYPE --> <div style="display:inline"> @@ -815,20 +878,38 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <span class="optional">optional</span> <span id="typeTemplate"> <span> - <span> - array of <span><span> + <span>array of string or string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the header value contains all of the specified strings.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>valueEquals</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> <span> <span>string</span> </span> - </span></span> - </span> - </span> </span> ) </div> </em> </dt> - <dd>Matches if the MIME media type of a response (from the HTTP Content-Type header) is <em>not</em> contained in the list.</dd> + <dd>Matches if the header value is equal to the specified string.</dd> <!-- OBJECT PROPERTIES --> <!-- OBJECT METHODS --> <!-- OBJECT EVENT FIELDS --> @@ -842,6 +923,34 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <!-- FUNCTION PARAMETERS --> </div> </div><div class="apiItem"> + <a name="type-declarativeWebRequest.CancelRequest"></a> + <h4>declarativeWebRequest.CancelRequest</h4> + <div> + <dt> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>object</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Declarative event action that cancels a network request.</dd> + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + </dl> + </dd> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div class="apiItem"> <a name="type-declarativeWebRequest.RedirectRequest"></a> <h4>declarativeWebRequest.RedirectRequest</h4> <div> @@ -1227,8 +1336,8 @@ very fast URL matching algorithm for hundreds of thousands of URLs. <!-- FUNCTION PARAMETERS --> </div> </div><div class="apiItem"> - <a name="type-declarativeWebRequest.CancelRequest"></a> - <h4>declarativeWebRequest.CancelRequest</h4> + <a name="type-declarativeWebRequest.RequestMatcher"></a> + <h4>declarativeWebRequest.RequestMatcher</h4> <div> <dt> <em> @@ -1244,10 +1353,186 @@ very fast URL matching algorithm for hundreds of thousands of URLs. </div> </em> </dt> - <dd>Declarative event action that cancels a network request.</dd> + <dd>Matches network events by various criteria.</dd> <!-- OBJECT PROPERTIES --> <dd> <dl> + <div> + <div> + <dt> + <var>url</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <a href="events.html#type-events.UrlFilter">events.UrlFilter</a> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the condition of the UrlFilter are fulfilled for the URL of the request.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>resourceType</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span> + array of <span><span> + <span> + <span>string</span> + <span>["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"]</span> + </span> + </span></span> + </span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the request type of a request is contained in the list. Requests that cannot match any of the types will be filtered out.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>contentType</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span> + array of <span><span> + <span> + <span>string</span> + </span> + </span></span> + </span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the MIME media type of a response (from the HTTP Content-Type header) is contained in the list.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>excludeContentType</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span> + array of <span><span> + <span> + <span>string</span> + </span> + </span></span> + </span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if the MIME media type of a response (from the HTTP Content-Type header) is <em>not</em> contained in the list.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>responseHeaders</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span> + array of <span><span> + <span> + <a href="declarativeWebRequest.html#type-declarativeWebRequest.HeaderFilter">declarativeWebRequest.HeaderFilter</a> + </span> + </span></span> + </span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if some of the response headers is matched by one of the HeaderFilters.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>excludeResponseHeaders</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span> + array of <span><span> + <span> + <a href="declarativeWebRequest.html#type-declarativeWebRequest.HeaderFilter">declarativeWebRequest.HeaderFilter</a> + </span> + </span></span> + </span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Matches if none of the response headers is matched by one of the HeaderFilters.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> </dl> </dd> <!-- OBJECT METHODS --> diff --git a/chrome/test/data/extensions/api_test/webrequest/declarative/headers.html.mock-http-headers b/chrome/test/data/extensions/api_test/webrequest/declarative/headers.html.mock-http-headers index 2e9ea99..76c436b 100644 --- a/chrome/test/data/extensions/api_test/webrequest/declarative/headers.html.mock-http-headers +++ b/chrome/test/data/extensions/api_test/webrequest/declarative/headers.html.mock-http-headers @@ -1,2 +1,7 @@ HTTP/1.1 200 OK Content-Type: text/plain; UTF-8 +Custom-Header: custom/value +Custom-Header-B: valueA +Custom-Header-B: valueB +Custom-Header-C: valueC, valueD +Custom-Header-D: diff --git a/chrome/test/data/extensions/api_test/webrequest/test_declarative.js b/chrome/test/data/extensions/api_test/webrequest/test_declarative.js index 3759f7b..adbc139 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_declarative.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_declarative.js @@ -105,7 +105,9 @@ runTests([ }, 'resourceType': ["main_frame"], 'contentType': ["text/plain"], - 'excludeContentType': ["image/png"]})], + 'excludeContentType': ["image/png"], + 'responseHeaders': [{ nameContains: ["content", "type"] } ], + 'excludeResponseHeaders': [{ valueContains: "nonsense" }] })], 'actions': [new CancelRequest()]} ], function() {navigateAndWait(getURLHttpWithHeaders());} |