// Copyright (c) 2019 The Chromium Embedded Framework 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 "libcef/browser/net_service/resource_request_handler_wrapper.h"

#include "libcef/browser/browser_host_base.h"
#include "libcef/browser/context.h"
#include "libcef/browser/iothread_state.h"
#include "libcef/browser/net_service/cookie_helper.h"
#include "libcef/browser/net_service/proxy_url_loader_factory.h"
#include "libcef/browser/net_service/resource_handler_wrapper.h"
#include "libcef/browser/net_service/response_filter_wrapper.h"
#include "libcef/browser/prefs/browser_prefs.h"
#include "libcef/browser/thread_util.h"
#include "libcef/common/app_manager.h"
#include "libcef/common/net/scheme_registration.h"
#include "libcef/common/net_service/net_service_util.h"
#include "libcef/common/request_impl.h"
#include "libcef/common/response_impl.h"

#include "chrome/browser/profiles/profile.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
#include "ui/base/page_transition_types.h"
#include "url/origin.h"

namespace net_service {

namespace {

const int kLoadNoCookiesFlags =
    net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES;

class RequestCallbackWrapper : public CefCallback {
 public:
  using Callback = base::OnceCallback<void(bool /* allow */)>;
  explicit RequestCallbackWrapper(Callback callback)
      : callback_(std::move(callback)),
        work_thread_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}

  ~RequestCallbackWrapper() override {
    if (!callback_.is_null()) {
      // Make sure it executes on the correct thread.
      work_thread_task_runner_->PostTask(
          FROM_HERE, base::BindOnce(std::move(callback_), true));
    }
  }

  void Continue() override { ContinueNow(true); }

  void Cancel() override { ContinueNow(false); }

 private:
  void ContinueNow(bool allow) {
    if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) {
      work_thread_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&RequestCallbackWrapper::ContinueNow, this, allow));
      return;
    }
    if (!callback_.is_null()) {
      std::move(callback_).Run(allow);
    }
  }

  Callback callback_;

  scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner_;

  IMPLEMENT_REFCOUNTING(RequestCallbackWrapper);
  DISALLOW_COPY_AND_ASSIGN(RequestCallbackWrapper);
};

