Home | History | Annotate | Download | only in pepper
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/renderer/pepper/pepper_url_loader_host.h"
      6 
      7 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
      8 #include "content/renderer/pepper/renderer_ppapi_host_impl.h"
      9 #include "content/renderer/pepper/url_request_info_util.h"
     10 #include "content/renderer/pepper/url_response_info_util.h"
     11 #include "net/base/net_errors.h"
     12 #include "ppapi/c/pp_errors.h"
     13 #include "ppapi/host/dispatch_host_message.h"
     14 #include "ppapi/host/host_message_context.h"
     15 #include "ppapi/host/ppapi_host.h"
     16 #include "ppapi/proxy/ppapi_messages.h"
     17 #include "ppapi/shared_impl/ppapi_globals.h"
     18 #include "third_party/WebKit/public/platform/WebURLError.h"
     19 #include "third_party/WebKit/public/platform/WebURLLoader.h"
     20 #include "third_party/WebKit/public/platform/WebURLRequest.h"
     21 #include "third_party/WebKit/public/platform/WebURLResponse.h"
     22 #include "third_party/WebKit/public/web/WebDocument.h"
     23 #include "third_party/WebKit/public/web/WebElement.h"
     24 #include "third_party/WebKit/public/web/WebKit.h"
     25 #include "third_party/WebKit/public/web/WebLocalFrame.h"
     26 #include "third_party/WebKit/public/web/WebPluginContainer.h"
     27 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
     28 #include "third_party/WebKit/public/web/WebURLLoaderOptions.h"
     29 
     30 using blink::WebLocalFrame;
     31 using blink::WebString;
     32 using blink::WebURL;
     33 using blink::WebURLError;
     34 using blink::WebURLLoader;
     35 using blink::WebURLLoaderOptions;
     36 using blink::WebURLRequest;
     37 using blink::WebURLResponse;
     38 
     39 #ifdef _MSC_VER
     40 // Do not warn about use of std::copy with raw pointers.
     41 #pragma warning(disable : 4996)
     42 #endif
     43 
     44 namespace content {
     45 
     46 PepperURLLoaderHost::PepperURLLoaderHost(RendererPpapiHostImpl* host,
     47                                          bool main_document_loader,
     48                                          PP_Instance instance,
     49                                          PP_Resource resource)
     50     : ResourceHost(host->GetPpapiHost(), instance, resource),
     51       renderer_ppapi_host_(host),
     52       main_document_loader_(main_document_loader),
     53       has_universal_access_(false),
     54       bytes_sent_(0),
     55       total_bytes_to_be_sent_(-1),
     56       bytes_received_(0),
     57       total_bytes_to_be_received_(-1),
     58       pending_response_(false),
     59       weak_factory_(this) {
     60   DCHECK((main_document_loader && !resource) ||
     61          (!main_document_loader && resource));
     62 }
     63 
     64 PepperURLLoaderHost::~PepperURLLoaderHost() {
     65   // Normally deleting this object will delete the loader which will implicitly
     66   // cancel the load. But this won't happen for the main document loader. So it
     67   // would be nice to issue a Close() here.
     68   //
     69   // However, the PDF plugin will cancel the document load and then close the
     70   // resource (which is reasonable). It then makes a second request to load the
     71   // document so it can set the "want progress" flags (which is unreasonable --
     72   // we should probably provide download progress on document loads).
     73   //
     74   // But a Close() on the main document (even if the request is already
     75   // canceled) will cancel all pending subresources, of which the second
     76   // request is one, and the load will fail. Even if we fixed the PDF reader to
     77   // change the timing or to send progress events to avoid the second request,
     78   // we don't want to cancel other loads when the main one is closed.
     79   //
     80   // "Leaking" the main document load here by not closing it will only affect
     81   // plugins handling main document loads (which are very few, mostly only PDF)
     82   // that dereference without explicitly closing the main document load (which
     83   // PDF doesn't do -- it explicitly closes it before issuing the second
     84   // request). And the worst thing that will happen is that any remaining data
     85   // will get queued inside WebKit.
     86   if (main_document_loader_) {
     87     // The PluginInstance has a non-owning pointer to us.
     88     PepperPluginInstanceImpl* instance_object =
     89         renderer_ppapi_host_->GetPluginInstanceImpl(pp_instance());
     90     if (instance_object) {
     91       DCHECK(instance_object->document_loader() == this);
     92       instance_object->set_document_loader(NULL);
     93     }
     94   }
     95 
     96   // There is a path whereby the destructor for the loader_ member can
     97   // invoke InstanceWasDeleted() upon this URLLoaderResource, thereby
     98   // re-entering the scoped_ptr destructor with the same scoped_ptr object
     99   // via loader_.reset(). Be sure that loader_ is first NULL then destroy
    100   // the scoped_ptr. See http://crbug.com/159429.
    101   scoped_ptr<blink::WebURLLoader> for_destruction_only(loader_.release());
    102 }
    103 
    104 int32_t PepperURLLoaderHost::OnResourceMessageReceived(
    105     const IPC::Message& msg,
    106     ppapi::host::HostMessageContext* context) {
    107   PPAPI_BEGIN_MESSAGE_MAP(PepperURLLoaderHost, msg)
    108     PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_URLLoader_Open,
    109                                       OnHostMsgOpen)
    110     PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_URLLoader_SetDeferLoading,
    111                                       OnHostMsgSetDeferLoading)
    112     PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_URLLoader_Close,
    113                                         OnHostMsgClose);
    114     PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
    115         PpapiHostMsg_URLLoader_GrantUniversalAccess,
    116         OnHostMsgGrantUniversalAccess)
    117   PPAPI_END_MESSAGE_MAP()
    118   return PP_ERROR_FAILED;
    119 }
    120 
    121 void PepperURLLoaderHost::willSendRequest(
    122     WebURLLoader* loader,
    123     WebURLRequest& new_request,
    124     const WebURLResponse& redirect_response) {
    125   DCHECK(out_of_order_replies_.empty());
    126   if (!request_data_.follow_redirects) {
    127     SaveResponse(redirect_response);
    128     SetDefersLoading(true);
    129   }
    130 }
    131 
    132 void PepperURLLoaderHost::didSendData(
    133     WebURLLoader* loader,
    134     unsigned long long bytes_sent,
    135     unsigned long long total_bytes_to_be_sent) {
    136   // TODO(darin): Bounds check input?
    137   bytes_sent_ = static_cast<int64_t>(bytes_sent);
    138   total_bytes_to_be_sent_ = static_cast<int64_t>(total_bytes_to_be_sent);
    139   UpdateProgress();
    140 }
    141 
    142 void PepperURLLoaderHost::didReceiveResponse(WebURLLoader* loader,
    143                                              const WebURLResponse& response) {
    144   // Sets -1 if the content length is unknown. Send before issuing callback.
    145   total_bytes_to_be_received_ = response.expectedContentLength();
    146   UpdateProgress();
    147 
    148   SaveResponse(response);
    149 }
    150 
    151 void PepperURLLoaderHost::didDownloadData(WebURLLoader* loader,
    152                                           int data_length,
    153                                           int encoded_data_length) {
    154   bytes_received_ += data_length;
    155   UpdateProgress();
    156 }
    157 
    158 void PepperURLLoaderHost::didReceiveData(WebURLLoader* loader,
    159                                          const char* data,
    160                                          int data_length,
    161                                          int encoded_data_length) {
    162   // Note that |loader| will be NULL for document loads.
    163   bytes_received_ += data_length;
    164   UpdateProgress();
    165 
    166   PpapiPluginMsg_URLLoader_SendData* message =
    167       new PpapiPluginMsg_URLLoader_SendData;
    168   message->WriteData(data, data_length);
    169   SendUpdateToPlugin(message);
    170 }
    171 
    172 void PepperURLLoaderHost::didFinishLoading(WebURLLoader* loader,
    173                                            double finish_time,
    174                                            int64_t total_encoded_data_length) {
    175   // Note that |loader| will be NULL for document loads.
    176   SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(PP_OK));
    177 }
    178 
    179 void PepperURLLoaderHost::didFail(WebURLLoader* loader,
    180                                   const WebURLError& error) {
    181   // Note that |loader| will be NULL for document loads.
    182   int32_t pp_error = PP_ERROR_FAILED;
    183   if (error.domain.equals(WebString::fromUTF8(net::kErrorDomain))) {
    184     // TODO(bbudge): Extend pp_errors.h to cover interesting network errors
    185     // from the net error domain.
    186     switch (error.reason) {
    187       case net::ERR_ACCESS_DENIED:
    188       case net::ERR_NETWORK_ACCESS_DENIED:
    189         pp_error = PP_ERROR_NOACCESS;
    190         break;
    191     }
    192   } else {
    193     // It's a WebKit error.
    194     pp_error = PP_ERROR_NOACCESS;
    195   }
    196   SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(pp_error));
    197 }
    198 
    199 void PepperURLLoaderHost::DidConnectPendingHostToResource() {
    200   for (size_t i = 0; i < pending_replies_.size(); i++)
    201     host()->SendUnsolicitedReply(pp_resource(), *pending_replies_[i]);
    202   pending_replies_.clear();
    203 }
    204 
    205 int32_t PepperURLLoaderHost::OnHostMsgOpen(
    206     ppapi::host::HostMessageContext* context,
    207     const ppapi::URLRequestInfoData& request_data) {
    208   // An "Open" isn't a resource Call so has no reply, but failure to open
    209   // implies a load failure. To make it harder to forget to send the load
    210   // failed reply from the open handler, we instead catch errors and convert
    211   // them to load failed messages.
    212   int32_t ret = InternalOnHostMsgOpen(context, request_data);
    213   DCHECK(ret != PP_OK_COMPLETIONPENDING);
    214 
    215   if (ret != PP_OK)
    216     SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(ret));
    217   return PP_OK;
    218 }
    219 
    220 // Since this is wrapped by OnHostMsgOpen, we can return errors here and they
    221 // will be translated into a FinishedLoading call automatically.
    222 int32_t PepperURLLoaderHost::InternalOnHostMsgOpen(
    223     ppapi::host::HostMessageContext* context,
    224     const ppapi::URLRequestInfoData& request_data) {
    225   // Main document loads are already open, so don't allow people to open them
    226   // again.
    227   if (main_document_loader_)
    228     return PP_ERROR_INPROGRESS;
    229 
    230   // Create a copy of the request data since CreateWebURLRequest will populate
    231   // the file refs.
    232   ppapi::URLRequestInfoData filled_in_request_data = request_data;
    233 
    234   if (URLRequestRequiresUniversalAccess(filled_in_request_data) &&
    235       !has_universal_access_) {
    236     ppapi::PpapiGlobals::Get()->LogWithSource(
    237         pp_instance(),
    238         PP_LOGLEVEL_ERROR,
    239         std::string(),
    240         "PPB_URLLoader.Open: The URL you're requesting is "
    241         " on a different security origin than your plugin. To request "
    242         " cross-origin resources, see "
    243         " PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS.");
    244     return PP_ERROR_NOACCESS;
    245   }
    246 
    247   if (loader_.get())
    248     return PP_ERROR_INPROGRESS;
    249 
    250   WebLocalFrame* frame = GetFrame();
    251   if (!frame)
    252     return PP_ERROR_FAILED;
    253 
    254   WebURLRequest web_request;
    255   if (!CreateWebURLRequest(
    256           pp_instance(), &filled_in_request_data, frame, &web_request)) {
    257     return PP_ERROR_FAILED;
    258   }
    259 
    260   web_request.setRequestContext(WebURLRequest::RequestContextPlugin);
    261   web_request.setRequestorProcessID(renderer_ppapi_host_->GetPluginPID());
    262 
    263   WebURLLoaderOptions options;
    264   if (has_universal_access_) {
    265     options.allowCredentials = true;
    266     options.crossOriginRequestPolicy =
    267         WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
    268   } else {
    269     // All other HTTP requests are untrusted.
    270     options.untrustedHTTP = true;
    271     if (filled_in_request_data.allow_cross_origin_requests) {
    272       // Allow cross-origin requests with access control. The request specifies
    273       // if credentials are to be sent.
    274       options.allowCredentials = filled_in_request_data.allow_credentials;
    275       options.crossOriginRequestPolicy =
    276           WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
    277     } else {
    278       // Same-origin requests can always send credentials.
    279       options.allowCredentials = true;
    280     }
    281   }
    282 
    283   loader_.reset(frame->createAssociatedURLLoader(options));
    284   if (!loader_.get())
    285     return PP_ERROR_FAILED;
    286 
    287   // Don't actually save the request until we know we're going to load.
    288   request_data_ = filled_in_request_data;
    289   loader_->loadAsynchronously(web_request, this);
    290 
    291   // Although the request is technically pending, this is not a "Call" message
    292   // so we don't return COMPLETIONPENDING.
    293   return PP_OK;
    294 }
    295 
    296 int32_t PepperURLLoaderHost::OnHostMsgSetDeferLoading(
    297     ppapi::host::HostMessageContext* context,
    298     bool defers_loading) {
    299   SetDefersLoading(defers_loading);
    300   return PP_OK;
    301 }
    302 
    303 int32_t PepperURLLoaderHost::OnHostMsgClose(
    304     ppapi::host::HostMessageContext* context) {
    305   Close();
    306   return PP_OK;
    307 }
    308 
    309 int32_t PepperURLLoaderHost::OnHostMsgGrantUniversalAccess(
    310     ppapi::host::HostMessageContext* context) {
    311   // Only plugins with private permission can bypass same origin.
    312   if (!host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE))
    313     return PP_ERROR_FAILED;
    314   has_universal_access_ = true;
    315   return PP_OK;
    316 }
    317 
    318 void PepperURLLoaderHost::SendUpdateToPlugin(IPC::Message* message) {
    319   // We must send messages to the plugin in the order that the responses are
    320   // received from webkit, even when the host isn't ready to send messages or
    321   // when the host performs an asynchronous operation.
    322   //
    323   // Only {FinishedLoading, ReceivedResponse, SendData} have ordering
    324   // contraints; all other messages are immediately added to pending_replies_.
    325   //
    326   // Accepted orderings for {FinishedLoading, ReceivedResponse, SendData} are:
    327   //   - {ReceivedResponse, SendData (zero or more times), FinishedLoading}
    328   //   - {FinishedLoading (when status != PP_OK)}
    329   if (message->type() == PpapiPluginMsg_URLLoader_SendData::ID ||
    330       message->type() == PpapiPluginMsg_URLLoader_FinishedLoading::ID) {
    331     // Messages that must be sent after ReceivedResponse.
    332     if (pending_response_) {
    333       out_of_order_replies_.push_back(message);
    334     } else {
    335       SendOrderedUpdateToPlugin(message);
    336     }
    337   } else if (message->type() == PpapiPluginMsg_URLLoader_ReceivedResponse::ID) {
    338     // Allow SendData and FinishedLoading into the ordered queue.
    339     DCHECK(pending_response_);
    340     SendOrderedUpdateToPlugin(message);
    341     for (size_t i = 0; i < out_of_order_replies_.size(); i++)
    342       SendOrderedUpdateToPlugin(out_of_order_replies_[i]);
    343     // SendOrderedUpdateToPlugin destroys the messages for us.
    344     out_of_order_replies_.weak_clear();
    345     pending_response_ = false;
    346   } else {
    347     // Messages without ordering constraints.
    348     SendOrderedUpdateToPlugin(message);
    349   }
    350 }
    351 
    352 void PepperURLLoaderHost::SendOrderedUpdateToPlugin(IPC::Message* message) {
    353   if (pp_resource() == 0) {
    354     pending_replies_.push_back(message);
    355   } else {
    356     host()->SendUnsolicitedReply(pp_resource(), *message);
    357     delete message;
    358   }
    359 }
    360 
    361 void PepperURLLoaderHost::Close() {
    362   if (loader_.get()) {
    363     loader_->cancel();
    364   } else if (main_document_loader_) {
    365     // TODO(raymes): Calling WebLocalFrame::stopLoading here is incorrect as it
    366     // cancels all URL loaders associated with the frame. If a client has opened
    367     // other URLLoaders and then closes the main one, the others should still
    368     // remain connected. Work out how to only cancel the main request:
    369     // crbug.com/384197.
    370     blink::WebLocalFrame* frame = GetFrame();
    371     if (frame)
    372       frame->stopLoading();
    373   }
    374 }
    375 
    376 blink::WebLocalFrame* PepperURLLoaderHost::GetFrame() {
    377   PepperPluginInstance* instance_object =
    378       renderer_ppapi_host_->GetPluginInstance(pp_instance());
    379   if (!instance_object)
    380     return NULL;
    381   return instance_object->GetContainer()->element().document().frame();
    382 }
    383 
    384 void PepperURLLoaderHost::SetDefersLoading(bool defers_loading) {
    385   if (loader_.get())
    386     loader_->setDefersLoading(defers_loading);
    387 
    388   // TODO(brettw) bug 96770: We need a way to set the defers loading flag on
    389   // main document loads (when the loader_ is null).
    390 }
    391 
    392 void PepperURLLoaderHost::SaveResponse(const WebURLResponse& response) {
    393   // When we're the main document loader, we send the response data up front,
    394   // so we don't want to trigger any callbacks in the plugin which aren't
    395   // expected. We should not be getting redirects so the response sent
    396   // up-front should be valid (plugin document loads happen after all
    397   // redirects are processed since WebKit has to know the MIME type).
    398   if (!main_document_loader_) {
    399     // We note when there's a callback in flight for a response to ensure that
    400     // messages we send to the plugin are not sent out of order. See
    401     // SendUpdateToPlugin() for more details.
    402     DCHECK(!pending_response_);
    403     pending_response_ = true;
    404 
    405     DataFromWebURLResponse(
    406         renderer_ppapi_host_,
    407         pp_instance(),
    408         response,
    409         base::Bind(&PepperURLLoaderHost::DidDataFromWebURLResponse,
    410                    weak_factory_.GetWeakPtr()));
    411   }
    412 }
    413 
    414 void PepperURLLoaderHost::DidDataFromWebURLResponse(
    415     const ppapi::URLResponseInfoData& data) {
    416   SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_ReceivedResponse(data));
    417 }
    418 
    419 void PepperURLLoaderHost::UpdateProgress() {
    420   bool record_download = request_data_.record_download_progress;
    421   bool record_upload = request_data_.record_upload_progress;
    422   if (record_download || record_upload) {
    423     // Here we go through some effort to only send the exact information that
    424     // the requestor wanted in the request flags. It would be just as
    425     // efficient to send all of it, but we don't want people to rely on
    426     // getting download progress when they happen to set the upload progress
    427     // flag.
    428     ppapi::proxy::ResourceMessageReplyParams params;
    429     SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_UpdateProgress(
    430         record_upload ? bytes_sent_ : -1,
    431         record_upload ? total_bytes_to_be_sent_ : -1,
    432         record_download ? bytes_received_ : -1,
    433         record_download ? total_bytes_to_be_received_ : -1));
    434   }
    435 }
    436 
    437 }  // namespace content
    438