// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <queue>
#include <tuple>

#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/extensions/event_router_forwarder.h"
#include "chrome/browser/net/chrome_network_delegate.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/about_handler/about_protocol_handler.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/prefs/pref_member.h"
#include "components/syncable_prefs/testing_pref_service_syncable.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/api/web_request/upload_data_presenter.h"
#include "extensions/browser/api/web_request/web_request_api.h"
#include "extensions/browser/api/web_request/web_request_api_constants.h"
#include "extensions/browser/api/web_request/web_request_api_helpers.h"
#include "extensions/browser/warning_set.h"
#include "extensions/common/api/web_request.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/features/feature.h"
#include "net/base/auth.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/request_priority.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_file_element_reader.h"
#include "net/dns/mock_host_resolver.h"
#include "net/log/test_net_log.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest-message.h"
#include "testing/gtest/include/gtest/gtest.h"

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

using base::BinaryValue;
using base::DictionaryValue;
using base::ListValue;
using base::StringValue;
using base::Time;
using base::TimeDelta;
using base::Value;
using helpers::CalculateOnAuthRequiredDelta;
using helpers::CalculateOnBeforeRequestDelta;
using helpers::CalculateOnBeforeSendHeadersDelta;
using helpers::CalculateOnHeadersReceivedDelta;
using helpers::CharListToString;
using helpers::EventResponseDelta;
using helpers::EventResponseDeltas;
using helpers::EventResponseDeltas;
using helpers::ExtraInfoSpec;
using helpers::InDecreasingExtensionInstallationTimeOrder;
using helpers::MergeCancelOfResponses;
using helpers::MergeOnBeforeRequestResponses;
using helpers::RequestCookieModification;
using helpers::ResponseCookieModification;
using helpers::ResponseHeader;
using helpers::ResponseHeaders;
using helpers::StringToCharList;

namespace extensions {

namespace {
static void EventHandledOnIOThread(
    void* profile,
    const std::string& extension_id,
    const std::string& event_name,
    const std::string& sub_event_name,
    uint64_t request_id,
    ExtensionWebRequestEventRouter::EventResponse* response) {
  ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
      profile, extension_id, event_name, sub_event_name, request_id,
      response);
}

// Searches |key| in |collection| by iterating over its elements and returns
// true if found.
template <typename Collection, typename Key>
bool Contains(const Collection& collection, const Key& key) {
  return std::find(collection.begin(), collection.end(), key) !=
      collection.end();
}

// Returns whether |warnings| contains an extension for |extension_id|.
bool HasWarning(const WarningSet& warnings,
                const std::string& extension_id) {
  for (WarningSet::const_iterator i = warnings.begin();
       i != warnings.end(); ++i) {
    if (i->extension_id() == extension_id)
      return true;
  }
  return false;
}

// Parses the JSON data attached to the |message| and tries to return it.
// |param| must outlive |out|. Returns NULL on failure.
void GetPartOfMessageArguments(IPC::Message* message,
                               const base::DictionaryValue** out,
                               ExtensionMsg_MessageInvoke::Param* param) {
  ASSERT_EQ(ExtensionMsg_MessageInvoke::ID, message->type());
  ASSERT_TRUE(ExtensionMsg_MessageInvoke::Read(message, param));
  ASSERT_GE(std::get<3>(*param).GetSize(), 2u);
  const base::Value* value = NULL;
  ASSERT_TRUE(std::get<3>(*param).Get(1, &value));
  const base::ListValue* list = NULL;
  ASSERT_TRUE(value->GetAsList(&list));
  ASSERT_EQ(1u, list->GetSize());
  ASSERT_TRUE(list->GetDictionary(0, out));
}

}  // namespace

// A mock event router that responds to events with a pre-arranged queue of
// Tasks.
class TestIPCSender : public IPC::Sender {
 public:
  typedef std::list<linked_ptr<IPC::Message> > SentMessages;

  // Adds a Task to the queue. We will fire these in order as events are
  // dispatched.
  void PushTask(const base::Closure& task) {
    task_queue_.push(task);
  }

  size_t GetNumTasks() { return task_queue_.size(); }

  SentMessages::const_iterator sent_begin() const {
    return sent_messages_.begin();
  }

  SentMessages::const_iterator sent_end() const {
    return sent_messages_.end();
  }

 private:
  // IPC::Sender
  bool Send(IPC::Message* message) override {
    EXPECT_EQ(ExtensionMsg_MessageInvoke::ID, message->type());

    EXPECT_FALSE(task_queue_.empty());
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  task_queue_.front());
    task_queue_.pop();

    sent_messages_.push_back(linked_ptr<IPC::Message>(message));
    return true;
  }

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

class ExtensionWebRequestTest : public testing::Test {
 public:
  ExtensionWebRequestTest()
      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
        profile_manager_(TestingBrowserProcess::GetGlobal()),
        event_router_(new EventRouterForwarder) {}

 protected:
  void SetUp() override {
    ASSERT_TRUE(profile_manager_.SetUp());
    ChromeNetworkDelegate::InitializePrefsOnUIThread(
        &enable_referrers_, NULL, NULL, NULL,
        profile_.GetTestingPrefService());
    network_delegate_.reset(
        new ChromeNetworkDelegate(event_router_.get(), &enable_referrers_));
    network_delegate_->set_profile(&profile_);
    network_delegate_->set_cookie_settings(
        CookieSettingsFactory::GetForProfile(&profile_).get());
    context_.reset(new net::TestURLRequestContext(true));
    context_->set_network_delegate(network_delegate_.get());
    context_->Init();
  }

  // Fires a URLRequest with the specified |method|, |content_type| and three
  // elements of upload data: bytes_1, a dummy empty file, bytes_2.
  void FireURLRequestWithData(const std::string& method,
                              const char* content_type,
                              const std::vector<char>& bytes_1,
                              const std::vector<char>& bytes_2);

  content::TestBrowserThreadBundle thread_bundle_;
  TestingProfile profile_;
  TestingProfileManager profile_manager_;
  net::TestDelegate delegate_;
  BooleanPrefMember enable_referrers_;
  TestIPCSender ipc_sender_;
  scoped_refptr<EventRouterForwarder> event_router_;
  scoped_refptr<InfoMap> extension_info_map_;
  scoped_ptr<ChromeNetworkDelegate> network_delegate_;
  scoped_ptr<net::TestURLRequestContext> context_;
};

// Tests that we handle disagreements among extensions about responses to
// blocking events (redirection) by choosing the response from the
// most-recently-installed extension.
TEST_F(ExtensionWebRequestTest, BlockingEventPrecedenceRedirect) {
  std::string extension1_id("1");
  std::string extension2_id("2");
  ExtensionWebRequestEventRouter::RequestFilter filter;
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension1_id, extension1_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension2_id, extension2_id, events::FOR_TEST, kEventName,
      kEventName + "/2", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  net::URLRequestJobFactoryImpl job_factory;
  job_factory.SetProtocolHandler(
      url::kAboutScheme,
      make_scoped_ptr(new about_handler::AboutProtocolHandler()));
  context_->set_job_factory(&job_factory);

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

  scoped_ptr<net::URLRequest> request(context_->CreateRequest(
      GURL("about:blank"), net::DEFAULT_PRIORITY, &delegate_));
  {
    // onBeforeRequest will be dispatched twice initially. The second response -
    // the redirect - should win, since it has a later |install_time|. The
    // redirect will dispatch another pair of onBeforeRequest. There, the first
    // response should win (later |install_time|).
    ExtensionWebRequestEventRouter::EventResponse* response = NULL;

    // Extension1 response. Arrives first, but ignored due to install_time.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension1_id, base::Time::FromDoubleT(1));
    response->new_url = not_chosen_redirect_url;
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension1_id, kEventName, kEventName + "/1",
            request->identifier(), response));

    // Extension2 response. Arrives second, and chosen because of install_time.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension2_id, base::Time::FromDoubleT(2));
    response->new_url = redirect_url;
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension2_id, kEventName, kEventName + "/2",
            request->identifier(), response));

    // Extension2 response to the redirected URL. Arrives first, and chosen.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension2_id, base::Time::FromDoubleT(2));
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension2_id, kEventName, kEventName + "/2",
            request->identifier(), response));

    // Extension1 response to the redirected URL. Arrives second, and ignored.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension1_id, base::Time::FromDoubleT(1));
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension1_id, kEventName, kEventName + "/1",
            request->identifier(), response));

    request->Start();
    base::MessageLoop::current()->Run();

    EXPECT_TRUE(!request->is_pending());
    EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
    EXPECT_EQ(0, request->status().error());
    EXPECT_EQ(redirect_url, request->url());
    EXPECT_EQ(2U, request->url_chain().size());
    EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
  }

  // Now test the same thing but the extensions answer in reverse order.
  scoped_ptr<net::URLRequest> request2(context_->CreateRequest(
      GURL("about:blank"), net::DEFAULT_PRIORITY, &delegate_));
  {
    ExtensionWebRequestEventRouter::EventResponse* response = NULL;

    // Extension2 response. Arrives first, and chosen because of install_time.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension2_id, base::Time::FromDoubleT(2));
    response->new_url = redirect_url;
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension2_id, kEventName, kEventName + "/2",
            request2->identifier(), response));

    // Extension1 response. Arrives second, but ignored due to install_time.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension1_id, base::Time::FromDoubleT(1));
    response->new_url = not_chosen_redirect_url;
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension1_id, kEventName, kEventName + "/1",
            request2->identifier(), response));

    // Extension2 response to the redirected URL. Arrives first, and chosen.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension2_id, base::Time::FromDoubleT(2));
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension2_id, kEventName, kEventName + "/2",
            request2->identifier(), response));

    // Extension1 response to the redirected URL. Arrives second, and ignored.
    response = new ExtensionWebRequestEventRouter::EventResponse(
        extension1_id, base::Time::FromDoubleT(1));
    ipc_sender_.PushTask(
        base::Bind(&EventHandledOnIOThread,
            &profile_, extension1_id, kEventName, kEventName + "/1",
            request2->identifier(), response));

    request2->Start();
    base::MessageLoop::current()->Run();

    EXPECT_TRUE(!request2->is_pending());
    EXPECT_EQ(net::URLRequestStatus::SUCCESS, request2->status().status());
    EXPECT_EQ(0, request2->status().error());
    EXPECT_EQ(redirect_url, request2->url());
    EXPECT_EQ(2U, request2->url_chain().size());
    EXPECT_EQ(0U, ipc_sender_.GetNumTasks());
  }

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

