// 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 <memory>
#include <tuple>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.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/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/extensions/event_router_forwarder.h"
#include "chrome/browser/net/chrome_extensions_network_delegate.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/sync_preferences/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 "google_apis/gaia/gaia_urls.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/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.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"

#if defined(OS_CHROMEOS)
#include "chromeos/login/scoped_test_public_session_login_state.h"
#include "components/crx_file/id_util.h"
#endif

namespace helpers = extension_web_request_api_helpers;
namespace keys = extension_web_request_api_constants;
namespace web_request = extensions::api::web_request;

using base::DictionaryValue;
using base::ListValue;
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 {

constexpr const char kExampleUrl[] = "http://example.com";

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);
}

// 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|.
void GetPartOfMessageArguments(IPC::Message* message,
                               const base::DictionaryValue** out,
                               ExtensionMsg_DispatchEvent::Param* param) {
  ASSERT_EQ(static_cast<uint32_t>(ExtensionMsg_DispatchEvent::ID),
            message->type());
  ASSERT_TRUE(ExtensionMsg_DispatchEvent::Read(message, param));
  const base::ListValue& list = std::get<1>(*param);
  ASSERT_EQ(1u, list.GetSize());
  ASSERT_TRUE(list.GetDictionary(0, out));
}

base::Value FormBinaryValue(base::StringPiece str) {
  base::Value list(base::Value::Type::LIST);
  list.GetList().emplace_back(base::Value(
      base::Value::BlobStorage(str.data(), str.data() + str.size())));
  return list;
}

base::Value FormStringValue(base::StringPiece str) {
  base::Value list(base::Value::Type::LIST);
  list.GetList().emplace_back(base::Value(str));
  return list;
}

}  // 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(static_cast<uint32_t>(ExtensionMsg_DispatchEvent::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;
  }

  base::queue<base::Closure> task_queue_;
  SentMessages sent_messages_;
};

class TestLogger : public WebRequestInfo::Logger {
 public:
  TestLogger() = default;
  ~TestLogger() override = default;

  size_t log_size() const { return events_.size(); }
  void clear() { events_.clear(); }

  // WebRequestInfo::Logger:
  void LogEvent(net::NetLogEventType event_type,
                const std::string& extension_id) override {
    events_.push_back({event_type, extension_id});
  }
  void LogBlockedBy(const std::string& blocker_info) override {}
  void LogUnblocked() override {}

 private:
  using Event = std::pair<net::NetLogEventType, std::string>;
  std::vector<Event> events_;

  DISALLOW_COPY_AND_ASSIGN(TestLogger);
};

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(
        nullptr, nullptr, nullptr, profile_.GetTestingPrefService());
    network_delegate_.reset(new ChromeNetworkDelegate(event_router_.get()));
    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_;
  TestIPCSender ipc_sender_;
  scoped_refptr<EventRouterForwarder> event_router_;
  std::unique_ptr<ChromeNetworkDelegate> network_delegate_;
  std::unique_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,
      base::WrapUnique(new about_handler::AboutProtocolHandler()));
  context_->set_job_factory(&job_factory);

  GURL redirect_url("about:redirected");
  GURL not_chosen_redirect_url("about:not_chosen");

  std::unique_ptr<net::URLRequest> request(
      context_->CreateRequest(GURL("about:blank"), net::DEFAULT_PRIORITY,
                              &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS));
  {
    // 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::RunLoop().Run();

    EXPECT_TRUE(!request->is_pending());
    EXPECT_EQ(net::OK, delegate_.request_status());
    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.
  std::unique_ptr<net::URLRequest> request2(
      context_->CreateRequest(GURL("about:blank"), net::DEFAULT_PRIORITY,
                              &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS));
  {
    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::RunLoop().Run();

    EXPECT_TRUE(!request2->is_pending());
    EXPECT_EQ(net::OK, delegate_.request_status());
    EXPECT_EQ(redirect_url, request2->url());
    EXPECT_EQ(2U, request2->url_chain().size());
    EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
  }

  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension1_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id2(
      &profile_, extension2_id, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id2,
                                                                     false);
}

