blob: 2c8774fd5964f6fdb004bf46c010081a81266cee [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <queue>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/prefs/pref_member.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/content_settings/cookie_settings.h"
#include "chrome/browser/extensions/api/web_request/upload_data_presenter.h"
#include "chrome/browser/extensions/api/web_request/web_request_api.h"
#include "chrome/browser/extensions/api/web_request/web_request_api_constants.h"
#include "chrome/browser/extensions/api/web_request/web_request_api_helpers.h"
#include "chrome/browser/extensions/event_router_forwarder.h"
#include "chrome/browser/extensions/extension_warning_set.h"
#include "chrome/browser/net/about_protocol_handler.h"
#include "chrome/browser/net/chrome_network_delegate.h"
#include "chrome/common/extensions/api/web_request.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_pref_service_syncable.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/common/features/feature.h"
#include "net/base/auth.h"
#include "net/base/capturing_net_log.h"
#include "net/base/net_util.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_file_element_reader.h"
#include "net/dns/mock_host_resolver.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest-message.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace helpers = extension_web_request_api_helpers;
namespace keys = extension_web_request_api_constants;
namespace web_request = extensions::api::web_request;
using base::BinaryValue;
using base::DictionaryValue;
using base::ListValue;
using base::StringValue;
using base::Time;
using base::TimeDelta;
using base::Value;
using chrome::VersionInfo;
using extensions::Feature;
using helpers::CalculateOnAuthRequiredDelta;
using helpers::CalculateOnBeforeRequestDelta;
using helpers::CalculateOnBeforeSendHeadersDelta;
using helpers::CalculateOnHeadersReceivedDelta;
using helpers::CharListToString;
using helpers::EventResponseDelta;
using helpers::EventResponseDeltas;
using helpers::EventResponseDeltas;
using helpers::InDecreasingExtensionInstallationTimeOrder;
using helpers::MergeCancelOfResponses;
using helpers::MergeOnBeforeRequestResponses;
using helpers::RequestCookieModification;
using helpers::ResponseCookieModification;
using helpers::ResponseHeader;
using helpers::ResponseHeaders;
using helpers::StringToCharList;
namespace extensions {
namespace {
static void EventHandledOnIOThread(
void* profile,
const std::string& extension_id,
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
ExtensionWebRequestEventRouter::EventResponse* response) {
ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
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();
}
// Returns whether |warnings| contains an extension for |extension_id|.
bool HasWarning(const ExtensionWarningSet& warnings,
const std::string& extension_id) {
for (ExtensionWarningSet::const_iterator i = warnings.begin();
i != warnings.end(); ++i) {
if (i->extension_id() == extension_id)
return true;
}
return false;
}
// Parses the JSON data attached to the |message| and tries to return it.
// |param| must outlive |out|. Returns NULL on failure.
void GetPartOfMessageArguments(IPC::Message* message,
const DictionaryValue** out,
ExtensionMsg_MessageInvoke::Param* param) {
ASSERT_EQ(ExtensionMsg_MessageInvoke::ID, message->type());
ASSERT_TRUE(ExtensionMsg_MessageInvoke::Read(message, param));
ASSERT_GE(param->d.GetSize(), 2u);
const Value* value = NULL;
ASSERT_TRUE(param->d.Get(1, &value));
const ListValue* list = NULL;
ASSERT_TRUE(value->GetAsList(&list));
ASSERT_EQ(1u, list->GetSize());
ASSERT_TRUE(list->GetDictionary(0, out));
}
} // namespace
// A mock event router that responds to events with a pre-arranged queue of
// Tasks.
class TestIPCSender : public IPC::Sender {
public:
typedef std::list<linked_ptr<IPC::Message> > SentMessages;
// Adds a Task to the queue. We will fire these in order as events are
// dispatched.
void PushTask(const base::Closure& task) {
task_queue_.push(task);
}
size_t GetNumTasks() { return task_queue_.size(); }
SentMessages::const_iterator sent_begin() const {
return sent_messages_.begin();
}
SentMessages::const_iterator sent_end() const {
return sent_messages_.end();
}
private:
// IPC::Sender
virtual bool Send(IPC::Message* message) OVERRIDE {
EXPECT_EQ(ExtensionMsg_MessageInvoke::ID, message->type());
EXPECT_FALSE(task_queue_.empty());
base::MessageLoop::current()->PostTask(FROM_HERE, task_queue_.front());
task_queue_.pop();
sent_messages_.push_back(linked_ptr<IPC::Message>(message));
return true;
}
std::queue<base::Closure> task_queue_;
SentMessages sent_messages_;
};
class ExtensionWebRequestTest : public testing::Test {
public:
ExtensionWebRequestTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
profile_manager_(TestingBrowserProcess::GetGlobal()),
event_router_(new EventRouterForwarder) {}
protected:
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(profile_manager_.SetUp());
ChromeNetworkDelegate::InitializePrefsOnUIThread(
&enable_referrers_, NULL, NULL, profile_.GetTestingPrefService());
network_delegate_.reset(
new ChromeNetworkDelegate(event_router_.get(), &enable_referrers_));
network_delegate_->set_profile(&profile_);
network_delegate_->set_cookie_settings(
CookieSettings::Factory::GetForProfile(&profile_).get());
context_.reset(new net::TestURLRequestContext(true));
context_->set_network_delegate(network_delegate_.get());
context_->Init();
}
// Fires a URLRequest with the specified |method|, |content_type| and three
// elements of upload data: bytes_1, a dummy empty file, bytes_2.
void FireURLRequestWithData(const std::string& method,
const char* content_type,
const std::vector<char>& bytes_1,
const std::vector<char>& bytes_2);
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
TestingProfileManager profile_manager_;
net::TestDelegate delegate_;
BooleanPrefMember enable_referrers_;
TestIPCSender ipc_sender_;
scoped_refptr<EventRouterForwarder> event_router_;
scoped_refptr<ExtensionInfoMap> extension_info_map_;
scoped_ptr<ChromeNetworkDelegate> network_delegate_;
scoped_ptr<net::TestURLRequestContext> context_;
};
// Tests that we handle disagreements among extensions about responses to
// blocking events (redirection) by choosing the response from the
// most-recently-installed extension.
TEST_F(ExtensionWebRequestTest, BlockingEventPrecedenceRedirect) {
std::string extension1_id("1");
std::string extension2_id("2");
ExtensionWebRequestEventRouter::RequestFilter filter;
const std::string kEventName(web_request::OnBeforeRequest::kEventName);
base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension1_id, extension1_id, kEventName, kEventName + "/1",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension2_id, extension2_id, kEventName, kEventName + "/2",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
net::URLRequestJobFactoryImpl job_factory;
job_factory.SetProtocolHandler(
chrome::kAboutScheme,
new chrome_browser_net::AboutProtocolHandler());
context_->set_job_factory(&job_factory);
GURL redirect_url("about:redirected");
GURL not_chosen_redirect_url("about:not_chosen");
net::URLRequest request(GURL("about:blank"), &delegate_, context_.get());
{
// onBeforeRequest will be dispatched twice initially. The second response -
// the redirect - should win, since it has a later |install_time|. The
// redirect will dispatch another pair of onBeforeRequest. There, the first
// response should win (later |install_time|).
ExtensionWebRequestEventRouter::EventResponse* response = NULL;
// Extension1 response. Arrives first, but ignored due to install_time.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension1_id, base::Time::FromDoubleT(1));
response->new_url = not_chosen_redirect_url;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension1_id, kEventName, kEventName + "/1",
request.identifier(), response));
// Extension2 response. Arrives second, and chosen because of install_time.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension2_id, base::Time::FromDoubleT(2));
response->new_url = redirect_url;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension2_id, kEventName, kEventName + "/2",
request.identifier(), response));
// Extension2 response to the redirected URL. Arrives first, and chosen.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension2_id, base::Time::FromDoubleT(2));
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension2_id, kEventName, kEventName + "/2",
request.identifier(), response));
// Extension1 response to the redirected URL. Arrives second, and ignored.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension1_id, base::Time::FromDoubleT(1));
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension1_id, kEventName, kEventName + "/1",
request.identifier(), response));
request.Start();
base::MessageLoop::current()->Run();
EXPECT_TRUE(!request.is_pending());
EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
EXPECT_EQ(0, request.status().error());
EXPECT_EQ(redirect_url, request.url());
EXPECT_EQ(2U, request.url_chain().size());
EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
}
// Now test the same thing but the extensions answer in reverse order.
net::URLRequest request2(GURL("about:blank"), &delegate_, context_.get());
{
ExtensionWebRequestEventRouter::EventResponse* response = NULL;
// Extension2 response. Arrives first, and chosen because of install_time.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension2_id, base::Time::FromDoubleT(2));
response->new_url = redirect_url;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension2_id, kEventName, kEventName + "/2",
request2.identifier(), response));
// Extension1 response. Arrives second, but ignored due to install_time.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension1_id, base::Time::FromDoubleT(1));
response->new_url = not_chosen_redirect_url;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension1_id, kEventName, kEventName + "/1",
request2.identifier(), response));
// Extension2 response to the redirected URL. Arrives first, and chosen.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension2_id, base::Time::FromDoubleT(2));
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension2_id, kEventName, kEventName + "/2",
request2.identifier(), response));
// Extension1 response to the redirected URL. Arrives second, and ignored.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension1_id, base::Time::FromDoubleT(1));
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension1_id, kEventName, kEventName + "/1",
request2.identifier(), response));
request2.Start();
base::MessageLoop::current()->Run();
EXPECT_TRUE(!request2.is_pending());
EXPECT_EQ(net::URLRequestStatus::SUCCESS, request2.status().status());
EXPECT_EQ(0, request2.status().error());
EXPECT_EQ(redirect_url, request2.url());
EXPECT_EQ(2U, request2.url_chain().size());
EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
}
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension1_id, kEventName + "/1");
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension2_id, kEventName + "/2");
}
// Test that a request is canceled if this is requested by any extension
// regardless whether it is the extension with the highest precedence.
TEST_F(ExtensionWebRequestTest, BlockingEventPrecedenceCancel) {
std::string extension1_id("1");
std::string extension2_id("2");
ExtensionWebRequestEventRouter::RequestFilter filter;
const std::string kEventName(web_request::OnBeforeRequest::kEventName);
base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension1_id, extension1_id, kEventName, kEventName + "/1",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension2_id, extension2_id, kEventName, kEventName + "/2",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
GURL request_url("about:blank");
net::URLRequest request(request_url, &delegate_, context_.get());
// onBeforeRequest will be dispatched twice. The second response -
// the redirect - would win, since it has a later |install_time|, but
// the first response takes precedence because cancel >> redirect.
GURL redirect_url("about:redirected");
ExtensionWebRequestEventRouter::EventResponse* response = NULL;
// Extension1 response. Arrives first, would be ignored in principle due to
// install_time but "cancel" always wins.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension1_id, base::Time::FromDoubleT(1));
response->cancel = true;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension1_id, kEventName, kEventName + "/1",
request.identifier(), response));
// Extension2 response. Arrives second, but has higher precedence
// due to its later install_time.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension2_id, base::Time::FromDoubleT(2));
response->new_url = redirect_url;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension2_id, kEventName, kEventName + "/2",
request.identifier(), response));
request.Start();
base::MessageLoop::current()->Run();
EXPECT_TRUE(!request.is_pending());
EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, request.status().error());
EXPECT_EQ(request_url, request.url());
EXPECT_EQ(1U, request.url_chain().size());
EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension1_id, kEventName + "/1");
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension2_id, kEventName + "/2");
}
TEST_F(ExtensionWebRequestTest, SimulateChancelWhileBlocked) {
// We subscribe to OnBeforeRequest and OnErrorOccurred.
// While the OnBeforeRequest handler is blocked, we cancel the request.
// We verify that the response of the blocked OnBeforeRequest handler
// is ignored.
std::string extension_id("1");
ExtensionWebRequestEventRouter::RequestFilter filter;
// Subscribe to OnBeforeRequest and OnErrorOccurred.
const std::string kEventName(web_request::OnBeforeRequest::kEventName);
const std::string kEventName2(web_request::OnErrorOccurred::kEventName);
base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension_id, extension_id, kEventName, kEventName + "/1",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension_id, extension_id, kEventName2, kEventName2 + "/1",
filter, 0, -1, MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
GURL request_url("about:blank");
net::URLRequest request(request_url, &delegate_, context_.get());
ExtensionWebRequestEventRouter::EventResponse* response = NULL;
// Extension response for the OnBeforeRequest handler. This should not be
// processed because request is canceled before the handler responds.
response = new ExtensionWebRequestEventRouter::EventResponse(
extension_id, base::Time::FromDoubleT(1));
GURL redirect_url("about:redirected");
response->new_url = redirect_url;
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, extension_id, kEventName, kEventName + "/1",
request.identifier(), response));
// Extension response for OnErrorOccurred: Terminate the message loop.
ipc_sender_.PushTask(
base::Bind(&base::MessageLoop::PostTask,
base::Unretained(base::MessageLoop::current()),
FROM_HERE, base::MessageLoop::QuitClosure()));
request.Start();
// request.Start() will have submitted OnBeforeRequest by the time we cancel.
request.Cancel();
base::MessageLoop::current()->Run();
EXPECT_TRUE(!request.is_pending());
EXPECT_EQ(net::URLRequestStatus::CANCELED, request.status().status());
EXPECT_EQ(net::ERR_ABORTED, request.status().error());
EXPECT_EQ(request_url, request.url());
EXPECT_EQ(1U, request.url_chain().size());
EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension_id, kEventName + "/1");
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension_id, kEventName2 + "/1");
}
namespace {
// Create the numerical representation of |values|, strings passed as
// extraInfoSpec by the event handler. Returns true on success, otherwise false.
bool GenerateInfoSpec(const std::string& values, int* result) {
// Create a ListValue of strings.
std::vector<std::string> split_values;
base::ListValue list_value;
size_t num_values = Tokenize(values, ",", &split_values);
for (size_t i = 0; i < num_values ; ++i)
list_value.Append(new base::StringValue(split_values[i]));
return ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue(
list_value, result);
}
} // namespace
void ExtensionWebRequestTest::FireURLRequestWithData(
const std::string& method,
const char* content_type,
const std::vector<char>& bytes_1,
const std::vector<char>& bytes_2) {
// The request URL can be arbitrary but must have an HTTP or HTTPS scheme.
GURL request_url("http://www.example.com");
net::URLRequest request(request_url, &delegate_, context_.get());
request.set_method(method);
if (content_type != NULL)
request.SetExtraRequestHeaderByName(net::HttpRequestHeaders::kContentType,
content_type,
true /* overwrite */);
ScopedVector<net::UploadElementReader> element_readers;
element_readers.push_back(new net::UploadBytesElementReader(
&(bytes_1[0]), bytes_1.size()));
element_readers.push_back(
new net::UploadFileElementReader(base::MessageLoopProxy::current().get(),
base::FilePath(),
0,
0,
base::Time()));
element_readers.push_back(
new net::UploadBytesElementReader(&(bytes_2[0]), bytes_2.size()));
request.set_upload(make_scoped_ptr(
new net::UploadDataStream(&element_readers, 0)));
ipc_sender_.PushTask(base::Bind(&base::DoNothing));
request.Start();
}
TEST_F(ExtensionWebRequestTest, AccessRequestBodyData) {
// We verify that URLRequest body is accessible to OnBeforeRequest listeners.
// These testing steps are repeated twice in a row:
// 1. Register an extension requesting "requestBody" in ExtraInfoSpec and
// file a POST URLRequest with a multipart-encoded form. See it getting
// parsed.
// 2. Do the same, but without requesting "requestBody". Nothing should be
// parsed.
// 3. With "requestBody", fire a POST URLRequest which is not a parseable
// HTML form. Raw data should be returned.
// 4. Do the same, but with a PUT method. Result should be the same.
const std::string kMethodPost("POST");
const std::string kMethodPut("PUT");
// Input.
const char kPlainBlock1[] = "abcd\n";
const size_t kPlainBlock1Length = sizeof(kPlainBlock1) - 1;
std::vector<char> plain_1(kPlainBlock1, kPlainBlock1 + kPlainBlock1Length);
const char kPlainBlock2[] = "1234\n";
const size_t kPlainBlock2Length = sizeof(kPlainBlock2) - 1;
std::vector<char> plain_2(kPlainBlock2, kPlainBlock2 + kPlainBlock2Length);
#define kBoundary "THIS_IS_A_BOUNDARY"
const char kFormBlock1[] = "--" kBoundary "\r\n"
"Content-Disposition: form-data; name=\"A\"\r\n"
"\r\n"
"test text\r\n"
"--" kBoundary "\r\n"
"Content-Disposition: form-data; name=\"B\"; filename=\"\"\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n";
std::vector<char> form_1(kFormBlock1, kFormBlock1 + sizeof(kFormBlock1) - 1);
const char kFormBlock2[] = "\r\n"
"--" kBoundary "\r\n"
"Content-Disposition: form-data; name=\"C\"\r\n"
"\r\n"
"test password\r\n"
"--" kBoundary "--";
std::vector<char> form_2(kFormBlock2, kFormBlock2 + sizeof(kFormBlock2) - 1);
// Expected output.
// Paths to look for in returned dictionaries.
const std::string kBodyPath(keys::kRequestBodyKey);
const std::string kFormDataPath(
kBodyPath + "." + keys::kRequestBodyFormDataKey);
const std::string kRawPath(kBodyPath + "." + keys::kRequestBodyRawKey);
const std::string kErrorPath(kBodyPath + "." + keys::kRequestBodyErrorKey);
const std::string* const kPath[] = {
&kFormDataPath,
&kBodyPath,
&kRawPath,
&kRawPath
};
// Contents of formData.
const char kFormData[] =
"{\"A\":[\"test text\"],\"B\":[\"\"],\"C\":[\"test password\"]}";
scoped_ptr<const Value> form_data(base::JSONReader::Read(kFormData));
ASSERT_TRUE(form_data.get() != NULL);
ASSERT_TRUE(form_data->GetType() == Value::TYPE_DICTIONARY);
// Contents of raw.
ListValue raw;
extensions::subtle::AppendKeyValuePair(
keys::kRequestBodyRawBytesKey,
BinaryValue::CreateWithCopiedBuffer(kPlainBlock1, kPlainBlock1Length),
&raw);
extensions::subtle::AppendKeyValuePair(
keys::kRequestBodyRawFileKey,
new base::StringValue(std::string()),
&raw);
extensions::subtle::AppendKeyValuePair(
keys::kRequestBodyRawBytesKey,
BinaryValue::CreateWithCopiedBuffer(kPlainBlock2, kPlainBlock2Length),
&raw);
// Summary.
const Value* const kExpected[] = {
form_data.get(),
NULL,
&raw,
&raw,
};
COMPILE_ASSERT(arraysize(kPath) == arraysize(kExpected),
the_arrays_kPath_and_kExpected_need_to_be_the_same_size);
// Header.
const char kMultipart[] = "multipart/form-data; boundary=" kBoundary;
#undef kBoundary
// Set up a dummy extension name.
const std::string kEventName(web_request::OnBeforeRequest::kEventName);
ExtensionWebRequestEventRouter::RequestFilter filter;
std::string extension_id("1");
const std::string string_spec_post("blocking,requestBody");
const std::string string_spec_no_post("blocking");
int extra_info_spec_empty = 0;
int extra_info_spec_body = 0;
base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
// Part 1.
// Subscribe to OnBeforeRequest with requestBody requirement.
ASSERT_TRUE(GenerateInfoSpec(string_spec_post, &extra_info_spec_body));
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension_id, extension_id, kEventName, kEventName + "/1",
filter, extra_info_spec_body, -1, MSG_ROUTING_NONE, -1,
ipc_sender_factory.GetWeakPtr());
FireURLRequestWithData(kMethodPost, kMultipart, form_1, form_2);
// We inspect the result in the message list of |ipc_sender_| later.
base::MessageLoop::current()->RunUntilIdle();
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension_id, kEventName + "/1");
// Part 2.
// Now subscribe to OnBeforeRequest *without* the requestBody requirement.
ASSERT_TRUE(
GenerateInfoSpec(string_spec_no_post, &extra_info_spec_empty));
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension_id, extension_id, kEventName, kEventName + "/1",
filter, extra_info_spec_empty, -1, MSG_ROUTING_NONE, -1,
ipc_sender_factory.GetWeakPtr());
FireURLRequestWithData(kMethodPost, kMultipart, form_1, form_2);
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension_id, kEventName + "/1");
// Subscribe to OnBeforeRequest with requestBody requirement.
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension_id, extension_id, kEventName, kEventName + "/1",
filter, extra_info_spec_body, -1, MSG_ROUTING_NONE, -1,
ipc_sender_factory.GetWeakPtr());
// Part 3.
// Now send a POST request with body which is not parseable as a form.
FireURLRequestWithData(kMethodPost, NULL /*no header*/, plain_1, plain_2);
// Part 4.
// Now send a PUT request with the same body as above.
FireURLRequestWithData(kMethodPut, NULL /*no header*/, plain_1, plain_2);
base::MessageLoop::current()->RunUntilIdle();
// Clean-up.
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension_id, kEventName + "/1");
IPC::Message* message = NULL;
TestIPCSender::SentMessages::const_iterator i = ipc_sender_.sent_begin();
for (size_t test = 0; test < arraysize(kExpected); ++test) {
SCOPED_TRACE(testing::Message("iteration number ") << test);
EXPECT_NE(i, ipc_sender_.sent_end());
message = (i++)->get();
const DictionaryValue* details;
ExtensionMsg_MessageInvoke::Param param;
GetPartOfMessageArguments(message, &details, &param);
ASSERT_TRUE(details != NULL);
const Value* result = NULL;
if (kExpected[test]) {
EXPECT_TRUE(details->Get(*(kPath[test]), &result));
EXPECT_TRUE(kExpected[test]->Equals(result));
} else {
EXPECT_FALSE(details->Get(*(kPath[test]), &result));
}
}
EXPECT_EQ(i, ipc_sender_.sent_end());
}
TEST_F(ExtensionWebRequestTest, NoAccessRequestBodyData) {
// We verify that URLRequest body is NOT accessible to OnBeforeRequest
// listeners when the type of the request is different from POST or PUT, or
// when the request body is empty. 3 requests are fired, without upload data,
// a POST, PUT and GET request. For none of them the "requestBody" object
// property should be present in the details passed to the onBeforeRequest
// event listener.
const char* kMethods[] = { "POST", "PUT", "GET" };
// Set up a dummy extension name.
const std::string kEventName(web_request::OnBeforeRequest::kEventName);
ExtensionWebRequestEventRouter::RequestFilter filter;
const std::string extension_id("1");
int extra_info_spec = 0;
ASSERT_TRUE(GenerateInfoSpec("blocking,requestBody", &extra_info_spec));
base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
// Subscribe to OnBeforeRequest with requestBody requirement.
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension_id, extension_id, kEventName, kEventName + "/1",
filter, extra_info_spec, -1, MSG_ROUTING_NONE, -1,
ipc_sender_factory.GetWeakPtr());
// The request URL can be arbitrary but must have an HTTP or HTTPS scheme.
const GURL request_url("http://www.example.com");
for (size_t i = 0; i < arraysize(kMethods); ++i) {
net::URLRequest request(request_url, &delegate_, context_.get());
request.set_method(kMethods[i]);
ipc_sender_.PushTask(base::Bind(&base::DoNothing));
request.Start();
}
// We inspect the result in the message list of |ipc_sender_| later.
base::MessageLoop::current()->RunUntilIdle();
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension_id, kEventName + "/1");
TestIPCSender::SentMessages::const_iterator i = ipc_sender_.sent_begin();
for (size_t test = 0; test < arraysize(kMethods); ++test, ++i) {
SCOPED_TRACE(testing::Message("iteration number ") << test);
EXPECT_NE(i, ipc_sender_.sent_end());
IPC::Message* message = i->get();
const DictionaryValue* details = NULL;
ExtensionMsg_MessageInvoke::Param param;
GetPartOfMessageArguments(message, &details, &param);
ASSERT_TRUE(details != NULL);
EXPECT_FALSE(details->HasKey(keys::kRequestBodyKey));
}
EXPECT_EQ(i, ipc_sender_.sent_end());
}
struct HeaderModificationTest_Header {
const char* name;
const char* value;
};
struct HeaderModificationTest_Modification {
enum Type {
SET,
REMOVE
};
int extension_id;
Type type;
const char* key;
const char* value;
};
struct HeaderModificationTest {
int before_size;
HeaderModificationTest_Header before[10];
int modification_size;
HeaderModificationTest_Modification modification[10];
int after_size;
HeaderModificationTest_Header after[10];
};
class ExtensionWebRequestHeaderModificationTest
: public testing::TestWithParam<HeaderModificationTest> {
public:
ExtensionWebRequestHeaderModificationTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
profile_manager_(TestingBrowserProcess::GetGlobal()),
event_router_(new EventRouterForwarder) {}
protected:
virtual void SetUp() {
ASSERT_TRUE(profile_manager_.SetUp());
ChromeNetworkDelegate::InitializePrefsOnUIThread(
&enable_referrers_, NULL, NULL, profile_.GetTestingPrefService());
network_delegate_.reset(
new ChromeNetworkDelegate(event_router_.get(), &enable_referrers_));
network_delegate_->set_profile(&profile_);
network_delegate_->set_cookie_settings(
CookieSettings::Factory::GetForProfile(&profile_).get());
context_.reset(new net::TestURLRequestContext(true));
host_resolver_.reset(new net::MockHostResolver());
host_resolver_->rules()->AddSimulatedFailure("doesnotexist");
context_->set_host_resolver(host_resolver_.get());
context_->set_network_delegate(network_delegate_.get());
context_->Init();
}
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
TestingProfileManager profile_manager_;
net::TestDelegate delegate_;
BooleanPrefMember enable_referrers_;
TestIPCSender ipc_sender_;
scoped_refptr<EventRouterForwarder> event_router_;
scoped_refptr<ExtensionInfoMap> extension_info_map_;
scoped_ptr<ChromeNetworkDelegate> network_delegate_;
scoped_ptr<net::MockHostResolver> host_resolver_;
scoped_ptr<net::TestURLRequestContext> context_;
};
TEST_P(ExtensionWebRequestHeaderModificationTest, TestModifications) {
std::string extension1_id("1");
std::string extension2_id("2");
std::string extension3_id("3");
ExtensionWebRequestEventRouter::RequestFilter filter;
const std::string kEventName(keys::kOnBeforeSendHeadersEvent);
base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
// Install two extensions that can modify headers. Extension 2 has
// higher precedence than extension 1.
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension1_id, extension1_id, kEventName, kEventName + "/1",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension2_id, extension2_id, kEventName, kEventName + "/2",
filter, ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
// Install one extension that observes the final headers.
ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
&profile_, extension3_id, extension3_id, keys::kOnSendHeadersEvent,
std::string(keys::kOnSendHeadersEvent) + "/3", filter,
ExtensionWebRequestEventRouter::ExtraInfoSpec::REQUEST_HEADERS, -1,
MSG_ROUTING_NONE, -1, ipc_sender_factory.GetWeakPtr());
GURL request_url("http://doesnotexist/does_not_exist.html");
net::URLRequest request(request_url, &delegate_, context_.get());
// Initialize headers available before extensions are notified of the
// onBeforeSendHeaders event.
HeaderModificationTest test = GetParam();
net::HttpRequestHeaders before_headers;
for (int i = 0; i < test.before_size; ++i)
before_headers.SetHeader(test.before[i].name, test.before[i].value);
request.SetExtraRequestHeaders(before_headers);
// Gather the modifications to the headers for the respective extensions.
// We assume here that all modifications of one extension are listed
// in a continuous block of |test.modifications_|.
ExtensionWebRequestEventRouter::EventResponse* response = NULL;
for (int i = 0; i < test.modification_size; ++i) {
const HeaderModificationTest_Modification& mod = test.modification[i];
if (response == NULL) {
response = new ExtensionWebRequestEventRouter::EventResponse(
mod.extension_id == 1 ? extension1_id : extension2_id,
base::Time::FromDoubleT(mod.extension_id));
response->request_headers.reset(new net::HttpRequestHeaders());
response->request_headers->MergeFrom(request.extra_request_headers());
}
switch (mod.type) {
case HeaderModificationTest_Modification::SET:
response->request_headers->SetHeader(mod.key, mod.value);
break;
case HeaderModificationTest_Modification::REMOVE:
response->request_headers->RemoveHeader(mod.key);
break;
}
// Trigger the result when this is the last modification statement or
// the block of modifications for the next extension starts.
if (i+1 == test.modification_size ||
mod.extension_id != test.modification[i+1].extension_id) {
ipc_sender_.PushTask(
base::Bind(&EventHandledOnIOThread,
&profile_, mod.extension_id == 1 ? extension1_id : extension2_id,
kEventName, kEventName + (mod.extension_id == 1 ? "/1" : "/2"),
request.identifier(), response));
response = NULL;
}
}
// Don't do anything for the onSendHeaders message.
ipc_sender_.PushTask(base::Bind(&base::DoNothing));
// Note that we mess up the headers slightly:
// request.Start() will first add additional headers (e.g. the User-Agent)
// and then send an event to the extension. When we have prepared our
// answers to the onBeforeSendHeaders events above, these headers did not
// exists and are therefore not listed in the responses. This makes
// them seem deleted.
request.Start();
base::MessageLoop::current()->Run();
EXPECT_TRUE(!request.is_pending());
// This cannot succeed as we send the request to a server that does not exist.
EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
EXPECT_EQ(request_url, request.url());
EXPECT_EQ(1U, request.url_chain().size());
EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
// Calculate the expected headers.
net::HttpRequestHeaders expected_headers;
for (int i = 0; i < test.after_size; ++i) {
expected_headers.SetHeader(test.after[i].name,
test.after[i].value);
}
// Counter for the number of observed onSendHeaders events.
int num_headers_observed = 0;
// Search the onSendHeaders signal in the IPC messages and check that
// it contained the correct headers.
TestIPCSender::SentMessages::const_iterator i;
for (i = ipc_sender_.sent_begin(); i != ipc_sender_.sent_end(); ++i) {
IPC::Message* message = i->get();
if (ExtensionMsg_MessageInvoke::ID != message->type())
continue;
ExtensionMsg_MessageInvoke::Param message_tuple;
ExtensionMsg_MessageInvoke::Read(message, &message_tuple);
ListValue& args = message_tuple.d;
std::string event_name;
if (!args.GetString(0, &event_name) ||
event_name != std::string(keys::kOnSendHeadersEvent) + "/3") {
continue;
}
ListValue* event_arg = NULL;
ASSERT_TRUE(args.GetList(1, &event_arg));
DictionaryValue* event_arg_dict = NULL;
ASSERT_TRUE(event_arg->GetDictionary(0, &event_arg_dict));
ListValue* request_headers = NULL;
ASSERT_TRUE(event_arg_dict->GetList(keys::kRequestHeadersKey,
&request_headers));
net::HttpRequestHeaders observed_headers;
for (size_t j = 0; j < request_headers->GetSize(); ++j) {
DictionaryValue* header = NULL;
ASSERT_TRUE(request_headers->GetDictionary(j, &header));
std::string key;
std::string value;
ASSERT_TRUE(header->GetString(keys::kHeaderNameKey, &key));
ASSERT_TRUE(header->GetString(keys::kHeaderValueKey, &value));
observed_headers.SetHeader(key, value);
}
EXPECT_EQ(expected_headers.ToString(), observed_headers.ToString());
++num_headers_observed;
}
EXPECT_EQ(1, num_headers_observed);
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension1_id, kEventName + "/1");
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension2_id, kEventName + "/2");
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
&profile_, extension3_id, std::string(keys::kOnSendHeadersEvent) + "/3");
};
namespace {
void TestInitFromValue(const std::string& values, bool expected_return_code,
int expected_extra_info_spec) {
int actual_info_spec;
bool actual_return_code = GenerateInfoSpec(values, &actual_info_spec);
EXPECT_EQ(expected_return_code, actual_return_code);
if (expected_return_code)
EXPECT_EQ(expected_extra_info_spec, actual_info_spec);
}
} // namespace
TEST_F(ExtensionWebRequestTest, InitFromValue) {
TestInitFromValue(std::string(), true, 0);
// Single valid values.
TestInitFromValue(
"requestHeaders",
true,
ExtensionWebRequestEventRouter::ExtraInfoSpec::REQUEST_HEADERS);
TestInitFromValue(
"responseHeaders",
true,
ExtensionWebRequestEventRouter::ExtraInfoSpec::RESPONSE_HEADERS);
TestInitFromValue(
"blocking",
true,
ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING);
TestInitFromValue(
"asyncBlocking",
true,
ExtensionWebRequestEventRouter::ExtraInfoSpec::ASYNC_BLOCKING);
TestInitFromValue(
"requestBody",
true,
ExtensionWebRequestEventRouter::ExtraInfoSpec::REQUEST_BODY);
// Multiple valid values are bitwise-or'ed.
TestInitFromValue(
"requestHeaders,blocking",
true,
ExtensionWebRequestEventRouter::ExtraInfoSpec::REQUEST_HEADERS |
ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING);
// Any invalid values lead to a bad parse.
TestInitFromValue("invalidValue", false, 0);
TestInitFromValue("blocking,invalidValue", false, 0);
TestInitFromValue("invalidValue1,invalidValue2", false, 0);
// BLOCKING and ASYNC_BLOCKING are mutually exclusive.
TestInitFromValue("blocking,asyncBlocking", false, 0);
}
namespace {
const HeaderModificationTest_Modification::Type SET =
HeaderModificationTest_Modification::SET;
const HeaderModificationTest_Modification::Type REMOVE =
HeaderModificationTest_Modification::REMOVE;
HeaderModificationTest kTests[] = {
// Check that extension 2 always wins when settings the same header.
{
// Headers before test.
2, { {"header1", "value1"},
{"header2", "value2"} },
// Modifications in test.
2, { {1, SET, "header1", "foo"},
{2, SET, "header1", "bar"} },
// Headers after test.
2, { {"header1", "bar"},
{"header2", "value2"} }
},
// Same as before in reverse execution order.
{
// Headers before test.
2, { {"header1", "value1"},
{"header2", "value2"} },
// Modifications in test.
2, { {2, SET, "header1", "bar"},
{1, SET, "header1", "foo"} },
// Headers after test.
2, { {"header1", "bar"},
{"header2", "value2"} }
},
// Check that two extensions can modify different headers that do not
// conflict.
{
// Headers before test.
2, { {"header1", "value1"},
{"header2", "value2"} },
// Modifications in test.
2, { {1, SET, "header1", "foo"},
{2, SET, "header2", "bar"} },
// Headers after test.
2, { {"header1", "foo"},
{"header2", "bar"} }
},
// Check insert/delete conflict.
{
// Headers before test.
1, { {"header1", "value1"} },
// Modifications in test.
2, { {1, SET, "header1", "foo"},
{2, REMOVE, "header1", NULL} },
// Headers after test.
0, { }
},
{
// Headers before test.
1, { {"header1", "value1"} },
// Modifications in test.
2, { {2, REMOVE, "header1", NULL},
{1, SET, "header1", "foo"} },
// Headers after test.
0, {}
},
{
// Headers before test.
1, { {"header1", "value1"} },
// Modifications in test.
2, { {1, REMOVE, "header1", NULL},
{2, SET, "header1", "foo"} },
// Headers after test.
1, { {"header1", "foo"} }
},
{
// Headers before test.
1, { {"header1", "value1"} },
// Modifications in test.
2, { {2, SET, "header1", "foo"},
{1, REMOVE, "header1", NULL} },
// Headers after test.
1, { {"header1", "foo"} }
},
// Check that edits are atomic (i.e. either all edit requests of an
// extension are executed or none).
{
// Headers before test.
0, { },
// Modifications in test.
3, { {1, SET, "header1", "value1"},
{1, SET, "header2", "value2"},
{2, SET, "header1", "foo"} },
// Headers after test.
1, { {"header1", "foo"} } // set(header2) is ignored
},
// Check that identical edits do not conflict (set(header2) would be ignored
// if set(header1) were considered a conflict).
{
// Headers before test.
0, { },
// Modifications in test.
3, { {1, SET, "header1", "value2"},
{1, SET, "header2", "foo"},
{2, SET, "header1", "value2"} },
// Headers after test.
2, { {"header1", "value2"},
{"header2", "foo"} }
},
// Check that identical deletes do not conflict (set(header2) would be ignored
// if delete(header1) were considered a conflict).
{
// Headers before test.
1, { {"header1", "value1"} },
// Modifications in test.
3, { {1, REMOVE, "header1", NULL},
{1, SET, "header2", "foo"},
{2, REMOVE, "header1", NULL} },
// Headers after test.
1, { {"header2", "foo"} }
},
// Check that setting a value to an identical value is not considered an
// edit operation that can conflict.
{
// Headers before test.
1, { {"header1", "value1"} },
// Modifications in test.
3, { {1, SET, "header1", "foo"},
{1, SET, "header2", "bar"},
{2, SET, "header1", "value1"} },
// Headers after test.
2, { {"header1", "foo"},
{"header2", "bar"} }
},
};
INSTANTIATE_TEST_CASE_P(
ExtensionWebRequest,
ExtensionWebRequestHeaderModificationTest,
::testing::ValuesIn(kTests));
} // namespace
TEST(ExtensionWebRequestHelpersTest,
TestInDecreasingExtensionInstallationTimeOrder) {
linked_ptr<EventResponseDelta> a(
new EventResponseDelta("ext_1", base::Time::FromInternalValue(0)));
linked_ptr<EventResponseDelta> b(
new EventResponseDelta("ext_2", base::Time::FromInternalValue(1000)));
EXPECT_FALSE(InDecreasingExtensionInstallationTimeOrder(a, a));
EXPECT_FALSE(InDecreasingExtensionInstallationTimeOrder(a, b));
EXPECT_TRUE(InDecreasingExtensionInstallationTimeOrder(b, a));
}
TEST(ExtensionWebRequestHelpersTest, TestStringToCharList) {
ListValue list_value;
list_value.Append(new base::FundamentalValue('1'));
list_value.Append(new base::FundamentalValue('2'));
list_value.Append(new base::FundamentalValue('3'));
list_value.Append(new base::FundamentalValue(0xFE));
list_value.Append(new base::FundamentalValue(0xD1));
unsigned char char_value[] = {'1', '2', '3', 0xFE, 0xD1};
std::string string_value(reinterpret_cast<char *>(char_value), 5);
scoped_ptr<ListValue> converted_list(StringToCharList(string_value));
EXPECT_TRUE(list_value.Equals(converted_list.get()));
std::string converted_string;
EXPECT_TRUE(CharListToString(&list_value, &converted_string));
EXPECT_EQ(string_value, converted_string);
}
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeRequestDelta) {
const bool cancel = true;
const GURL localhost("http://localhost");
scoped_ptr<EventResponseDelta> delta(
CalculateOnBeforeRequestDelta("extid", base::Time::Now(),
cancel, localhost));
ASSERT_TRUE(delta.get());
EXPECT_TRUE(delta->cancel);
EXPECT_EQ(localhost, delta->new_url);
}
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeSendHeadersDelta) {
const bool cancel = true;
std::string value;
net::HttpRequestHeaders old_headers;
old_headers.AddHeadersFromString("key1: value1\r\n"
"key2: value2\r\n");
// Test adding a header.
net::HttpRequestHeaders new_headers_added;
new_headers_added.AddHeadersFromString("key1: value1\r\n"
"key3: value3\r\n"
"key2: value2\r\n");
scoped_ptr<EventResponseDelta> delta_added(
CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
&old_headers, &new_headers_added));
ASSERT_TRUE(delta_added.get());
EXPECT_TRUE(delta_added->cancel);
ASSERT_TRUE(delta_added->modified_request_headers.GetHeader("key3", &value));
EXPECT_EQ("value3", value);
// Test deleting a header.
net::HttpRequestHeaders new_headers_deleted;
new_headers_deleted.AddHeadersFromString("key1: value1\r\n");
scoped_ptr<EventResponseDelta> delta_deleted(
CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
&old_headers, &new_headers_deleted));
ASSERT_TRUE(delta_deleted.get());
ASSERT_EQ(1u, delta_deleted->deleted_request_headers.size());
ASSERT_EQ("key2", delta_deleted->deleted_request_headers.front());
// Test modifying a header.
net::HttpRequestHeaders new_headers_modified;
new_headers_modified.AddHeadersFromString("key1: value1\r\n"
"key2: value3\r\n");
scoped_ptr<EventResponseDelta> delta_modified(
CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
&old_headers, &new_headers_modified));
ASSERT_TRUE(delta_modified.get());
EXPECT_TRUE(delta_modified->deleted_request_headers.empty());
ASSERT_TRUE(
delta_modified->modified_request_headers.GetHeader("key2", &value));
EXPECT_EQ("value3", value);
// Test modifying a header if extension author just appended a new (key,
// value) pair with a key that existed before. This is incorrect
// usage of the API that shall be handled gracefully.
net::HttpRequestHeaders new_headers_modified2;
new_headers_modified2.AddHeadersFromString("key1: value1\r\n"
"key2: value2\r\n"
"key2: value3\r\n");
scoped_ptr<EventResponseDelta> delta_modified2(
CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
&old_headers, &new_headers_modified));
ASSERT_TRUE(delta_modified2.get());
EXPECT_TRUE(delta_modified2->deleted_request_headers.empty());
ASSERT_TRUE(
delta_modified2->modified_request_headers.GetHeader("key2", &value));
EXPECT_EQ("value3", value);
}
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) {
const bool cancel = true;
char base_headers_string[] =
"HTTP/1.0 200 OK\r\n"
"Key1: Value1\r\n"
"Key2: Value2, Bar\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, base_headers.get(), &new_headers));
ASSERT_TRUE(delta.get());
EXPECT_TRUE(delta->cancel);
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, Bar")));
EXPECT_TRUE(Contains(delta->deleted_response_headers,
ResponseHeader("Key3", "Value3")));
}
TEST(ExtensionWebRequestHelpersTest, TestCalculateOnAuthRequiredDelta) {
const bool cancel = true;
string16 username = ASCIIToUTF16("foo");
string16 password = ASCIIToUTF16("bar");
scoped_ptr<net::AuthCredentials> credentials(
new net::AuthCredentials(username, password));
scoped_ptr<EventResponseDelta> delta(
CalculateOnAuthRequiredDelta("extid", base::Time::Now(), cancel,
&credentials));
ASSERT_TRUE(delta.get());
EXPECT_TRUE(delta->cancel);
ASSERT_TRUE(delta->auth_credentials.get());
EXPECT_EQ(username, delta->auth_credentials->username());
EXPECT_EQ(password, delta->auth_credentials->password());
}
TEST(ExtensionWebRequestHelpersTest, TestMergeCancelOfResponses) {
EventResponseDeltas deltas;
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
bool canceled = false;
// Single event that does not cancel.
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(1000)));
d1->cancel = false;
deltas.push_back(d1);
MergeCancelOfResponses(deltas, &canceled, &net_log);
EXPECT_FALSE(canceled);
EXPECT_EQ(0u, capturing_net_log.GetSize());
// Second event that cancels the request
linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(500)));
d2->cancel = true;
deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
MergeCancelOfResponses(deltas, &canceled, &net_log);
EXPECT_TRUE(canceled);
EXPECT_EQ(1u, capturing_net_log.GetSize());
}
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses) {
EventResponseDeltas deltas;
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
GURL effective_new_url;
// No redirect
linked_ptr<EventResponseDelta> d0(
new EventResponseDelta("extid0", base::Time::FromInternalValue(0)));
deltas.push_back(d0);
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_TRUE(effective_new_url.is_empty());
// Single redirect.
GURL new_url_1("http://foo.com");
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(1000)));
d1->new_url = GURL(new_url_1);
deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_1, effective_new_url);
EXPECT_TRUE(warning_set.empty());
EXPECT_EQ(1u, capturing_net_log.GetSize());
// Ignored redirect (due to precedence).
GURL new_url_2("http://bar.com");
linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(500)));
d2->new_url = GURL(new_url_2);
deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_1, effective_new_url);
EXPECT_EQ(1u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(2u, capturing_net_log.GetSize());
// Overriding redirect.
GURL new_url_3("http://baz.com");
linked_ptr<EventResponseDelta> d3(
new EventResponseDelta("extid3", base::Time::FromInternalValue(1500)));
d3->new_url = GURL(new_url_3);
deltas.push_back(d3);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_3, effective_new_url);
EXPECT_EQ(2u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid1"));
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(3u, capturing_net_log.GetSize());
// Check that identical redirects don't cause a conflict.
linked_ptr<EventResponseDelta> d4(
new EventResponseDelta("extid4", base::Time::FromInternalValue(2000)));
d4->new_url = GURL(new_url_3);
deltas.push_back(d4);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_3, effective_new_url);
EXPECT_EQ(2u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid1"));
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(4u, capturing_net_log.GetSize());
}
// This tests that we can redirect to data:// urls, which is considered
// a kind of cancelling requests.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses2) {
EventResponseDeltas deltas;
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
GURL effective_new_url;
// Single redirect.
GURL new_url_0("http://foo.com");
linked_ptr<EventResponseDelta> d0(
new EventResponseDelta("extid0", base::Time::FromInternalValue(2000)));
d0->new_url = GURL(new_url_0);
deltas.push_back(d0);
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_0, effective_new_url);
// Cancel request by redirecting to a data:// URL. This shall override
// the other redirect but not cause any conflict warnings.
GURL new_url_1("data://foo");
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(1500)));
d1->new_url = GURL(new_url_1);
deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_1, effective_new_url);
EXPECT_TRUE(warning_set.empty());
EXPECT_EQ(1u, capturing_net_log.GetSize());
// Cancel request by redirecting to the same data:// URL. This shall
// not create any conflicts as it is in line with d1.
GURL new_url_2("data://foo");
linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(1000)));
d2->new_url = GURL(new_url_2);
deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_1, effective_new_url);
EXPECT_TRUE(warning_set.empty());
EXPECT_EQ(2u, capturing_net_log.GetSize());
// Cancel redirect by redirecting to a different data:// URL. This needs
// to create a conflict.
GURL new_url_3("data://something_totally_different");
linked_ptr<EventResponseDelta> d3(
new EventResponseDelta("extid3", base::Time::FromInternalValue(500)));
d3->new_url = GURL(new_url_3);
deltas.push_back(d3);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_1, effective_new_url);
EXPECT_EQ(1u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid3"));
EXPECT_EQ(3u, capturing_net_log.GetSize());
}
// This tests that we can redirect to about:blank, which is considered
// a kind of cancelling requests.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses3) {
EventResponseDeltas deltas;
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
GURL effective_new_url;
// Single redirect.
GURL new_url_0("http://foo.com");
linked_ptr<EventResponseDelta> d0(
new EventResponseDelta("extid0", base::Time::FromInternalValue(2000)));
d0->new_url = GURL(new_url_0);
deltas.push_back(d0);
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_0, effective_new_url);
// Cancel request by redirecting to about:blank. This shall override
// the other redirect but not cause any conflict warnings.
GURL new_url_1("about:blank");
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(1500)));
d1->new_url = GURL(new_url_1);
deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
MergeOnBeforeRequestResponses(
deltas, &effective_new_url, &warning_set, &net_log);
EXPECT_EQ(new_url_1, effective_new_url);
EXPECT_TRUE(warning_set.empty());
EXPECT_EQ(1u, capturing_net_log.GetSize());
}
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
net::HttpRequestHeaders base_headers;
base_headers.AddHeaderFromString("key1: value 1");
base_headers.AddHeaderFromString("key2: value 2");
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
std::string header_value;
EventResponseDeltas deltas;
// Check that we can handle not changing the headers.
linked_ptr<EventResponseDelta> d0(
new EventResponseDelta("extid0", base::Time::FromInternalValue(2500)));
deltas.push_back(d0);
net::HttpRequestHeaders headers0;
headers0.MergeFrom(base_headers);
MergeOnBeforeSendHeadersResponses(deltas, &headers0, &warning_set, &net_log);
ASSERT_TRUE(headers0.GetHeader("key1", &header_value));
EXPECT_EQ("value 1", header_value);
ASSERT_TRUE(headers0.GetHeader("key2", &header_value));
EXPECT_EQ("value 2", header_value);
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(0u, capturing_net_log.GetSize());
// Delete, modify and add a header.
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
d1->deleted_request_headers.push_back("key1");
d1->modified_request_headers.AddHeaderFromString("key2: value 3");
d1->modified_request_headers.AddHeaderFromString("key3: value 3");
deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
net::HttpRequestHeaders headers1;
headers1.MergeFrom(base_headers);
MergeOnBeforeSendHeadersResponses(deltas, &headers1, &warning_set, &net_log);
EXPECT_FALSE(headers1.HasHeader("key1"));
ASSERT_TRUE(headers1.GetHeader("key2", &header_value));
EXPECT_EQ("value 3", header_value);
ASSERT_TRUE(headers1.GetHeader("key3", &header_value));
EXPECT_EQ("value 3", header_value);
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(1u, capturing_net_log.GetSize());
// Check that conflicts are atomic, i.e. if one header modification
// collides all other conflicts of the same extension are declined as well.
linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
// This one conflicts:
d2->modified_request_headers.AddHeaderFromString("key3: value 0");
d2->modified_request_headers.AddHeaderFromString("key4: value 4");
deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
net::HttpRequestHeaders headers2;
headers2.MergeFrom(base_headers);
MergeOnBeforeSendHeadersResponses(deltas, &headers2, &warning_set, &net_log);
EXPECT_FALSE(headers2.HasHeader("key1"));
ASSERT_TRUE(headers2.GetHeader("key2", &header_value));
EXPECT_EQ("value 3", header_value);
ASSERT_TRUE(headers2.GetHeader("key3", &header_value));
EXPECT_EQ("value 3", header_value);
EXPECT_FALSE(headers2.HasHeader("key4"));
EXPECT_EQ(1u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(2u, capturing_net_log.GetSize());
// Check that identical modifications don't conflict and operations
// can be merged.
linked_ptr<EventResponseDelta> d3(
new EventResponseDelta("extid3", base::Time::FromInternalValue(1000)));
d3->deleted_request_headers.push_back("key1");
d3->modified_request_headers.AddHeaderFromString("key2: value 3");
d3->modified_request_headers.AddHeaderFromString("key5: value 5");
deltas.push_back(d3);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
net::HttpRequestHeaders headers3;
headers3.MergeFrom(base_headers);
MergeOnBeforeSendHeadersResponses(deltas, &headers3, &warning_set, &net_log);
EXPECT_FALSE(headers3.HasHeader("key1"));
ASSERT_TRUE(headers3.GetHeader("key2", &header_value));
EXPECT_EQ("value 3", header_value);
ASSERT_TRUE(headers3.GetHeader("key3", &header_value));
EXPECT_EQ("value 3", header_value);
ASSERT_TRUE(headers3.GetHeader("key5", &header_value));
EXPECT_EQ("value 5", header_value);
EXPECT_EQ(1u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(3u, capturing_net_log.GetSize());
}
TEST(ExtensionWebRequestHelpersTest,
TestMergeOnBeforeSendHeadersResponses_Cookies) {
net::HttpRequestHeaders base_headers;
base_headers.AddHeaderFromString(
"Cookie: name=value; name2=value2; name3=\"value3\"");
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
std::string header_value;
EventResponseDeltas deltas;
linked_ptr<RequestCookieModification> add_cookie =
make_linked_ptr(new RequestCookieModification);
add_cookie->type = helpers::ADD;
add_cookie->modification.reset(new helpers::RequestCookie);
add_cookie->modification->name.reset(new std::string("name4"));
add_cookie->modification->value.reset(new std::string("\"value 4\""));
linked_ptr<RequestCookieModification> add_cookie_2 =
make_linked_ptr(new RequestCookieModification);
add_cookie_2->type = helpers::ADD;
add_cookie_2->modification.reset(new helpers::RequestCookie);
add_cookie_2->modification->name.reset(new std::string("name"));
add_cookie_2->modification->value.reset(new std::string("new value"));
linked_ptr<RequestCookieModification> edit_cookie =
make_linked_ptr(new RequestCookieModification);
edit_cookie->type = helpers::EDIT;
edit_cookie->filter.reset(new helpers::RequestCookie);
edit_cookie->filter->name.reset(new std::string("name2"));
edit_cookie->modification.reset(new helpers::RequestCookie);
edit_cookie->modification->value.reset(new std::string("new value"));
linked_ptr<RequestCookieModification> remove_cookie =
make_linked_ptr(new RequestCookieModification);
remove_cookie->type = helpers::REMOVE;
remove_cookie->filter.reset(new helpers::RequestCookie);
remove_cookie->filter->name.reset(new std::string("name3"));
linked_ptr<RequestCookieModification> operations[] = {
add_cookie, add_cookie_2, edit_cookie, remove_cookie
};
for (size_t i = 0; i < arraysize(operations); ++i) {
linked_ptr<EventResponseDelta> delta(
new EventResponseDelta("extid0", base::Time::FromInternalValue(i * 5)));
delta->request_cookie_modifications.push_back(operations[i]);
deltas.push_back(delta);
}
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
net::HttpRequestHeaders headers1;
headers1.MergeFrom(base_headers);
warning_set.clear();
MergeOnBeforeSendHeadersResponses(deltas, &headers1, &warning_set, &net_log);
EXPECT_TRUE(headers1.HasHeader("Cookie"));
ASSERT_TRUE(headers1.GetHeader("Cookie", &header_value));
EXPECT_EQ("name=new value; name2=new value; name4=\"value 4\"", header_value);
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(0u, capturing_net_log.GetSize());
}
namespace {
std::string GetCookieExpirationDate(int delta_secs) {
const char* const kWeekDays[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
const char* const kMonthNames[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
Time::Exploded exploded_time;
(Time::Now() + TimeDelta::FromSeconds(delta_secs)).UTCExplode(&exploded_time);
return base::StringPrintf("%s, %d %s %d %.2d:%.2d:%.2d GMT",
kWeekDays[exploded_time.day_of_week],
exploded_time.day_of_month,
kMonthNames[exploded_time.month - 1],
exploded_time.year,
exploded_time.hour,
exploded_time.minute,
exploded_time.second);
}
} // namespace
TEST(ExtensionWebRequestHelpersTest,
TestMergeCookiesInOnHeadersReceivedResponses) {
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
std::string header_value;
EventResponseDeltas deltas;
std::string cookie_expiration = GetCookieExpirationDate(1200);
std::string base_headers_string =
"HTTP/1.0 200 OK\r\n"
"Foo: Bar\r\n"
"Set-Cookie: name=value; DOMAIN=google.com; Secure\r\n"
"Set-Cookie: name2=value2\r\n"
"Set-Cookie: name3=value3\r\n"
"Set-Cookie: lBound1=value5; Expires=" + cookie_expiration + "\r\n"
"Set-Cookie: lBound2=value6; Max-Age=1200\r\n"
"Set-Cookie: lBound3=value7; Max-Age=2000\r\n"
"Set-Cookie: uBound1=value8; Expires=" + cookie_expiration + "\r\n"
"Set-Cookie: uBound2=value9; Max-Age=1200\r\n"
"Set-Cookie: uBound3=value10; Max-Age=2000\r\n"
"Set-Cookie: uBound4=value11; Max-Age=2500\r\n"
"Set-Cookie: uBound5=value12; Max-Age=600; Expires=" +
cookie_expiration + "\r\n"
"Set-Cookie: uBound6=removed; Max-Age=600\r\n"
"Set-Cookie: sessionCookie=removed; Max-Age=INVALID\r\n"
"Set-Cookie: sessionCookie2=removed\r\n"
"\r\n";
scoped_refptr<net::HttpResponseHeaders> base_headers(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
base_headers_string.c_str(), base_headers_string.size())));
// 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;
MergeCookiesInOnHeadersReceivedResponses(
deltas, base_headers.get(), &new_headers0, &warning_set, &net_log);
EXPECT_FALSE(new_headers0.get());
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(0u, capturing_net_log.GetSize());
linked_ptr<ResponseCookieModification> add_cookie =
make_linked_ptr(new ResponseCookieModification);
add_cookie->type = helpers::ADD;
add_cookie->modification.reset(new helpers::ResponseCookie);
add_cookie->modification->name.reset(new std::string("name4"));
add_cookie->modification->value.reset(new std::string("\"value4\""));
linked_ptr<ResponseCookieModification> edit_cookie =
make_linked_ptr(new ResponseCookieModification);
edit_cookie->type = helpers::EDIT;
edit_cookie->filter.reset(new helpers::FilterResponseCookie);
edit_cookie->filter->name.reset(new std::string("name2"));
edit_cookie->modification.reset(new helpers::ResponseCookie);
edit_cookie->modification->value.reset(new std::string("new value"));
linked_ptr<ResponseCookieModification> edit_cookie_2 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_2->type = helpers::EDIT;
edit_cookie_2->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_2->filter->secure.reset(new bool(false));
edit_cookie_2->modification.reset(new helpers::ResponseCookie);
edit_cookie_2->modification->secure.reset(new bool(true));
// Tests 'ageLowerBound' filter when cookie lifetime is set
// in cookie's 'max-age' attribute and its value is greater than
// the filter's value.
linked_ptr<ResponseCookieModification> edit_cookie_3 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_3->type = helpers::EDIT;
edit_cookie_3->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_3->filter->name.reset(new std::string("lBound1"));
edit_cookie_3->filter->age_lower_bound.reset(new int(600));
edit_cookie_3->modification.reset(new helpers::ResponseCookie);
edit_cookie_3->modification->value.reset(new std::string("greater_1"));
// Cookie lifetime is set in the cookie's 'expires' attribute.
linked_ptr<ResponseCookieModification> edit_cookie_4 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_4->type = helpers::EDIT;
edit_cookie_4->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_4->filter->name.reset(new std::string("lBound2"));
edit_cookie_4->filter->age_lower_bound.reset(new int(600));
edit_cookie_4->modification.reset(new helpers::ResponseCookie);
edit_cookie_4->modification->value.reset(new std::string("greater_2"));
// Tests equality of the cookie lifetime with the filter value when
// lifetime is set in the cookie's 'max-age' attribute.
// Note: we don't test the equality when the lifetime is set in the 'expires'
// attribute because the tests will be flaky. The reason is calculations will
// depend on fetching the current time.
linked_ptr<ResponseCookieModification> edit_cookie_5 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_5->type = helpers::EDIT;
edit_cookie_5->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_5->filter->name.reset(new std::string("lBound3"));
edit_cookie_5->filter->age_lower_bound.reset(new int(2000));
edit_cookie_5->modification.reset(new helpers::ResponseCookie);
edit_cookie_5->modification->value.reset(new std::string("equal_2"));
// Tests 'ageUpperBound' filter when cookie lifetime is set
// in cookie's 'max-age' attribute and its value is lower than
// the filter's value.
linked_ptr<ResponseCookieModification> edit_cookie_6 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_6->type = helpers::EDIT;
edit_cookie_6->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_6->filter->name.reset(new std::string("uBound1"));
edit_cookie_6->filter->age_upper_bound.reset(new int(2000));
edit_cookie_6->modification.reset(new helpers::ResponseCookie);
edit_cookie_6->modification->value.reset(new std::string("smaller_1"));
// Cookie lifetime is set in the cookie's 'expires' attribute.
linked_ptr<ResponseCookieModification> edit_cookie_7 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_7->type = helpers::EDIT;
edit_cookie_7->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_7->filter->name.reset(new std::string("uBound2"));
edit_cookie_7->filter->age_upper_bound.reset(new int(2000));
edit_cookie_7->modification.reset(new helpers::ResponseCookie);
edit_cookie_7->modification->value.reset(new std::string("smaller_2"));
// Tests equality of the cookie lifetime with the filter value when
// lifetime is set in the cookie's 'max-age' attribute.
linked_ptr<ResponseCookieModification> edit_cookie_8 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_8->type = helpers::EDIT;
edit_cookie_8->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_8->filter->name.reset(new std::string("uBound3"));
edit_cookie_8->filter->age_upper_bound.reset(new int(2000));
edit_cookie_8->modification.reset(new helpers::ResponseCookie);
edit_cookie_8->modification->value.reset(new std::string("equal_4"));
// Tests 'ageUpperBound' filter when cookie lifetime is greater
// than the filter value. No modification is expected to be applied.
linked_ptr<ResponseCookieModification> edit_cookie_9 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_9->type = helpers::EDIT;
edit_cookie_9->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_9->filter->name.reset(new std::string("uBound4"));
edit_cookie_9->filter->age_upper_bound.reset(new int(2501));
edit_cookie_9->modification.reset(new helpers::ResponseCookie);
edit_cookie_9->modification->value.reset(new std::string("Will not change"));
// Tests 'ageUpperBound' filter when both 'max-age' and 'expires' cookie
// attributes are provided. 'expires' value matches the filter, however
// no modification to the cookie is expected because 'max-age' overrides
// 'expires' and it does not match the filter.
linked_ptr<ResponseCookieModification> edit_cookie_10 =
make_linked_ptr(new ResponseCookieModification);
edit_cookie_10->type = helpers::EDIT;
edit_cookie_10->filter.reset(new helpers::FilterResponseCookie);
edit_cookie_10->filter->name.reset(new std::string("uBound5"));
edit_cookie_10->filter->age_upper_bound.reset(new int(800));
edit_cookie_10->modification.reset(new helpers::ResponseCookie);
edit_cookie_10->modification->value.reset(new std::string("Will not change"));
linked_ptr<ResponseCookieModification> remove_cookie =
make_linked_ptr(new ResponseCookieModification);
remove_cookie->type = helpers::REMOVE;
remove_cookie->filter.reset(new helpers::FilterResponseCookie);
remove_cookie->filter->name.reset(new std::string("name3"));
linked_ptr<ResponseCookieModification> remove_cookie_2 =
make_linked_ptr(new ResponseCookieModification);
remove_cookie_2->type = helpers::REMOVE;
remove_cookie_2->filter.reset(new helpers::FilterResponseCookie);
remove_cookie_2->filter->name.reset(new std::string("uBound6"));
remove_cookie_2->filter->age_upper_bound.reset(new int(700));
linked_ptr<ResponseCookieModification> remove_cookie_3 =
make_linked_ptr(new ResponseCookieModification);
remove_cookie_3->type = helpers::REMOVE;
remove_cookie_3->filter.reset(new helpers::FilterResponseCookie);
remove_cookie_3->filter->name.reset(new std::string("sessionCookie"));
remove_cookie_3->filter->session_cookie.reset(new bool(true));
linked_ptr<ResponseCookieModification> remove_cookie_4 =
make_linked_ptr(new ResponseCookieModification);
remove_cookie_4->type = helpers::REMOVE;
remove_cookie_4->filter.reset(new helpers::FilterResponseCookie);
remove_cookie_4->filter->name.reset(new std::string("sessionCookie2"));
remove_cookie_4->filter->session_cookie.reset(new bool(true));
linked_ptr<ResponseCookieModification> operations[] = {
add_cookie, edit_cookie, edit_cookie_2, edit_cookie_3, edit_cookie_4,
edit_cookie_5, edit_cookie_6, edit_cookie_7, edit_cookie_8,
edit_cookie_9, edit_cookie_10, remove_cookie, remove_cookie_2,
remove_cookie_3, remove_cookie_4
};
for (size_t i = 0; i < arraysize(operations); ++i) {
linked_ptr<EventResponseDelta> delta(
new EventResponseDelta("extid0", base::Time::FromInternalValue(i * 5)));
delta->response_cookie_modifications.push_back(operations[i]);
deltas.push_back(delta);
}
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
scoped_refptr<net::HttpResponseHeaders> headers1(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
base_headers_string.c_str(), base_headers_string.size())));
scoped_refptr<net::HttpResponseHeaders> new_headers1;
warning_set.clear();
MergeCookiesInOnHeadersReceivedResponses(
deltas, headers1.get(), &new_headers1, &warning_set, &net_log);
EXPECT_TRUE(new_headers1->HasHeader("Foo"));
void* iter = NULL;
std::string cookie_string;
std::set<std::string> expected_cookies;
expected_cookies.insert("name=value; domain=google.com; secure");
expected_cookies.insert("name2=value2; secure");
expected_cookies.insert("name4=\"value4\"; secure");
expected_cookies.insert(
"lBound1=greater_1; expires=" + cookie_expiration + "; secure");
expected_cookies.insert("lBound2=greater_2; max-age=1200; secure");
expected_cookies.insert("lBound3=equal_2; max-age=2000; secure");
expected_cookies.insert(
"uBound1=smaller_1; expires=" + cookie_expiration + "; secure");
expected_cookies.insert("uBound2=smaller_2; max-age=1200; secure");
expected_cookies.insert("uBound3=equal_4; max-age=2000; secure");
expected_cookies.insert("uBound4=value11; max-age=2500; secure");
expected_cookies.insert(
"uBound5=value12; max-age=600; expires=" + cookie_expiration+ "; secure");
std::set<std::string> actual_cookies;
while (new_headers1->EnumerateHeader(&iter, "Set-Cookie", &cookie_string))
actual_cookies.insert(cookie_string);
EXPECT_EQ(expected_cookies, actual_cookies);
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(0u, capturing_net_log.GetSize());
}
TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) {
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
std::string header_value;
EventResponseDeltas deltas;
char base_headers_string[] =
"HTTP/1.0 200 OK\r\n"
"Key1: Value1\r\n"
"Key2: Value2, Foo\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, base_headers.get(), &new_headers0,
&warning_set, &net_log);
EXPECT_FALSE(new_headers0.get());
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(0u, capturing_net_log.GetSize());
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
d1->deleted_response_headers.push_back(ResponseHeader("KEY1", "Value1"));
d1->deleted_response_headers.push_back(ResponseHeader("KEY2", "Value2, Foo"));
d1->added_response_headers.push_back(ResponseHeader("Key2", "Value3"));
deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
scoped_refptr<net::HttpResponseHeaders> new_headers1;
MergeOnHeadersReceivedResponses(
deltas, base_headers.get(), &new_headers1, &warning_set, &net_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, warning_set.size());
EXPECT_EQ(1u, capturing_net_log.GetSize());
// Check that we replace response headers only once.
linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
// Note that we use a different capitalization of KeY2. This should not
// matter.
d2->deleted_response_headers.push_back(ResponseHeader("KeY2", "Value2, Foo"));
d2->added_response_headers.push_back(ResponseHeader("Key2", "Value4"));
deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
scoped_refptr<net::HttpResponseHeaders> new_headers2;
MergeOnHeadersReceivedResponses(
deltas, base_headers.get(), &new_headers2, &warning_set, &net_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, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(2u, capturing_net_log.GetSize());
}
// Check that we do not delete too much
TEST(ExtensionWebRequestHelpersTest,
TestMergeOnHeadersReceivedResponsesDeletion) {
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
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, &warning_set, &net_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, warning_set.size());
EXPECT_EQ(1u, capturing_net_log.GetSize());
}
TEST(ExtensionWebRequestHelpersTest, TestMergeOnAuthRequiredResponses) {
net::CapturingBoundNetLog capturing_net_log;
net::BoundNetLog net_log = capturing_net_log.bound();
ExtensionWarningSet warning_set;
EventResponseDeltas deltas;
string16 username = ASCIIToUTF16("foo");
string16 password = ASCIIToUTF16("bar");
string16 password2 = ASCIIToUTF16("baz");
// Check that we can handle if not returning credentials.
linked_ptr<EventResponseDelta> d0(
new EventResponseDelta("extid0", base::Time::FromInternalValue(3000)));
deltas.push_back(d0);
net::AuthCredentials auth0;
bool credentials_set = MergeOnAuthRequiredResponses(
deltas, &auth0, &warning_set, &net_log);
EXPECT_FALSE(credentials_set);
EXPECT_TRUE(auth0.Empty());
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(0u, capturing_net_log.GetSize());
// Check that we can set AuthCredentials.
linked_ptr<EventResponseDelta> d1(
new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
d1->auth_credentials.reset(new net::AuthCredentials(username, password));
deltas.push_back(d1);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
net::AuthCredentials auth1;
credentials_set = MergeOnAuthRequiredResponses(
deltas, &auth1, &warning_set, &net_log);
EXPECT_TRUE(credentials_set);
EXPECT_FALSE(auth1.Empty());
EXPECT_EQ(username, auth1.username());
EXPECT_EQ(password, auth1.password());
EXPECT_EQ(0u, warning_set.size());
EXPECT_EQ(1u, capturing_net_log.GetSize());
// Check that we set AuthCredentials only once.
linked_ptr<EventResponseDelta> d2(
new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
d2->auth_credentials.reset(new net::AuthCredentials(username, password2));
deltas.push_back(d2);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
net::AuthCredentials auth2;
credentials_set = MergeOnAuthRequiredResponses(
deltas, &auth2, &warning_set, &net_log);
EXPECT_TRUE(credentials_set);
EXPECT_FALSE(auth2.Empty());
EXPECT_EQ(username, auth1.username());
EXPECT_EQ(password, auth1.password());
EXPECT_EQ(1u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(2u, capturing_net_log.GetSize());
// Check that we can set identical AuthCredentials twice without causing
// a conflict.
linked_ptr<EventResponseDelta> d3(
new EventResponseDelta("extid3", base::Time::FromInternalValue(1000)));
d3->auth_credentials.reset(new net::AuthCredentials(username, password));
deltas.push_back(d3);
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
warning_set.clear();
capturing_net_log.Clear();
net::AuthCredentials auth3;
credentials_set = MergeOnAuthRequiredResponses(
deltas, &auth3, &warning_set, &net_log);
EXPECT_TRUE(credentials_set);
EXPECT_FALSE(auth3.Empty());
EXPECT_EQ(username, auth1.username());
EXPECT_EQ(password, auth1.password());
EXPECT_EQ(1u, warning_set.size());
EXPECT_TRUE(HasWarning(warning_set, "extid2"));
EXPECT_EQ(3u, capturing_net_log.GetSize());
}
} // namespace extensions