// Test that a request is canceled if this is requested by any extension
// regardless whether it is the extension with the highest precedence.
TEST_F(ExtensionWebRequestTest, BlockingEventPrecedenceCancel) {
  std::string extension1_id("1");
  std::string extension2_id("2");
  ExtensionWebRequestEventRouter::RequestFilter filter;
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension1_id, extension1_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension2_id, extension2_id, events::FOR_TEST, kEventName,
      kEventName + "/2", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  GURL request_url("about:blank");
  scoped_ptr<net::URLRequest> request(context_->CreateRequest(
      request_url, net::DEFAULT_PRIORITY, &delegate_));

  // onBeforeRequest will be dispatched twice. The second response -
  // the redirect - would win, since it has a later |install_time|, but
  // the first response takes precedence because cancel >> redirect.
  GURL redirect_url("about:redirected");
  ExtensionWebRequestEventRouter::EventResponse* response = NULL;

  // Extension1 response. Arrives first, would be ignored in principle due to
  // install_time but "cancel" always wins.
  response = new ExtensionWebRequestEventRouter::EventResponse(
      extension1_id, base::Time::FromDoubleT(1));
  response->cancel = true;
  ipc_sender_.PushTask(
      base::Bind(&EventHandledOnIOThread,
          &profile_, extension1_id, kEventName, kEventName + "/1",
          request->identifier(), response));

  // Extension2 response. Arrives second, but has higher precedence
  // due to its later install_time.
  response = new ExtensionWebRequestEventRouter::EventResponse(
      extension2_id, base::Time::FromDoubleT(2));
  response->new_url = redirect_url;
  ipc_sender_.PushTask(
      base::Bind(&EventHandledOnIOThread,
          &profile_, extension2_id, kEventName, kEventName + "/2",
          request->identifier(), response));

  request->Start();

  base::MessageLoop::current()->Run();

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

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

TEST_F(ExtensionWebRequestTest, SimulateChancelWhileBlocked) {
  // We subscribe to OnBeforeRequest and OnErrorOccurred.
  // While the OnBeforeRequest handler is blocked, we cancel the request.
  // We verify that the response of the blocked OnBeforeRequest handler
  // is ignored.

  std::string extension_id("1");
  ExtensionWebRequestEventRouter::RequestFilter filter;

  // Subscribe to OnBeforeRequest and OnErrorOccurred.
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  const std::string kEventName2(web_request::OnErrorOccurred::kEventName);
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id, extension_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id, extension_id, events::FOR_TEST, kEventName2,
      kEventName2 + "/1", filter, 0, 0, 0, ipc_sender_factory.GetWeakPtr());

  GURL request_url("about:blank");
  scoped_ptr<net::URLRequest> request(context_->CreateRequest(
      request_url, net::DEFAULT_PRIORITY, &delegate_));

  ExtensionWebRequestEventRouter::EventResponse* response = NULL;

  // Extension response for the OnBeforeRequest handler. This should not be
  // processed because request is canceled before the handler responds.
  response = new ExtensionWebRequestEventRouter::EventResponse(
      extension_id, base::Time::FromDoubleT(1));
  GURL redirect_url("about:redirected");
  response->new_url = redirect_url;
  ipc_sender_.PushTask(
      base::Bind(&EventHandledOnIOThread,
          &profile_, extension_id, kEventName, kEventName + "/1",
          request->identifier(), response));

  // Extension response for OnErrorOccurred: Terminate the message loop.
  ipc_sender_.PushTask(
      base::Bind(&base::MessageLoop::PostTask,
                 base::Unretained(base::MessageLoop::current()), FROM_HERE,
                 base::MessageLoop::QuitWhenIdleClosure()));

  request->Start();
  // request->Start() will have submitted OnBeforeRequest by the time we cancel.
  request->Cancel();
  base::MessageLoop::current()->Run();

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

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

namespace {

// Create the numerical representation of |values|, strings passed as
// extraInfoSpec by the event handler. Returns true on success, otherwise false.
bool GenerateInfoSpec(const std::string& values, int* result) {
  // Create a base::ListValue of strings.
  base::ListValue list_value;
  for (const std::string& cur :
       base::SplitString(values, ",", base::KEEP_WHITESPACE,
                         base::SPLIT_WANT_NONEMPTY))
    list_value.Append(new base::StringValue(cur));
  return ExtraInfoSpec::InitFromValue(list_value, result);
}

}  // namespace

void ExtensionWebRequestTest::FireURLRequestWithData(
    const std::string& method,
    const char* content_type,
    const std::vector<char>& bytes_1,
    const std::vector<char>& bytes_2) {
  // The request URL can be arbitrary but must have an HTTP or HTTPS scheme.
  GURL request_url("http://www.example.com");
  scoped_ptr<net::URLRequest> request(context_->CreateRequest(
      request_url, net::DEFAULT_PRIORITY, &delegate_));
  request->set_method(method);
  if (content_type != NULL) {
    request->SetExtraRequestHeaderByName(net::HttpRequestHeaders::kContentType,
                                         content_type,
                                         true /* overwrite */);
  }
  std::vector<scoped_ptr<net::UploadElementReader>> element_readers;
  element_readers.push_back(make_scoped_ptr(
      new net::UploadBytesElementReader(&(bytes_1[0]), bytes_1.size())));
  element_readers.push_back(make_scoped_ptr(new net::UploadFileElementReader(
      base::ThreadTaskRunnerHandle::Get().get(), base::FilePath(), 0, 0,
      base::Time())));
  element_readers.push_back(make_scoped_ptr(
      new net::UploadBytesElementReader(&(bytes_2[0]), bytes_2.size())));
  request->set_upload(make_scoped_ptr(
      new net::ElementsUploadDataStream(std::move(element_readers), 0)));
  ipc_sender_.PushTask(base::Bind(&base::DoNothing));
  request->Start();
}

TEST_F(ExtensionWebRequestTest, AccessRequestBodyData) {
  // We verify that URLRequest body is accessible to OnBeforeRequest listeners.
  // These testing steps are repeated twice in a row:
  // 1. Register an extension requesting "requestBody" in ExtraInfoSpec and
  //    file a POST URLRequest with a multipart-encoded form. See it getting
  //    parsed.
  // 2. Do the same, but without requesting "requestBody". Nothing should be
  //    parsed.
  // 3. With "requestBody", fire a POST URLRequest which is not a parseable
  //    HTML form. Raw data should be returned.
  // 4. Do the same, but with a PUT method. Result should be the same.
  const std::string kMethodPost("POST");
  const std::string kMethodPut("PUT");

  // Input.
  const char kPlainBlock1[] = "abcd\n";
  const size_t kPlainBlock1Length = sizeof(kPlainBlock1) - 1;
  std::vector<char> plain_1(kPlainBlock1, kPlainBlock1 + kPlainBlock1Length);
  const char kPlainBlock2[] = "1234\n";
  const size_t kPlainBlock2Length = sizeof(kPlainBlock2) - 1;
  std::vector<char> plain_2(kPlainBlock2, kPlainBlock2 + kPlainBlock2Length);
#define kBoundary "THIS_IS_A_BOUNDARY"
  const char kFormBlock1[] = "--" kBoundary "\r\n"
      "Content-Disposition: form-data; name=\"A\"\r\n"
      "\r\n"
      "test text\r\n"
      "--" kBoundary "\r\n"
      "Content-Disposition: form-data; name=\"B\"; filename=\"\"\r\n"
      "Content-Type: application/octet-stream\r\n"
      "\r\n";
  std::vector<char> form_1(kFormBlock1, kFormBlock1 + sizeof(kFormBlock1) - 1);
  const char kFormBlock2[] = "\r\n"
      "--" kBoundary "\r\n"
      "Content-Disposition: form-data; name=\"C\"\r\n"
      "\r\n"
      "test password\r\n"
      "--" kBoundary "--";
  std::vector<char> form_2(kFormBlock2, kFormBlock2 + sizeof(kFormBlock2) - 1);

  // Expected output.
  // Paths to look for in returned dictionaries.
  const std::string kBodyPath(keys::kRequestBodyKey);
  const std::string kFormDataPath(
      kBodyPath + "." + keys::kRequestBodyFormDataKey);
  const std::string kRawPath(kBodyPath + "." + keys::kRequestBodyRawKey);
  const std::string kErrorPath(kBodyPath + "." + keys::kRequestBodyErrorKey);
  const std::string* const kPath[] = {
    &kFormDataPath,
    &kBodyPath,
    &kRawPath,
    &kRawPath
  };
  // Contents of formData.
  const char kFormData[] =
      "{\"A\":[\"test text\"],\"B\":[\"\"],\"C\":[\"test password\"]}";
  scoped_ptr<const base::Value> form_data = base::JSONReader::Read(kFormData);
  ASSERT_TRUE(form_data.get() != NULL);
  ASSERT_TRUE(form_data->GetType() == base::Value::TYPE_DICTIONARY);
  // Contents of raw.
  base::ListValue raw;
  extensions::subtle::AppendKeyValuePair(
      keys::kRequestBodyRawBytesKey,
      BinaryValue::CreateWithCopiedBuffer(kPlainBlock1, kPlainBlock1Length),
      &raw);
  extensions::subtle::AppendKeyValuePair(
      keys::kRequestBodyRawFileKey,
      new base::StringValue(std::string()),
      &raw);
  extensions::subtle::AppendKeyValuePair(
      keys::kRequestBodyRawBytesKey,
      BinaryValue::CreateWithCopiedBuffer(kPlainBlock2, kPlainBlock2Length),
      &raw);
  // Summary.
  const base::Value* const kExpected[] = {
    form_data.get(),
    NULL,
    &raw,
    &raw,
  };
  static_assert(arraysize(kPath) == arraysize(kExpected),
                "kPath and kExpected arrays should have the same number "
                "of elements");
  // Header.
  const char kMultipart[] = "multipart/form-data; boundary=" kBoundary;
#undef kBoundary

  // Set up a dummy extension name.
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  ExtensionWebRequestEventRouter::RequestFilter filter;
  std::string extension_id("1");
  const std::string string_spec_post("blocking,requestBody");
  const std::string string_spec_no_post("blocking");
  int extra_info_spec_empty = 0;
  int extra_info_spec_body = 0;
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);

  // Part 1.
  // Subscribe to OnBeforeRequest with requestBody requirement.
  ASSERT_TRUE(GenerateInfoSpec(string_spec_post, &extra_info_spec_body));
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id, extension_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, extra_info_spec_body, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  FireURLRequestWithData(kMethodPost, kMultipart, form_1, form_2);

  // We inspect the result in the message list of |ipc_sender_| later.
  base::MessageLoop::current()->RunUntilIdle();

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

  // Part 2.
  // Now subscribe to OnBeforeRequest *without* the requestBody requirement.
  ASSERT_TRUE(
      GenerateInfoSpec(string_spec_no_post, &extra_info_spec_empty));
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id, extension_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, extra_info_spec_empty, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  FireURLRequestWithData(kMethodPost, kMultipart, form_1, form_2);

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

  // Subscribe to OnBeforeRequest with requestBody requirement.
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id, extension_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, extra_info_spec_body, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  // Part 3.
  // Now send a POST request with body which is not parseable as a form.
  FireURLRequestWithData(kMethodPost, NULL /*no header*/, plain_1, plain_2);

  // Part 4.
  // Now send a PUT request with the same body as above.
  FireURLRequestWithData(kMethodPut, NULL /*no header*/, plain_1, plain_2);

  base::MessageLoop::current()->RunUntilIdle();

  // Clean-up.
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension_id, kEventName + "/1", 0, 0);

  IPC::Message* message = NULL;
  TestIPCSender::SentMessages::const_iterator i = ipc_sender_.sent_begin();
  for (size_t test = 0; test < arraysize(kExpected); ++test) {
    SCOPED_TRACE(testing::Message("iteration number ") << test);
    EXPECT_NE(i, ipc_sender_.sent_end());
    message = (i++)->get();
    const base::DictionaryValue* details;
    ExtensionMsg_MessageInvoke::Param param;
    GetPartOfMessageArguments(message, &details, &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::Bind(&base::DoNothing));

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

  base::MessageLoop::current()->RunUntilIdle();

  // Clean-up
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension_id1, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension_id2, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension_id1, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension_id2, kEventName + "/2", 0, 0);

  TestIPCSender::SentMessages::const_iterator i = ipc_sender_.sent_begin();

  for (size_t test = 0; test < arraysize(kExpected); ++test, ++i) {
    SCOPED_TRACE(testing::Message("iteration number ") << test);
    EXPECT_NE(i, ipc_sender_.sent_end());
    IPC::Message* message = i->get();
    const base::DictionaryValue* details = nullptr;
    ExtensionMsg_MessageInvoke::Param param;
    GetPartOfMessageArguments(message, &details, &param);
    ASSERT_TRUE(details != nullptr);
    EXPECT_EQ(kExpected[test], details->HasKey(keys::kRequestBodyKey));
  }

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

