// 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 #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(nullptr); } } // 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 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 WebURL& new_url, 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(bytes_sent); total_bytes_to_be_sent_ = static_cast(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 = std::make_unique(); 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( std::make_unique(PP_OK)); } void PepperURLLoaderHost::DidFail(const WebURLError& error) { // Note that |loader| will be NULL for document loads. int32_t pp_error = PP_ERROR_FAILED; // 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; } if (error.is_web_security_violation()) pp_error = PP_ERROR_NOACCESS; SendUpdateToPlugin( std::make_unique(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( std::make_unique(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.SetPluginChildID(renderer_ppapi_host_->GetPluginChildId()); // Requests from plug-ins must skip service workers, see the comment in // CreateWebURLRequest. DCHECK_EQ(web_request.GetServiceWorkerMode(), WebURLRequest::ServiceWorkerMode::kNone); 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(network::mojom::FetchRequestMode::kCORS); web_request.SetFetchCredentialsMode( filled_in_request_data.allow_credentials ? network::mojom::FetchCredentialsMode::kInclude : network::mojom::FetchCredentialsMode::kOmit); } else { web_request.SetFetchRequestMode( network::mojom::FetchRequestMode::kSameOrigin); // 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 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 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( renderer_ppapi_host_->GetPluginInstance(pp_instance())); if (!instance_object || instance_object->is_deleted()) return nullptr; 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( std::make_unique(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( std::make_unique( 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