class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
 public:
  struct RequestState {
    RequestState() {}

    void Reset(CefRefPtr<CefResourceRequestHandler> handler,
               CefRefPtr<CefSchemeHandlerFactory> scheme_factory,
               CefRefPtr<CefRequestImpl> request,
               bool request_was_redirected,
               CancelRequestCallback cancel_callback) {
      handler_ = handler;
      scheme_factory_ = scheme_factory;
      cookie_filter_ = nullptr;
      pending_request_ = request;
      pending_response_ = nullptr;
      request_was_redirected_ = request_was_redirected;
      was_custom_handled_ = false;
      cancel_callback_ = std::move(cancel_callback);
    }

    CefRefPtr<CefResourceRequestHandler> handler_;
    CefRefPtr<CefSchemeHandlerFactory> scheme_factory_;
    CefRefPtr<CefCookieAccessFilter> cookie_filter_;
    CefRefPtr<CefRequestImpl> pending_request_;
    CefRefPtr<CefResponseImpl> pending_response_;
    bool request_was_redirected_ = false;
    bool was_custom_handled_ = false;
    CancelRequestCallback cancel_callback_;
  };

  struct PendingRequest {
    PendingRequest(int32_t request_id,
                   network::ResourceRequest* request,
                   bool request_was_redirected,
                   OnBeforeRequestResultCallback callback,
                   CancelRequestCallback cancel_callback)
        : id_(request_id),
          request_(request),
          request_was_redirected_(request_was_redirected),
          callback_(std::move(callback)),
          cancel_callback_(std::move(cancel_callback)) {}

    ~PendingRequest() {
      if (cancel_callback_) {
        std::move(cancel_callback_).Run(net::ERR_ABORTED);
      }
    }

    void Run(InterceptedRequestHandlerWrapper* self) {
      self->OnBeforeRequest(id_, request_, request_was_redirected_,
                            std::move(callback_), std::move(cancel_callback_));
    }

    const int32_t id_;
    network::ResourceRequest* const request_;
    const bool request_was_redirected_;
    OnBeforeRequestResultCallback callback_;
    CancelRequestCallback cancel_callback_;
  };

  // Observer to receive notification of CEF context or associated browser
  // destruction. Only one of the *Destroyed() methods will be called.
  class DestructionObserver : public CefBrowserHostBase::Observer,
                              public CefContext::Observer {
   public:
    explicit DestructionObserver(CefBrowserHostBase* browser) {
      if (browser) {
        browser_info_ = browser->browser_info();
        browser->AddObserver(this);
      } else {
        CefContext::Get()->AddObserver(this);
      }
    }

    virtual ~DestructionObserver() {
      CEF_REQUIRE_UIT();
      if (!registered_)
        return;

      // Verify that the browser or context still exists before attempting to
      // remove the observer.
      if (browser_info_) {
        auto browser = browser_info_->browser();
        if (browser)
          browser->RemoveObserver(this);
      } else if (CefContext::Get()) {
        // Network requests may be torn down during shutdown, so we can't check
        // CONTEXT_STATE_VALID() here.
        CefContext::Get()->RemoveObserver(this);
      }
    }

    void SetWrapper(base::WeakPtr<InterceptedRequestHandlerWrapper> wrapper) {
      CEF_REQUIRE_IOT();
      wrapper_ = wrapper;
    }

    void OnBrowserDestroyed(CefBrowserHostBase* browser) override {
      CEF_REQUIRE_UIT();
      browser->RemoveObserver(this);
      registered_ = false;
      browser_info_ = nullptr;
      NotifyOnDestroyed();
    }

    void OnContextDestroyed() override {
      CEF_REQUIRE_UIT();
      CefContext::Get()->RemoveObserver(this);
      registered_ = false;
      NotifyOnDestroyed();
    }

   private:
    void NotifyOnDestroyed() {
      if (wrapper_.MaybeValid()) {
        // This will be a no-op if the WeakPtr is invalid.
        CEF_POST_TASK(
            CEF_IOT,
            base::BindOnce(&InterceptedRequestHandlerWrapper::OnDestroyed,
                           wrapper_));
      }
    }

    scoped_refptr<CefBrowserInfo> browser_info_;
    bool registered_ = true;

    base::WeakPtr<InterceptedRequestHandlerWrapper> wrapper_;
    DISALLOW_COPY_AND_ASSIGN(DestructionObserver);
  };

  // Holds state information for InterceptedRequestHandlerWrapper. State is
  // initialized on the UI thread and later passed to the *Wrapper object on
  // the IO thread.
  struct InitState {
    InitState() {}

    ~InitState() {
      if (destruction_observer_) {
        if (initialized_) {
          // Clear the reference added in
          // InterceptedRequestHandlerWrapper::SetInitialized().
          destruction_observer_->SetWrapper(nullptr);
        }
        DeleteDestructionObserver();
      }
    }

    void Initialize(content::BrowserContext* browser_context,
                    CefRefPtr<CefBrowserHostBase> browser,
                    CefRefPtr<CefFrame> frame,
                    const content::GlobalRenderFrameHostId& global_id,
                    bool is_navigation,
                    bool is_download,
                    const url::Origin& request_initiator,
                    const base::RepeatingClosure& unhandled_request_callback) {
      CEF_REQUIRE_UIT();

      browser_context_ = browser_context;

      auto profile = Profile::FromBrowserContext(browser_context);
      auto cef_browser_context = CefBrowserContext::FromProfile(profile);
      iothread_state_ = cef_browser_context->iothread_state();
      CHECK(iothread_state_);
      cookieable_schemes_ = cef_browser_context->GetCookieableSchemes();

      // We register to be notified of CEF context or browser destruction so
      // that we can stop accepting new requests and cancel pending/in-progress
      // requests in a timely manner (e.g. before we start asserting about
      // leaked objects during CEF shutdown).
      destruction_observer_.reset(new DestructionObserver(browser.get()));

      if (browser) {
        // These references will be released in OnDestroyed().
        browser_ = browser;
        frame_ = frame;
      }

      global_id_ = global_id;
      is_navigation_ = is_navigation;
      is_download_ = is_download;
      request_initiator_ = request_initiator.Serialize();
      unhandled_request_callback_ = unhandled_request_callback;

      // Default values for standard headers.
      accept_language_ = browser_prefs::GetAcceptLanguageList(
          cef_browser_context, browser.get(), /*expand=*/true);
      DCHECK(!accept_language_.empty());
      user_agent_ =
          CefAppManager::Get()->GetContentClient()->browser()->GetUserAgent();
      DCHECK(!user_agent_.empty());
    }

    void DeleteDestructionObserver() {
      DCHECK(destruction_observer_);
      CEF_POST_TASK(
          CEF_UIT,
          base::BindOnce(&InitState::DeleteDestructionObserverOnUIThread,
                         std::move(destruction_observer_)));
    }

    static void DeleteDestructionObserverOnUIThread(
        std::unique_ptr<DestructionObserver> observer) {}

    // Only accessed on the UI thread.
    content::BrowserContext* browser_context_ = nullptr;

    bool initialized_ = false;

    CefRefPtr<CefBrowserHostBase> browser_;
    CefRefPtr<CefFrame> frame_;
    scoped_refptr<CefIOThreadState> iothread_state_;
    CefBrowserContext::CookieableSchemes cookieable_schemes_;
    content::GlobalRenderFrameHostId global_id_;
    bool is_navigation_ = true;
    bool is_download_ = false;
    CefString request_initiator_;
    base::RepeatingClosure unhandled_request_callback_;

    // Default values for standard headers.
    std::string accept_language_;
    std::string user_agent_;

    // Used to receive destruction notification.
    std::unique_ptr<DestructionObserver> destruction_observer_;
  };

  // Manages InterceptedRequestHandlerWrapper initialization. The *Wrapper
  // object is owned by ProxyURLLoaderFactory and may be deleted before
  // SetInitialized() is called.
  struct InitHelper : base::RefCountedThreadSafe<InitHelper> {
   public:
    explicit InitHelper(InterceptedRequestHandlerWrapper* wrapper)
        : wrapper_(wrapper) {}

    void MaybeSetInitialized(std::unique_ptr<InitState> init_state) {
      CEF_POST_TASK(CEF_IOT, base::BindOnce(&InitHelper::SetInitialized, this,
                                            std::move(init_state)));
    }

    void Disconnect() {
      base::AutoLock lock_scope(lock_);
      wrapper_ = nullptr;
    }

   private:
    void SetInitialized(std::unique_ptr<InitState> init_state) {
      base::AutoLock lock_scope(lock_);
      // May be nullptr if the InterceptedRequestHandlerWrapper has already
      // been deleted.
      if (!wrapper_)
        return;
      wrapper_->SetInitialized(std::move(init_state));
      wrapper_ = nullptr;
    }

    base::Lock lock_;
    InterceptedRequestHandlerWrapper* wrapper_;
  };

  InterceptedRequestHandlerWrapper()
      : init_helper_(base::MakeRefCounted<InitHelper>(this)),
        weak_ptr_factory_(this) {}

  ~InterceptedRequestHandlerWrapper() override {
    CEF_REQUIRE_IOT();

    // There should be no in-progress requests during destruction.
    DCHECK(request_map_.empty());

    // Don't continue with initialization if we get deleted before
    // SetInitialized is called asynchronously.
    init_helper_->Disconnect();
  }

  scoped_refptr<InitHelper> init_helper() const { return init_helper_; }

  void SetInitialized(std::unique_ptr<InitState> init_state) {
    CEF_REQUIRE_IOT();
    DCHECK(!init_state_);
    init_state_ = std::move(init_state);

    // Check that the CEF context or associated browser was not destroyed
    // between the calls to Initialize and SetInitialized, in which case
    // we won't get an OnDestroyed callback from DestructionObserver.
    if (init_state_->browser_) {
      if (!init_state_->browser_->browser_info()->browser()) {
        OnDestroyed();
        return;
      }
    } else if (!CONTEXT_STATE_VALID()) {
      OnDestroyed();
      return;
    }

    init_state_->initialized_ = true;
    init_state_->destruction_observer_->SetWrapper(
        weak_ptr_factory_.GetWeakPtr());

    // Continue any pending requests.
    if (!pending_requests_.empty()) {
      for (const auto& request : pending_requests_)
        request->Run(this);
      pending_requests_.clear();
    }
  }

  // InterceptedRequestHandler methods:
  void OnBeforeRequest(int32_t request_id,
                       network::ResourceRequest* request,
                       bool request_was_redirected,
                       OnBeforeRequestResultCallback callback,
                       CancelRequestCallback cancel_callback) override {
    CEF_REQUIRE_IOT();

    if (shutting_down_) {
      // Abort immediately.
      std::move(cancel_callback).Run(net::ERR_ABORTED);
      return;
    }

    if (!init_state_) {
      // Queue requests until we're initialized.
      pending_requests_.push_back(std::make_unique<PendingRequest>(
          request_id, request, request_was_redirected, std::move(callback),
          std::move(cancel_callback)));
      return;
    }

    // State may already exist for restarted requests.
    RequestState* state = GetOrCreateState(request_id);

    // Add standard headers, if currently unspecified.
    request->headers.SetHeaderIfMissing(
        net::HttpRequestHeaders::kAcceptLanguage,
        init_state_->accept_language_);
    request->headers.SetHeaderIfMissing(net::HttpRequestHeaders::kUserAgent,
                                        init_state_->user_agent_);

    const bool is_external = IsExternalRequest(request);

    // External requests will not have a default handler.
    bool intercept_only = is_external;

    CefRefPtr<CefRequestImpl> requestPtr;
    CefRefPtr<CefResourceRequestHandler> handler =
        GetHandler(request_id, request, &intercept_only, requestPtr);

    CefRefPtr<CefSchemeHandlerFactory> scheme_factory =
        init_state_->iothread_state_->GetSchemeHandlerFactory(request->url);
    if (scheme_factory && !requestPtr) {
      requestPtr = MakeRequest(request, request_id, true);
    }

    // True if there's a possibility that the client might handle the request.
    const bool maybe_intercept_request = handler || scheme_factory;
    if (!maybe_intercept_request && requestPtr)
      requestPtr = nullptr;

    // May have a handler and/or scheme factory.
    state->Reset(handler, scheme_factory, requestPtr, request_was_redirected,
                 std::move(cancel_callback));

    if (handler) {
      state->cookie_filter_ = handler->GetCookieAccessFilter(
          init_state_->browser_, init_state_->frame_, requestPtr.get());
    }

    auto exec_callback =
        base::BindOnce(std::move(callback), maybe_intercept_request,
                       is_external ? true : intercept_only);

    if (!maybe_intercept_request) {
      // Cookies will be handled by the NetworkService.
      std::move(exec_callback).Run();
      return;
    }

    MaybeLoadCookies(request_id, state, request, std::move(exec_callback));
  }

  void MaybeLoadCookies(int32_t request_id,
                        RequestState* state,
                        network::ResourceRequest* request,
                        base::OnceClosure callback) {
    CEF_REQUIRE_IOT();

    if (!cookie_helper::IsCookieableScheme(request->url,
                                           init_state_->cookieable_schemes_)) {
      // The scheme does not support cookies.
      std::move(callback).Run();
      return;
    }

    // We need to load/save cookies ourselves for custom-handled requests, or
    // if we're using a cookie filter.
    auto allow_cookie_callback =
        state->cookie_filter_
            ? base::BindRepeating(
                  &InterceptedRequestHandlerWrapper::AllowCookieLoad,
                  weak_ptr_factory_.GetWeakPtr(), request_id)
            : base::BindRepeating(
                  &InterceptedRequestHandlerWrapper::AllowCookieAlways);
    auto done_cookie_callback = base::BindOnce(
        &InterceptedRequestHandlerWrapper::ContinueWithLoadedCookies,
        weak_ptr_factory_.GetWeakPtr(), request_id, request,
        std::move(callback));
    cookie_helper::LoadCookies(init_state_->browser_context_, *request,
                               allow_cookie_callback,
                               std::move(done_cookie_callback));
  }

  static void AllowCookieAlways(const net::CanonicalCookie& cookie,
                                bool* allow) {
    *allow = true;
  }

  void AllowCookieLoad(int32_t request_id,
                       const net::CanonicalCookie& cookie,
                       bool* allow) {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled while the async callback was
      // pending.
      return;
    }

    DCHECK(state->cookie_filter_);

    CefCookie cef_cookie;
    if (net_service::MakeCefCookie(cookie, cef_cookie)) {
      *allow = state->cookie_filter_->CanSendCookie(
          init_state_->browser_, init_state_->frame_,
          state->pending_request_.get(), cef_cookie);
    }
  }

  void ContinueWithLoadedCookies(int32_t request_id,
                                 network::ResourceRequest* request,
                                 base::OnceClosure callback,
                                 int total_count,
                                 net::CookieList allowed_cookies) {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled while the async callback was
      // pending.
      return;
    }

    if (state->cookie_filter_) {
      // Also add/save cookies ourselves for default-handled network requests
      // so that we can filter them. This will be a no-op for custom-handled
      // requests.
      request->load_flags |= kLoadNoCookiesFlags;
    }

    if (!allowed_cookies.empty()) {
      const std::string& cookie_line =
          net::CanonicalCookie::BuildCookieLine(allowed_cookies);
      request->headers.SetHeader(net::HttpRequestHeaders::kCookie, cookie_line);

      state->pending_request_->SetReadOnly(false);
      state->pending_request_->SetHeaderByName(net::HttpRequestHeaders::kCookie,
                                               cookie_line, true);
      state->pending_request_->SetReadOnly(true);
    }

    std::move(callback).Run();
  }

  void ShouldInterceptRequest(
      int32_t request_id,
      network::ResourceRequest* request,
      ShouldInterceptRequestResultCallback callback) override {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled during destruction.
      return;
    }

    // Must have a handler and/or scheme factory.
    DCHECK(state->handler_ || state->scheme_factory_);
    DCHECK(state->pending_request_);

    if (state->handler_) {
      // The client may modify |pending_request_| before executing the callback.
      state->pending_request_->SetReadOnly(false);
      state->pending_request_->SetTrackChanges(true,
                                               true /* backup_on_change */);

      CefRefPtr<RequestCallbackWrapper> callbackPtr =
          new RequestCallbackWrapper(base::BindOnce(
              &InterceptedRequestHandlerWrapper::ContinueShouldInterceptRequest,
              weak_ptr_factory_.GetWeakPtr(), request_id,
              base::Unretained(request), std::move(callback)));

      cef_return_value_t retval = state->handler_->OnBeforeResourceLoad(
          init_state_->browser_, init_state_->frame_,
          state->pending_request_.get(), callbackPtr.get());
      if (retval != RV_CONTINUE_ASYNC) {
        if (retval == RV_CONTINUE) {
          // Continue the request immediately.
          callbackPtr->Continue();
        } else {
          // Cancel the request immediately.
          callbackPtr->Cancel();
        }
      }
    } else {
      // The scheme factory may choose to handle it.
      ContinueShouldInterceptRequest(request_id, request, std::move(callback),
                                     true);
    }
  }

  void ContinueShouldInterceptRequest(
      int32_t request_id,
      network::ResourceRequest* request,
      ShouldInterceptRequestResultCallback callback,
      bool allow) {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled while the async callback was
      // pending.
      return;
    }

    // Must have a handler and/or scheme factory.
    DCHECK(state->handler_ || state->scheme_factory_);
    DCHECK(state->pending_request_);

    if (state->handler_) {
      if (allow) {
        // Apply any |requestPtr| changes to |request|.
        state->pending_request_->Get(request, true /* changed_only */);
      }

      const bool redirect =
          (state->pending_request_->GetChanges() & CefRequestImpl::kChangedUrl);
      if (redirect) {
        // Revert any changes for now. We'll get them back after the redirect.
        state->pending_request_->RevertChanges();
      }

      state->pending_request_->SetReadOnly(true);
      state->pending_request_->SetTrackChanges(false);

      if (!allow) {
        // Cancel the request.
        if (state->cancel_callback_) {
          std::move(state->cancel_callback_).Run(net::ERR_ABORTED);
        }
        return;
      }

      if (redirect) {
        // Performing a redirect.
        std::move(callback).Run(nullptr);
        return;
      }
    }

    CefRefPtr<CefResourceHandler> resource_handler;

    if (state->handler_) {
      // Does the client want to handle the request?
      resource_handler = state->handler_->GetResourceHandler(
          init_state_->browser_, init_state_->frame_,
          state->pending_request_.get());
    }
    if (!resource_handler && state->scheme_factory_) {
      // Does the scheme factory want to handle the request?
      resource_handler = state->scheme_factory_->Create(
          init_state_->browser_, init_state_->frame_, request->url.scheme(),
          state->pending_request_.get());
    }

    std::unique_ptr<ResourceResponse> resource_response;
    if (resource_handler) {
      resource_response = CreateResourceResponse(request_id, resource_handler);
      DCHECK(resource_response);
      state->was_custom_handled_ = true;
    } else {
      // The request will be handled by the NetworkService. Remove the
      // "Accept-Language" header here so that it can be re-added in
      // URLRequestHttpJob::AddExtraHeaders with correct ordering applied.
      request->headers.RemoveHeader(net::HttpRequestHeaders::kAcceptLanguage);
    }

    // Continue the request.
    std::move(callback).Run(std::move(resource_response));
  }

  void ProcessResponseHeaders(int32_t request_id,
                              const network::ResourceRequest& request,
                              const GURL& redirect_url,
                              net::HttpResponseHeaders* headers) override {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled during destruction.
      return;
    }

    if (!state->handler_)
      return;

    if (!state->pending_response_)
      state->pending_response_ = new CefResponseImpl();
    else
      state->pending_response_->SetReadOnly(false);

    if (headers)
      state->pending_response_->SetResponseHeaders(*headers);

    state->pending_response_->SetReadOnly(true);
  }

  void OnRequestResponse(int32_t request_id,
                         network::ResourceRequest* request,
                         net::HttpResponseHeaders* headers,
                         absl::optional<net::RedirectInfo> redirect_info,
                         OnRequestResponseResultCallback callback) override {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled during destruction.
      return;
    }

    if (state->cookie_filter_) {
      // Remove the flags that were added in ContinueWithLoadedCookies.
      request->load_flags &= ~kLoadNoCookiesFlags;
    }

    if (!state->handler_) {
      // Cookies may come from a scheme handler.
      MaybeSaveCookies(
          request_id, state, request, headers,
          base::BindOnce(
              std::move(callback), ResponseMode::CONTINUE, nullptr,
              redirect_info.has_value() ? redirect_info->new_url : GURL()));
      return;
    }

    DCHECK(state->pending_request_);
    DCHECK(state->pending_response_);

    if (redirect_info.has_value()) {
      HandleRedirect(request_id, state, request, headers, *redirect_info,
                     std::move(callback));
    } else {
      HandleResponse(request_id, state, request, headers, std::move(callback));
    }
  }

  void HandleRedirect(int32_t request_id,
                      RequestState* state,
                      network::ResourceRequest* request,
                      net::HttpResponseHeaders* headers,
                      const net::RedirectInfo& redirect_info,
                      OnRequestResponseResultCallback callback) {
    GURL new_url = redirect_info.new_url;
    CefString newUrl = redirect_info.new_url.spec();
    CefString oldUrl = newUrl;
    bool url_changed = false;
    state->handler_->OnResourceRedirect(
        init_state_->browser_, init_state_->frame_,
        state->pending_request_.get(), state->pending_response_.get(), newUrl);
    if (newUrl != oldUrl) {
      // Also support relative URLs.
      const GURL& url = redirect_info.new_url.Resolve(newUrl.ToString());
      if (url.is_valid()) {
        url_changed = true;
        new_url = url;
      }
    }

    // Update the |pending_request_| object with the new info.
    state->pending_request_->SetReadOnly(false);
    state->pending_request_->Set(redirect_info);
    if (url_changed) {
      state->pending_request_->SetURL(new_url.spec());
    }
    state->pending_request_->SetReadOnly(true);

    auto exec_callback = base::BindOnce(
        std::move(callback), ResponseMode::CONTINUE, nullptr, new_url);

    MaybeSaveCookies(request_id, state, request, headers,
                     std::move(exec_callback));
  }

  void HandleResponse(int32_t request_id,
                      RequestState* state,
                      network::ResourceRequest* request,
                      net::HttpResponseHeaders* headers,
                      OnRequestResponseResultCallback callback) {
    // The client may modify |pending_request_| in OnResourceResponse.
    state->pending_request_->SetReadOnly(false);
    state->pending_request_->SetTrackChanges(true, true /* backup_on_change */);

    auto response_mode = ResponseMode::CONTINUE;
    GURL new_url;

    if (state->handler_->OnResourceResponse(
            init_state_->browser_, init_state_->frame_,
            state->pending_request_.get(), state->pending_response_.get())) {
      // The request may have been modified.
      const auto changes = state->pending_request_->GetChanges();
      if (changes) {
        state->pending_request_->Get(request, true /* changed_only */);

        if (changes & CefRequestImpl::kChangedUrl) {
          // Redirect to the new URL.
          new_url = GURL(state->pending_request_->GetURL().ToString());
        } else {
          // Restart the request.
          response_mode = ResponseMode::RESTART;
        }
      }
    }

    // Revert any changes for now. We'll get them back after the redirect or
    // restart.
    state->pending_request_->RevertChanges();

    state->pending_request_->SetReadOnly(true);
    state->pending_request_->SetTrackChanges(false);

    auto exec_callback =
        base::BindOnce(std::move(callback), response_mode, nullptr, new_url);

    if (response_mode == ResponseMode::RESTART) {
      // Get any cookies after the restart.
      std::move(exec_callback).Run();
      return;
    }

    MaybeSaveCookies(request_id, state, request, headers,
                     std::move(exec_callback));
  }

  void MaybeSaveCookies(int32_t request_id,
                        RequestState* state,
                        network::ResourceRequest* request,
                        net::HttpResponseHeaders* headers,
                        base::OnceClosure callback) {
    CEF_REQUIRE_IOT();

    if (!state->cookie_filter_ && !state->was_custom_handled_) {
      // The NetworkService saves the cookies for default-handled requests.
      std::move(callback).Run();
      return;
    }

    if (!cookie_helper::IsCookieableScheme(request->url,
                                           init_state_->cookieable_schemes_)) {
      // The scheme does not support cookies.
      std::move(callback).Run();
      return;
    }

    // We need to load/save cookies ourselves for custom-handled requests, or
    // if we're using a cookie filter.
    auto allow_cookie_callback =
        state->cookie_filter_
            ? base::BindRepeating(
                  &InterceptedRequestHandlerWrapper::AllowCookieSave,
                  weak_ptr_factory_.GetWeakPtr(), request_id)
            : base::BindRepeating(
                  &InterceptedRequestHandlerWrapper::AllowCookieAlways);
    auto done_cookie_callback = base::BindOnce(
        &InterceptedRequestHandlerWrapper::ContinueWithSavedCookies,
        weak_ptr_factory_.GetWeakPtr(), request_id, std::move(callback));
    cookie_helper::SaveCookies(init_state_->browser_context_, *request, headers,
                               allow_cookie_callback,
                               std::move(done_cookie_callback));
  }

  void AllowCookieSave(int32_t request_id,
                       const net::CanonicalCookie& cookie,
                       bool* allow) {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled while the async callback was
      // pending.
      return;
    }

    DCHECK(state->cookie_filter_);

    CefCookie cef_cookie;
    if (net_service::MakeCefCookie(cookie, cef_cookie)) {
      *allow = state->cookie_filter_->CanSaveCookie(
          init_state_->browser_, init_state_->frame_,
          state->pending_request_.get(), state->pending_response_.get(),
          cef_cookie);
    }
  }

  void ContinueWithSavedCookies(int32_t request_id,
                                base::OnceClosure callback,
                                int total_count,
                                net::CookieList allowed_cookies) {
    CEF_REQUIRE_IOT();
    std::move(callback).Run();
  }

  mojo::ScopedDataPipeConsumerHandle OnFilterResponseBody(
      int32_t request_id,
      const network::ResourceRequest& request,
      mojo::ScopedDataPipeConsumerHandle body) override {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled during destruction.
      return body;
    }

    if (state->handler_) {
      auto filter = state->handler_->GetResourceResponseFilter(
          init_state_->browser_, init_state_->frame_,
          state->pending_request_.get(), state->pending_response_.get());
      if (filter) {
        return CreateResponseFilterHandler(
            filter, std::move(body),
            base::BindOnce(&InterceptedRequestHandlerWrapper::OnFilterError,
                           weak_ptr_factory_.GetWeakPtr(), request_id));
      }
    }

    return body;
  }

  void OnFilterError(int32_t request_id) {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been canceled while the async callback was
      // pending.
      return;
    }

    if (state->cancel_callback_) {
      std::move(state->cancel_callback_).Run(net::ERR_CONTENT_DECODING_FAILED);
    }
  }

  void OnRequestComplete(
      int32_t request_id,
      const network::ResourceRequest& request,
      const network::URLLoaderCompletionStatus& status) override {
    CEF_REQUIRE_IOT();

    RequestState* state = GetState(request_id);
    if (!state) {
      // The request may have been aborted during initialization or canceled
      // during destruction. This method will always be called before a request
      // is deleted, so if the request is currently pending also remove it from
      // the list.
      if (!pending_requests_.empty()) {
        PendingRequests::iterator it = pending_requests_.begin();
        for (; it != pending_requests_.end(); ++it) {
          if ((*it)->id_ == request_id) {
            pending_requests_.erase(it);
            break;
          }
        }
      }
      return;
    }

    const bool is_external = IsExternalRequest(&request);

    // Redirection of standard custom schemes is handled with a restart, so we
    // get completion notifications for both the original (redirected) request
    // and the final request. Don't report completion of the redirected request.
    const bool ignore_result = is_external && request.url.IsStandard() &&
                               status.error_code == net::ERR_ABORTED &&
                               state->pending_response_.get() &&
                               net::HttpResponseHeaders::IsRedirectResponseCode(
                                   state->pending_response_->GetStatus());

    if (state->handler_ && !ignore_result) {
      DCHECK(state->pending_request_);

      CallHandlerOnComplete(state, status);

      if (status.error_code != 0 && status.error_code != ERR_ABORTED &&
          is_external) {
        bool allow_os_execution = false;
        state->handler_->OnProtocolExecution(
            init_state_->browser_, init_state_->frame_,
            state->pending_request_.get(), allow_os_execution);
        if (allow_os_execution && init_state_->unhandled_request_callback_) {
          init_state_->unhandled_request_callback_.Run();
        }
      }
    }

    RemoveState(request_id);
  }

 private:
  void CallHandlerOnComplete(RequestState* state,
                             const network::URLLoaderCompletionStatus& status) {
    if (!state->handler_ || !state->pending_request_)
      return;

    // The request object may be currently flagged as writable in cases where we
    // abort a request that is waiting on a pending callack.
    if (!state->pending_request_->IsReadOnly()) {
      state->pending_request_->SetReadOnly(true);
    }

    if (!state->pending_response_) {
      // If the request failed there may not be a response object yet.
      state->pending_response_ = new CefResponseImpl();
    } else {
      state->pending_response_->SetReadOnly(false);
    }
    state->pending_response_->SetError(
        static_cast<cef_errorcode_t>(status.error_code));
    state->pending_response_->SetReadOnly(true);

    state->handler_->OnResourceLoadComplete(
        init_state_->browser_, init_state_->frame_,
        state->pending_request_.get(), state->pending_response_.get(),
        status.error_code == 0 ? UR_SUCCESS : UR_FAILED,
        status.encoded_body_length);
  }

  // Returns the handler, if any, that should be used for this request.
  CefRefPtr<CefResourceRequestHandler> GetHandler(
      int32_t request_id,
      network::ResourceRequest* request,
      bool* intercept_only,
      CefRefPtr<CefRequestImpl>& requestPtr) const {
    CefRefPtr<CefResourceRequestHandler> handler;

    if (init_state_->browser_) {
      // Maybe the browser's client wants to handle it?
      CefRefPtr<CefClient> client =
          init_state_->browser_->GetHost()->GetClient();
      if (client) {
        CefRefPtr<CefRequestHandler> request_handler =
            client->GetRequestHandler();
        if (request_handler) {
          requestPtr = MakeRequest(request, request_id, true);

          handler = request_handler->GetResourceRequestHandler(
              init_state_->browser_, init_state_->frame_, requestPtr.get(),
              init_state_->is_navigation_, init_state_->is_download_,
              init_state_->request_initiator_, *intercept_only);
        }
      }
    }

    if (!handler) {
      // Maybe the request context wants to handle it?
      CefRefPtr<CefRequestContextHandler> context_handler =
          init_state_->iothread_state_->GetHandler(
              init_state_->global_id_, /*require_frame_match=*/false);
      if (context_handler) {
        if (!requestPtr)
          requestPtr = MakeRequest(request, request_id, true);

        handler = context_handler->GetResourceRequestHandler(
            init_state_->browser_, init_state_->frame_, requestPtr.get(),
            init_state_->is_navigation_, init_state_->is_download_,
            init_state_->request_initiator_, *intercept_only);
      }
    }

    return handler;
  }

  RequestState* GetOrCreateState(int32_t request_id) {
    RequestState* state = GetState(request_id);
    if (!state) {
      state = new RequestState();
      request_map_.insert(std::make_pair(request_id, base::WrapUnique(state)));
    }
    return state;
  }

  RequestState* GetState(int32_t request_id) const {
    RequestMap::const_iterator it = request_map_.find(request_id);
    if (it != request_map_.end())
      return it->second.get();
    return nullptr;
  }

  void RemoveState(int32_t request_id) {
    RequestMap::iterator it = request_map_.find(request_id);
    DCHECK(it != request_map_.end());
    if (it != request_map_.end())
      request_map_.erase(it);
  }

  // Stop accepting new requests and cancel pending/in-flight requests when the
  // CEF context or associated browser is destroyed.
  void OnDestroyed() {
    CEF_REQUIRE_IOT();
    DCHECK(init_state_);

    init_state_->DeleteDestructionObserver();

    // Stop accepting new requests.
    shutting_down_ = true;

    // Stop the delivery of pending callbacks.
    weak_ptr_factory_.InvalidateWeakPtrs();

    // Take ownership of any pending requests.
    PendingRequests pending_requests;
    pending_requests.swap(pending_requests_);

    // Take ownership of any in-progress requests.
    RequestMap request_map;
    request_map.swap(request_map_);

    // Notify handlers for in-progress requests.
    for (const auto& pair : request_map) {
      CallHandlerOnComplete(
          pair.second.get(),
          network::URLLoaderCompletionStatus(net::ERR_ABORTED));
    }

    if (init_state_->browser_) {
      // Clear objects that reference the browser.
      init_state_->browser_ = nullptr;
      init_state_->frame_ = nullptr;
    }

    // Execute cancel callbacks and delete pending and in-progress requests.
    // This may result in the request being torn down sooner, or it may be
    // ignored if the request is already in the process of being torn down. When
    // the last callback is executed it may result in |this| being deleted.
    pending_requests.clear();

    for (auto& pair : request_map) {
      auto state = std::move(pair.second);
      if (state->cancel_callback_) {
        std::move(state->cancel_callback_).Run(net::ERR_ABORTED);
      }
    }
  }

  static CefRefPtr<CefRequestImpl> MakeRequest(
      const network::ResourceRequest* request,
      int64 request_id,
      bool read_only) {
    CefRefPtr<CefRequestImpl> requestPtr = new CefRequestImpl();
    requestPtr->Set(request, request_id);
    if (read_only)
      requestPtr->SetReadOnly(true);
    else
      requestPtr->SetTrackChanges(true);
    return requestPtr;
  }

  // Returns true if |request| cannot be handled internally.
  static bool IsExternalRequest(const network::ResourceRequest* request) {
    return !scheme::IsInternalHandledScheme(request->url.scheme());
  }

  scoped_refptr<InitHelper> init_helper_;
  std::unique_ptr<InitState> init_state_;

  bool shutting_down_ = false;

  using RequestMap = std::map<int32_t, std::unique_ptr<RequestState>>;
  RequestMap request_map_;

  using PendingRequests = std::vector<std::unique_ptr<PendingRequest>>;
  PendingRequests pending_requests_;

  base::WeakPtrFactory<InterceptedRequestHandlerWrapper> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(InterceptedRequestHandlerWrapper);
};

}  // namespace