TEST_F(ExtensionWebRequestTest, NoAccessRequestBodyData) {
  // We verify that URLRequest body is NOT accessible to OnBeforeRequest
  // listeners when the type of the request is different from POST or PUT, or
  // when the request body is empty. 3 requests are fired, without upload data,
  // a POST, PUT and GET request. For none of them the "requestBody" object
  // property should be present in the details passed to the onBeforeRequest
  // event listener.
  const char* const kMethods[] = { "POST", "PUT", "GET" };

  // Set up a dummy extension name.
  const std::string kEventName(web_request::OnBeforeRequest::kEventName);
  ExtensionWebRequestEventRouter::RequestFilter filter;
  const std::string extension_id("1");
  int extra_info_spec = 0;
  ASSERT_TRUE(GenerateInfoSpec("blocking,requestBody", &extra_info_spec));
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);

  // Subscribe to OnBeforeRequest with requestBody requirement.
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension_id, extension_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, extra_info_spec, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  // The request URL can be arbitrary but must have an HTTP or HTTPS scheme.
  const GURL request_url("http://www.example.com");

  for (size_t i = 0; i < arraysize(kMethods); ++i) {
    scoped_ptr<net::URLRequest> request(context_->CreateRequest(
        request_url, net::DEFAULT_PRIORITY, &delegate_));
    request->set_method(kMethods[i]);
    ipc_sender_.PushTask(base::Bind(&base::DoNothing));
    request->Start();
  }

  // We inspect the result in the message list of |ipc_sender_| later.
  base::MessageLoop::current()->RunUntilIdle();

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

  TestIPCSender::SentMessages::const_iterator i = ipc_sender_.sent_begin();
  for (size_t test = 0; test < arraysize(kMethods); ++test, ++i) {
    SCOPED_TRACE(testing::Message("iteration number ") << test);
    EXPECT_NE(i, ipc_sender_.sent_end());
    IPC::Message* message = i->get();
    const base::DictionaryValue* details = NULL;
    ExtensionMsg_MessageInvoke::Param param;
    GetPartOfMessageArguments(message, &details, &param);
    ASSERT_TRUE(details != NULL);
    EXPECT_FALSE(details->HasKey(keys::kRequestBodyKey));
  }

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

struct HeaderModificationTest_Header {
  const char* name;
  const char* value;
};

struct HeaderModificationTest_Modification {
  enum Type {
    SET,
    REMOVE
  };

  int extension_id;
  Type type;
  const char* key;
  const char* value;
};

struct HeaderModificationTest {
  int before_size;
  HeaderModificationTest_Header before[10];
  int modification_size;
  HeaderModificationTest_Modification modification[10];
  int after_size;
  HeaderModificationTest_Header after[10];
};

class ExtensionWebRequestHeaderModificationTest
    : public testing::TestWithParam<HeaderModificationTest> {
 public:
  ExtensionWebRequestHeaderModificationTest()
      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
        profile_manager_(TestingBrowserProcess::GetGlobal()),
        event_router_(new EventRouterForwarder) {}

 protected:
  void SetUp() override {
    ASSERT_TRUE(profile_manager_.SetUp());
    ChromeNetworkDelegate::InitializePrefsOnUIThread(
        &enable_referrers_, NULL, NULL, NULL,
        profile_.GetTestingPrefService());
    network_delegate_.reset(
        new ChromeNetworkDelegate(event_router_.get(), &enable_referrers_));
    network_delegate_->set_profile(&profile_);
    network_delegate_->set_cookie_settings(
        CookieSettingsFactory::GetForProfile(&profile_).get());
    context_.reset(new net::TestURLRequestContext(true));
    host_resolver_.reset(new net::MockHostResolver());
    host_resolver_->rules()->AddSimulatedFailure("doesnotexist");
    context_->set_host_resolver(host_resolver_.get());
    context_->set_network_delegate(network_delegate_.get());
    context_->Init();
  }

  content::TestBrowserThreadBundle thread_bundle_;
  TestingProfile profile_;
  TestingProfileManager profile_manager_;
  net::TestDelegate delegate_;
  BooleanPrefMember enable_referrers_;
  TestIPCSender ipc_sender_;
  scoped_refptr<EventRouterForwarder> event_router_;
  scoped_refptr<InfoMap> extension_info_map_;
  scoped_ptr<ChromeNetworkDelegate> network_delegate_;
  scoped_ptr<net::MockHostResolver> host_resolver_;
  scoped_ptr<net::TestURLRequestContext> context_;
};

