text: // 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/renderer/pepper/pepper_url_loader_host.h"
  
  #include <stddef.h>
  
  #include "base/memory/ptr_util.h"
  #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
  #include "content/renderer/pepper/renderer_ppapi_host_impl.h"
  #include "content/renderer/pepper/url_request_info_util.h"
  #include "content/renderer/pepper/url_response_info_util.h"
  #include "net/base/net_errors.h"
  #include "ppapi/c/pp_errors.h"
  #include "ppapi/host/dispatch_host_message.h"
  #include "ppapi/host/host_message_context.h"
  #include "ppapi/host/ppapi_host.h"
  #include "ppapi/proxy/ppapi_messages.h"
  #include "ppapi/shared_impl/ppapi_globals.h"
  #include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
  #include "third_party/WebKit/public/platform/WebURLError.h"
  #include "third_party/WebKit/public/platform/WebURLRequest.h"
  #include "third_party/WebKit/public/platform/WebURLResponse.h"
  #include "third_party/WebKit/public/web/WebAssociatedURLLoader.h"
  #include "third_party/WebKit/public/web/WebAssociatedURLLoaderOptions.h"
  #include "third_party/WebKit/public/web/WebDocument.h"
  #include "third_party/WebKit/public/web/WebElement.h"
  #include "third_party/WebKit/public/web/WebKit.h"
  #include "third_party/WebKit/public/web/WebLocalFrame.h"
  #include "third_party/WebKit/public/web/WebPluginContainer.h"
  
  using blink::WebAssociatedURLLoader;
  using blink::WebAssociatedURLLoaderOptions;
  using blink::WebLocalFrame;
  using blink::WebString;
  using blink::WebURL;
  using blink::WebURLError;
  using blink::WebURLRequest;
  using blink::WebURLResponse;
  
  #ifdef _MSC_VER
  // Do not warn about use of std::copy with raw pointers.
  #pragma warning(disable : 4996)
  #endif
  
  namespace content {
  
  PepperURLLoaderHost::PepperURLLoaderHost(RendererPpapiHostImpl* host,
                                           bool main_document_loader,
                                           PP_Instance instance,
                                           PP_Resource resource)
      : ResourceHost(host->GetPpapiHost(), instance, resource),
        renderer_ppapi_host_(host),
        main_document_loader_(main_document_loader),
        has_universal_access_(false),
        bytes_sent_(0),
        total_bytes_to_be_sent_(-1),
        bytes_received_(0),
        total_bytes_to_be_received_(-1),
        pending_response_(false),
        weak_factory_(this) {
    DCHECK((main_document_loader && !resource) ||
           (!main_document_loader && resource));
  }
  
  PepperURLLoaderHost::~PepperURLLoaderHost() {
    // Normally deleting this object will delete the loader which will implicitly
    // cancel the load. But this won't happen for the main document loader. So it
    // would be nice to issue a Close() here.
    //
    // However, the PDF plugin will cancel the document load and then close the
    // resource (which is reasonable). It then makes a second request to load the
    // document so it can set the "want progress" flags (which is unreasonable --
    // we should probably provide download progress on document loads).
    //
    // But a Close() on the main document (even if the request is already
    // canceled) will cancel all pending subresources, of which the second
    // request is one, and the load will fail. Even if we fixed the PDF reader to
    // change the timing or to send progress events to avoid the second request,
    // we don't want to cancel other loads when the main one is closed.
    //
    // "Leaking" the main document load here by not closing it will only affect
    // plugins handling main document loads (which are very few, mostly only PDF)
    // that dereference without explicitly closing the main document load (which
    // PDF doesn't do -- it explicitly closes it before issuing the second
    // request). And the worst thing that will happen is that any remaining data
    // will get queued inside WebKit.
    if (main_document_loader_) {
      // The PluginInstance has a non-owning pointer to us.
      PepperPluginInstanceImpl* instance_object =
          renderer_ppapi_host_->GetPluginInstanceImpl(pp_instance());
      if (instance_object) {
        DCHECK(instance_object->document_loader() == this);
        instance_object->set_document_loader(NULL);
      }
    }
  
    // There is a path whereby the destructor for the loader_ member can
    // invoke InstanceWasDeleted() upon this URLLoaderResource, thereby
    // re-entering the scoped_ptr destructor with the same scoped_ptr object
    // via loader_.reset(). Be sure that loader_ is first NULL then destroy
    // the scoped_ptr. See http://crbug.com/159429.
    std::unique_ptr<WebAssociatedURLLoader> for_destruction_only(
        loader_.release());
  }
  
  int32_t PepperURLLoaderHost::OnResourceMessageReceived(
      const IPC::Message& msg,
      ppapi::host::HostMessageContext* context) {
    PPAPI_BEGIN_MESSAGE_MAP(PepperURLLoaderHost, msg)
      PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_URLLoader_Open,
                                        OnHostMsgOpen)
      PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_URLLoader_SetDeferLoading,
                                        OnHostMsgSetDeferLoading)
      PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_URLLoader_Close,
                                          OnHostMsgClose);
      PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
          PpapiHostMsg_URLLoader_GrantUniversalAccess,
          OnHostMsgGrantUniversalAccess)
    PPAPI_END_MESSAGE_MAP()
    return PP_ERROR_FAILED;
  }
  
  bool PepperURLLoaderHost::WillFollowRedirect(
      const WebURLRequest& new_request,
      const WebURLResponse& redirect_response) {
    DCHECK(out_of_order_replies_.empty());
    if (!request_data_.follow_redirects) {
      SaveResponse(redirect_response);
      SetDefersLoading(true);
      // Defer the request and wait the plugin to audit the redirect. We
      // shouldn't return false here as decision has been delegated to the
      // plugin.
    }
    return true;
  }
  
  void PepperURLLoaderHost::DidSendData(
      unsigned long long bytes_sent,
      unsigned long long total_bytes_to_be_sent) {
    // TODO(darin): Bounds check input?
    bytes_sent_ = static_cast<int64_t>(bytes_sent);
    total_bytes_to_be_sent_ = static_cast<int64_t>(total_bytes_to_be_sent);
    UpdateProgress();
  }
  
  void PepperURLLoaderHost::DidReceiveResponse(const WebURLResponse& response) {
    // Sets -1 if the content length is unknown. Send before issuing callback.
    total_bytes_to_be_received_ = response.ExpectedContentLength();
    UpdateProgress();
  
    SaveResponse(response);
  }
  
  void PepperURLLoaderHost::DidDownloadData(int data_length) {
    bytes_received_ += data_length;
    UpdateProgress();
  }
  
  void PepperURLLoaderHost::DidReceiveData(const char* data, int data_length) {
    // Note that |loader| will be NULL for document loads.
    bytes_received_ += data_length;
    UpdateProgress();
  
    auto message = base::MakeUnique<PpapiPluginMsg_URLLoader_SendData>();
    message->WriteData(data, data_length);
    SendUpdateToPlugin(std::move(message));
  }
  
  void PepperURLLoaderHost::DidFinishLoading(double finish_time) {
    // Note that |loader| will be NULL for document loads.
    SendUpdateToPlugin(
        base::MakeUnique<PpapiPluginMsg_URLLoader_FinishedLoading>(PP_OK));
  }
  
  void PepperURLLoaderHost::DidFail(const WebURLError& error) {
    // Note that |loader| will be NULL for document loads.
    int32_t pp_error = PP_ERROR_FAILED;
    if (error.domain.Equals(WebString::FromUTF8(net::kErrorDomain))) {
      // TODO(bbudge): Extend pp_errors.h to cover interesting network errors
      // from the net error domain.
      switch (error.reason) {
        case net::ERR_ACCESS_DENIED:
        case net::ERR_NETWORK_ACCESS_DENIED:
          pp_error = PP_ERROR_NOACCESS;
          break;
      }
    } else {
      // It's a WebKit error.
      pp_error = PP_ERROR_NOACCESS;
    }
    SendUpdateToPlugin(
        base::MakeUnique<PpapiPluginMsg_URLLoader_FinishedLoading>(pp_error));
  }
  
  void PepperURLLoaderHost::DidConnectPendingHostToResource() {
    for (const auto& reply : pending_replies_)
      host()->SendUnsolicitedReply(pp_resource(), *reply);
    pending_replies_.clear();
  }
  
  int32_t PepperURLLoaderHost::OnHostMsgOpen(
      ppapi::host::HostMessageContext* context,
      const ppapi::URLRequestInfoData& request_data) {
    // An "Open" isn't a resource Call so has no reply, but failure to open
    // implies a load failure. To make it harder to forget to send the load
    // failed reply from the open handler, we instead catch errors and convert
    // them to load failed messages.
    int32_t ret = InternalOnHostMsgOpen(context, request_data);
    DCHECK(ret != PP_OK_COMPLETIONPENDING);
  
    if (ret != PP_OK)
      SendUpdateToPlugin(
          base::MakeUnique<PpapiPluginMsg_URLLoader_FinishedLoading>(ret));
    return PP_OK;
  }
  
  // Since this is wrapped by OnHostMsgOpen, we can return errors here and they
  // will be translated into a FinishedLoading call automatically.
  int32_t PepperURLLoaderHost::InternalOnHostMsgOpen(
      ppapi::host::HostMessageContext* context,
      const ppapi::URLRequestInfoData& request_data) {
    // Main document loads are already open, so don't allow people to open them
    // again.
    if (main_document_loader_)
      return PP_ERROR_INPROGRESS;
  
    // Create a copy of the request data since CreateWebURLRequest will populate
    // the file refs.
    ppapi::URLRequestInfoData filled_in_request_data = request_data;
  
    if (URLRequestRequiresUniversalAccess(filled_in_request_data) &&
        !has_universal_access_) {
      ppapi::PpapiGlobals::Get()->LogWithSource(
          pp_instance(),
          PP_LOGLEVEL_ERROR,
          std::string(),
          "PPB_URLLoader.Open: The URL you're requesting is "
          " on a different security origin than your plugin. To request "
          " cross-origin resources, see "
          " PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS.");
      return PP_ERROR_NOACCESS;
    }
  
    if (loader_.get())
      return PP_ERROR_INPROGRESS;
  
    WebLocalFrame* frame = GetFrame();
    if (!frame)
      return PP_ERROR_FAILED;
  
    WebURLRequest web_request;
    if (!CreateWebURLRequest(
            pp_instance(), &filled_in_request_data, frame, &web_request)) {
      return PP_ERROR_FAILED;
    }
  
    web_request.SetRequestContext(WebURLRequest::kRequestContextPlugin);
    web_request.SetRequestorProcessID(renderer_ppapi_host_->GetPluginPID());
    // The requests from the plugins with private permission which can bypass same
    // origin must skip the ServiceWorker.
    web_request.SetServiceWorkerMode(
        host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE)
            ? WebURLRequest::ServiceWorkerMode::kNone
            : WebURLRequest::ServiceWorkerMode::kAll);
  
    WebAssociatedURLLoaderOptions options;
    if (!has_universal_access_) {
      // All other HTTP requests are untrusted.
      options.untrusted_http = true;
      if (filled_in_request_data.allow_cross_origin_requests) {
        // Allow cross-origin requests with access control. The request specifies
        // if credentials are to be sent.
        web_request.SetFetchRequestMode(WebURLRequest::kFetchRequestModeCORS);
        web_request.SetFetchCredentialsMode(
            filled_in_request_data.allow_credentials
                ? WebURLRequest::kFetchCredentialsModeInclude
                : WebURLRequest::kFetchCredentialsModeOmit);
      } else {
        web_request.SetFetchRequestMode(
            WebURLRequest::kFetchRequestModeSameOrigin);
        // Same-origin requests can always send credentials. Use the default
        // credentials mode "include".
      }
    }
  
    loader_.reset(frame->CreateAssociatedURLLoader(options));
    if (!loader_.get())
      return PP_ERROR_FAILED;
  
    // Don't actually save the request until we know we're going to load.
    request_data_ = filled_in_request_data;
    loader_->LoadAsynchronously(web_request, this);
  
    // Although the request is technically pending, this is not a "Call" message
    // so we don't return COMPLETIONPENDING.
    return PP_OK;
  }
  
  int32_t PepperURLLoaderHost::OnHostMsgSetDeferLoading(
      ppapi::host::HostMessageContext* context,
      bool defers_loading) {
    SetDefersLoading(defers_loading);
    return PP_OK;
  }
  
  int32_t PepperURLLoaderHost::OnHostMsgClose(
      ppapi::host::HostMessageContext* context) {
    Close();
    return PP_OK;
  }
  
  int32_t PepperURLLoaderHost::OnHostMsgGrantUniversalAccess(
      ppapi::host::HostMessageContext* context) {
    // Only plugins with private permission can bypass same origin.
    if (!host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE))
      return PP_ERROR_FAILED;
    has_universal_access_ = true;
    return PP_OK;
  }
  
  void PepperURLLoaderHost::SendUpdateToPlugin(
      std::unique_ptr<IPC::Message> message) {
    // We must send messages to the plugin in the order that the responses are
    // received from webkit, even when the host isn't ready to send messages or
    // when the host performs an asynchronous operation.
    //
    // Only {FinishedLoading, ReceivedResponse, SendData} have ordering
    // contraints; all other messages are immediately added to pending_replies_.
    //
    // Accepted orderings for {FinishedLoading, ReceivedResponse, SendData} are:
    //   - {ReceivedResponse, SendData (zero or more times), FinishedLoading}
    //   - {FinishedLoading (when status != PP_OK)}
    if (message->type() == PpapiPluginMsg_URLLoader_SendData::ID ||
        message->type() == PpapiPluginMsg_URLLoader_FinishedLoading::ID) {
      // Messages that must be sent after ReceivedResponse.
      if (pending_response_) {
        out_of_order_replies_.push_back(std::move(message));
      } else {
        SendOrderedUpdateToPlugin(std::move(message));
      }
    } else if (message->type() == PpapiPluginMsg_URLLoader_ReceivedResponse::ID) {
      // Allow SendData and FinishedLoading into the ordered queue.
      DCHECK(pending_response_);
      SendOrderedUpdateToPlugin(std::move(message));
      for (auto& reply : out_of_order_replies_)
        SendOrderedUpdateToPlugin(std::move(reply));
      out_of_order_replies_.clear();
      pending_response_ = false;
    } else {
      // Messages without ordering constraints.
      SendOrderedUpdateToPlugin(std::move(message));
    }
  }
  
  void PepperURLLoaderHost::SendOrderedUpdateToPlugin(
      std::unique_ptr<IPC::Message> message) {
    if (pp_resource() == 0) {
      pending_replies_.push_back(std::move(message));
    } else {
      host()->SendUnsolicitedReply(pp_resource(), *message);
    }
  }
  
  void PepperURLLoaderHost::Close() {
    if (loader_.get()) {
      loader_->Cancel();
    } else if (main_document_loader_) {
      // TODO(raymes): Calling WebLocalFrame::stopLoading here is incorrect as it
      // cancels all URL loaders associated with the frame. If a client has opened
      // other URLLoaders and then closes the main one, the others should still
      // remain connected. Work out how to only cancel the main request:
      // crbug.com/384197.
      WebLocalFrame* frame = GetFrame();
      if (frame)
        frame->StopLoading();
    }
  }
  
  WebLocalFrame* PepperURLLoaderHost::GetFrame() {
    PepperPluginInstanceImpl* instance_object =
        static_cast<PepperPluginInstanceImpl*>(
            renderer_ppapi_host_->GetPluginInstance(pp_instance()));
    if (!instance_object || instance_object->is_deleted())
      return NULL;
    return instance_object->GetContainer()->GetDocument().GetFrame();
  }
  
  void PepperURLLoaderHost::SetDefersLoading(bool defers_loading) {
    if (loader_.get())
      loader_->SetDefersLoading(defers_loading);
  
    // TODO(brettw) bug 96770: We need a way to set the defers loading flag on
    // main document loads (when the loader_ is null).
  }
  
  void PepperURLLoaderHost::SaveResponse(const WebURLResponse& response) {
    // When we're the main document loader, we send the response data up front,
    // so we don't want to trigger any callbacks in the plugin which aren't
    // expected. We should not be getting redirects so the response sent
    // up-front should be valid (plugin document loads happen after all
    // redirects are processed since WebKit has to know the MIME type).
    if (!main_document_loader_) {
      // We note when there's a callback in flight for a response to ensure that
      // messages we send to the plugin are not sent out of order. See
      // SendUpdateToPlugin() for more details.
      DCHECK(!pending_response_);
      pending_response_ = true;
  
      DataFromWebURLResponse(
          renderer_ppapi_host_,
          pp_instance(),
          response,
          base::Bind(&PepperURLLoaderHost::DidDataFromWebURLResponse,
                     weak_factory_.GetWeakPtr()));
    }
  }
  
  void PepperURLLoaderHost::DidDataFromWebURLResponse(
      const ppapi::URLResponseInfoData& data) {
    SendUpdateToPlugin(
        base::MakeUnique<PpapiPluginMsg_URLLoader_ReceivedResponse>(data));
  }
  
  void PepperURLLoaderHost::UpdateProgress() {
    bool record_download = request_data_.record_download_progress;
    bool record_upload = request_data_.record_upload_progress;
    if (record_download || record_upload) {
      // Here we go through some effort to only send the exact information that
      // the requestor wanted in the request flags. It would be just as
      // efficient to send all of it, but we don't want people to rely on
      // getting download progress when they happen to set the upload progress
      // flag.
      ppapi::proxy::ResourceMessageReplyParams params;
      SendUpdateToPlugin(
          base::MakeUnique<PpapiPluginMsg_URLLoader_UpdateProgress>(
              record_upload ? bytes_sent_ : -1,
              record_upload ? total_bytes_to_be_sent_ : -1,
              record_download ? bytes_received_ : -1,
              record_download ? total_bytes_to_be_received_ : -1));
    }
  }
  
  }  // namespace content
  