std::unique_ptr<InterceptedRequestHandler> CreateInterceptedRequestHandler(
    content::BrowserContext* browser_context,
    content::RenderFrameHost* frame,
    int render_process_id,
    bool is_navigation,
    bool is_download,
    const url::Origin& request_initiator) {
  CEF_REQUIRE_UIT();
  CHECK(browser_context);

  CefRefPtr<CefBrowserHostBase> browserPtr;
  CefRefPtr<CefFrame> framePtr;

  // Default to handlers for the same process in case |frame| doesn't have an
  // associated CefBrowserHost.
  content::GlobalRenderFrameHostId global_id(render_process_id,
                                             MSG_ROUTING_NONE);

  // |frame| may be nullptr for service worker requests.
  if (frame) {
    // May return nullptr for requests originating from guest views.
    browserPtr = CefBrowserHostBase::GetBrowserForHost(frame);
    if (browserPtr) {
      framePtr = browserPtr->GetFrameForHost(frame);
      CHECK(framePtr);
      global_id = frame->GetGlobalId();
    }
  }

  auto init_state =
      std::make_unique<InterceptedRequestHandlerWrapper::InitState>();
  init_state->Initialize(browser_context, browserPtr, framePtr, global_id,
                         is_navigation, is_download, request_initiator,
                         base::RepeatingClosure());

  auto wrapper = std::make_unique<InterceptedRequestHandlerWrapper>();
  wrapper->init_helper()->MaybeSetInitialized(std::move(init_state));

  return wrapper;
}