TEST_P(ExtensionWebRequestHeaderModificationTest, TestModifications) {
  std::string extension1_id("1");
  std::string extension2_id("2");
  std::string extension3_id("3");
  ExtensionWebRequestEventRouter::RequestFilter filter;
  const std::string kEventName(keys::kOnBeforeSendHeadersEvent);
  base::WeakPtrFactory<TestIPCSender> ipc_sender_factory(&ipc_sender_);

  // Install two extensions that can modify headers. Extension 2 has
  // higher precedence than extension 1.
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension1_id, extension1_id, events::FOR_TEST, kEventName,
      kEventName + "/1", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension2_id, extension2_id, events::FOR_TEST, kEventName,
      kEventName + "/2", filter, ExtraInfoSpec::BLOCKING, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  // Install one extension that observes the final headers.
  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
      &profile_, extension3_id, extension3_id, events::FOR_TEST,
      keys::kOnSendHeadersEvent, std::string(keys::kOnSendHeadersEvent) + "/3",
      filter, ExtraInfoSpec::REQUEST_HEADERS, 0, 0,
      ipc_sender_factory.GetWeakPtr());

  GURL request_url("http://doesnotexist/does_not_exist.html");
  scoped_ptr<net::URLRequest> request(context_->CreateRequest(
      request_url, net::DEFAULT_PRIORITY, &delegate_));

  // Initialize headers available before extensions are notified of the
  // onBeforeSendHeaders event.
  HeaderModificationTest test = GetParam();
  net::HttpRequestHeaders before_headers;
  for (int i = 0; i < test.before_size; ++i)
    before_headers.SetHeader(test.before[i].name, test.before[i].value);
  request->SetExtraRequestHeaders(before_headers);

  // Gather the modifications to the headers for the respective extensions.
  // We assume here that all modifications of one extension are listed
  // in a continuous block of |test.modifications_|.
  ExtensionWebRequestEventRouter::EventResponse* response = NULL;
  for (int i = 0; i < test.modification_size; ++i) {
    const HeaderModificationTest_Modification& mod = test.modification[i];
    if (response == NULL) {
      response = new ExtensionWebRequestEventRouter::EventResponse(
          mod.extension_id == 1 ? extension1_id : extension2_id,
          base::Time::FromDoubleT(mod.extension_id));
      response->request_headers.reset(new net::HttpRequestHeaders());
      response->request_headers->MergeFrom(request->extra_request_headers());
    }

    switch (mod.type) {
      case HeaderModificationTest_Modification::SET:
        response->request_headers->SetHeader(mod.key, mod.value);
        break;
      case HeaderModificationTest_Modification::REMOVE:
        response->request_headers->RemoveHeader(mod.key);
        break;
    }

    // Trigger the result when this is the last modification statement or
    // the block of modifications for the next extension starts.
    if (i+1 == test.modification_size ||
        mod.extension_id != test.modification[i+1].extension_id) {
      ipc_sender_.PushTask(
          base::Bind(&EventHandledOnIOThread,
              &profile_, mod.extension_id == 1 ? extension1_id : extension2_id,
              kEventName, kEventName + (mod.extension_id == 1 ? "/1" : "/2"),
              request->identifier(), response));
      response = NULL;
    }
  }

  // Don't do anything for the onSendHeaders message.
  ipc_sender_.PushTask(base::Bind(&base::DoNothing));

  // Note that we mess up the headers slightly:
  // request->Start() will first add additional headers (e.g. the User-Agent)
  // and then send an event to the extension. When we have prepared our
  // answers to the onBeforeSendHeaders events above, these headers did not
  // exists and are therefore not listed in the responses. This makes
  // them seem deleted.
  request->Start();
  base::MessageLoop::current()->Run();

  EXPECT_TRUE(!request->is_pending());
  // This cannot succeed as we send the request to a server that does not exist.
  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
  EXPECT_EQ(request_url, request->url());
  EXPECT_EQ(1U, request->url_chain().size());
  EXPECT_EQ(0U, ipc_sender_.GetNumTasks());

  // Calculate the expected headers.
  net::HttpRequestHeaders expected_headers;
  for (int i = 0; i < test.after_size; ++i) {
    expected_headers.SetHeader(test.after[i].name,
                               test.after[i].value);
  }

  // Counter for the number of observed onSendHeaders events.
  int num_headers_observed = 0;

  // Search the onSendHeaders signal in the IPC messages and check that
  // it contained the correct headers.
  TestIPCSender::SentMessages::const_iterator i;
  for (i = ipc_sender_.sent_begin(); i != ipc_sender_.sent_end(); ++i) {
    IPC::Message* message = i->get();
    if (ExtensionMsg_MessageInvoke::ID != message->type())
      continue;
    ExtensionMsg_MessageInvoke::Param message_tuple;
    ExtensionMsg_MessageInvoke::Read(message, &message_tuple);
    base::ListValue& args = std::get<3>(message_tuple);

    std::string event_name;
    if (!args.GetString(0, &event_name) ||
        event_name !=  std::string(keys::kOnSendHeadersEvent) + "/3") {
      continue;
    }

    base::ListValue* event_arg = NULL;
    ASSERT_TRUE(args.GetList(1, &event_arg));

    base::DictionaryValue* event_arg_dict = NULL;
    ASSERT_TRUE(event_arg->GetDictionary(0, &event_arg_dict));

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

    net::HttpRequestHeaders observed_headers;
    for (size_t j = 0; j < request_headers->GetSize(); ++j) {
      base::DictionaryValue* header = NULL;
      ASSERT_TRUE(request_headers->GetDictionary(j, &header));
      std::string key;
      std::string value;
      ASSERT_TRUE(header->GetString(keys::kHeaderNameKey, &key));
      ASSERT_TRUE(header->GetString(keys::kHeaderValueKey, &value));
      observed_headers.SetHeader(key, value);
    }

    EXPECT_EQ(expected_headers.ToString(), observed_headers.ToString());
    ++num_headers_observed;
  }
  EXPECT_EQ(1, num_headers_observed);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension1_id, kEventName + "/1", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension2_id, kEventName + "/2", 0, 0);
  ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
      &profile_, extension3_id,
      std::string(keys::kOnSendHeadersEvent) + "/3", 0, 0);
};

namespace {

void TestInitFromValue(const std::string& values, bool expected_return_code,
                       int expected_extra_info_spec) {
  int actual_info_spec;
  bool actual_return_code = GenerateInfoSpec(values, &actual_info_spec);
  EXPECT_EQ(expected_return_code, actual_return_code);
  if (expected_return_code)
    EXPECT_EQ(expected_extra_info_spec, actual_info_spec);
}

}  // namespace

TEST_F(ExtensionWebRequestTest, InitFromValue) {
  TestInitFromValue(std::string(), true, 0);

  // Single valid values.
  TestInitFromValue(
      "requestHeaders",
      true,
      ExtraInfoSpec::REQUEST_HEADERS);
  TestInitFromValue(
      "responseHeaders",
      true,
      ExtraInfoSpec::RESPONSE_HEADERS);
  TestInitFromValue(
      "blocking",
      true,
      ExtraInfoSpec::BLOCKING);
  TestInitFromValue(
      "asyncBlocking",
      true,
      ExtraInfoSpec::ASYNC_BLOCKING);
  TestInitFromValue(
      "requestBody",
      true,
      ExtraInfoSpec::REQUEST_BODY);

  // Multiple valid values are bitwise-or'ed.
  TestInitFromValue(
      "requestHeaders,blocking",
      true,
      ExtraInfoSpec::REQUEST_HEADERS | ExtraInfoSpec::BLOCKING);

  // Any invalid values lead to a bad parse.
  TestInitFromValue("invalidValue", false, 0);
  TestInitFromValue("blocking,invalidValue", false, 0);
  TestInitFromValue("invalidValue1,invalidValue2", false, 0);

  // BLOCKING and ASYNC_BLOCKING are mutually exclusive.
  TestInitFromValue("blocking,asyncBlocking", false, 0);
}

namespace {

const HeaderModificationTest_Modification::Type SET =
    HeaderModificationTest_Modification::SET;
const HeaderModificationTest_Modification::Type REMOVE =
    HeaderModificationTest_Modification::REMOVE;

HeaderModificationTest kTests[] = {
  // Check that extension 2 always wins when settings the same header.
  {
    // Headers before test.
    2, { {"header1", "value1"},
         {"header2", "value2"} },
    // Modifications in test.
    2, { {1, SET, "header1", "foo"},
         {2, SET, "header1", "bar"} },
    // Headers after test.
    2, { {"header1", "bar"},
         {"header2", "value2"} }
  },
  // Same as before in reverse execution order.
  {
    // Headers before test.
    2, { {"header1", "value1"},
         {"header2", "value2"} },
    // Modifications in test.
    2, { {2, SET, "header1", "bar"},
         {1, SET, "header1", "foo"} },
    // Headers after test.
    2, { {"header1", "bar"},
         {"header2", "value2"} }
  },
  // Check that two extensions can modify different headers that do not
  // conflict.
  {
    // Headers before test.
    2, { {"header1", "value1"},
         {"header2", "value2"} },
    // Modifications in test.
    2, { {1, SET, "header1", "foo"},
         {2, SET, "header2", "bar"} },
    // Headers after test.
    2, { {"header1", "foo"},
         {"header2", "bar"} }
  },
  // Check insert/delete conflict.
  {
    // Headers before test.
    1, { {"header1", "value1"} },
    // Modifications in test.
    2, { {1, SET, "header1", "foo"},
         {2, REMOVE, "header1", NULL} },
    // Headers after test.
    0, { }
  },
  {
    // Headers before test.
    1, { {"header1", "value1"} },
    // Modifications in test.
    2, { {2, REMOVE, "header1", NULL},
         {1, SET, "header1", "foo"} },
    // Headers after test.
    0, {}
  },
  {
    // Headers before test.
    1, { {"header1", "value1"} },
    // Modifications in test.
    2, { {1, REMOVE, "header1", NULL},
         {2, SET, "header1", "foo"} },
    // Headers after test.
    1, { {"header1", "foo"} }
  },
  {
    // Headers before test.
    1, { {"header1", "value1"} },
    // Modifications in test.
    2, { {2, SET, "header1", "foo"},
         {1, REMOVE, "header1", NULL} },
    // Headers after test.
    1, { {"header1", "foo"} }
  },
  // Check that edits are atomic (i.e. either all edit requests of an
  // extension are executed or none).
  {
    // Headers before test.
    0, { },
    // Modifications in test.
    3, { {1, SET, "header1", "value1"},
         {1, SET, "header2", "value2"},
         {2, SET, "header1", "foo"} },
    // Headers after test.
    1, { {"header1", "foo"} }  // set(header2) is ignored
  },
  // Check that identical edits do not conflict (set(header2) would be ignored
  // if set(header1) were considered a conflict).
  {
    // Headers before test.
    0, { },
    // Modifications in test.
    3, { {1, SET, "header1", "value2"},
         {1, SET, "header2", "foo"},
         {2, SET, "header1", "value2"} },
    // Headers after test.
    2, { {"header1", "value2"},
         {"header2", "foo"} }
  },
  // Check that identical deletes do not conflict (set(header2) would be ignored
  // if delete(header1) were considered a conflict).
  {
    // Headers before test.
    1, { {"header1", "value1"} },
    // Modifications in test.
    3, { {1, REMOVE, "header1", NULL},
         {1, SET, "header2", "foo"},
         {2, REMOVE, "header1", NULL} },
    // Headers after test.
    1, { {"header2", "foo"} }
  },
  // Check that setting a value to an identical value is not considered an
  // edit operation that can conflict.
  {
    // Headers before test.
    1, { {"header1", "value1"} },
    // Modifications in test.
    3, { {1, SET, "header1", "foo"},
         {1, SET, "header2", "bar"},
         {2, SET, "header1", "value1"} },
    // Headers after test.
    2, { {"header1", "foo"},
         {"header2", "bar"} }
  },
};

INSTANTIATE_TEST_CASE_P(
    ExtensionWebRequest,
    ExtensionWebRequestHeaderModificationTest,
    ::testing::ValuesIn(kTests));

}  // namespace


