Commit b85d14c8 authored by battre@chromium.org's avatar battre@chromium.org

Improve merging of header modifications in webRequest.OnHeadersReceived

This CL enables two independent extensions to modify headers of HTTP responses in case they don't conflict (two extension try to edit the same header).


BUG=none
TEST=no


Review URL: http://codereview.chromium.org/8511063

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110900 0039d316-1c4b-4281-b951-d872f2087c98
parent b89047e6
...@@ -1197,10 +1197,11 @@ helpers::EventResponseDelta* CalculateDelta( ...@@ -1197,10 +1197,11 @@ helpers::EventResponseDelta* CalculateDelta(
case ExtensionWebRequestEventRouter::kOnHeadersReceived: { case ExtensionWebRequestEventRouter::kOnHeadersReceived: {
net::HttpResponseHeaders* old_headers = net::HttpResponseHeaders* old_headers =
blocked_request->original_response_headers.get(); blocked_request->original_response_headers.get();
helpers::ResponseHeaders* new_headers =
response->response_headers.get();
return helpers::CalculateOnHeadersReceivedDelta( return helpers::CalculateOnHeadersReceivedDelta(
response->extension_id, response->extension_install_time, response->extension_id, response->extension_install_time,
response->cancel, old_headers->GetStatusLine(), response->cancel, old_headers, new_headers);
response->response_headers_string);
} }
case ExtensionWebRequestEventRouter::kOnAuthRequired: case ExtensionWebRequestEventRouter::kOnAuthRequired:
return helpers::CalculateOnAuthRequiredDelta( return helpers::CalculateOnAuthRequiredDelta(
...@@ -1277,6 +1278,7 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount( ...@@ -1277,6 +1278,7 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount(
CHECK(blocked_request.callback); CHECK(blocked_request.callback);
helpers::MergeOnHeadersReceivedResponses( helpers::MergeOnHeadersReceivedResponses(
blocked_request.response_deltas, blocked_request.response_deltas,
blocked_request.original_response_headers.get(),
blocked_request.override_response_headers, blocked_request.override_response_headers,
&conflicting_extensions, &conflicting_extensions,
&event_log_entries); &event_log_entries);
...@@ -1554,7 +1556,8 @@ bool WebRequestEventHandled::RunImpl() { ...@@ -1554,7 +1556,8 @@ bool WebRequestEventHandled::RunImpl() {
} }
if (value->HasKey("responseHeaders")) { if (value->HasKey("responseHeaders")) {
std::string response_headers_string; helpers::ResponseHeaders* response_headers =
new helpers::ResponseHeaders();
ListValue* response_headers_value = NULL; ListValue* response_headers_value = NULL;
EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kResponseHeadersKey, EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kResponseHeadersKey,
&response_headers_value)); &response_headers_value));
...@@ -1566,10 +1569,9 @@ bool WebRequestEventHandled::RunImpl() { ...@@ -1566,10 +1569,9 @@ bool WebRequestEventHandled::RunImpl() {
response_headers_value->GetDictionary(i, &header_value)); response_headers_value->GetDictionary(i, &header_value));
EXTENSION_FUNCTION_VALIDATE( EXTENSION_FUNCTION_VALIDATE(
FromHeaderDictionary(header_value, &name, &value)); FromHeaderDictionary(header_value, &name, &value));
response_headers_string += name + ": " + value + '\n'; response_headers->push_back(helpers::ResponseHeader(name, value));
} }
response_headers_string += '\n'; response->response_headers.reset(response_headers);
response->response_headers_string.swap(response_headers_string);
} }
if (value->HasKey(keys::kAuthCredentialsKey)) { if (value->HasKey(keys::kAuthCredentialsKey)) {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "base/time.h" #include "base/time.h"
#include "chrome/browser/extensions/extension_function.h" #include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/extensions/extension_webrequest_api_helpers.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/url_pattern_set.h" #include "chrome/common/extensions/url_pattern_set.h"
#include "ipc/ipc_message.h" #include "ipc/ipc_message.h"
...@@ -30,6 +31,7 @@ class GURL; ...@@ -30,6 +31,7 @@ class GURL;
namespace base { namespace base {
class DictionaryValue; class DictionaryValue;
class ListValue; class ListValue;
class StringValue;
} }
namespace content { namespace content {
...@@ -110,8 +112,9 @@ class ExtensionWebRequestEventRouter { ...@@ -110,8 +112,9 @@ class ExtensionWebRequestEventRouter {
bool cancel; bool cancel;
GURL new_url; GURL new_url;
scoped_ptr<net::HttpRequestHeaders> request_headers; scoped_ptr<net::HttpRequestHeaders> request_headers;
// Contains all header lines after the status line, lines are \n separated. scoped_ptr<extension_webrequest_api_helpers::ResponseHeaders>
std::string response_headers_string; response_headers;
scoped_ptr<net::AuthCredentials> auth_credentials; scoped_ptr<net::AuthCredentials> auth_credentials;
EventResponse(const std::string& extension_id, EventResponse(const std::string& extension_id,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/extensions/extension_webrequest_api_helpers.h" #include "chrome/browser/extensions/extension_webrequest_api_helpers.h"
#include "base/string_util.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/extensions/extension_webrequest_api.h" #include "chrome/browser/extensions/extension_webrequest_api.h"
#include "net/http/http_util.h" #include "net/http/http_util.h"
...@@ -165,21 +166,54 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta( ...@@ -165,21 +166,54 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta(
const std::string& extension_id, const std::string& extension_id,
const base::Time& extension_install_time, const base::Time& extension_install_time,
bool cancel, bool cancel,
const std::string& status_line, net::HttpResponseHeaders* old_response_headers,
const std::string& response_headers_string) { ResponseHeaders* new_response_headers) {
EventResponseDelta* result = EventResponseDelta* result =
new EventResponseDelta(extension_id, extension_install_time); new EventResponseDelta(extension_id, extension_install_time);
result->cancel = cancel; result->cancel = cancel;
if (!response_headers_string.empty()) { if (!new_response_headers)
std::string new_headers_string = return result;
status_line + "\n" + response_headers_string;
result->new_response_headers = // Find deleted headers (header keys are treated case insensitively).
new net::HttpResponseHeaders( {
net::HttpUtil::AssembleRawHeaders(new_headers_string.c_str(), void* iter = NULL;
new_headers_string.length())); std::string name;
std::string value;
while (old_response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
std::string name_lowercase(name);
StringToLowerASCII(&name_lowercase);
bool header_found = false;
for (ResponseHeaders::const_iterator i = new_response_headers->begin();
i != new_response_headers->end(); ++i) {
if (LowerCaseEqualsASCII(i->first, name_lowercase.c_str()) &&
value == i->second) {
header_found = true;
break;
}
}
if (!header_found)
result->deleted_response_headers.push_back(ResponseHeader(name, value));
}
} }
// Find added headers (header keys are treated case insensitively).
{
for (ResponseHeaders::const_iterator i = new_response_headers->begin();
i != new_response_headers->end(); ++i) {
void* iter = NULL;
std::string value;
bool header_found = false;
while (old_response_headers->EnumerateHeader(&iter, i->first, &value) &&
!header_found) {
header_found = (value == i->second);
}
if (!header_found)
result->added_response_headers.push_back(*i);
}
}
return result; return result;
} }
...@@ -346,33 +380,83 @@ void MergeOnBeforeSendHeadersResponses( ...@@ -346,33 +380,83 @@ void MergeOnBeforeSendHeadersResponses(
} }
} }
// Converts the key of the (key, value) pair to lower case.
static ResponseHeader ToLowerCase(const ResponseHeader& header) {
std::string lower_key(header.first);
StringToLowerASCII(&lower_key);
return ResponseHeader(lower_key, header.second);
}
void MergeOnHeadersReceivedResponses( void MergeOnHeadersReceivedResponses(
const EventResponseDeltas& deltas, const EventResponseDeltas& deltas,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers, scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
std::set<std::string>* conflicting_extensions, std::set<std::string>* conflicting_extensions,
EventLogEntries* event_log_entries) { EventLogEntries* event_log_entries) {
EventResponseDeltas::const_iterator delta; EventResponseDeltas::const_iterator delta;
// Whether any extension has overridden the response headers, yet. // Here we collect which headers we have removed or added so far due to
bool headers_overridden = false; // extensions of higher precedence. Header keys are always stored as
// lower case.
std::set<ResponseHeader> removed_headers;
std::set<ResponseHeader> added_headers;
// We assume here that the deltas are sorted in decreasing extension // We assume here that the deltas are sorted in decreasing extension
// precedence (i.e. decreasing extension installation time). // precedence (i.e. decreasing extension installation time).
for (delta = deltas.begin(); delta != deltas.end(); ++delta) { for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
if (!(*delta)->new_response_headers.get()) if ((*delta)->added_response_headers.empty() &&
(*delta)->deleted_response_headers.empty()) {
continue; continue;
}
scoped_refptr<NetLogModificationParameter> log( // Only create a copy if we really want to modify the response headers.
new NetLogModificationParameter((*delta)->extension_id)); if (override_response_headers->get() == NULL) {
*override_response_headers = new net::HttpResponseHeaders(
original_response_headers->raw_headers());
}
// Conflict if a second extension returned new response headers; // We consider modifications as pairs of (delete, add) operations.
bool extension_conflicts = headers_overridden; // If a header is deleted twice by different extensions we assume that the
// intention was to modify it to different values and consider this a
// conflict. As deltas is sorted by decreasing extension installation order,
// this takes care of precedence.
bool extension_conflicts = false;
ResponseHeaders::const_iterator i;
for (i = (*delta)->deleted_response_headers.begin();
i != (*delta)->deleted_response_headers.end(); ++i) {
if (removed_headers.find(ToLowerCase(*i)) != removed_headers.end()) {
extension_conflicts = true;
break;
}
}
// Now execute the modifications if there were no conflicts.
if (!extension_conflicts) { if (!extension_conflicts) {
headers_overridden = true; // Delete headers
*override_response_headers = (*delta)->new_response_headers; {
for (i = (*delta)->deleted_response_headers.begin();
i != (*delta)->deleted_response_headers.end(); ++i) {
(*override_response_headers)->RemoveHeaderWithValue(i->first,
i->second);
removed_headers.insert(ToLowerCase(*i));
}
}
// Add headers.
{
for (i = (*delta)->added_response_headers.begin();
i != (*delta)->added_response_headers.end(); ++i) {
ResponseHeader lowercase_header(ToLowerCase(*i));
if (added_headers.find(lowercase_header) != added_headers.end())
continue;
added_headers.insert(lowercase_header);
(*override_response_headers)->AddHeader(i->first + ": " + i->second);
}
}
EventLogEntry log_entry( EventLogEntry log_entry(
net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS, log); net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS,
make_scoped_refptr(
new NetLogModificationParameter((*delta)->extension_id)));
event_log_entries->push_back(log_entry); event_log_entries->push_back(log_entry);
} else { } else {
conflicting_extensions->insert((*delta)->extension_id); conflicting_extensions->insert((*delta)->extension_id);
......
...@@ -29,6 +29,9 @@ class Value; ...@@ -29,6 +29,9 @@ class Value;
namespace extension_webrequest_api_helpers { namespace extension_webrequest_api_helpers {
typedef std::pair<std::string, std::string> ResponseHeader;
typedef std::vector<ResponseHeader> ResponseHeaders;
// Contains the modification an extension wants to perform on an event. // Contains the modification an extension wants to perform on an event.
struct EventResponseDelta { struct EventResponseDelta {
// ID of the extension that sent this response. // ID of the extension that sent this response.
...@@ -49,8 +52,12 @@ struct EventResponseDelta { ...@@ -49,8 +52,12 @@ struct EventResponseDelta {
// Keys of request headers to be deleted. // Keys of request headers to be deleted.
std::vector<std::string> deleted_request_headers; std::vector<std::string> deleted_request_headers;
// Complete set of response headers that will replace the original ones. // Headers that were added to the response. A modification of a header
scoped_refptr<net::HttpResponseHeaders> new_response_headers; // corresponds to a deletion and subsequent addition of the new header.
ResponseHeaders added_response_headers;
// Headers that were deleted from the response.
ResponseHeaders deleted_response_headers;
// Authentication Credentials to use. // Authentication Credentials to use.
scoped_ptr<net::AuthCredentials> auth_credentials; scoped_ptr<net::AuthCredentials> auth_credentials;
...@@ -110,16 +117,12 @@ EventResponseDelta* CalculateOnBeforeSendHeadersDelta( ...@@ -110,16 +117,12 @@ EventResponseDelta* CalculateOnBeforeSendHeadersDelta(
bool cancel, bool cancel,
net::HttpRequestHeaders* old_headers, net::HttpRequestHeaders* old_headers,
net::HttpRequestHeaders* new_headers); net::HttpRequestHeaders* new_headers);
// |status_line| contains the status line of the original request. Together
// with |response_headers_string| we can assemble a complete new response
// header. |response_headers_string| contains all headers with \n as line
// separator. It terminates with two \n.
EventResponseDelta* CalculateOnHeadersReceivedDelta( EventResponseDelta* CalculateOnHeadersReceivedDelta(
const std::string& extension_id, const std::string& extension_id,
const base::Time& extension_install_time, const base::Time& extension_install_time,
bool cancel, bool cancel,
const std::string& status_line, net::HttpResponseHeaders* old_response_headers,
const std::string& response_headers_string); ResponseHeaders* new_response_headers);
// Destructively moves the auth credentials from |auth_credentials| to the // Destructively moves the auth credentials from |auth_credentials| to the
// returned EventResponseDelta. // returned EventResponseDelta.
EventResponseDelta* CalculateOnAuthRequiredDelta( EventResponseDelta* CalculateOnAuthRequiredDelta(
...@@ -154,11 +157,12 @@ void MergeOnBeforeSendHeadersResponses( ...@@ -154,11 +157,12 @@ void MergeOnBeforeSendHeadersResponses(
net::HttpRequestHeaders* request_headers, net::HttpRequestHeaders* request_headers,
std::set<std::string>* conflicting_extensions, std::set<std::string>* conflicting_extensions,
EventLogEntries* event_log_entries); EventLogEntries* event_log_entries);
// Overrides |override_response_headers| with the response headers returned // Stores a copy of |original_response_header| into |override_response_headers|
// by the extension with the highest precedence. // that is modified according to |deltas|. If |deltas| does not instruct to
// TODO(battre) Implement merging of response header modifications. // modify the response headers, |override_response_headers| remains empty.
void MergeOnHeadersReceivedResponses( void MergeOnHeadersReceivedResponses(
const EventResponseDeltas& deltas, const EventResponseDeltas& deltas,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers, scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
std::set<std::string>* conflicting_extensions, std::set<std::string>* conflicting_extensions,
EventLogEntries* event_log_entries); EventLogEntries* event_log_entries);
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include <queue> #include <queue>
#include <map>
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
...@@ -42,6 +43,15 @@ static void EventHandledOnIOThread( ...@@ -42,6 +43,15 @@ static void EventHandledOnIOThread(
profile, extension_id, event_name, sub_event_name, request_id, profile, extension_id, event_name, sub_event_name, request_id,
response); response);
} }
// Searches |key| in |collection| by iterating over its elements and returns
// true if found.
template <typename Collection, typename Key>
bool Contains(const Collection& collection, const Key& key) {
return std::find(collection.begin(), collection.end(), key) !=
collection.end();
}
} // namespace } // namespace
// A mock event router that responds to events with a pre-arranged queue of // A mock event router that responds to events with a pre-arranged queue of
...@@ -821,21 +831,38 @@ TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeSendHeadersDelta) { ...@@ -821,21 +831,38 @@ TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeSendHeadersDelta) {
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) { TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) {
using namespace extension_webrequest_api_helpers; using namespace extension_webrequest_api_helpers;
const bool cancel = true; const bool cancel = true;
const char status_line[] = "HTTP/1.0 200 OK"; char base_headers_string[] =
const char response_headers_string[] = "key1: value1\n" "HTTP/1.0 200 OK\r\n"
"key2: value2\n\n"; "Key1: Value1\r\n"
"Key2: Value2\r\n"
"Key3: Value3\r\n"
"\r\n";
scoped_refptr<net::HttpResponseHeaders> base_headers(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
base_headers_string, sizeof(base_headers_string))));
ResponseHeaders new_headers;
new_headers.push_back(ResponseHeader("kEy1", "Value1")); // Unchanged
new_headers.push_back(ResponseHeader("Key2", "Value1")); // Modified
// Key3 is deleted
new_headers.push_back(ResponseHeader("Key4", "Value4")); // Added
scoped_ptr<EventResponseDelta> delta( scoped_ptr<EventResponseDelta> delta(
CalculateOnHeadersReceivedDelta("extid", base::Time::Now(), cancel, CalculateOnHeadersReceivedDelta("extid", base::Time::Now(), cancel,
status_line, response_headers_string)); base_headers, &new_headers));
ASSERT_TRUE(delta.get()); ASSERT_TRUE(delta.get());
EXPECT_TRUE(delta->cancel); EXPECT_TRUE(delta->cancel);
ASSERT_TRUE(delta->new_response_headers.get()); EXPECT_EQ(2u, delta->added_response_headers.size());
EXPECT_TRUE(delta->new_response_headers->HasHeader("key1")); EXPECT_TRUE(Contains(delta->added_response_headers,
EXPECT_TRUE(delta->new_response_headers->HasHeader("key2")); ResponseHeader("Key2", "Value1")));
EXPECT_EQ(status_line, delta->new_response_headers->GetStatusLine()); EXPECT_TRUE(Contains(delta->added_response_headers,
// net::HttpResponseHeaders does not have easy access to header values. ResponseHeader("Key4", "Value4")));
// Let's be lazy and not test it here. EXPECT_EQ(2u, delta->deleted_response_headers.size());
EXPECT_TRUE(Contains(delta->deleted_response_headers,
ResponseHeader("Key2", "Value2")));
EXPECT_TRUE(Contains(delta->deleted_response_headers,
ResponseHeader("Key3", "Value3")));
} }
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnAuthRequiredDelta) { TEST(ExtensionWebRequestHelpersTest, TestCalculateOnAuthRequiredDelta) {
...@@ -1060,10 +1087,20 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) { ...@@ -1060,10 +1087,20 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) { TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) {
using namespace extension_webrequest_api_helpers; using namespace extension_webrequest_api_helpers;
EventLogEntries event_log; EventLogEntries event_log;
std::set<std::string> conflicting_extensions; std::set<std::string> conflicting_extensions;
std::string header_value; std::string header_value;
EventResponseDeltas deltas; EventResponseDeltas deltas;
char base_headers_string[] =
"HTTP/1.0 200 OK\r\n"
"Key1: Value1\r\n"
"Key2: Value2\r\n"
"\r\n";
scoped_refptr<net::HttpResponseHeaders> base_headers(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
base_headers_string, sizeof(base_headers_string))));
// Check that we can handle if not touching the response headers. // Check that we can handle if not touching the response headers.
linked_ptr<EventResponseDelta> d0( linked_ptr<EventResponseDelta> d0(
...@@ -1071,59 +1108,112 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) { ...@@ -1071,59 +1108,112 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) {
deltas.push_back(d0); deltas.push_back(d0);
scoped_refptr<net::HttpResponseHeaders> new_headers0; scoped_refptr<net::HttpResponseHeaders> new_headers0;
MergeOnHeadersReceivedResponses( MergeOnHeadersReceivedResponses(
deltas, &new_headers0, &conflicting_extensions, &event_log); deltas, base_headers.get(), &new_headers0, &conflicting_extensions,
&event_log);
EXPECT_FALSE(new_headers0.get()); EXPECT_FALSE(new_headers0.get());
EXPECT_EQ(0u, conflicting_extensions.size()); EXPECT_EQ(0u, conflicting_extensions.size());
EXPECT_EQ(0u, event_log.size()); EXPECT_EQ(0u, event_log.size());
// Check that we can replace response headers.
char headers1_string[] =
"HTTP/1.0 200 OK\r\n"
"Foo: bar\r\n"
"\r\n";
scoped_refptr<net::HttpResponseHeaders> headers1(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
headers1_string, sizeof(headers1_string))));
linked_ptr<EventResponseDelta> d1( linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(2000))); new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
d1->new_response_headers = headers1; d1->deleted_response_headers.push_back(ResponseHeader("KEY1", "Value1"));
d1->deleted_response_headers.push_back(ResponseHeader("KEY2", "Value2"));
d1->added_response_headers.push_back(ResponseHeader("Key2", "Value3"));
deltas.push_back(d1); deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder); deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
scoped_refptr<net::HttpResponseHeaders> new_headers1;
conflicting_extensions.clear(); conflicting_extensions.clear();
event_log.clear(); event_log.clear();
scoped_refptr<net::HttpResponseHeaders> new_headers1;
MergeOnHeadersReceivedResponses( MergeOnHeadersReceivedResponses(
deltas, &new_headers1, &conflicting_extensions, &event_log); deltas, base_headers.get(), &new_headers1, &conflicting_extensions,
EXPECT_EQ(headers1.get(), new_headers1.get()); &event_log);
ASSERT_TRUE(new_headers1.get());
std::multimap<std::string, std::string> expected1;
expected1.insert(std::pair<std::string, std::string>("Key2", "Value3"));
void* iter = NULL;
std::string name;
std::string value;
std::multimap<std::string, std::string> actual1;
while (new_headers1->EnumerateHeaderLines(&iter, &name, &value)) {
actual1.insert(std::pair<std::string, std::string>(name, value));
}
EXPECT_EQ(expected1, actual1);
EXPECT_EQ(0u, conflicting_extensions.size()); EXPECT_EQ(0u, conflicting_extensions.size());
EXPECT_EQ(1u, event_log.size()); EXPECT_EQ(1u, event_log.size());
// Check that we replace response headers only once. // Check that we replace response headers only once.
char headers2_string[] =
"HTTP/1.0 200 OK\r\n"
"Foo: baz\r\n"
"\r\n";
scoped_refptr<net::HttpResponseHeaders> headers2(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
headers2_string, sizeof(headers2_string))));
linked_ptr<EventResponseDelta> d2( linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(1500))); new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
d2->new_response_headers = headers2; // Note that we use a different capitalization of KeY2. This should not
// matter.
d2->deleted_response_headers.push_back(ResponseHeader("KeY2", "Value2"));
d2->added_response_headers.push_back(ResponseHeader("Key2", "Value4"));
deltas.push_back(d2); deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder); deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
scoped_refptr<net::HttpResponseHeaders> new_headers2;
conflicting_extensions.clear(); conflicting_extensions.clear();
event_log.clear(); event_log.clear();
scoped_refptr<net::HttpResponseHeaders> new_headers2;
MergeOnHeadersReceivedResponses( MergeOnHeadersReceivedResponses(
deltas, &new_headers2, &conflicting_extensions, &event_log); deltas, base_headers.get(), &new_headers2, &conflicting_extensions,
EXPECT_EQ(headers1.get(), new_headers1.get()); &event_log);
ASSERT_TRUE(new_headers2.get());
iter = NULL;
std::multimap<std::string, std::string> actual2;
while (new_headers2->EnumerateHeaderLines(&iter, &name, &value)) {
actual2.insert(std::pair<std::string, std::string>(name, value));
}
EXPECT_EQ(expected1, actual2);
EXPECT_EQ(1u, conflicting_extensions.size()); EXPECT_EQ(1u, conflicting_extensions.size());
EXPECT_TRUE(ContainsKey(conflicting_extensions, "extid2")); EXPECT_TRUE(ContainsKey(conflicting_extensions, "extid2"));
EXPECT_EQ(2u, event_log.size()); EXPECT_EQ(2u, event_log.size());
} }
// Check that we do not delete too much
TEST(ExtensionWebRequestHelpersTest,
TestMergeOnHeadersReceivedResponsesDeletion) {
using namespace extension_webrequest_api_helpers;
EventLogEntries event_log;
std::set<std::string> conflicting_extensions;
std::string header_value;
EventResponseDeltas deltas;
char base_headers_string[] =
"HTTP/1.0 200 OK\r\n"
"Key1: Value1\r\n"
"Key1: Value2\r\n"
"Key1: Value3\r\n"
"Key2: Value4\r\n"
"\r\n";
scoped_refptr<net::HttpResponseHeaders> base_headers(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
base_headers_string, sizeof(base_headers_string))));
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
d1->deleted_response_headers.push_back(ResponseHeader("KEY1", "Value2"));
deltas.push_back(d1);
scoped_refptr<net::HttpResponseHeaders> new_headers1;
MergeOnHeadersReceivedResponses(
deltas, base_headers.get(), &new_headers1, &conflicting_extensions,
&event_log);
ASSERT_TRUE(new_headers1.get());
std::multimap<std::string, std::string> expected1;
expected1.insert(std::pair<std::string, std::string>("Key1", "Value1"));
expected1.insert(std::pair<std::string, std::string>("Key1", "Value3"));
expected1.insert(std::pair<std::string, std::string>("Key2", "Value4"));
void* iter = NULL;
std::string name;
std::string value;
std::multimap<std::string, std::string> actual1;
while (new_headers1->EnumerateHeaderLines(&iter, &name, &value)) {
actual1.insert(std::pair<std::string, std::string>(name, value));
}
EXPECT_EQ(expected1, actual1);
EXPECT_EQ(0u, conflicting_extensions.size());
EXPECT_EQ(1u, event_log.size());
}
TEST(ExtensionWebRequestHelpersTest, TestMergeOnAuthRequiredResponses) { TEST(ExtensionWebRequestHelpersTest, TestMergeOnAuthRequiredResponses) {
using namespace extension_webrequest_api_helpers; using namespace extension_webrequest_api_helpers;
EventLogEntries event_log; EventLogEntries event_log;
......
...@@ -294,6 +294,42 @@ void HttpResponseHeaders::MergeWithHeaders(const std::string& raw_headers, ...@@ -294,6 +294,42 @@ void HttpResponseHeaders::MergeWithHeaders(const std::string& raw_headers,
Parse(new_raw_headers); Parse(new_raw_headers);
} }
void HttpResponseHeaders::MergeWithHeadersWithValue(
const std::string& raw_headers,
const std::string& header_to_remove_name,
const std::string& header_to_remove_value) {
std::string header_to_remove_name_lowercase(header_to_remove_name);
StringToLowerASCII(&header_to_remove_name_lowercase);
std::string new_raw_headers(raw_headers);
for (size_t i = 0; i < parsed_.size(); ++i) {
DCHECK(!parsed_[i].is_continuation());
// Locate the start of the next header.
size_t k = i;
while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
--k;
std::string name(parsed_[i].name_begin, parsed_[i].name_end);
StringToLowerASCII(&name);
std::string value(parsed_[i].value_begin, parsed_[i].value_end);
if (name != header_to_remove_name_lowercase ||
value != header_to_remove_value) {
// It's ok to preserve this header in the final result.
new_raw_headers.append(parsed_[i].name_begin, parsed_[k].value_end);
new_raw_headers.push_back('\0');
}
i = k;
}
new_raw_headers.push_back('\0');
// Make this object hold the new data.
raw_headers_.clear();
parsed_.clear();
Parse(new_raw_headers);
}
void HttpResponseHeaders::RemoveHeader(const std::string& name) { void HttpResponseHeaders::RemoveHeader(const std::string& name) {
// Copy up to the null byte. This just copies the status line. // Copy up to the null byte. This just copies the status line.
std::string new_raw_headers(raw_headers_.c_str()); std::string new_raw_headers(raw_headers_.c_str());
...@@ -306,6 +342,15 @@ void HttpResponseHeaders::RemoveHeader(const std::string& name) { ...@@ -306,6 +342,15 @@ void HttpResponseHeaders::RemoveHeader(const std::string& name) {
MergeWithHeaders(new_raw_headers, to_remove); MergeWithHeaders(new_raw_headers, to_remove);
} }
void HttpResponseHeaders::RemoveHeaderWithValue(const std::string& name,
const std::string& value) {
// Copy up to the null byte. This just copies the status line.
std::string new_raw_headers(raw_headers_.c_str());
new_raw_headers.push_back('\0');
MergeWithHeadersWithValue(new_raw_headers, name, value);
}
void HttpResponseHeaders::AddHeader(const std::string& header) { void HttpResponseHeaders::AddHeader(const std::string& header) {
CheckDoesNotHaveEmbededNulls(header); CheckDoesNotHaveEmbededNulls(header);
DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]); DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
......
...@@ -63,6 +63,10 @@ class NET_EXPORT HttpResponseHeaders ...@@ -63,6 +63,10 @@ class NET_EXPORT HttpResponseHeaders
// Removes all instances of a particular header. // Removes all instances of a particular header.
void RemoveHeader(const std::string& name); void RemoveHeader(const std::string& name);
// Removes a particular header. The header name is compared
// case-insensitively.
void RemoveHeaderWithValue(const std::string& name, const std::string& value);
// Adds a particular header. |header| has to be a single header without any // Adds a particular header. |header| has to be a single header without any
// EOL termination, just [<header-name>: <header-values>] // EOL termination, just [<header-name>: <header-values>]
// If a header with the same name is already stored, the two headers are not // If a header with the same name is already stored, the two headers are not
...@@ -300,6 +304,16 @@ class NET_EXPORT HttpResponseHeaders ...@@ -300,6 +304,16 @@ class NET_EXPORT HttpResponseHeaders
void MergeWithHeaders(const std::string& raw_headers, void MergeWithHeaders(const std::string& raw_headers,
const HeaderSet& headers_to_remove); const HeaderSet& headers_to_remove);
// Replaces the current headers with the merged version of |raw_headers| and
// the current headers with out the header consisting of
// |header_to_remove_name| and |header_to_remove_value|. Note that
// |header_to_remove_name| is compared case-insensitively.
// Note that the header to remove is removed from the current headers (before
// the merge), not after the merge.
void MergeWithHeadersWithValue(const std::string& raw_headers,
const std::string& header_to_remove_name,
const std::string& header_to_remove_value);
// Adds the values from any 'cache-control: no-cache="foo,bar"' headers. // Adds the values from any 'cache-control: no-cache="foo,bar"' headers.
void AddNonCacheableHeaders(HeaderSet* header_names) const; void AddNonCacheableHeaders(HeaderSet* header_names) const;
......
...@@ -1624,6 +1624,72 @@ TEST(HttpResponseHeadersTest, RemoveHeader) { ...@@ -1624,6 +1624,72 @@ TEST(HttpResponseHeadersTest, RemoveHeader) {
} }
} }
TEST(HttpResponseHeadersTest, RemoveIndividualHeader) {
const struct {
const char* orig_headers;
const char* to_remove_name;
const char* to_remove_value;
const char* expected_headers;
} tests[] = {
{ "HTTP/1.1 200 OK\n"
"connection: keep-alive\n"
"Cache-control: max-age=10000\n"
"Content-Length: 450\n",
"Content-Length",
"450",
"HTTP/1.1 200 OK\n"
"connection: keep-alive\n"
"Cache-control: max-age=10000\n"
},
{ "HTTP/1.1 200 OK\n"
"connection: keep-alive \n"
"Content-Length : 450 \n"
"Cache-control: max-age=10000\n",
"Content-Length",
"450",
"HTTP/1.1 200 OK\n"
"connection: keep-alive\n"
"Cache-control: max-age=10000\n"
},
{ "HTTP/1.1 200 OK\n"
"connection: keep-alive \n"
"Content-Length: 450\n"
"Cache-control: max-age=10000\n",
"Content-Length", // Matching name.
"999", // Mismatching value.
"HTTP/1.1 200 OK\n"
"connection: keep-alive\n"
"Content-Length: 450\n"
"Cache-control: max-age=10000\n"
},
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
std::string orig_headers(tests[i].orig_headers);
HeadersToRaw(&orig_headers);
scoped_refptr<net::HttpResponseHeaders> parsed(
new net::HttpResponseHeaders(orig_headers));
std::string name(tests[i].to_remove_name);
std::string value(tests[i].to_remove_value);
parsed->RemoveHeaderWithValue(name, value);
std::string resulting_headers;
parsed->GetNormalizedHeaders(&resulting_headers);
EXPECT_EQ(std::string(tests[i].expected_headers), resulting_headers);
}
}
TEST(HttpResponseHeadersTest, ReplaceStatus) { TEST(HttpResponseHeadersTest, ReplaceStatus) {
const struct { const struct {
const char* orig_headers; const char* orig_headers;
......
...@@ -213,6 +213,9 @@ class URLRequestHttpJob : public URLRequestJob { ...@@ -213,6 +213,9 @@ class URLRequestHttpJob : public URLRequestJob {
OldCompletionCallbackImpl<URLRequestHttpJob> on_headers_received_callback_; OldCompletionCallbackImpl<URLRequestHttpJob> on_headers_received_callback_;
// We allow the network delegate to modify a copy of the response headers.
// This prevents modifications of headers that are shared with the underlying
// layers of the network stack.
scoped_refptr<HttpResponseHeaders> override_response_headers_; scoped_refptr<HttpResponseHeaders> override_response_headers_;
// Flag used to verify that |this| is not deleted while we are awaiting // Flag used to verify that |this| is not deleted while we are awaiting
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment