// Copyright (c) 2013 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/renderer_host/pepper/pepper_network_proxy_host.h"

#include "base/bind.h"
#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/socket_permission_request.h"
#include "net/base/net_errors.h"
#include "net/proxy/proxy_info.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"

namespace content {

PepperNetworkProxyHost::PepperNetworkProxyHost(BrowserPpapiHostImpl* host,
                                               PP_Instance instance,
                                               PP_Resource resource)
    : ResourceHost(host->GetPpapiHost(), instance, resource),
      proxy_service_(NULL),
      is_allowed_(false),
      waiting_for_ui_thread_data_(true),
      weak_factory_(this) {
  int render_process_id(0), render_frame_id(0);
  host->GetRenderFrameIDsForInstance(
      instance, &render_process_id, &render_frame_id);
  BrowserThread::PostTaskAndReplyWithResult(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&GetUIThreadDataOnUIThread,
                 render_process_id,
                 render_frame_id,
                 host->external_plugin()),
      base::Bind(&PepperNetworkProxyHost::DidGetUIThreadData,
                 weak_factory_.GetWeakPtr()));
}

PepperNetworkProxyHost::~PepperNetworkProxyHost() {
  while (!pending_requests_.empty()) {
    // If the proxy_service_ is NULL, we shouldn't have any outstanding
    // requests.
    DCHECK(proxy_service_);
    net::ProxyService::PacRequest* request = pending_requests_.front();
    proxy_service_->CancelPacRequest(request);
    pending_requests_.pop();
  }
}

PepperNetworkProxyHost::UIThreadData::UIThreadData() : is_allowed(false) {}

PepperNetworkProxyHost::UIThreadData::~UIThreadData() {}

// static
PepperNetworkProxyHost::UIThreadData
PepperNetworkProxyHost::GetUIThreadDataOnUIThread(int render_process_id,
                                                  int render_frame_id,
                                                  bool is_external_plugin) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  PepperNetworkProxyHost::UIThreadData result;
  RenderProcessHost* render_process_host =
      RenderProcessHost::FromID(render_process_id);
  if (render_process_host && render_process_host->GetBrowserContext()) {
    result.context_getter =
        render_process_host->GetBrowserContext()
            ->GetRequestContextForRenderProcess(render_process_id);
  }

  SocketPermissionRequest request(
      content::SocketPermissionRequest::RESOLVE_PROXY, std::string(), 0);
  result.is_allowed =
      pepper_socket_utils::CanUseSocketAPIs(is_external_plugin,
                                            false /* is_private_api */,
                                            &request,
                                            render_process_id,
                                            render_frame_id);
  return result;
}

void PepperNetworkProxyHost::DidGetUIThreadData(
    const UIThreadData& ui_thread_data) {
  is_allowed_ = ui_thread_data.is_allowed;
  if (ui_thread_data.context_getter.get() &&
      ui_thread_data.context_getter->GetURLRequestContext()) {
    proxy_service_ =
        ui_thread_data.context_getter->GetURLRequestContext()->proxy_service();
  }
  waiting_for_ui_thread_data_ = false;
  if (!proxy_service_) {
    DLOG_IF(WARNING, proxy_service_)
        << "Failed to find a ProxyService for Pepper plugin.";
  }
  TryToSendUnsentRequests();
}

int32_t PepperNetworkProxyHost::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context) {
  PPAPI_BEGIN_MESSAGE_MAP(PepperNetworkProxyHost, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_NetworkProxy_GetProxyForURL,
                                      OnMsgGetProxyForURL)
  PPAPI_END_MESSAGE_MAP()
  return PP_ERROR_FAILED;
}

int32_t PepperNetworkProxyHost::OnMsgGetProxyForURL(
    ppapi::host::HostMessageContext* context,
    const std::string& url) {
  GURL gurl(url);
  if (gurl.is_valid()) {
    UnsentRequest request = {gurl, context->MakeReplyMessageContext()};
    unsent_requests_.push(request);
    TryToSendUnsentRequests();
  } else {
    SendFailureReply(PP_ERROR_BADARGUMENT, context->MakeReplyMessageContext());
  }
  return PP_OK_COMPLETIONPENDING;
}

void PepperNetworkProxyHost::TryToSendUnsentRequests() {
  if (waiting_for_ui_thread_data_)
    return;

  while (!unsent_requests_.empty()) {
    const UnsentRequest& request = unsent_requests_.front();
    if (!proxy_service_) {
      SendFailureReply(PP_ERROR_FAILED, request.reply_context);
    } else if (!is_allowed_) {
      SendFailureReply(PP_ERROR_NOACCESS, request.reply_context);
    } else {
      // Everything looks valid, so try to resolve the proxy.
      net::ProxyInfo* proxy_info = new net::ProxyInfo;
      net::ProxyService::PacRequest* pending_request = NULL;
      base::Callback<void(int)> callback =
          base::Bind(&PepperNetworkProxyHost::OnResolveProxyCompleted,
                     weak_factory_.GetWeakPtr(),
                     request.reply_context,
                     base::Owned(proxy_info));
      int result = proxy_service_->ResolveProxy(request.url,
                                                proxy_info,
                                                callback,
                                                &pending_request,
                                                net::BoundNetLog());
      pending_requests_.push(pending_request);
      // If it was handled synchronously, we must run the callback now;
      // proxy_service_ won't run it for us in this case.
      if (result != net::ERR_IO_PENDING)
        callback.Run(result);
    }
    unsent_requests_.pop();
  }
}

void PepperNetworkProxyHost::OnResolveProxyCompleted(
    ppapi::host::ReplyMessageContext context,
    net::ProxyInfo* proxy_info,
    int result) {
  pending_requests_.pop();

  if (result != net::OK) {
    // Currently, the only proxy-specific error we could get is
    // MANDATORY_PROXY_CONFIGURATION_FAILED. There's really no action a plugin
    // can take in this case, so there's no need to distinguish it from other
    // failures.
    context.params.set_result(PP_ERROR_FAILED);
  }
  host()->SendReply(context,
                    PpapiPluginMsg_NetworkProxy_GetProxyForURLReply(
                        proxy_info->ToPacString()));
}

void PepperNetworkProxyHost::SendFailureReply(
    int32_t error,
    ppapi::host::ReplyMessageContext context) {
  context.params.set_result(error);
  host()->SendReply(
      context, PpapiPluginMsg_NetworkProxy_GetProxyForURLReply(std::string()));
}

}  // namespace content