TEST(ExtensionWebRequestHelpersTest,
     TestInDecreasingExtensionInstallationTimeOrder) {
  linked_ptr<EventResponseDelta> a(
      new EventResponseDelta("ext_1", base::Time::FromInternalValue(0)));
  linked_ptr<EventResponseDelta> b(
      new EventResponseDelta("ext_2", base::Time::FromInternalValue(1000)));
  EXPECT_FALSE(InDecreasingExtensionInstallationTimeOrder(a, a));
  EXPECT_FALSE(InDecreasingExtensionInstallationTimeOrder(a, b));
  EXPECT_TRUE(InDecreasingExtensionInstallationTimeOrder(b, a));
}

TEST(ExtensionWebRequestHelpersTest, TestStringToCharList) {
  base::ListValue list_value;
  list_value.Append(new base::FundamentalValue('1'));
  list_value.Append(new base::FundamentalValue('2'));
  list_value.Append(new base::FundamentalValue('3'));
  list_value.Append(new base::FundamentalValue(0xFE));
  list_value.Append(new base::FundamentalValue(0xD1));

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

  scoped_ptr<base::ListValue> converted_list(StringToCharList(string_value));
  EXPECT_TRUE(list_value.Equals(converted_list.get()));

  std::string converted_string;
  EXPECT_TRUE(CharListToString(&list_value, &converted_string));
  EXPECT_EQ(string_value, converted_string);
}

TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeRequestDelta) {
  const bool cancel = true;
  const GURL localhost("http://localhost");
  scoped_ptr<EventResponseDelta> delta(
      CalculateOnBeforeRequestDelta("extid", base::Time::Now(),
          cancel, localhost));
  ASSERT_TRUE(delta.get());
  EXPECT_TRUE(delta->cancel);
  EXPECT_EQ(localhost, delta->new_url);
}

TEST(ExtensionWebRequestHelpersTest, TestCalculateOnBeforeSendHeadersDelta) {
  const bool cancel = true;
  std::string value;
  net::HttpRequestHeaders old_headers;
  old_headers.AddHeadersFromString("key1: value1\r\n"
                                   "key2: value2\r\n");

  // Test adding a header.
  net::HttpRequestHeaders new_headers_added;
  new_headers_added.AddHeadersFromString("key1: value1\r\n"
                                         "key3: value3\r\n"
                                         "key2: value2\r\n");
  scoped_ptr<EventResponseDelta> delta_added(
      CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
          &old_headers, &new_headers_added));
  ASSERT_TRUE(delta_added.get());
  EXPECT_TRUE(delta_added->cancel);
  ASSERT_TRUE(delta_added->modified_request_headers.GetHeader("key3", &value));
  EXPECT_EQ("value3", value);

  // Test deleting a header.
  net::HttpRequestHeaders new_headers_deleted;
  new_headers_deleted.AddHeadersFromString("key1: value1\r\n");
  scoped_ptr<EventResponseDelta> delta_deleted(
      CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
          &old_headers, &new_headers_deleted));
  ASSERT_TRUE(delta_deleted.get());
  ASSERT_EQ(1u, delta_deleted->deleted_request_headers.size());
  ASSERT_EQ("key2", delta_deleted->deleted_request_headers.front());

  // Test modifying a header.
  net::HttpRequestHeaders new_headers_modified;
  new_headers_modified.AddHeadersFromString("key1: value1\r\n"
                                            "key2: value3\r\n");
  scoped_ptr<EventResponseDelta> delta_modified(
      CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
          &old_headers, &new_headers_modified));
  ASSERT_TRUE(delta_modified.get());
  EXPECT_TRUE(delta_modified->deleted_request_headers.empty());
  ASSERT_TRUE(
      delta_modified->modified_request_headers.GetHeader("key2", &value));
  EXPECT_EQ("value3", value);

  // Test modifying a header if extension author just appended a new (key,
  // value) pair with a key that existed before. This is incorrect
  // usage of the API that shall be handled gracefully.
  net::HttpRequestHeaders new_headers_modified2;
  new_headers_modified2.AddHeadersFromString("key1: value1\r\n"
                                             "key2: value2\r\n"
                                             "key2: value3\r\n");
  scoped_ptr<EventResponseDelta> delta_modified2(
      CalculateOnBeforeSendHeadersDelta("extid", base::Time::Now(), cancel,
          &old_headers, &new_headers_modified));
  ASSERT_TRUE(delta_modified2.get());
  EXPECT_TRUE(delta_modified2->deleted_request_headers.empty());
  ASSERT_TRUE(
      delta_modified2->modified_request_headers.GetHeader("key2", &value));
  EXPECT_EQ("value3", value);
}

TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) {
  const bool cancel = true;
  char base_headers_string[] =
      "HTTP/1.0 200 OK\r\n"
      "Key1: Value1\r\n"
      "Key2: Value2, Bar\r\n"
      "Key3: Value3\r\n"
      "Key5: Value5, end5\r\n"
      "\r\n";
  scoped_refptr<net::HttpResponseHeaders> base_headers(
      new net::HttpResponseHeaders(
        net::HttpUtil::AssembleRawHeaders(
            base_headers_string, sizeof(base_headers_string))));

  ResponseHeaders new_headers;
  new_headers.push_back(ResponseHeader("kEy1", "Value1"));  // Unchanged
  new_headers.push_back(ResponseHeader("Key2", "Value1"));  // Modified
  // Key3 is deleted
  new_headers.push_back(ResponseHeader("Key4", "Value4"));  // Added
  new_headers.push_back(ResponseHeader("Key5", "Value5, end5"));  // Unchanged
  GURL effective_new_url;

  scoped_ptr<EventResponseDelta> delta(
      CalculateOnHeadersReceivedDelta("extid",
                                      base::Time::Now(),
                                      cancel,
                                      effective_new_url,
                                      base_headers.get(),
                                      &new_headers));
  ASSERT_TRUE(delta.get());
  EXPECT_TRUE(delta->cancel);
  EXPECT_EQ(2u, delta->added_response_headers.size());
  EXPECT_TRUE(Contains(delta->added_response_headers,
                       ResponseHeader("Key2", "Value1")));
  EXPECT_TRUE(Contains(delta->added_response_headers,
                       ResponseHeader("Key4", "Value4")));
  EXPECT_EQ(2u, delta->deleted_response_headers.size());
  EXPECT_TRUE(Contains(delta->deleted_response_headers,
                        ResponseHeader("Key2", "Value2, Bar")));
  EXPECT_TRUE(Contains(delta->deleted_response_headers,
                       ResponseHeader("Key3", "Value3")));
}

TEST(ExtensionWebRequestHelpersTest, TestCalculateOnAuthRequiredDelta) {
  const bool cancel = true;

  base::string16 username = base::ASCIIToUTF16("foo");
  base::string16 password = base::ASCIIToUTF16("bar");
  scoped_ptr<net::AuthCredentials> credentials(
      new net::AuthCredentials(username, password));

  scoped_ptr<EventResponseDelta> delta(
      CalculateOnAuthRequiredDelta("extid", base::Time::Now(), cancel,
          &credentials));
  ASSERT_TRUE(delta.get());
  EXPECT_TRUE(delta->cancel);
  ASSERT_TRUE(delta->auth_credentials.get());
  EXPECT_EQ(username, delta->auth_credentials->username());
  EXPECT_EQ(password, delta->auth_credentials->password());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeCancelOfResponses) {
  EventResponseDeltas deltas;
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  bool canceled = false;

  // Single event that does not cancel.
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(1000)));
  d1->cancel = false;
  deltas.push_back(d1);
  MergeCancelOfResponses(deltas, &canceled, &net_log);
  EXPECT_FALSE(canceled);
  EXPECT_EQ(0u, capturing_net_log.GetSize());

  // Second event that cancels the request
  linked_ptr<EventResponseDelta> d2(
      new EventResponseDelta("extid2", base::Time::FromInternalValue(500)));
  d2->cancel = true;
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  MergeCancelOfResponses(deltas, &canceled, &net_log);
  EXPECT_TRUE(canceled);
  EXPECT_EQ(1u, capturing_net_log.GetSize());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses) {
  EventResponseDeltas deltas;
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  GURL effective_new_url;

  // No redirect
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(0)));
  deltas.push_back(d0);
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_TRUE(effective_new_url.is_empty());

  // Single redirect.
  GURL new_url_1("http://foo.com");
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(1000)));
  d1->new_url = GURL(new_url_1);
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, capturing_net_log.GetSize());

  // Ignored redirect (due to precedence).
  GURL new_url_2("http://bar.com");
  linked_ptr<EventResponseDelta> d2(
      new EventResponseDelta("extid2", base::Time::FromInternalValue(500)));
  d2->new_url = GURL(new_url_2);
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(2u, capturing_net_log.GetSize());

  // Overriding redirect.
  GURL new_url_3("http://baz.com");
  linked_ptr<EventResponseDelta> d3(
      new EventResponseDelta("extid3", base::Time::FromInternalValue(1500)));
  d3->new_url = GURL(new_url_3);
  deltas.push_back(d3);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_3, effective_new_url);
  EXPECT_EQ(2u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid1"));
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(3u, capturing_net_log.GetSize());

  // Check that identical redirects don't cause a conflict.
  linked_ptr<EventResponseDelta> d4(
      new EventResponseDelta("extid4", base::Time::FromInternalValue(2000)));
  d4->new_url = GURL(new_url_3);
  deltas.push_back(d4);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_3, effective_new_url);
  EXPECT_EQ(2u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid1"));
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(4u, capturing_net_log.GetSize());
}

// This tests that we can redirect to data:// urls, which is considered
// a kind of cancelling requests.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses2) {
  EventResponseDeltas deltas;
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  GURL effective_new_url;

  // Single redirect.
  GURL new_url_0("http://foo.com");
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(2000)));
  d0->new_url = GURL(new_url_0);
  deltas.push_back(d0);
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_0, effective_new_url);

  // Cancel request by redirecting to a data:// URL. This shall override
  // the other redirect but not cause any conflict warnings.
  GURL new_url_1("data://foo");
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(1500)));
  d1->new_url = GURL(new_url_1);
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, capturing_net_log.GetSize());

  // Cancel request by redirecting to the same data:// URL. This shall
  // not create any conflicts as it is in line with d1.
  GURL new_url_2("data://foo");
  linked_ptr<EventResponseDelta> d2(
      new EventResponseDelta("extid2", base::Time::FromInternalValue(1000)));
  d2->new_url = GURL(new_url_2);
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(2u, capturing_net_log.GetSize());

  // Cancel redirect by redirecting to a different data:// URL. This needs
  // to create a conflict.
  GURL new_url_3("data://something_totally_different");
  linked_ptr<EventResponseDelta> d3(
      new EventResponseDelta("extid3", base::Time::FromInternalValue(500)));
  d3->new_url = GURL(new_url_3);
  deltas.push_back(d3);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid3"));
  EXPECT_EQ(3u, capturing_net_log.GetSize());
}