// 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");
  std::unique_ptr<net::URLRequest> request(
      context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_,
                              TRAFFIC_ANNOTATION_FOR_TESTS));

  // 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::RunLoop().Run();

  EXPECT_TRUE(!request->is_pending());
  EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, delegate_.request_status());
  EXPECT_EQ(request_url, request->url());
  EXPECT_EQ(1U, request->url_chain().size());
  EXPECT_EQ(0U, ipc_sender_.GetNumTasks());

  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension1_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id2(
      &profile_, extension2_id, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id2,
                                                                     false);
}

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");
  std::unique_ptr<net::URLRequest> request(
      context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_,
                              TRAFFIC_ANNOTATION_FOR_TESTS));

  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));

  base::RunLoop run_loop;

  // Extension response for OnErrorOccurred: Terminate the message loop.
  ipc_sender_.PushTask(
      base::Bind(base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask),
                 base::ThreadTaskRunnerHandle::Get(), FROM_HERE,
                 run_loop.QuitWhenIdleClosure()));

  request->Start();
  // request->Start() will have submitted OnBeforeRequest by the time we cancel.
  int net_error = request->Cancel();
  run_loop.Run();

  EXPECT_EQ(net::ERR_ABORTED, net_error);
  EXPECT_TRUE(!request->is_pending());
  EXPECT_EQ(request_url, request->url());
  EXPECT_EQ(1U, request->url_chain().size());
  EXPECT_EQ(0U, ipc_sender_.GetNumTasks());

  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id2(
      &profile_, extension_id, kEventName2 + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id2,
                                                                     false);
}

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.AppendString(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");
  std::unique_ptr<net::URLRequest> request(
      context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_,
                              TRAFFIC_ANNOTATION_FOR_TESTS));
  request->set_method(method);
  if (content_type != NULL) {
    request->SetExtraRequestHeaderByName(net::HttpRequestHeaders::kContentType,
                                         content_type,
                                         true /* overwrite */);
  }
  std::vector<std::unique_ptr<net::UploadElementReader>> element_readers;
  element_readers.push_back(std::make_unique<net::UploadBytesElementReader>(
      &(bytes_1[0]), bytes_1.size()));
  element_readers.push_back(std::make_unique<net::UploadFileElementReader>(
      base::ThreadTaskRunnerHandle::Get().get(), base::FilePath(), 0, 0,
      base::Time()));
  element_readers.push_back(std::make_unique<net::UploadBytesElementReader>(
      &(bytes_2[0]), bytes_2.size()));
  request->set_upload(std::make_unique<net::ElementsUploadDataStream>(
      std::move(element_readers), 0));
  ipc_sender_.PushTask(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"
      "--" kBoundary
      "\r\n"
      "Content-Disposition: form-data; name=\"B_content\"\r\n"
      "Content-Type: application/octet-stream\r\n"
      "\r\n"
      "\uffff\uffff\uffff\uffff\r\n"
      "--" kBoundary "\r\n";
  std::vector<char> form_1(kFormBlock1, kFormBlock1 + sizeof(kFormBlock1) - 1);
  const char kFormBlock2[] =
      "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.
  struct KeyValuePairs {
    const char* key;
    base::Value value;
  };
  KeyValuePairs kFormDataPairs[] = {
      {"A", FormStringValue("test text")},
      {"B", FormStringValue("")},
      {"B_content", FormBinaryValue("\uffff\uffff\uffff\uffff")},
      {"C", FormStringValue("test password")}};
  std::unique_ptr<base::Value> form_data =
      std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
  for (auto& pair : kFormDataPairs) {
    form_data->SetKey(pair.key, std::move(pair.value));
  }

  ASSERT_TRUE(form_data.get() != NULL);
  ASSERT_TRUE(form_data->type() == base::Value::Type::DICTIONARY);
  // Contents of raw.
  base::ListValue raw;
  extensions::subtle::AppendKeyValuePair(
      keys::kRequestBodyRawBytesKey,
      Value::CreateWithCopiedBuffer(kPlainBlock1, kPlainBlock1Length), &raw);
  extensions::subtle::AppendKeyValuePair(
      keys::kRequestBodyRawFileKey,
      std::make_unique<base::Value>(std::string()), &raw);
  extensions::subtle::AppendKeyValuePair(
      keys::kRequestBodyRawBytesKey,
      Value::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::RunLoop().RunUntilIdle();

  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);

  // 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(id1,
                                                                     false);

  // 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::RunLoop().RunUntilIdle();

  // Clean-up.
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);

  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 = nullptr;
    ExtensionMsg_DispatchEvent::Param param;
    GetPartOfMessageArguments(message, &details, &param);
    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::DoNothing());

  const std::vector<char> part_of_body(1);
  FireURLRequestWithData("POST", nullptr, part_of_body, part_of_body);

  base::RunLoop().RunUntilIdle();

  // Clean-up
  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension_id1, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id2(
      &profile_, extension_id1, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id3(
      &profile_, extension_id2, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id4(
      &profile_, extension_id2, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id2,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id3,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id4,
                                                                     false);

  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_DispatchEvent::Param param;
    GetPartOfMessageArguments(message, &details, &param);
    ASSERT_TRUE(details != nullptr);
    EXPECT_EQ(kExpected[test], details->HasKey(keys::kRequestBodyKey));
  }

  EXPECT_EQ(i, ipc_sender_.sent_end());
}

#if defined(OS_CHROMEOS)
// Tests that proper filtering is applied in public session (non-whitelisted
// extension gets some things filtered out, while there's no filtering applied
// for a whitelisted extension).
TEST_F(ExtensionWebRequestTest, ProperFilteringInPublicSession) {
  chromeos::ScopedTestPublicSessionLoginState state;
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  ExtensionWebRequestEventRouter::RequestFilter filter;
  // Whitelisted extension (User Agent Switcher).
  const std::string extension_id1("djflhoibgkdhkhhcedjiklpkjnoahfmg");
  const std::string extension_id2 =
      crx_file::id_util::GenerateId("nonwhitelisted");
  int extra_info_spec_body = 0;
  ASSERT_TRUE(GenerateInfoSpec("requestBody", &extra_info_spec_body));
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);

  bool kExpected[] = {
    true,
    false,
  };

  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());
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id2, extension_id2, events::FOR_TEST, kEventName,
      kEventName + "/1", 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::DoNothing());

  const std::vector<char> part_of_body(1);
  FireURLRequestWithData("POST", nullptr, part_of_body, part_of_body);

  base::RunLoop().RunUntilIdle();

  // Clean-up
  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension_id1, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id2(
      &profile_, extension_id2, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id2,
                                                                     false);

  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_DispatchEvent::Param param;
    GetPartOfMessageArguments(message, &details, &param);
    ASSERT_TRUE(details != nullptr);
    EXPECT_EQ(kExpected[test], details->HasKey(keys::kRequestBodyKey));
  }

  EXPECT_EQ(i, ipc_sender_.sent_end());
}
#endif

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) {
    std::unique_ptr<net::URLRequest> request(
        context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_,
                                TRAFFIC_ANNOTATION_FOR_TESTS));
    request->set_method(kMethods[i]);
    ipc_sender_.PushTask(base::DoNothing());
    request->Start();
  }

  // We inspect the result in the message list of |ipc_sender_| later.
  base::RunLoop().RunUntilIdle();

  ExtensionWebRequestEventRouter::EventListener::ID id1(
      &profile_, extension_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);

  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 = nullptr;
    ExtensionMsg_DispatchEvent::Param param;
    GetPartOfMessageArguments(message, &details, &param);
    ASSERT_TRUE(details != NULL);
    EXPECT_FALSE(details->HasKey(keys::kRequestBodyKey));
  }

  EXPECT_EQ(i, ipc_sender_.sent_end());
}

