// 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 "ppapi/proxy/url_loader_resource.h" #include "base/logging.h" #include "ppapi/c/pp_completion_callback.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppb_url_loader.h" #include "ppapi/proxy/dispatch_reply_message.h" #include "ppapi/proxy/file_ref_resource.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/url_request_info_resource.h" #include "ppapi/proxy/url_response_info_resource.h" #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/url_response_info_data.h" #include "ppapi/thunk/enter.h" #include "ppapi/thunk/resource_creation_api.h" using ppapi::thunk::EnterResourceNoLock; using ppapi::thunk::PPB_URLLoader_API; using ppapi::thunk::PPB_URLRequestInfo_API; #ifdef _MSC_VER // Do not warn about use of std::copy with raw pointers. #pragma warning(disable : 4996) #endif namespace ppapi { namespace proxy { URLLoaderResource::URLLoaderResource(Connection connection, PP_Instance instance) : PluginResource(connection, instance), mode_(MODE_WAITING_TO_OPEN), status_callback_(NULL), bytes_sent_(0), total_bytes_to_be_sent_(-1), bytes_received_(0), total_bytes_to_be_received_(-1), user_buffer_(NULL), user_buffer_size_(0), done_status_(PP_OK_COMPLETIONPENDING), is_streaming_to_file_(false), is_asynchronous_load_suspended_(false) { SendCreate(RENDERER, PpapiHostMsg_URLLoader_Create()); } URLLoaderResource::URLLoaderResource(Connection connection, PP_Instance instance, int pending_main_document_loader_id, const ppapi::URLResponseInfoData& data) : PluginResource(connection, instance), mode_(MODE_OPENING), status_callback_(NULL), bytes_sent_(0), total_bytes_to_be_sent_(-1), bytes_received_(0), total_bytes_to_be_received_(-1), user_buffer_(NULL), user_buffer_size_(0), done_status_(PP_OK_COMPLETIONPENDING), is_streaming_to_file_(false), is_asynchronous_load_suspended_(false) { AttachToPendingHost(RENDERER, pending_main_document_loader_id); SaveResponseInfo(data); } URLLoaderResource::~URLLoaderResource() { } PPB_URLLoader_API* URLLoaderResource::AsPPB_URLLoader_API() { return this; } int32_t URLLoaderResource::Open(PP_Resource request_id, scoped_refptr callback) { EnterResourceNoLock enter_request(request_id, true); if (enter_request.failed()) { Log(PP_LOGLEVEL_ERROR, "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper" " users: use the ResourceRequest constructor that takes an instance or" " else the request will be null.)"); return PP_ERROR_BADARGUMENT; } return Open(enter_request.object()->GetData(), 0, callback); } int32_t URLLoaderResource::Open( const ::ppapi::URLRequestInfoData& request_data, int requestor_pid, scoped_refptr callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (mode_ != MODE_WAITING_TO_OPEN) return PP_ERROR_INPROGRESS; request_data_ = request_data; mode_ = MODE_OPENING; is_asynchronous_load_suspended_ = false; RegisterCallback(callback); Post(RENDERER, PpapiHostMsg_URLLoader_Open(request_data)); return PP_OK_COMPLETIONPENDING; } int32_t URLLoaderResource::FollowRedirect( scoped_refptr callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (mode_ != MODE_OPENING) return PP_ERROR_INPROGRESS; SetDefersLoading(false); // Allow the redirect to continue. RegisterCallback(callback); return PP_OK_COMPLETIONPENDING; } PP_Bool URLLoaderResource::GetUploadProgress(int64_t* bytes_sent, int64_t* total_bytes_to_be_sent) { if (!request_data_.record_upload_progress) { *bytes_sent = 0; *total_bytes_to_be_sent = 0; return PP_FALSE; } *bytes_sent = bytes_sent_; *total_bytes_to_be_sent = total_bytes_to_be_sent_; return PP_TRUE; } PP_Bool URLLoaderResource::GetDownloadProgress( int64_t* bytes_received, int64_t* total_bytes_to_be_received) { if (!request_data_.record_download_progress) { *bytes_received = 0; *total_bytes_to_be_received = 0; return PP_FALSE; } *bytes_received = bytes_received_; *total_bytes_to_be_received = total_bytes_to_be_received_; return PP_TRUE; } PP_Resource URLLoaderResource::GetResponseInfo() { if (response_info_.get()) return response_info_->GetReference(); return 0; } int32_t URLLoaderResource::ReadResponseBody( void* buffer, int32_t bytes_to_read, scoped_refptr callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (!response_info_.get()) return PP_ERROR_FAILED; // Fail if we have a valid file ref. // ReadResponseBody() is for reading to a user-provided buffer. if (response_info_->data().body_as_file_ref.IsValid()) return PP_ERROR_FAILED; if (bytes_to_read <= 0 || !buffer) return PP_ERROR_BADARGUMENT; user_buffer_ = static_cast(buffer); user_buffer_size_ = bytes_to_read; if (!buffer_.empty()) return FillUserBuffer(); // We may have already reached EOF. if (done_status_ != PP_OK_COMPLETIONPENDING) { user_buffer_ = NULL; user_buffer_size_ = 0; return done_status_; } RegisterCallback(callback); return PP_OK_COMPLETIONPENDING; } int32_t URLLoaderResource::FinishStreamingToFile( scoped_refptr callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (!response_info_.get()) return PP_ERROR_FAILED; // Fail if we do not have a valid file ref. if (!response_info_->data().body_as_file_ref.IsValid()) return PP_ERROR_FAILED; // We may have already reached EOF. if (done_status_ != PP_OK_COMPLETIONPENDING) return done_status_; is_streaming_to_file_ = true; if (is_asynchronous_load_suspended_) SetDefersLoading(false); // Wait for didFinishLoading / didFail. RegisterCallback(callback); return PP_OK_COMPLETIONPENDING; } void URLLoaderResource::Close() { mode_ = MODE_LOAD_COMPLETE; done_status_ = PP_ERROR_ABORTED; Post(RENDERER, PpapiHostMsg_URLLoader_Close()); // Abort the callbacks, the plugin doesn't want to be called back after this. // TODO(brettw) this should fix bug 69457, mark it fixed. ============ if (TrackedCallback::IsPending(pending_callback_)) pending_callback_->PostAbort(); } void URLLoaderResource::GrantUniversalAccess() { Post(RENDERER, PpapiHostMsg_URLLoader_GrantUniversalAccess()); } void URLLoaderResource::RegisterStatusCallback( PP_URLLoaderTrusted_StatusCallback callback) { status_callback_ = callback; } void URLLoaderResource::OnReplyReceived( const ResourceMessageReplyParams& params, const IPC::Message& msg) { PPAPI_BEGIN_MESSAGE_MAP(URLLoaderResource, msg) case PpapiPluginMsg_URLLoader_SendData::ID: // Special message, manually dispatch since we don't want the automatic // unpickling. OnPluginMsgSendData(params, msg); break; PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( PpapiPluginMsg_URLLoader_ReceivedResponse, OnPluginMsgReceivedResponse) PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( PpapiPluginMsg_URLLoader_FinishedLoading, OnPluginMsgFinishedLoading) PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( PpapiPluginMsg_URLLoader_UpdateProgress, OnPluginMsgUpdateProgress) PPAPI_END_MESSAGE_MAP() } void URLLoaderResource::OnPluginMsgReceivedResponse( const ResourceMessageReplyParams& params, const URLResponseInfoData& data) { SaveResponseInfo(data); RunCallback(PP_OK); } void URLLoaderResource::OnPluginMsgSendData( const ResourceMessageReplyParams& params, const IPC::Message& message) { PickleIterator iter(message); const char* data; int data_length; if (!iter.ReadData(&data, &data_length)) { NOTREACHED() << "Expecting data"; return; } mode_ = MODE_STREAMING_DATA; buffer_.insert(buffer_.end(), data, data + data_length); // To avoid letting the network stack download an entire stream all at once, // defer loading when we have enough buffer. // Check for this before we run the callback, even though that could move // data out of the buffer. Doing anything after the callback is unsafe. DCHECK(request_data_.prefetch_buffer_lower_threshold < request_data_.prefetch_buffer_upper_threshold); if (!is_streaming_to_file_ && !is_asynchronous_load_suspended_ && (buffer_.size() >= static_cast( request_data_.prefetch_buffer_upper_threshold))) { DVLOG(1) << "Suspending async load - buffer size: " << buffer_.size(); SetDefersLoading(true); } if (user_buffer_) RunCallback(FillUserBuffer()); else DCHECK(!TrackedCallback::IsPending(pending_callback_)); } void URLLoaderResource::OnPluginMsgFinishedLoading( const ResourceMessageReplyParams& params, int32_t result) { mode_ = MODE_LOAD_COMPLETE; done_status_ = result; user_buffer_ = NULL; user_buffer_size_ = 0; // If the client hasn't called any function that takes a callback since // the initial call to Open, or called ReadResponseBody and got a // synchronous return, then the callback will be NULL. if (TrackedCallback::IsPending(pending_callback_)) RunCallback(done_status_); } void URLLoaderResource::OnPluginMsgUpdateProgress( const ResourceMessageReplyParams& params, int64_t bytes_sent, int64_t total_bytes_to_be_sent, int64_t bytes_received, int64_t total_bytes_to_be_received) { bytes_sent_ = bytes_sent; total_bytes_to_be_sent_ = total_bytes_to_be_sent; bytes_received_ = bytes_received; total_bytes_to_be_received_ = total_bytes_to_be_received; if (status_callback_) status_callback_(pp_instance(), pp_resource(), bytes_sent_, total_bytes_to_be_sent_, bytes_received_, total_bytes_to_be_received_); } void URLLoaderResource::SetDefersLoading(bool defers_loading) { Post(RENDERER, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading)); } int32_t URLLoaderResource::ValidateCallback( scoped_refptr callback) { DCHECK(callback.get()); if (TrackedCallback::IsPending(pending_callback_)) return PP_ERROR_INPROGRESS; return PP_OK; } void URLLoaderResource::RegisterCallback( scoped_refptr callback) { DCHECK(!TrackedCallback::IsPending(pending_callback_)); pending_callback_ = callback; } void URLLoaderResource::RunCallback(int32_t result) { // This may be null when this is a main document loader. if (!pending_callback_.get()) return; // If |user_buffer_| was set as part of registering a callback, the paths // which trigger that callack must have cleared it since the callback is now // free to delete it. DCHECK(!user_buffer_); // As a second line of defense, clear the |user_buffer_| in case the // callbacks get called in an unexpected order. user_buffer_ = NULL; user_buffer_size_ = 0; pending_callback_->Run(result); } void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData& data) { // Create a proxy resource for the the file ref host resource if needed. PP_Resource body_as_file_ref = 0; if (data.body_as_file_ref.IsValid()) { body_as_file_ref = FileRefResource::CreateFileRef(connection(), pp_instance(), data.body_as_file_ref); } response_info_ = new URLResponseInfoResource( connection(), pp_instance(), data, body_as_file_ref); } size_t URLLoaderResource::FillUserBuffer() { DCHECK(user_buffer_); DCHECK(user_buffer_size_); size_t bytes_to_copy = std::min(buffer_.size(), user_buffer_size_); std::copy(buffer_.begin(), buffer_.begin() + bytes_to_copy, user_buffer_); buffer_.erase(buffer_.begin(), buffer_.begin() + bytes_to_copy); // If the buffer is getting too empty, resume asynchronous loading. if (is_asynchronous_load_suspended_ && buffer_.size() <= static_cast( request_data_.prefetch_buffer_lower_threshold)) { DVLOG(1) << "Resuming async load - buffer size: " << buffer_.size(); SetDefersLoading(false); } // Reset for next time. user_buffer_ = NULL; user_buffer_size_ = 0; return bytes_to_copy; } } // namespace proxy } // namespace ppapi