std::unique_ptr<InterceptedRequestHandler> CreateInterceptedRequestHandler(
    content::WebContents::Getter web_contents_getter,
    int frame_tree_node_id,
    const network::ResourceRequest& request,
    const base::RepeatingClosure& unhandled_request_callback) {
  CEF_REQUIRE_UIT();

  content::WebContents* web_contents = web_contents_getter.Run();
  CHECK(web_contents);

  content::BrowserContext* browser_context = web_contents->GetBrowserContext();
  CHECK(browser_context);

  content::RenderFrameHost* frame = nullptr;

  if (request.is_main_frame ||
      static_cast<blink::mojom::ResourceType>(request.resource_type) ==
          blink::mojom::ResourceType::kMainFrame) {
    frame = web_contents->GetMainFrame();
    CHECK(frame);
  } else {
    // May return nullptr for frames in inner WebContents.
    auto node = content::FrameTreeNode::GloballyFindByID(frame_tree_node_id);
    if (node) {
      frame = node->current_frame_host();

      // RFHs can move between FrameTreeNodes. Make sure this one hasn't. See
      // documentation on RenderFrameHost::GetFrameTreeNodeId() for background.
      if (content::WebContents::FromRenderFrameHost(frame) != web_contents) {
        frame = nullptr;
      }
    }

    if (!frame) {
      // Use the main frame for the CefBrowserHost.
      frame = web_contents->GetMainFrame();
      CHECK(frame);
    }
  }

  CefRefPtr<CefBrowserHostBase> browserPtr;
  CefRefPtr<CefFrame> framePtr;

  // Default to handlers for the same process in case |frame| doesn't have an
  // associated CefBrowserHost.
  content::GlobalRenderFrameHostId global_id(frame->GetProcess()->GetID(),
                                             MSG_ROUTING_NONE);

  // May return nullptr for requests originating from guest views.
  browserPtr = CefBrowserHostBase::GetBrowserForHost(frame);
  if (browserPtr) {
    framePtr = browserPtr->GetFrameForHost(frame);
    DCHECK(framePtr);
    global_id = frame->GetGlobalId();
  }

  const bool is_navigation = ui::PageTransitionIsNewNavigation(
      static_cast<ui::PageTransition>(request.transition_type));
  // TODO(navigation): Can we determine the |is_download| value?
  const bool is_download = false;
  url::Origin request_initiator;
  if (request.request_initiator.has_value())
    request_initiator = *request.request_initiator;

  auto init_state =
      std::make_unique<InterceptedRequestHandlerWrapper::InitState>();
  init_state->Initialize(browser_context, browserPtr, framePtr, global_id,
                         is_navigation, is_download, request_initiator,
                         unhandled_request_callback);

  auto wrapper = std::make_unique<InterceptedRequestHandlerWrapper>();
  wrapper->init_helper()->MaybeSetInitialized(std::move(init_state));

  return wrapper;
}

}  // namespace net_service