// Tests that |embedder_process_id| is not relevant for adding and removing
// listeners with |web_view_instance_id| = 0.
TEST_F(ExtensionWebRequestTest, AddAndRemoveListeners) {
  std::string ext_id("abcdefghijklmnopabcdefghijklmnop");
  ExtensionWebRequestEventRouter::RequestFilter filter;
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  const std::string kSubEventName = kEventName + "/1";
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
  EXPECT_EQ(
      0u,
      ExtensionWebRequestEventRouter::GetInstance()->GetListenerCountForTesting(
          &profile_, kEventName));

  // Add two non-webview listeners.
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, ext_id, ext_id, events::FOR_TEST, kEventName, kSubEventName,
      filter, 0, 1 /* embedder_process_id */, 0,
      ipc_sender_factory.GetWeakPtr());
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, ext_id, ext_id, events::FOR_TEST, kEventName, kSubEventName,
      filter, 0, 2 /* embedder_process_id */, 0,
      ipc_sender_factory.GetWeakPtr());
  EXPECT_EQ(
      2u,
      ExtensionWebRequestEventRouter::GetInstance()->GetListenerCountForTesting(
          &profile_, kEventName));

  // Now remove the events without passing an explicit process ID.
  ExtensionWebRequestEventRouter::EventListener::ID id1(&profile_, ext_id,
                                                        kSubEventName, 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  EXPECT_EQ(
      1u,
      ExtensionWebRequestEventRouter::GetInstance()->GetListenerCountForTesting(
          &profile_, kEventName));

  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  EXPECT_EQ(
      0u,
      ExtensionWebRequestEventRouter::GetInstance()->GetListenerCountForTesting(
          &profile_, kEventName));
}

