// 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 "content/browser/resolve_proxy_msg_helper.h"

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "net/proxy_resolution/proxy_info.h"
#include "services/network/public/mojom/network_context.mojom.h"

namespace content {

ResolveProxyMsgHelper::ResolveProxyMsgHelper(int render_process_host_id)
    : BrowserMessageFilter(ViewMsgStart),
      render_process_host_id_(render_process_host_id),
      binding_(this) {}

void ResolveProxyMsgHelper::OverrideThreadForMessage(
    const IPC::Message& message,
    BrowserThread::ID* thread) {
  if (message.type() == ViewHostMsg_ResolveProxy::ID)
    *thread = BrowserThread::UI;
}

bool ResolveProxyMsgHelper::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ResolveProxyMsgHelper, message)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ResolveProxy, OnResolveProxy)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void ResolveProxyMsgHelper::OnResolveProxy(const GURL& url,
                                           IPC::Message* reply_msg) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Enqueue the pending request.
  pending_requests_.push_back(PendingRequest(url, reply_msg));

  // If nothing is in progress, start.
  if (!binding_.is_bound()) {
    DCHECK_EQ(1u, pending_requests_.size());
    StartPendingRequest();
  }
}

ResolveProxyMsgHelper::~ResolveProxyMsgHelper() {
  DCHECK(!owned_self_);
  DCHECK(!binding_.is_bound());
}

void ResolveProxyMsgHelper::StartPendingRequest() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!binding_.is_bound());
  DCHECK(!pending_requests_.empty());

  // Start the request.
  network::mojom::ProxyLookupClientPtr proxy_lookup_client;
  binding_.Bind(mojo::MakeRequest(&proxy_lookup_client));
  binding_.set_connection_error_handler(
      base::BindOnce(&ResolveProxyMsgHelper::OnProxyLookupComplete,
                     base::Unretained(this), net::ERR_ABORTED, base::nullopt));
  owned_self_ = this;
  if (!SendRequestToNetworkService(pending_requests_.front().url,
                                   std::move(proxy_lookup_client))) {
    OnProxyLookupComplete(net::ERR_FAILED, base::nullopt);
  }
}

bool ResolveProxyMsgHelper::SendRequestToNetworkService(
    const GURL& url,
    network::mojom::ProxyLookupClientPtr proxy_lookup_client) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  RenderProcessHost* render_process_host =
      RenderProcessHost::FromID(render_process_host_id_);
  // Fail the request if there's no such RenderProcessHost;
  if (!render_process_host)
    return false;
  render_process_host->GetStoragePartition()
      ->GetNetworkContext()
      ->LookUpProxyForURL(url, std::move(proxy_lookup_client));
  return true;
}

void ResolveProxyMsgHelper::OnProxyLookupComplete(
    int32_t net_error,
    const base::Optional<net::ProxyInfo>& proxy_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!pending_requests_.empty());

  binding_.Close();

  // Need to keep |this| alive until the end of this method, and then release
  // this reference. StartPendingRequest(), if called, will grab other
  // reference, and a reference may be owned by the IO thread or by other
  // posted tasks, so |this| may or may not be deleted at the end of this
  // method.
  scoped_refptr<ResolveProxyMsgHelper> owned_self = std::move(owned_self_);

  // If all references except |owned_self| have been released, then there's
  // nothing waiting for pending requests to complete. So just exit this method,
  // which will release the last reference, destroying |this|.
  if (HasOneRef())
    return;

  // Clear the current (completed) request.
  PendingRequest completed_req = std::move(pending_requests_.front());
  pending_requests_.pop_front();

  ViewHostMsg_ResolveProxy::WriteReplyParams(
      completed_req.reply_msg.get(), !!proxy_info,
      proxy_info ? proxy_info->ToPacString() : std::string());
  Send(completed_req.reply_msg.release());

  // Start the next request.
  if (!pending_requests_.empty())
    StartPendingRequest();
}

ResolveProxyMsgHelper::PendingRequest::PendingRequest(const GURL& url,
                                                      IPC::Message* reply_msg)
    : url(url), reply_msg(reply_msg) {}

ResolveProxyMsgHelper::PendingRequest::PendingRequest(
    PendingRequest&& pending_request) noexcept = default;

ResolveProxyMsgHelper::PendingRequest::~PendingRequest() noexcept = default;

ResolveProxyMsgHelper::PendingRequest& ResolveProxyMsgHelper::PendingRequest::
operator=(PendingRequest&& pending_request) noexcept = default;

}  // namespace content