// This tests that we can redirect to about:blank, which is considered
// a kind of cancelling requests.
TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeRequestResponses3) {
  EventResponseDeltas deltas;
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  GURL effective_new_url;

  // Single redirect.
  GURL new_url_0("http://foo.com");
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(2000)));
  d0->new_url = GURL(new_url_0);
  deltas.push_back(d0);
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_0, effective_new_url);

  // Cancel request by redirecting to about:blank. This shall override
  // the other redirect but not cause any conflict warnings.
  GURL new_url_1("about:blank");
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(1500)));
  d1->new_url = GURL(new_url_1);
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  MergeOnBeforeRequestResponses(
      deltas, &effective_new_url, &warning_set, &net_log);
  EXPECT_EQ(new_url_1, effective_new_url);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, capturing_net_log.GetSize());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
  net::HttpRequestHeaders base_headers;
  base_headers.AddHeaderFromString("key1: value 1");
  base_headers.AddHeaderFromString("key2: value 2");
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  std::string header_value;
  EventResponseDeltas deltas;

  // Check that we can handle not changing the headers.
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(2500)));
  deltas.push_back(d0);
  net::HttpRequestHeaders headers0;
  headers0.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(deltas, &headers0, &warning_set, &net_log);
  ASSERT_TRUE(headers0.GetHeader("key1", &header_value));
  EXPECT_EQ("value 1", header_value);
  ASSERT_TRUE(headers0.GetHeader("key2", &header_value));
  EXPECT_EQ("value 2", header_value);
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());

  // Delete, modify and add a header.
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
  d1->deleted_request_headers.push_back("key1");
  d1->modified_request_headers.AddHeaderFromString("key2: value 3");
  d1->modified_request_headers.AddHeaderFromString("key3: value 3");
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  net::HttpRequestHeaders headers1;
  headers1.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(deltas, &headers1, &warning_set, &net_log);
  EXPECT_FALSE(headers1.HasHeader("key1"));
  ASSERT_TRUE(headers1.GetHeader("key2", &header_value));
  EXPECT_EQ("value 3", header_value);
  ASSERT_TRUE(headers1.GetHeader("key3", &header_value));
  EXPECT_EQ("value 3", header_value);
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(1u, capturing_net_log.GetSize());

  // Check that conflicts are atomic, i.e. if one header modification
  // collides all other conflicts of the same extension are declined as well.
  linked_ptr<EventResponseDelta> d2(
      new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
  // This one conflicts:
  d2->modified_request_headers.AddHeaderFromString("key3: value 0");
  d2->modified_request_headers.AddHeaderFromString("key4: value 4");
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  net::HttpRequestHeaders headers2;
  headers2.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(deltas, &headers2, &warning_set, &net_log);
  EXPECT_FALSE(headers2.HasHeader("key1"));
  ASSERT_TRUE(headers2.GetHeader("key2", &header_value));
  EXPECT_EQ("value 3", header_value);
  ASSERT_TRUE(headers2.GetHeader("key3", &header_value));
  EXPECT_EQ("value 3", header_value);
  EXPECT_FALSE(headers2.HasHeader("key4"));
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(2u, capturing_net_log.GetSize());

  // Check that identical modifications don't conflict and operations
  // can be merged.
  linked_ptr<EventResponseDelta> d3(
      new EventResponseDelta("extid3", base::Time::FromInternalValue(1000)));
  d3->deleted_request_headers.push_back("key1");
  d3->modified_request_headers.AddHeaderFromString("key2: value 3");
  d3->modified_request_headers.AddHeaderFromString("key5: value 5");
  deltas.push_back(d3);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  net::HttpRequestHeaders headers3;
  headers3.MergeFrom(base_headers);
  MergeOnBeforeSendHeadersResponses(deltas, &headers3, &warning_set, &net_log);
  EXPECT_FALSE(headers3.HasHeader("key1"));
  ASSERT_TRUE(headers3.GetHeader("key2", &header_value));
  EXPECT_EQ("value 3", header_value);
  ASSERT_TRUE(headers3.GetHeader("key3", &header_value));
  EXPECT_EQ("value 3", header_value);
  ASSERT_TRUE(headers3.GetHeader("key5", &header_value));
  EXPECT_EQ("value 5", header_value);
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(3u, capturing_net_log.GetSize());
}

TEST(ExtensionWebRequestHelpersTest,
     TestMergeOnBeforeSendHeadersResponses_Cookies) {
  net::HttpRequestHeaders base_headers;
  base_headers.AddHeaderFromString(
      "Cookie: name=value; name2=value2; name3=\"value3\"");
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  std::string header_value;
  EventResponseDeltas deltas;

  linked_ptr<RequestCookieModification> add_cookie =
      make_linked_ptr(new RequestCookieModification);
  add_cookie->type = helpers::ADD;
  add_cookie->modification.reset(new helpers::RequestCookie);
  add_cookie->modification->name.reset(new std::string("name4"));
  add_cookie->modification->value.reset(new std::string("\"value 4\""));

  linked_ptr<RequestCookieModification> add_cookie_2 =
      make_linked_ptr(new RequestCookieModification);
  add_cookie_2->type = helpers::ADD;
  add_cookie_2->modification.reset(new helpers::RequestCookie);
  add_cookie_2->modification->name.reset(new std::string("name"));
  add_cookie_2->modification->value.reset(new std::string("new value"));

  linked_ptr<RequestCookieModification> edit_cookie =
      make_linked_ptr(new RequestCookieModification);
  edit_cookie->type = helpers::EDIT;
  edit_cookie->filter.reset(new helpers::RequestCookie);
  edit_cookie->filter->name.reset(new std::string("name2"));
  edit_cookie->modification.reset(new helpers::RequestCookie);
  edit_cookie->modification->value.reset(new std::string("new value"));

  linked_ptr<RequestCookieModification> remove_cookie =
      make_linked_ptr(new RequestCookieModification);
  remove_cookie->type = helpers::REMOVE;
  remove_cookie->filter.reset(new helpers::RequestCookie);
  remove_cookie->filter->name.reset(new std::string("name3"));

  linked_ptr<RequestCookieModification> operations[] = {
      add_cookie, add_cookie_2, edit_cookie, remove_cookie
  };

  for (size_t i = 0; i < arraysize(operations); ++i) {
    linked_ptr<EventResponseDelta> delta(
        new EventResponseDelta("extid0", base::Time::FromInternalValue(i * 5)));
    delta->request_cookie_modifications.push_back(operations[i]);
    deltas.push_back(delta);
  }
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  net::HttpRequestHeaders headers1;
  headers1.MergeFrom(base_headers);
  warning_set.clear();
  MergeOnBeforeSendHeadersResponses(deltas, &headers1, &warning_set, &net_log);
  EXPECT_TRUE(headers1.HasHeader("Cookie"));
  ASSERT_TRUE(headers1.GetHeader("Cookie", &header_value));
  EXPECT_EQ("name=new value; name2=new value; name4=\"value 4\"", header_value);
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());
}

namespace {

std::string GetCookieExpirationDate(int delta_secs) {
  const char* const kWeekDays[] = {
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
  };
  const char* const kMonthNames[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };

  Time::Exploded exploded_time;
  (Time::Now() + TimeDelta::FromSeconds(delta_secs)).UTCExplode(&exploded_time);

  return base::StringPrintf("%s, %d %s %d %.2d:%.2d:%.2d GMT",
                            kWeekDays[exploded_time.day_of_week],
                            exploded_time.day_of_month,
                            kMonthNames[exploded_time.month - 1],
                            exploded_time.year,
                            exploded_time.hour,
                            exploded_time.minute,
                            exploded_time.second);
}

}  // namespace