// The set of blocked requests should not grow unbounded.
TEST_F(ExtensionWebRequestTest, BlockedRequestsAreRemoved) {
  std::string extension_id("1");
  ExtensionWebRequestEventRouter::RequestFilter filter;

  // Subscribe to OnBeforeRequest.
  const std::string kEventName(web_request::OnBeforeRequest::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::EventListener::ID id(&profile_, extension_id,
                                                       kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener* listener =
      ExtensionWebRequestEventRouter::GetInstance()->FindEventListener(id);
  ASSERT_NE(nullptr, listener);
  EXPECT_EQ(0u, listener->blocked_requests.size());

  // Send a request. It should block. Wait for the run loop to become idle.
  GURL request_url("about:blank");
  std::unique_ptr<net::URLRequest> request(
      context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_,
                              TRAFFIC_ANNOTATION_FOR_TESTS));
  // Extension response for OnErrorOccurred: Terminate the message loop.
  {
    base::RunLoop run_loop;
    ipc_sender_.PushTask(
        base::Bind(base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask),
                   base::ThreadTaskRunnerHandle::Get(), FROM_HERE,
                   run_loop.QuitWhenIdleClosure()));
    request->Start();
    run_loop.Run();
  }

  // Confirm that there is a blocked request.
  EXPECT_EQ(1u, listener->blocked_requests.size());

  // Send a response through.
  ExtensionWebRequestEventRouter::EventResponse* response =
      new ExtensionWebRequestEventRouter::EventResponse(
          extension_id, base::Time::FromDoubleT(1));
  response->cancel = true;
  ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
      &profile_, extension_id, kEventName, kEventName + "/1",
      request->identifier(), response);
  {
    base::RunLoop run_loop;
    run_loop.RunUntilIdle();
  }

  // Now there should be no blocked requests.
  EXPECT_EQ(0u, listener->blocked_requests.size());

  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(0U, ipc_sender_.GetNumTasks());

  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id, false);
}

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(
        nullptr, nullptr, nullptr, profile_.GetTestingPrefService());
    network_delegate_.reset(new ChromeNetworkDelegate(event_router_.get()));
    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_;
  TestIPCSender ipc_sender_;
  scoped_refptr<EventRouterForwarder> event_router_;
  std::unique_ptr<ChromeNetworkDelegate> network_delegate_;
  std::unique_ptr<net::MockHostResolver> host_resolver_;
  std::unique_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");
  std::unique_ptr<net::URLRequest> request(
      context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_,
                              TRAFFIC_ANNOTATION_FOR_TESTS));

  // 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::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::RunLoop().Run();

  EXPECT_TRUE(!request->is_pending());
  // This cannot succeed as we send the request to a server that does not exist.
  EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, delegate_.request_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_DispatchEvent::ID != message->type())
      continue;
    ExtensionMsg_DispatchEvent::Param message_tuple;
    ExtensionMsg_DispatchEvent::Read(message, &message_tuple);
    const ExtensionMsg_DispatchEvent_Params& params =
        std::get<0>(message_tuple);

    if (params.event_name != std::string(keys::kOnSendHeadersEvent) + "/3")
      continue;

    const base::ListValue& event_args = std::get<1>(message_tuple);
    const base::DictionaryValue* event_arg_dict = nullptr;
    ASSERT_TRUE(event_args.GetDictionary(0, &event_arg_dict));

    const base::ListValue* request_headers = nullptr;
    ASSERT_TRUE(event_arg_dict->GetList(keys::kRequestHeadersKey,
                                        &request_headers));

    net::HttpRequestHeaders observed_headers;
    for (size_t j = 0; j < request_headers->GetSize(); ++j) {
      const base::DictionaryValue* header = nullptr;
      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::EventListener::ID id1(
      &profile_, extension1_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id2(
      &profile_, extension2_id, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::EventListener::ID id3(
      &profile_, extension3_id, std::string(keys::kOnSendHeadersEvent) + "/3",
      0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id1,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id2,
                                                                     false);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(id3,
                                                                     false);
};

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.AppendInteger('1');
  list_value.AppendInteger('2');
  list_value.AppendInteger('3');
  list_value.AppendInteger(0xFE);
  list_value.AppendInteger(0xD1);

  unsigned char char_value[] = {'1', '2', '3', 0xFE, 0xD1};
  std::string string_value(reinterpret_cast<char *>(char_value), 5);

  std::unique_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");
  std::unique_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");
  std::unique_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");
  std::unique_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");
  std::unique_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");
  std::unique_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"
      "X-Chrome-ID-Consistency-Response: Value6\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
  new_headers.push_back(ResponseHeader("X-Chrome-ID-Consistency-Response",
                                       "Value1"));  // Modified
  GURL url;

  // The X-Chrome-ID-Consistency-Response is a protected header, but only for
  // Gaia URLs. It should be modifiable when sent from anywhere else.
  // Non-Gaia URL:
  std::unique_ptr<EventResponseDelta> delta(
      CalculateOnHeadersReceivedDelta("extid", base::Time::Now(), cancel, url,
                                      url, base_headers.get(), &new_headers));
  ASSERT_TRUE(delta.get());
  EXPECT_TRUE(delta->cancel);
  EXPECT_EQ(3u, delta->added_response_headers.size());
  EXPECT_TRUE(base::ContainsValue(delta->added_response_headers,
                                  ResponseHeader("Key2", "Value1")));
  EXPECT_TRUE(base::ContainsValue(delta->added_response_headers,
                                  ResponseHeader("Key4", "Value4")));
  EXPECT_TRUE(base::ContainsValue(
      delta->added_response_headers,
      ResponseHeader("X-Chrome-ID-Consistency-Response", "Value1")));
  EXPECT_EQ(3u, delta->deleted_response_headers.size());
  EXPECT_TRUE(base::ContainsValue(delta->deleted_response_headers,
                                  ResponseHeader("Key2", "Value2, Bar")));
  EXPECT_TRUE(base::ContainsValue(delta->deleted_response_headers,
                                  ResponseHeader("Key3", "Value3")));
  EXPECT_TRUE(base::ContainsValue(
      delta->deleted_response_headers,
      ResponseHeader("X-Chrome-ID-Consistency-Response", "Value6")));

  // Gaia URL:
  delta.reset(CalculateOnHeadersReceivedDelta(
      "extid", base::Time::Now(), cancel, GaiaUrls::GetInstance()->gaia_url(),
      url, base_headers.get(), &new_headers));
  ASSERT_TRUE(delta.get());
  EXPECT_TRUE(delta->cancel);
  EXPECT_EQ(2u, delta->added_response_headers.size());
  EXPECT_TRUE(base::ContainsValue(delta->added_response_headers,
                                  ResponseHeader("Key2", "Value1")));
  EXPECT_TRUE(base::ContainsValue(delta->added_response_headers,
                                  ResponseHeader("Key4", "Value4")));
  EXPECT_EQ(2u, delta->deleted_response_headers.size());
  EXPECT_TRUE(base::ContainsValue(delta->deleted_response_headers,
                                  ResponseHeader("Key2", "Value2, Bar")));
  EXPECT_TRUE(base::ContainsValue(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");
  std::unique_ptr<net::AuthCredentials> credentials(
      new net::AuthCredentials(username, password));

  std::unique_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;
  TestLogger logger;
  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, &logger);
  EXPECT_FALSE(canceled);
  EXPECT_EQ(0u, logger.log_size());

  // 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, &logger);
  EXPECT_TRUE(canceled);
  EXPECT_EQ(1u, logger.log_size());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses) {
  EventResponseDeltas deltas;
  TestLogger logger;
  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(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  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);
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, logger.log_size());

  // 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();
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(2u, logger.log_size());

  // 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();
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  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, logger.log_size());

  // 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();
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  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, logger.log_size());
}

