// 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 <stddef.h> #include <stdint.h> #include <map> #include <queue> #include <tuple> #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/location.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/thread_task_runner_handle.h" #include "base/time/time.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/extensions/event_router_forwarder.h" #include "chrome/browser/net/chrome_network_delegate.h" #include "chrome/test/base/testing_browser_process.h" #include "chrome/test/base/testing_profile.h" #include "chrome/test/base/testing_profile_manager.h" #include "components/about_handler/about_protocol_handler.h" #include "components/content_settings/core/browser/cookie_settings.h" #include "components/prefs/pref_member.h" #include "components/syncable_prefs/testing_pref_service_syncable.h" #include "content/public/common/url_constants.h" #include "content/public/test/test_browser_thread_bundle.h" #include "extensions/browser/api/web_request/upload_data_presenter.h" #include "extensions/browser/api/web_request/web_request_api.h" #include "extensions/browser/api/web_request/web_request_api_constants.h" #include "extensions/browser/api/web_request/web_request_api_helpers.h" #include "extensions/browser/warning_set.h" #include "extensions/common/api/web_request.h" #include "extensions/common/extension_messages.h" #include "extensions/common/features/feature.h" #include "net/base/auth.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_file_element_reader.h" #include "net/dns/mock_host_resolver.h" #include "net/log/test_net_log.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 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::ExtraInfoSpec; 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_t 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 WarningSet& warnings, const std::string& extension_id) { for (WarningSet::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 base::DictionaryValue** out, ExtensionMsg_MessageInvoke::Param* param) { ASSERT_EQ(ExtensionMsg_MessageInvoke::ID, message->type()); ASSERT_TRUE(ExtensionMsg_MessageInvoke::Read(message, param)); ASSERT_GE(std::get<3>(*param).GetSize(), 2u); const base::Value* value = NULL; ASSERT_TRUE(std::get<3>(*param).Get(1, &value)); const base::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 bool Send(IPC::Message* message) override { EXPECT_EQ(ExtensionMsg_MessageInvoke::ID, message->type()); EXPECT_FALSE(task_queue_.empty()); base::ThreadTaskRunnerHandle::Get()->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: void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); ChromeNetworkDelegate::InitializePrefsOnUIThread( &enable_referrers_, NULL, NULL, NULL, profile_.GetTestingPrefService()); network_delegate_.reset( new ChromeNetworkDelegate(event_router_.get(), &enable_referrers_)); network_delegate_->set_profile(&profile_); network_delegate_->set_cookie_settings( CookieSettingsFactory::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<InfoMap> 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension2_id, extension2_id, events::FOR_TEST, kEventName, kEventName + "/2", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); net::URLRequestJobFactoryImpl job_factory; job_factory.SetProtocolHandler( url::kAboutScheme, make_scoped_ptr(new about_handler::AboutProtocolHandler())); context_->set_job_factory(&job_factory); GURL redirect_url("about:redirected"); GURL not_chosen_redirect_url("about:not_chosen"); scoped_ptr<net::URLRequest> request(context_->CreateRequest( GURL("about:blank"), net::DEFAULT_PRIORITY, &delegate_)); { // 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. scoped_ptr<net::URLRequest> request2(context_->CreateRequest( GURL("about:blank"), net::DEFAULT_PRIORITY, &delegate_)); { 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", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension2_id, kEventName + "/2", 0, 0); } // 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension2_id, extension2_id, events::FOR_TEST, kEventName, kEventName + "/2", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); GURL request_url("about:blank"); scoped_ptr<net::URLRequest> request(context_->CreateRequest( request_url, net::DEFAULT_PRIORITY, &delegate_)); // 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", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension2_id, kEventName + "/2", 0, 0); } 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension_id, extension_id, events::FOR_TEST, kEventName2, kEventName2 + "/1", filter, 0, 0, 0, ipc_sender_factory.GetWeakPtr()); GURL request_url("about:blank"); scoped_ptr<net::URLRequest> request(context_->CreateRequest( request_url, net::DEFAULT_PRIORITY, &delegate_)); 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::QuitWhenIdleClosure())); 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", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension_id, kEventName2 + "/1", 0, 0); } 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 base::ListValue of strings. base::ListValue list_value; for (const std::string& cur : base::SplitString(values, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) list_value.Append(new base::StringValue(cur)); return 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"); scoped_ptr<net::URLRequest> request(context_->CreateRequest( request_url, net::DEFAULT_PRIORITY, &delegate_)); request->set_method(method); if (content_type != NULL) { request->SetExtraRequestHeaderByName(net::HttpRequestHeaders::kContentType, content_type, true /* overwrite */); } std::vector<scoped_ptr<net::UploadElementReader>> element_readers; element_readers.push_back(make_scoped_ptr( new net::UploadBytesElementReader(&(bytes_1[0]), bytes_1.size()))); element_readers.push_back(make_scoped_ptr(new net::UploadFileElementReader( base::ThreadTaskRunnerHandle::Get().get(), base::FilePath(), 0, 0, base::Time()))); element_readers.push_back(make_scoped_ptr( new net::UploadBytesElementReader(&(bytes_2[0]), bytes_2.size()))); request->set_upload(make_scoped_ptr( new net::ElementsUploadDataStream(std::move(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 base::Value> form_data = base::JSONReader::Read(kFormData); ASSERT_TRUE(form_data.get() != NULL); ASSERT_TRUE(form_data->GetType() == base::Value::TYPE_DICTIONARY); // Contents of raw. base::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 base::Value* const kExpected[] = { form_data.get(), NULL, &raw, &raw, }; static_assert(arraysize(kPath) == arraysize(kExpected), "kPath and kExpected arrays should have the same number " "of elements"); // 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, extra_info_spec_body, 0, 0, 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", 0, 0); // 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, extra_info_spec_empty, 0, 0, ipc_sender_factory.GetWeakPtr()); FireURLRequestWithData(kMethodPost, kMultipart, form_1, form_2); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension_id, kEventName + "/1", 0, 0); // Subscribe to OnBeforeRequest with requestBody requirement. ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension_id, extension_id, events::FOR_TEST, kEventName, kEventName + "/1", filter, extra_info_spec_body, 0, 0, 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", 0, 0); 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 base::DictionaryValue* details; ExtensionMsg_MessageInvoke::Param param; GetPartOfMessageArguments(message, &details, ¶m); ASSERT_TRUE(details != NULL); const base::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()); } // Tests whether requestBody is only present on the events that requested it. TEST_F(ExtensionWebRequestTest, MinimalAccessRequestBodyData) { const std::string kEventName(web_request::OnBeforeRequest::kEventName); ExtensionWebRequestEventRouter::RequestFilter filter; const std::string extension_id1("1"); const std::string extension_id2("2"); int extra_info_spec_body = 0; int extra_info_spec_empty = 0; ASSERT_TRUE(GenerateInfoSpec("requestBody", &extra_info_spec_body)); base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_); bool kExpected[] = { true, false, false, true, }; // Extension 1 with requestBody spec. ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension_id1, extension_id1, events::FOR_TEST, kEventName, kEventName + "/1", filter, extra_info_spec_body, 0, 0, ipc_sender_factory.GetWeakPtr()); // Extension 1 without requestBody spec. ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension_id1, extension_id1, events::FOR_TEST, kEventName, kEventName + "/2", filter, extra_info_spec_empty, 0, 0, ipc_sender_factory.GetWeakPtr()); // Extension 2, without requestBody spec. ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension_id2, extension_id2, events::FOR_TEST, kEventName, kEventName + "/1", filter, extra_info_spec_empty, 0, 0, ipc_sender_factory.GetWeakPtr()); // Extension 2, with requestBody spec. ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension_id2, extension_id2, events::FOR_TEST, kEventName, kEventName + "/2", filter, extra_info_spec_body, 0, 0, ipc_sender_factory.GetWeakPtr()); // Only one request is sent, but more than one event will be triggered. for (size_t i = 1; i < arraysize(kExpected); ++i) ipc_sender_.PushTask(base::Bind(&base::DoNothing)); const std::vector<char> part_of_body(1); FireURLRequestWithData("POST", nullptr, part_of_body, part_of_body); base::MessageLoop::current()->RunUntilIdle(); // Clean-up ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension_id1, kEventName + "/1", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension_id2, kEventName + "/2", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension_id1, kEventName + "/1", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension_id2, kEventName + "/2", 0, 0); TestIPCSender::SentMessages::const_iterator i = ipc_sender_.sent_begin(); for (size_t test = 0; test < arraysize(kExpected); ++test, ++i) { SCOPED_TRACE(testing::Message("iteration number ") << test); EXPECT_NE(i, ipc_sender_.sent_end()); IPC::Message* message = i->get(); const base::DictionaryValue* details = nullptr; ExtensionMsg_MessageInvoke::Param param; GetPartOfMessageArguments(message, &details, ¶m); ASSERT_TRUE(details != nullptr); EXPECT_EQ(kExpected[test], details->HasKey(keys::kRequestBodyKey)); } 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* const 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, extra_info_spec, 0, 0, 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) { scoped_ptr<net::URLRequest> request(context_->CreateRequest( request_url, net::DEFAULT_PRIORITY, &delegate_)); 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", 0, 0); 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 base::DictionaryValue* details = NULL; ExtensionMsg_MessageInvoke::Param param; GetPartOfMessageArguments(message, &details, ¶m); 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: void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); ChromeNetworkDelegate::InitializePrefsOnUIThread( &enable_referrers_, NULL, NULL, NULL, profile_.GetTestingPrefService()); network_delegate_.reset( new ChromeNetworkDelegate(event_router_.get(), &enable_referrers_)); network_delegate_->set_profile(&profile_); network_delegate_->set_cookie_settings( CookieSettingsFactory::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<InfoMap> 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, events::FOR_TEST, kEventName, kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension2_id, extension2_id, events::FOR_TEST, kEventName, kEventName + "/2", filter, ExtraInfoSpec::BLOCKING, 0, 0, ipc_sender_factory.GetWeakPtr()); // Install one extension that observes the final headers. ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( &profile_, extension3_id, extension3_id, events::FOR_TEST, keys::kOnSendHeadersEvent, std::string(keys::kOnSendHeadersEvent) + "/3", filter, ExtraInfoSpec::REQUEST_HEADERS, 0, 0, ipc_sender_factory.GetWeakPtr()); GURL request_url("http://doesnotexist/does_not_exist.html"); scoped_ptr<net::URLRequest> request(context_->CreateRequest( request_url, net::DEFAULT_PRIORITY, &delegate_)); // 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); base::ListValue& args = std::get<3>(message_tuple); std::string event_name; if (!args.GetString(0, &event_name) || event_name != std::string(keys::kOnSendHeadersEvent) + "/3") { continue; } base::ListValue* event_arg = NULL; ASSERT_TRUE(args.GetList(1, &event_arg)); base::DictionaryValue* event_arg_dict = NULL; ASSERT_TRUE(event_arg->GetDictionary(0, &event_arg_dict)); base::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) { base::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", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension2_id, kEventName + "/2", 0, 0); ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener( &profile_, extension3_id, std::string(keys::kOnSendHeadersEvent) + "/3", 0, 0); }; 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, ExtraInfoSpec::REQUEST_HEADERS); TestInitFromValue( "responseHeaders", true, ExtraInfoSpec::RESPONSE_HEADERS); TestInitFromValue( "blocking", true, ExtraInfoSpec::BLOCKING); TestInitFromValue( "asyncBlocking", true, ExtraInfoSpec::ASYNC_BLOCKING); TestInitFromValue( "requestBody", true, ExtraInfoSpec::REQUEST_BODY); // Multiple valid values are bitwise-or'ed. TestInitFromValue( "requestHeaders,blocking", true, ExtraInfoSpec::REQUEST_HEADERS | 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) { base::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<base::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" "Key5: Value5, end5\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 new_headers.push_back(ResponseHeader("Key5", "Value5, end5")); // Unchanged GURL effective_new_url; scoped_ptr<EventResponseDelta> delta( CalculateOnHeadersReceivedDelta("extid", base::Time::Now(), cancel, effective_new_url, 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; base::string16 username = base::ASCIIToUTF16("foo"); base::string16 password = base::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::BoundTestNetLog 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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")); size_t iter = 0; 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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; GURL allowed_unsafe_redirect_url0; MergeOnHeadersReceivedResponses(deltas, base_headers.get(), &new_headers0, &allowed_unsafe_redirect_url0, &warning_set, &net_log); EXPECT_FALSE(new_headers0.get()); EXPECT_TRUE(allowed_unsafe_redirect_url0.is_empty()); 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; GURL allowed_unsafe_redirect_url1; MergeOnHeadersReceivedResponses(deltas, base_headers.get(), &new_headers1, &allowed_unsafe_redirect_url1, &warning_set, &net_log); ASSERT_TRUE(new_headers1.get()); EXPECT_TRUE(allowed_unsafe_redirect_url1.is_empty()); std::multimap<std::string, std::string> expected1; expected1.insert(std::pair<std::string, std::string>("Key2", "Value3")); size_t iter = 0; 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; GURL allowed_unsafe_redirect_url2; MergeOnHeadersReceivedResponses(deltas, base_headers.get(), &new_headers2, &allowed_unsafe_redirect_url2, &warning_set, &net_log); ASSERT_TRUE(new_headers2.get()); EXPECT_TRUE(allowed_unsafe_redirect_url2.is_empty()); iter = 0; 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::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet 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; GURL allowed_unsafe_redirect_url1; MergeOnHeadersReceivedResponses(deltas, base_headers.get(), &new_headers1, &allowed_unsafe_redirect_url1, &warning_set, &net_log); ASSERT_TRUE(new_headers1.get()); EXPECT_TRUE(allowed_unsafe_redirect_url1.is_empty()); 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")); size_t iter = 0; 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()); } // Tests whether onHeadersReceived can initiate a redirect. // The URL merge logic is shared with onBeforeRequest, so we only need to test // whether the URLs are merged at all. TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponsesRedirect) { EventResponseDeltas deltas; net::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet warning_set; char base_headers_string[] = "HTTP/1.0 200 OK\r\n" "\r\n"; scoped_refptr<net::HttpResponseHeaders> base_headers( new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( base_headers_string, sizeof(base_headers_string)))); // No redirect linked_ptr<EventResponseDelta> d0( new EventResponseDelta("extid0", base::Time::FromInternalValue(0))); deltas.push_back(d0); scoped_refptr<net::HttpResponseHeaders> new_headers0; GURL allowed_unsafe_redirect_url0; MergeOnHeadersReceivedResponses(deltas, base_headers.get(), &new_headers0, &allowed_unsafe_redirect_url0, &warning_set, &net_log); EXPECT_FALSE(new_headers0.get()); EXPECT_TRUE(allowed_unsafe_redirect_url0.is_empty()); EXPECT_EQ(0u, warning_set.size()); EXPECT_EQ(0u, capturing_net_log.GetSize()); // 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(); scoped_refptr<net::HttpResponseHeaders> new_headers1; GURL allowed_unsafe_redirect_url1; MergeOnHeadersReceivedResponses(deltas, base_headers.get(), &new_headers1, &allowed_unsafe_redirect_url1, &warning_set, &net_log); EXPECT_TRUE(new_headers1.get()); EXPECT_TRUE(new_headers1->HasHeaderValue("Location", new_url_1.spec())); EXPECT_EQ(new_url_1, allowed_unsafe_redirect_url1); EXPECT_TRUE(warning_set.empty()); EXPECT_EQ(1u, capturing_net_log.GetSize()); } TEST(ExtensionWebRequestHelpersTest, TestMergeOnAuthRequiredResponses) { net::BoundTestNetLog capturing_net_log; net::BoundNetLog net_log = capturing_net_log.bound(); WarningSet warning_set; EventResponseDeltas deltas; base::string16 username = base::ASCIIToUTF16("foo"); base::string16 password = base::ASCIIToUTF16("bar"); base::string16 password2 = base::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