TEST(ExtensionWebRequestHelpersTest,
     TestMergeCookiesInOnHeadersReceivedResponses) {
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  std::string header_value;
  EventResponseDeltas deltas;

  std::string cookie_expiration = GetCookieExpirationDate(1200);
  std::string base_headers_string =
      "HTTP/1.0 200 OK\r\n"
      "Foo: Bar\r\n"
      "Set-Cookie: name=value; DOMAIN=google.com; Secure\r\n"
      "Set-Cookie: name2=value2\r\n"
      "Set-Cookie: name3=value3\r\n"
      "Set-Cookie: lBound1=value5; Expires=" + cookie_expiration + "\r\n"
      "Set-Cookie: lBound2=value6; Max-Age=1200\r\n"
      "Set-Cookie: lBound3=value7; Max-Age=2000\r\n"
      "Set-Cookie: uBound1=value8; Expires=" + cookie_expiration + "\r\n"
      "Set-Cookie: uBound2=value9; Max-Age=1200\r\n"
      "Set-Cookie: uBound3=value10; Max-Age=2000\r\n"
      "Set-Cookie: uBound4=value11; Max-Age=2500\r\n"
      "Set-Cookie: uBound5=value12; Max-Age=600; Expires=" +
      cookie_expiration + "\r\n"
      "Set-Cookie: uBound6=removed; Max-Age=600\r\n"
      "Set-Cookie: sessionCookie=removed; Max-Age=INVALID\r\n"
      "Set-Cookie: sessionCookie2=removed\r\n"
      "\r\n";
  scoped_refptr<net::HttpResponseHeaders> base_headers(
      new net::HttpResponseHeaders(
          net::HttpUtil::AssembleRawHeaders(
              base_headers_string.c_str(), base_headers_string.size())));

  // Check that we can handle if not touching the response headers.
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(3000)));
  deltas.push_back(d0);
  scoped_refptr<net::HttpResponseHeaders> new_headers0;
  MergeCookiesInOnHeadersReceivedResponses(
        deltas, base_headers.get(), &new_headers0, &warning_set, &net_log);
  EXPECT_FALSE(new_headers0.get());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());

  linked_ptr<ResponseCookieModification> add_cookie =
      make_linked_ptr(new ResponseCookieModification);
  add_cookie->type = helpers::ADD;
  add_cookie->modification.reset(new helpers::ResponseCookie);
  add_cookie->modification->name.reset(new std::string("name4"));
  add_cookie->modification->value.reset(new std::string("\"value4\""));

  linked_ptr<ResponseCookieModification> edit_cookie =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie->type = helpers::EDIT;
  edit_cookie->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie->filter->name.reset(new std::string("name2"));
  edit_cookie->modification.reset(new helpers::ResponseCookie);
  edit_cookie->modification->value.reset(new std::string("new value"));

  linked_ptr<ResponseCookieModification> edit_cookie_2 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_2->type = helpers::EDIT;
  edit_cookie_2->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_2->filter->secure.reset(new bool(false));
  edit_cookie_2->modification.reset(new helpers::ResponseCookie);
  edit_cookie_2->modification->secure.reset(new bool(true));

  // Tests 'ageLowerBound' filter when cookie lifetime is set
  // in cookie's 'max-age' attribute and its value is greater than
  // the filter's value.
  linked_ptr<ResponseCookieModification> edit_cookie_3 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_3->type = helpers::EDIT;
  edit_cookie_3->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_3->filter->name.reset(new std::string("lBound1"));
  edit_cookie_3->filter->age_lower_bound.reset(new int(600));
  edit_cookie_3->modification.reset(new helpers::ResponseCookie);
  edit_cookie_3->modification->value.reset(new std::string("greater_1"));

  // Cookie lifetime is set in the cookie's 'expires' attribute.
  linked_ptr<ResponseCookieModification> edit_cookie_4 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_4->type = helpers::EDIT;
  edit_cookie_4->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_4->filter->name.reset(new std::string("lBound2"));
  edit_cookie_4->filter->age_lower_bound.reset(new int(600));
  edit_cookie_4->modification.reset(new helpers::ResponseCookie);
  edit_cookie_4->modification->value.reset(new std::string("greater_2"));

  // Tests equality of the cookie lifetime with the filter value when
  // lifetime is set in the cookie's 'max-age' attribute.
  // Note: we don't test the equality when the lifetime is set in the 'expires'
  // attribute because the tests will be flaky. The reason is calculations will
  // depend on fetching the current time.
  linked_ptr<ResponseCookieModification> edit_cookie_5 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_5->type = helpers::EDIT;
  edit_cookie_5->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_5->filter->name.reset(new std::string("lBound3"));
  edit_cookie_5->filter->age_lower_bound.reset(new int(2000));
  edit_cookie_5->modification.reset(new helpers::ResponseCookie);
  edit_cookie_5->modification->value.reset(new std::string("equal_2"));

  // Tests 'ageUpperBound' filter when cookie lifetime is set
  // in cookie's 'max-age' attribute and its value is lower than
  // the filter's value.
  linked_ptr<ResponseCookieModification> edit_cookie_6 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_6->type = helpers::EDIT;
  edit_cookie_6->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_6->filter->name.reset(new std::string("uBound1"));
  edit_cookie_6->filter->age_upper_bound.reset(new int(2000));
  edit_cookie_6->modification.reset(new helpers::ResponseCookie);
  edit_cookie_6->modification->value.reset(new std::string("smaller_1"));

  // Cookie lifetime is set in the cookie's 'expires' attribute.
  linked_ptr<ResponseCookieModification> edit_cookie_7 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_7->type = helpers::EDIT;
  edit_cookie_7->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_7->filter->name.reset(new std::string("uBound2"));
  edit_cookie_7->filter->age_upper_bound.reset(new int(2000));
  edit_cookie_7->modification.reset(new helpers::ResponseCookie);
  edit_cookie_7->modification->value.reset(new std::string("smaller_2"));

  // Tests equality of the cookie lifetime with the filter value when
  // lifetime is set in the cookie's 'max-age' attribute.
  linked_ptr<ResponseCookieModification> edit_cookie_8 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_8->type = helpers::EDIT;
  edit_cookie_8->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_8->filter->name.reset(new std::string("uBound3"));
  edit_cookie_8->filter->age_upper_bound.reset(new int(2000));
  edit_cookie_8->modification.reset(new helpers::ResponseCookie);
  edit_cookie_8->modification->value.reset(new std::string("equal_4"));

  // Tests 'ageUpperBound' filter when cookie lifetime is greater
  // than the filter value. No modification is expected to be applied.
  linked_ptr<ResponseCookieModification> edit_cookie_9 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_9->type = helpers::EDIT;
  edit_cookie_9->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_9->filter->name.reset(new std::string("uBound4"));
  edit_cookie_9->filter->age_upper_bound.reset(new int(2501));
  edit_cookie_9->modification.reset(new helpers::ResponseCookie);
  edit_cookie_9->modification->value.reset(new std::string("Will not change"));

  // Tests 'ageUpperBound' filter when both 'max-age' and 'expires' cookie
  // attributes are provided. 'expires' value matches the filter, however
  // no modification to the cookie is expected because 'max-age' overrides
  // 'expires' and it does not match the filter.
  linked_ptr<ResponseCookieModification> edit_cookie_10 =
      make_linked_ptr(new ResponseCookieModification);
  edit_cookie_10->type = helpers::EDIT;
  edit_cookie_10->filter.reset(new helpers::FilterResponseCookie);
  edit_cookie_10->filter->name.reset(new std::string("uBound5"));
  edit_cookie_10->filter->age_upper_bound.reset(new int(800));
  edit_cookie_10->modification.reset(new helpers::ResponseCookie);
  edit_cookie_10->modification->value.reset(new std::string("Will not change"));

  linked_ptr<ResponseCookieModification> remove_cookie =
      make_linked_ptr(new ResponseCookieModification);
  remove_cookie->type = helpers::REMOVE;
  remove_cookie->filter.reset(new helpers::FilterResponseCookie);
  remove_cookie->filter->name.reset(new std::string("name3"));

  linked_ptr<ResponseCookieModification> remove_cookie_2 =
      make_linked_ptr(new ResponseCookieModification);
  remove_cookie_2->type = helpers::REMOVE;
  remove_cookie_2->filter.reset(new helpers::FilterResponseCookie);
  remove_cookie_2->filter->name.reset(new std::string("uBound6"));
  remove_cookie_2->filter->age_upper_bound.reset(new int(700));

  linked_ptr<ResponseCookieModification> remove_cookie_3 =
      make_linked_ptr(new ResponseCookieModification);
  remove_cookie_3->type = helpers::REMOVE;
  remove_cookie_3->filter.reset(new helpers::FilterResponseCookie);
  remove_cookie_3->filter->name.reset(new std::string("sessionCookie"));
  remove_cookie_3->filter->session_cookie.reset(new bool(true));

  linked_ptr<ResponseCookieModification> remove_cookie_4 =
        make_linked_ptr(new ResponseCookieModification);
  remove_cookie_4->type = helpers::REMOVE;
  remove_cookie_4->filter.reset(new helpers::FilterResponseCookie);
  remove_cookie_4->filter->name.reset(new std::string("sessionCookie2"));
  remove_cookie_4->filter->session_cookie.reset(new bool(true));

  linked_ptr<ResponseCookieModification> operations[] = {
      add_cookie, edit_cookie, edit_cookie_2, edit_cookie_3, edit_cookie_4,
      edit_cookie_5, edit_cookie_6, edit_cookie_7, edit_cookie_8,
      edit_cookie_9, edit_cookie_10, remove_cookie, remove_cookie_2,
      remove_cookie_3, remove_cookie_4
  };

  for (size_t i = 0; i < arraysize(operations); ++i) {
    linked_ptr<EventResponseDelta> delta(
        new EventResponseDelta("extid0", base::Time::FromInternalValue(i * 5)));
    delta->response_cookie_modifications.push_back(operations[i]);
    deltas.push_back(delta);
  }
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  scoped_refptr<net::HttpResponseHeaders> headers1(
      new net::HttpResponseHeaders(
          net::HttpUtil::AssembleRawHeaders(
              base_headers_string.c_str(), base_headers_string.size())));
  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  warning_set.clear();
  MergeCookiesInOnHeadersReceivedResponses(
      deltas, headers1.get(), &new_headers1, &warning_set, &net_log);

  EXPECT_TRUE(new_headers1->HasHeader("Foo"));
  size_t iter = 0;
  std::string cookie_string;
  std::set<std::string> expected_cookies;
  expected_cookies.insert("name=value; domain=google.com; secure");
  expected_cookies.insert("name2=value2; secure");
  expected_cookies.insert("name4=\"value4\"; secure");
  expected_cookies.insert(
      "lBound1=greater_1; expires=" + cookie_expiration + "; secure");
  expected_cookies.insert("lBound2=greater_2; max-age=1200; secure");
  expected_cookies.insert("lBound3=equal_2; max-age=2000; secure");
  expected_cookies.insert(
      "uBound1=smaller_1; expires=" + cookie_expiration + "; secure");
  expected_cookies.insert("uBound2=smaller_2; max-age=1200; secure");
  expected_cookies.insert("uBound3=equal_4; max-age=2000; secure");
  expected_cookies.insert("uBound4=value11; max-age=2500; secure");
  expected_cookies.insert(
      "uBound5=value12; max-age=600; expires=" + cookie_expiration+ "; secure");
  std::set<std::string> actual_cookies;
  while (new_headers1->EnumerateHeader(&iter, "Set-Cookie", &cookie_string))
    actual_cookies.insert(cookie_string);
  EXPECT_EQ(expected_cookies, actual_cookies);
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnHeadersReceivedResponses) {
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  std::string header_value;
  EventResponseDeltas deltas;

  char base_headers_string[] =
      "HTTP/1.0 200 OK\r\n"
      "Key1: Value1\r\n"
      "Key2: Value2, Foo\r\n"
      "\r\n";
  scoped_refptr<net::HttpResponseHeaders> base_headers(
      new net::HttpResponseHeaders(
        net::HttpUtil::AssembleRawHeaders(
            base_headers_string, sizeof(base_headers_string))));

  // Check that we can handle if not touching the response headers.
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(3000)));
  deltas.push_back(d0);
  scoped_refptr<net::HttpResponseHeaders> new_headers0;
  GURL allowed_unsafe_redirect_url0;
  MergeOnHeadersReceivedResponses(deltas,
                                  base_headers.get(),
                                  &new_headers0,
                                  &allowed_unsafe_redirect_url0,
                                  &warning_set,
                                  &net_log);
  EXPECT_FALSE(new_headers0.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url0.is_empty());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());

  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
  d1->deleted_response_headers.push_back(ResponseHeader("KEY1", "Value1"));
  d1->deleted_response_headers.push_back(ResponseHeader("KEY2", "Value2, Foo"));
  d1->added_response_headers.push_back(ResponseHeader("Key2", "Value3"));
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  GURL allowed_unsafe_redirect_url1;
  MergeOnHeadersReceivedResponses(deltas,
                                  base_headers.get(),
                                  &new_headers1,
                                  &allowed_unsafe_redirect_url1,
                                  &warning_set,
                                  &net_log);
  ASSERT_TRUE(new_headers1.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url1.is_empty());
  std::multimap<std::string, std::string> expected1;
  expected1.insert(std::pair<std::string, std::string>("Key2", "Value3"));
  size_t iter = 0;
  std::string name;
  std::string value;
  std::multimap<std::string, std::string> actual1;
  while (new_headers1->EnumerateHeaderLines(&iter, &name, &value)) {
    actual1.insert(std::pair<std::string, std::string>(name, value));
  }
  EXPECT_EQ(expected1, actual1);
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(1u, capturing_net_log.GetSize());

  // Check that we replace response headers only once.
  linked_ptr<EventResponseDelta> d2(
      new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
  // Note that we use a different capitalization of KeY2. This should not
  // matter.
  d2->deleted_response_headers.push_back(ResponseHeader("KeY2", "Value2, Foo"));
  d2->added_response_headers.push_back(ResponseHeader("Key2", "Value4"));
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  scoped_refptr<net::HttpResponseHeaders> new_headers2;
  GURL allowed_unsafe_redirect_url2;
  MergeOnHeadersReceivedResponses(deltas,
                                  base_headers.get(),
                                  &new_headers2,
                                  &allowed_unsafe_redirect_url2,
                                  &warning_set,
                                  &net_log);
  ASSERT_TRUE(new_headers2.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url2.is_empty());
  iter = 0;
  std::multimap<std::string, std::string> actual2;
  while (new_headers2->EnumerateHeaderLines(&iter, &name, &value)) {
    actual2.insert(std::pair<std::string, std::string>(name, value));
  }
  EXPECT_EQ(expected1, actual2);
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(2u, capturing_net_log.GetSize());
}