// This tests that we can redirect to data:// urls, which is considered
// a kind of cancelling requests.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses2) {
  EventResponseDeltas deltas;
  TestLogger logger;
  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(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  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();
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, logger.log_size());

  // 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();
  logger.clear();

  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(2u, logger.log_size());

  // 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();
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid3"));
  EXPECT_EQ(3u, logger.log_size());
}

// This tests that we can redirect to about:blank, which is considered
// a kind of cancelling requests.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses3) {
  EventResponseDeltas deltas;
  TestLogger logger;
  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(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  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();
  logger.clear();
  MergeOnBeforeRequestResponses(GURL(kExampleUrl), deltas, &effective_new_url,
                                &warning_set, &logger);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, logger.log_size());
}

// This tests that WebSocket requests can not be redirected.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses4) {
  EventResponseDeltas deltas;
  TestLogger logger;
  WarningSet warning_set;
  GURL effective_new_url;

  // Single redirect.
  linked_ptr<EventResponseDelta> delta(
      new EventResponseDelta("extid", base::Time::FromInternalValue(2000)));
  delta->new_url = GURL("http://foo.com");
  deltas.push_back(delta);
  MergeOnBeforeRequestResponses(GURL("ws://example.com"), deltas,
                                &effective_new_url, &warning_set, &logger);
  EXPECT_EQ(GURL(), effective_new_url);
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
  net::HttpRequestHeaders base_headers;
  base_headers.SetHeader("key1", "value 1");
  base_headers.SetHeader("key2", "value 2");
  TestLogger logger;
  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);
  bool request_headers_modified0;
  net::HttpRequestHeaders headers0;
  headers0.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(GURL(), deltas, &headers0, &warning_set,
                                    &logger, &request_headers_modified0);
  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, logger.log_size());
  EXPECT_FALSE(request_headers_modified0);

  // 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.SetHeader("key2", "value 3");
  d1->modified_request_headers.SetHeader("key3", "value 3");
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  logger.clear();
  bool request_headers_modified1;
  net::HttpRequestHeaders headers1;
  headers1.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(GURL(), deltas, &headers1, &warning_set,
                                    &logger, &request_headers_modified1);
  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, logger.log_size());
  EXPECT_TRUE(request_headers_modified1);

  // 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.SetHeader("key3", "value 0");
  d2->modified_request_headers.SetHeader("key4", "value 4");
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  logger.clear();
  bool request_headers_modified2;
  net::HttpRequestHeaders headers2;
  headers2.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(GURL(), deltas, &headers2, &warning_set,
                                    &logger, &request_headers_modified2);
  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, logger.log_size());
  EXPECT_TRUE(request_headers_modified2);

  // 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.SetHeader("key2", "value 3");
  d3->modified_request_headers.SetHeader("key5", "value 5");
  deltas.push_back(d3);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  logger.clear();
  bool request_headers_modified3;
  net::HttpRequestHeaders headers3;
  headers3.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(GURL(), deltas, &headers3, &warning_set,
                                    &logger, &request_headers_modified3);
  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, logger.log_size());
  EXPECT_TRUE(request_headers_modified3);
}

