| // 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 "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/WebURLError.h" |
| #include "third_party/WebKit/public/platform/WebURLLoader.h" |
| #include "third_party/WebKit/public/platform/WebURLRequest.h" |
| #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| #include "third_party/WebKit/public/web/WebDocument.h" |
| #include "third_party/WebKit/public/web/WebElement.h" |
| #include "third_party/WebKit/public/web/WebFrame.h" |
| #include "third_party/WebKit/public/web/WebKit.h" |
| #include "third_party/WebKit/public/web/WebPluginContainer.h" |
| #include "third_party/WebKit/public/web/WebSecurityOrigin.h" |
| #include "third_party/WebKit/public/web/WebURLLoaderOptions.h" |
| |
| using blink::WebFrame; |
| using blink::WebString; |
| using blink::WebURL; |
| using blink::WebURLError; |
| using blink::WebURLLoader; |
| using blink::WebURLLoaderOptions; |
| 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. |
| scoped_ptr<blink::WebURLLoader> for_destruction_only(loader_.release()); |
| } |
| |
| int32_t PepperURLLoaderHost::OnResourceMessageReceived( |
| const IPC::Message& msg, |
| ppapi::host::HostMessageContext* context) { |
| IPC_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) |
| IPC_END_MESSAGE_MAP() |
| return PP_ERROR_FAILED; |
| } |
| |
| void PepperURLLoaderHost::willSendRequest( |
| WebURLLoader* loader, |
| WebURLRequest& new_request, |
| const WebURLResponse& redirect_response) { |
| DCHECK(out_of_order_replies_.empty()); |
| if (!request_data_.follow_redirects) { |
| SaveResponse(redirect_response); |
| SetDefersLoading(true); |
| } |
| } |
| |
| void PepperURLLoaderHost::didSendData( |
| WebURLLoader* loader, |
| 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(WebURLLoader* loader, |
| 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(WebURLLoader* loader, |
| int data_length, |
| int encoded_data_length) { |
| bytes_received_ += data_length; |
| UpdateProgress(); |
| } |
| |
| void PepperURLLoaderHost::didReceiveData(WebURLLoader* loader, |
| const char* data, |
| int data_length, |
| int encoded_data_length) { |
| // Note that |loader| will be NULL for document loads. |
| bytes_received_ += data_length; |
| UpdateProgress(); |
| |
| PpapiPluginMsg_URLLoader_SendData* message = |
| new PpapiPluginMsg_URLLoader_SendData; |
| message->WriteData(data, data_length); |
| SendUpdateToPlugin(message); |
| } |
| |
| void PepperURLLoaderHost::didFinishLoading(WebURLLoader* loader, |
| double finish_time) { |
| // Note that |loader| will be NULL for document loads. |
| SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(PP_OK)); |
| } |
| |
| void PepperURLLoaderHost::didFail(WebURLLoader* loader, |
| 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(new PpapiPluginMsg_URLLoader_FinishedLoading(pp_error)); |
| } |
| |
| void PepperURLLoaderHost::DidConnectPendingHostToResource() { |
| for (size_t i = 0; i < pending_replies_.size(); i++) |
| host()->SendUnsolicitedReply(pp_resource(), *pending_replies_[i]); |
| 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(new 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; |
| |
| WebFrame* 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.setTargetType(WebURLRequest::TargetIsObject); |
| web_request.setRequestorProcessID(renderer_ppapi_host_->GetPluginPID()); |
| |
| WebURLLoaderOptions options; |
| if (has_universal_access_) { |
| options.allowCredentials = true; |
| options.crossOriginRequestPolicy = |
| WebURLLoaderOptions::CrossOriginRequestPolicyAllow; |
| } else { |
| // All other HTTP requests are untrusted. |
| options.untrustedHTTP = 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. |
| options.allowCredentials = filled_in_request_data.allow_credentials; |
| options.crossOriginRequestPolicy = |
| WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; |
| } else { |
| // Same-origin requests can always send credentials. |
| options.allowCredentials = true; |
| } |
| } |
| |
| 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(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(message); |
| } else { |
| SendOrderedUpdateToPlugin(message); |
| } |
| } else if (message->type() == PpapiPluginMsg_URLLoader_ReceivedResponse::ID) { |
| // Allow SendData and FinishedLoading into the ordered queue. |
| DCHECK(pending_response_); |
| SendOrderedUpdateToPlugin(message); |
| for (size_t i = 0; i < out_of_order_replies_.size(); i++) |
| SendOrderedUpdateToPlugin(out_of_order_replies_[i]); |
| // SendOrderedUpdateToPlugin destroys the messages for us. |
| out_of_order_replies_.weak_clear(); |
| pending_response_ = false; |
| } else { |
| // Messages without ordering constraints. |
| SendOrderedUpdateToPlugin(message); |
| } |
| } |
| |
| void PepperURLLoaderHost::SendOrderedUpdateToPlugin(IPC::Message* message) { |
| if (pp_resource() == 0) { |
| pending_replies_.push_back(message); |
| } else { |
| host()->SendUnsolicitedReply(pp_resource(), *message); |
| delete message; |
| } |
| } |
| |
| void PepperURLLoaderHost::Close() { |
| if (loader_.get()) |
| loader_->cancel(); |
| else if (main_document_loader_) |
| GetFrame()->stopLoading(); |
| } |
| |
| blink::WebFrame* PepperURLLoaderHost::GetFrame() { |
| PepperPluginInstance* instance_object = |
| renderer_ppapi_host_->GetPluginInstance(pp_instance()); |
| if (!instance_object) |
| return NULL; |
| return instance_object->GetContainer()->element().document().frame(); |
| } |
| |
| 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(new 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(new 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 |