// Check that we do not delete too much
TEST(ExtensionWebRequestHelpersTest,
     TestMergeOnHeadersReceivedResponsesDeletion) {
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  std::string header_value;
  EventResponseDeltas deltas;

  char base_headers_string[] =
      "HTTP/1.0 200 OK\r\n"
      "Key1: Value1\r\n"
      "Key1: Value2\r\n"
      "Key1: Value3\r\n"
      "Key2: Value4\r\n"
      "\r\n";
  scoped_refptr<net::HttpResponseHeaders> base_headers(
      new net::HttpResponseHeaders(
        net::HttpUtil::AssembleRawHeaders(
            base_headers_string, sizeof(base_headers_string))));

  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
  d1->deleted_response_headers.push_back(ResponseHeader("KEY1", "Value2"));
  deltas.push_back(d1);
  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  GURL allowed_unsafe_redirect_url1;
  MergeOnHeadersReceivedResponses(deltas,
                                  base_headers.get(),
                                  &new_headers1,
                                  &allowed_unsafe_redirect_url1,
                                  &warning_set,
                                  &net_log);
  ASSERT_TRUE(new_headers1.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url1.is_empty());
  std::multimap<std::string, std::string> expected1;
  expected1.insert(std::pair<std::string, std::string>("Key1", "Value1"));
  expected1.insert(std::pair<std::string, std::string>("Key1", "Value3"));
  expected1.insert(std::pair<std::string, std::string>("Key2", "Value4"));
  size_t iter = 0;
  std::string name;
  std::string value;
  std::multimap<std::string, std::string> actual1;
  while (new_headers1->EnumerateHeaderLines(&iter, &name, &value)) {
    actual1.insert(std::pair<std::string, std::string>(name, value));
  }
  EXPECT_EQ(expected1, actual1);
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(1u, capturing_net_log.GetSize());
}

// Tests whether onHeadersReceived can initiate a redirect.
// The URL merge logic is shared with onBeforeRequest, so we only need to test
// whether the URLs are merged at all.
TEST(ExtensionWebRequestHelpersTest,
     TestMergeOnHeadersReceivedResponsesRedirect) {
  EventResponseDeltas deltas;
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;

  char base_headers_string[] =
      "HTTP/1.0 200 OK\r\n"
      "\r\n";
  scoped_refptr<net::HttpResponseHeaders> base_headers(
      new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
          base_headers_string, sizeof(base_headers_string))));

  // No redirect
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(0)));
  deltas.push_back(d0);
  scoped_refptr<net::HttpResponseHeaders> new_headers0;
  GURL allowed_unsafe_redirect_url0;
  MergeOnHeadersReceivedResponses(deltas,
                                  base_headers.get(),
                                  &new_headers0,
                                  &allowed_unsafe_redirect_url0,
                                  &warning_set,
                                  &net_log);
  EXPECT_FALSE(new_headers0.get());
  EXPECT_TRUE(allowed_unsafe_redirect_url0.is_empty());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());

  // Single redirect.
  GURL new_url_1("http://foo.com");
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(1000)));
  d1->new_url = GURL(new_url_1);
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  capturing_net_log.Clear();
  scoped_refptr<net::HttpResponseHeaders> new_headers1;
  GURL allowed_unsafe_redirect_url1;
  MergeOnHeadersReceivedResponses(deltas,
                                  base_headers.get(),
                                  &new_headers1,
                                  &allowed_unsafe_redirect_url1,
                                  &warning_set,
                                  &net_log);

  EXPECT_TRUE(new_headers1.get());
  EXPECT_TRUE(new_headers1->HasHeaderValue("Location", new_url_1.spec()));
  EXPECT_EQ(new_url_1, allowed_unsafe_redirect_url1);
  EXPECT_TRUE(warning_set.empty());
  EXPECT_EQ(1u, capturing_net_log.GetSize());
}

TEST(ExtensionWebRequestHelpersTest, TestMergeOnAuthRequiredResponses) {
  net::BoundTestNetLog capturing_net_log;
  net::BoundNetLog net_log = capturing_net_log.bound();
  WarningSet warning_set;
  EventResponseDeltas deltas;
  base::string16 username = base::ASCIIToUTF16("foo");
  base::string16 password = base::ASCIIToUTF16("bar");
  base::string16 password2 = base::ASCIIToUTF16("baz");

  // Check that we can handle if not returning credentials.
  linked_ptr<EventResponseDelta> d0(
      new EventResponseDelta("extid0", base::Time::FromInternalValue(3000)));
  deltas.push_back(d0);
  net::AuthCredentials auth0;
  bool credentials_set = MergeOnAuthRequiredResponses(
      deltas, &auth0, &warning_set, &net_log);
  EXPECT_FALSE(credentials_set);
  EXPECT_TRUE(auth0.Empty());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(0u, capturing_net_log.GetSize());

  // Check that we can set AuthCredentials.
  linked_ptr<EventResponseDelta> d1(
      new EventResponseDelta("extid1", base::Time::FromInternalValue(2000)));
  d1->auth_credentials.reset(new net::AuthCredentials(username, password));
  deltas.push_back(d1);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  net::AuthCredentials auth1;
  credentials_set = MergeOnAuthRequiredResponses(
      deltas, &auth1, &warning_set, &net_log);
  EXPECT_TRUE(credentials_set);
  EXPECT_FALSE(auth1.Empty());
  EXPECT_EQ(username, auth1.username());
  EXPECT_EQ(password, auth1.password());
  EXPECT_EQ(0u, warning_set.size());
  EXPECT_EQ(1u, capturing_net_log.GetSize());

  // Check that we set AuthCredentials only once.
  linked_ptr<EventResponseDelta> d2(
      new EventResponseDelta("extid2", base::Time::FromInternalValue(1500)));
  d2->auth_credentials.reset(new net::AuthCredentials(username, password2));
  deltas.push_back(d2);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  net::AuthCredentials auth2;
  credentials_set = MergeOnAuthRequiredResponses(
      deltas, &auth2, &warning_set, &net_log);
  EXPECT_TRUE(credentials_set);
  EXPECT_FALSE(auth2.Empty());
  EXPECT_EQ(username, auth1.username());
  EXPECT_EQ(password, auth1.password());
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(2u, capturing_net_log.GetSize());

  // Check that we can set identical AuthCredentials twice without causing
  // a conflict.
  linked_ptr<EventResponseDelta> d3(
      new EventResponseDelta("extid3", base::Time::FromInternalValue(1000)));
  d3->auth_credentials.reset(new net::AuthCredentials(username, password));
  deltas.push_back(d3);
  deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
  warning_set.clear();
  capturing_net_log.Clear();
  net::AuthCredentials auth3;
  credentials_set = MergeOnAuthRequiredResponses(
      deltas, &auth3, &warning_set, &net_log);
  EXPECT_TRUE(credentials_set);
  EXPECT_FALSE(auth3.Empty());
  EXPECT_EQ(username, auth1.username());
  EXPECT_EQ(password, auth1.password());
  EXPECT_EQ(1u, warning_set.size());
  EXPECT_TRUE(HasWarning(warning_set, "extid2"));
  EXPECT_EQ(3u, capturing_net_log.GetSize());
}

}  // namespace extensions