TEST(ExtensionWebRequestHelpersTest,
     TestMergeOnBeforeSendHeadersResponses_Cookies) {
  net::HttpRequestHeaders base_headers;
  base_headers.AddHeaderFromString(
      "Cookie: name=value; name2=value2; name3=\"value3\"");
  TestLogger logger;
  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);
  bool request_headers_modified1;
  net::HttpRequestHeaders headers1;
  headers1.MergeFrom(base_headers);
  warning_set.clear();
  MergeOnBeforeSendHeadersResponses(GURL(), deltas, &headers1, &warning_set,
                                    &logger, &request_headers_modified1);
  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, logger.log_size());
  EXPECT_FALSE(request_headers_modified1);
}

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) {
  TestLogger logger;
  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(
      GURL(), deltas, base_headers.get(), &new_headers0, &warning_set, &logger);
  EXPECT_FALSE(new_headers0.get());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, logger.log_size());

  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(
      GURL(), deltas, headers1.get(), &new_headers1, &warning_set, &logger);

  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, logger.log_size());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) {
  TestLogger logger;
  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);
  bool response_headers_modified0;
  scoped_refptr<net::HttpResponseHeaders> new_headers0;
  GURL allowed_unsafe_redirect_url0;
  MergeOnHeadersReceivedResponses(GURL(kExampleUrl), deltas, base_headers.get(),
                                  &new_headers0, &allowed_unsafe_redirect_url0,
                                  &warning_set, &logger,
                                  &response_headers_modified0);
  EXPECT_FALSE(new_headers0.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url0.is_empty());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, logger.log_size());
  EXPECT_FALSE(response_headers_modified0);

  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();
  logger.clear();
  bool response_headers_modified1;
  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  GURL allowed_unsafe_redirect_url1;
  MergeOnHeadersReceivedResponses(GURL(kExampleUrl), deltas, base_headers.get(),
                                  &new_headers1, &allowed_unsafe_redirect_url1,
                                  &warning_set, &logger,
                                  &response_headers_modified1);
  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, logger.log_size());
  EXPECT_TRUE(response_headers_modified1);

  // 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();
  logger.clear();
  bool response_headers_modified2;
  scoped_refptr<net::HttpResponseHeaders> new_headers2;
  GURL allowed_unsafe_redirect_url2;
  MergeOnHeadersReceivedResponses(GURL(kExampleUrl), deltas, base_headers.get(),
                                  &new_headers2, &allowed_unsafe_redirect_url2,
                                  &warning_set, &logger,
                                  &response_headers_modified2);
  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, logger.log_size());
  EXPECT_TRUE(response_headers_modified2);
}

// Check that we do not delete too much
TEST(ExtensionWebRequestHelpersTest,
     TestMergeOnHeadersReceivedResponsesDeletion) {
  TestLogger logger;
  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);
  bool response_headers_modified1;
  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  GURL allowed_unsafe_redirect_url1;
  MergeOnHeadersReceivedResponses(GURL(kExampleUrl), deltas, base_headers.get(),
                                  &new_headers1, &allowed_unsafe_redirect_url1,
                                  &warning_set, &logger,
                                  &response_headers_modified1);
  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, logger.log_size());
  EXPECT_TRUE(response_headers_modified1);
}

// 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;
  TestLogger logger;
  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);
  bool response_headers_modified0;
  scoped_refptr<net::HttpResponseHeaders> new_headers0;
  GURL allowed_unsafe_redirect_url0;
  MergeOnHeadersReceivedResponses(GURL(kExampleUrl), deltas, base_headers.get(),
                                  &new_headers0, &allowed_unsafe_redirect_url0,
                                  &warning_set, &logger,
                                  &response_headers_modified0);
  EXPECT_FALSE(new_headers0.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url0.is_empty());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, logger.log_size());
  EXPECT_FALSE(response_headers_modified0);

  // 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);
  logger.clear();
  bool response_headers_modified1;

  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  GURL allowed_unsafe_redirect_url1;
  MergeOnHeadersReceivedResponses(GURL(kExampleUrl), deltas, base_headers.get(),
                                  &new_headers1, &allowed_unsafe_redirect_url1,
                                  &warning_set, &logger,
                                  &response_headers_modified1);

  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, logger.log_size());
  EXPECT_FALSE(response_headers_modified1);
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnAuthRequiredResponses) {
  TestLogger logger;
  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, &logger);
  EXPECT_FALSE(credentials_set);
  EXPECT_TRUE(auth0.Empty());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, logger.log_size());

  // 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();
  logger.clear();
  net::AuthCredentials auth1;
  credentials_set =
      MergeOnAuthRequiredResponses(deltas, &auth1, &warning_set, &logger);
  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, logger.log_size());

  // 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();
  logger.clear();
  net::AuthCredentials auth2;
  credentials_set =
      MergeOnAuthRequiredResponses(deltas, &auth2, &warning_set, &logger);
  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, logger.log_size());

  // 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();
  logger.clear();
  net::AuthCredentials auth3;
  credentials_set =
      MergeOnAuthRequiredResponses(deltas, &auth3, &warning_set, &logger);
  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, logger.log_size());
}

}  // namespace extensions
