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(
case ExtensionWebRequestEventRouter::kOnHeadersReceived: {
net::HttpResponseHeaders* old_headers =
blocked_request->original_response_headers.get();
helpers::ResponseHeaders* new_headers =
response->response_headers.get();
return helpers::CalculateOnHeadersReceivedDelta(
response->extension_id, response->extension_install_time,
response->cancel, old_headers->GetStatusLine(),
response->response_headers_string);
response->cancel, old_headers, new_headers);
}
case ExtensionWebRequestEventRouter::kOnAuthRequired:
return helpers::CalculateOnAuthRequiredDelta(
......@@ -1277,6 +1278,7 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount(
CHECK(blocked_request.callback);
helpers::MergeOnHeadersReceivedResponses(
blocked_request.response_deltas,
blocked_request.original_response_headers.get(),
blocked_request.override_response_headers,
&conflicting_extensions,
&event_log_entries);
......@@ -1554,7 +1556,8 @@ bool WebRequestEventHandled::RunImpl() {
}
if (value->HasKey("responseHeaders")) {
std::string response_headers_string;
helpers::ResponseHeaders* response_headers =
new helpers::ResponseHeaders();
ListValue* response_headers_value = NULL;
EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kResponseHeadersKey,
&response_headers_value));
......@@ -1566,10 +1569,9 @@ bool WebRequestEventHandled::RunImpl() {
response_headers_value->GetDictionary(i, &header_value));
EXTENSION_FUNCTION_VALIDATE(
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_string.swap(response_headers_string);
response->response_headers.reset(response_headers);
}
if (value->HasKey(keys::kAuthCredentialsKey)) {
......
......@@ -15,6 +15,7 @@
#include "base/memory/singleton.h"
#include "base/time.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/common/extensions/url_pattern_set.h"
#include "ipc/ipc_message.h"
......@@ -30,6 +31,7 @@ class GURL;
namespace base {
class DictionaryValue;
class ListValue;
class StringValue;
}
namespace content {
......@@ -110,8 +112,9 @@ class ExtensionWebRequestEventRouter {
bool cancel;
GURL new_url;
scoped_ptr<net::HttpRequestHeaders> request_headers;
// Contains all header lines after the status line, lines are \n separated.
std::string response_headers_string;
scoped_ptr<extension_webrequest_api_helpers::ResponseHeaders>
response_headers;
scoped_ptr<net::AuthCredentials> auth_credentials;
EventResponse(const std::string& extension_id,
......
......@@ -4,6 +4,7 @@
#include "chrome/browser/extensions/extension_webrequest_api_helpers.h"
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_webrequest_api.h"
#include "net/http/http_util.h"
......@@ -165,21 +166,54 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta(
const std::string& extension_id,
const base::Time& extension_install_time,
bool cancel,
const std::string& status_line,
const std::string& response_headers_string) {
net::HttpResponseHeaders* old_response_headers,
ResponseHeaders* new_response_headers) {
EventResponseDelta* result =
new EventResponseDelta(extension_id, extension_install_time);
result->cancel = cancel;
if (!response_headers_string.empty()) {
std::string new_headers_string =
status_line + "\n" + response_headers_string;
if (!new_response_headers)
return result;
// Find deleted headers (header keys are treated case insensitively).
{
void* iter = NULL;
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));
}
}
result->new_response_headers =
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(new_headers_string.c_str(),
new_headers_string.length()));
// 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;
}
......@@ -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(
const EventResponseDeltas& deltas,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
std::set<std::string>* conflicting_extensions,
EventLogEntries* event_log_entries) {
EventResponseDeltas::const_iterator delta;
// Whether any extension has overridden the response headers, yet.
bool headers_overridden = false;
// Here we collect which headers we have removed or added so far due to
// 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
// precedence (i.e. decreasing extension installation time).
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;
}
scoped_refptr<NetLogModificationParameter> log(
new NetLogModificationParameter((*delta)->extension_id));
// Only create a copy if we really want to modify the response headers.
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;
bool extension_conflicts = headers_overridden;
// We consider modifications as pairs of (delete, add) operations.
// 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) {
headers_overridden = true;
*override_response_headers = (*delta)->new_response_headers;
// Delete 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(
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);
} else {
conflicting_extensions->insert((*delta)->extension_id);
......
......@@ -29,6 +29,9 @@ class Value;
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.
struct EventResponseDelta {
// ID of the extension that sent this response.
......@@ -49,8 +52,12 @@ struct EventResponseDelta {
// Keys of request headers to be deleted.
std::vector<std::string> deleted_request_headers;
// Complete set of response headers that will replace the original ones.
scoped_refptr<net::HttpResponseHeaders> new_response_headers;
// Headers that were added to the response. A modification of a header
// 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.
scoped_ptr<net::AuthCredentials> auth_credentials;
......@@ -110,16 +117,12 @@ EventResponseDelta* CalculateOnBeforeSendHeadersDelta(
bool cancel,
net::HttpRequestHeaders* old_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(
const std::string& extension_id,
const base::Time& extension_install_time,
bool cancel,
const std::string& status_line,
const std::string& response_headers_string);
net::HttpResponseHeaders* old_response_headers,
ResponseHeaders* new_response_headers);
// Destructively moves the auth credentials from |auth_credentials| to the
// returned EventResponseDelta.
EventResponseDelta* CalculateOnAuthRequiredDelta(
......@@ -154,11 +157,12 @@ void MergeOnBeforeSendHeadersResponses(
net::HttpRequestHeaders* request_headers,
std::set<std::string>* conflicting_extensions,
EventLogEntries* event_log_entries);
// Overrides |override_response_headers| with the response headers returned
// by the extension with the highest precedence.
// TODO(battre) Implement merging of response header modifications.
// Stores a copy of |original_response_header| into |override_response_headers|
// that is modified according to |deltas|. If |deltas| does not instruct to
// modify the response headers, |override_response_headers| remains empty.
void MergeOnHeadersReceivedResponses(
const EventResponseDeltas& deltas,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
std::set<std::string>* conflicting_extensions,
EventLogEntries* event_log_entries);
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include <queue>
#include <map>
#include "base/bind.h"
#include "base/callback.h"
......@@ -42,6 +43,15 @@ static void EventHandledOnIOThread(
profile, extension_id, event_name, sub_event_name, request_id,
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
// A mock event router that responds to events with a pre-arranged queue of
......@@ -821,21 +831,38 @@ TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeSendHeadersDelta) {
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) {
using namespace extension_webrequest_api_helpers;
const bool cancel = true;
const char status_line[] = "HTTP/1.0 200 OK";
const char response_headers_string[] = "key1: value1\n"
"key2: value2\n\n";
char base_headers_string[] =
"HTTP/1.0 200 OK\r\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(
CalculateOnHeadersReceivedDelta("extid", base::Time::Now(), cancel,
status_line, response_headers_string));
base_headers, &new_headers));
ASSERT_TRUE(delta.get());
EXPECT_TRUE(delta->cancel);
ASSERT_TRUE(delta->new_response_headers.get());
EXPECT_TRUE(delta->new_response_headers->HasHeader("key1"));
EXPECT_TRUE(delta->new_response_headers->HasHeader("key2"));
EXPECT_EQ(status_line, delta->new_response_headers->GetStatusLine());
// net::HttpResponseHeaders does not have easy access to header values.
// Let's be lazy and not test it here.
EXPECT_EQ(2u, delta->added_response_headers.size());
EXPECT_TRUE(Contains(delta->added_response_headers,
ResponseHeader("Key2", "Value1")));
EXPECT_TRUE(Contains(delta->added_response_headers,
ResponseHeader("Key4", "Value4")));
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) {
......@@ -1065,65 +1092,128 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) {
std::string header_value;
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.
linked_ptr<EventResponseDelta> d0(
new EventResponseDelta("extid0", base::Time::FromInternalValue(3000)));
deltas.push_back(d0);
scoped_refptr<net::HttpResponseHeaders> new_headers0;
MergeOnHeadersReceivedResponses(
deltas, &new_headers0, &conflicting_extensions, &event_log);
deltas, base_headers.get(), &new_headers0, &conflicting_extensions,
&event_log);
EXPECT_FALSE(new_headers0.get());
EXPECT_EQ(0u, conflicting_extensions.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(
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.sort(&InDecreasingExtensionInstallationTimeOrder);
scoped_refptr<net::HttpResponseHeaders> new_headers1;
conflicting_extensions.clear();
event_log.clear();
scoped_refptr<net::HttpResponseHeaders> new_headers1;
MergeOnHeadersReceivedResponses(
deltas, &new_headers1, &conflicting_extensions, &event_log);
EXPECT_EQ(headers1.get(), new_headers1.get());
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>("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(1u, event_log.size());
// 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(
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.sort(&InDecreasingExtensionInstallationTimeOrder);
scoped_refptr<net::HttpResponseHeaders> new_headers2;
conflicting_extensions.clear();
event_log.clear();
scoped_refptr<net::HttpResponseHeaders> new_headers2;
MergeOnHeadersReceivedResponses(
deltas, &new_headers2, &conflicting_extensions, &event_log);
EXPECT_EQ(headers1.get(), new_headers1.get());
deltas, base_headers.get(), &new_headers2, &conflicting_extensions,
&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_TRUE(ContainsKey(conflicting_extensions, "extid2"));
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) {
using namespace extension_webrequest_api_helpers;
EventLogEntries event_log;
......
......@@ -294,6 +294,42 @@ void HttpResponseHeaders::MergeWithHeaders(const std::string& 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) {
// Copy up to the null byte. This just copies the status line.
std::string new_raw_headers(raw_headers_.c_str());
......@@ -306,6 +342,15 @@ void HttpResponseHeaders::RemoveHeader(const std::string& name) {
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) {
CheckDoesNotHaveEmbededNulls(header);
DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
......
......@@ -63,6 +63,10 @@ class NET_EXPORT HttpResponseHeaders
// Removes all instances of a particular header.
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
// EOL termination, just [<header-name>: <header-values>]
// If a header with the same name is already stored, the two headers are not
......@@ -300,6 +304,16 @@ class NET_EXPORT HttpResponseHeaders
void MergeWithHeaders(const std::string& raw_headers,
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.
void AddNonCacheableHeaders(HeaderSet* header_names) const;
......
......@@ -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) {
const struct {
const char* orig_headers;
......
......@@ -213,6 +213,9 @@ class URLRequestHttpJob : public URLRequestJob {
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_;
// 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