// Copyright (c) 2012 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 "webkit/plugins/ppapi/ppb_url_loader_impl.h" #include "base/logging.h" #include "net/base/net_errors.h" #include "ppapi/c/pp_completion_callback.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppb_url_loader.h" #include "ppapi/c/trusted/ppb_url_loader_trusted.h" #include "ppapi/thunk/enter.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebKitPlatformSupport.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebPluginContainer.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLError.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLLoader.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLRequest.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLResponse.h" #include "webkit/appcache/web_application_cache_host_impl.h" #include "webkit/plugins/ppapi/common.h" #include "webkit/plugins/ppapi/plugin_module.h" #include "webkit/plugins/ppapi/ppapi_plugin_instance.h" #include "webkit/plugins/ppapi/ppb_url_request_info_impl.h" #include "webkit/plugins/ppapi/ppb_url_response_info_impl.h" #include "webkit/plugins/ppapi/resource_helper.h" using appcache::WebApplicationCacheHostImpl; using ppapi::Resource; using ppapi::thunk::EnterResourceNoLock; using ppapi::thunk::PPB_URLLoader_API; using ppapi::thunk::PPB_URLRequestInfo_API; using ppapi::TrackedCallback; using WebKit::WebFrame; using WebKit::WebString; using WebKit::WebURL; using WebKit::WebURLError; using WebKit::WebURLLoader; using WebKit::WebURLLoaderOptions; using WebKit::WebURLRequest; using WebKit::WebURLResponse; #ifdef _MSC_VER // Do not warn about use of std::copy with raw pointers. #pragma warning(disable : 4996) #endif namespace webkit { namespace ppapi { namespace { WebFrame* GetFrameForResource(const Resource* resource) { PluginInstance* plugin_instance = ResourceHelper::GetPluginInstance(resource); if (!plugin_instance) return NULL; return plugin_instance->container()->element().document().frame(); } } // namespace PPB_URLLoader_Impl::PPB_URLLoader_Impl(PP_Instance instance, bool main_document_loader) : Resource(::ppapi::OBJECT_IS_IMPL, instance), main_document_loader_(main_document_loader), pending_callback_(), 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), has_universal_access_(false), status_callback_(NULL) { } PPB_URLLoader_Impl::~PPB_URLLoader_Impl() { } PPB_URLLoader_API* PPB_URLLoader_Impl::AsPPB_URLLoader_API() { return this; } void PPB_URLLoader_Impl::InstanceWasDeleted() { Resource::InstanceWasDeleted(); loader_.reset(); } int32_t PPB_URLLoader_Impl::Open(PP_Resource request_id, PP_CompletionCallback callback) { // Main document loads are already open, so don't allow people to open them // again. if (main_document_loader_) return PP_ERROR_INPROGRESS; 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; } PPB_URLRequestInfo_Impl* request = static_cast( enter_request.object()); int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (request->RequiresUniversalAccess() && !has_universal_access_) { Log(PP_LOGLEVEL_ERROR, "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 = GetFrameForResource(this); if (!frame) return PP_ERROR_FAILED; WebURLRequest web_request; if (!request->ToWebURLRequest(frame, &web_request)) return PP_ERROR_FAILED; // Save a copy of the request info so the plugin can continue to use and // change it while we're doing the request without affecting us. We must do // this after ToWebURLRequest since that fills out the file refs. request_data_ = request->GetData(); WebURLLoaderOptions options; if (has_universal_access_) { options.allowCredentials = true; options.crossOriginRequestPolicy = WebURLLoaderOptions::CrossOriginRequestPolicyAllow; } else { // All other HTTP requests are untrusted. options.untrustedHTTP = true; if (request_data_.allow_cross_origin_requests) { // Allow cross-origin requests with access control. The request specifies // if credentials are to be sent. options.allowCredentials = request_data_.allow_credentials; options.crossOriginRequestPolicy = WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; } else { // Same-origin requests can always send credentials. options.allowCredentials = true; } } is_asynchronous_load_suspended_ = false; loader_.reset(frame->createAssociatedURLLoader(options)); if (!loader_.get()) return PP_ERROR_FAILED; loader_->loadAsynchronously(web_request, this); // Notify completion when we receive a redirect or response headers. RegisterCallback(callback); return PP_OK_COMPLETIONPENDING; } int32_t PPB_URLLoader_Impl::FollowRedirect(PP_CompletionCallback callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; WebURL redirect_url = GURL(response_info_->redirect_url()); SetDefersLoading(false); // Allow the redirect to continue. RegisterCallback(callback); return PP_OK_COMPLETIONPENDING; } PP_Bool PPB_URLLoader_Impl::GetUploadProgress(int64_t* bytes_sent, int64_t* total_bytes_to_be_sent) { if (!RecordUploadProgress()) { *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 PPB_URLLoader_Impl::GetDownloadProgress( int64_t* bytes_received, int64_t* total_bytes_to_be_received) { if (!RecordDownloadProgress()) { *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 PPB_URLLoader_Impl::GetResponseInfo() { if (!response_info_) return 0; return response_info_->GetReference(); } int32_t PPB_URLLoader_Impl::ReadResponseBody(void* buffer, int32_t bytes_to_read, PP_CompletionCallback callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (!response_info_ || response_info_->body()) 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 PPB_URLLoader_Impl::FinishStreamingToFile( PP_CompletionCallback callback) { int32_t rv = ValidateCallback(callback); if (rv != PP_OK) return rv; if (!response_info_ || !response_info_->body()) 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 PPB_URLLoader_Impl::Close() { if (loader_.get()) loader_->cancel(); else if (main_document_loader_) GetFrameForResource(this)->stopLoading(); // TODO(viettrungluu): Check what happens to the callback (probably the // wrong thing). May need to post abort here. crbug.com/69457 } void PPB_URLLoader_Impl::GrantUniversalAccess() { has_universal_access_ = true; } void PPB_URLLoader_Impl::SetStatusCallback( PP_URLLoaderTrusted_StatusCallback cb) { status_callback_ = cb; } void PPB_URLLoader_Impl::willSendRequest( WebURLLoader* loader, WebURLRequest& new_request, const WebURLResponse& redirect_response) { if (!request_data_.follow_redirects) { SaveResponse(redirect_response); SetDefersLoading(true); RunCallback(PP_OK); } } void PPB_URLLoader_Impl::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(bytes_sent); total_bytes_to_be_sent_ = static_cast(total_bytes_to_be_sent); UpdateStatus(); } void PPB_URLLoader_Impl::didReceiveResponse(WebURLLoader* loader, const WebURLResponse& response) { SaveResponse(response); // Sets -1 if the content length is unknown. total_bytes_to_be_received_ = response.expectedContentLength(); UpdateStatus(); RunCallback(PP_OK); } void PPB_URLLoader_Impl::didDownloadData(WebURLLoader* loader, int data_length) { bytes_received_ += data_length; UpdateStatus(); } void PPB_URLLoader_Impl::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; UpdateStatus(); 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 PPB_URLLoader_Impl::didFinishLoading(WebURLLoader* loader, double finish_time) { FinishLoading(PP_OK); } void PPB_URLLoader_Impl::didFail(WebURLLoader* loader, const WebURLError& error) { 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; } FinishLoading(pp_error); } void PPB_URLLoader_Impl::SetDefersLoading(bool defers_loading) { if (loader_.get()) { loader_->setDefersLoading(defers_loading); is_asynchronous_load_suspended_ = 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 PPB_URLLoader_Impl::FinishLoading(int32_t done_status) { done_status_ = done_status; // 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_); } int32_t PPB_URLLoader_Impl::ValidateCallback(PP_CompletionCallback callback) { // We only support non-blocking calls. if (!callback.func) return PP_ERROR_BLOCKS_MAIN_THREAD; if (TrackedCallback::IsPending(pending_callback_)) return PP_ERROR_INPROGRESS; return PP_OK; } void PPB_URLLoader_Impl::RegisterCallback(PP_CompletionCallback callback) { DCHECK(callback.func); DCHECK(!TrackedCallback::IsPending(pending_callback_)); PluginModule* plugin_module = ResourceHelper::GetPluginModule(this); if (!plugin_module) return; pending_callback_ = new TrackedCallback(this, callback); } void PPB_URLLoader_Impl::RunCallback(int32_t result) { // This may be null only when this is a main document loader. if (!pending_callback_.get()) { CHECK(main_document_loader_); return; } TrackedCallback::ClearAndRun(&pending_callback_, result); } size_t PPB_URLLoader_Impl::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; } void PPB_URLLoader_Impl::SaveResponse(const WebURLResponse& response) { scoped_refptr response_info( new PPB_URLResponseInfo_Impl(pp_instance())); if (response_info->Initialize(response)) response_info_ = response_info; } void PPB_URLLoader_Impl::UpdateStatus() { if (status_callback_ && (RecordDownloadProgress() || RecordUploadProgress())) { // 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. status_callback_( pp_instance(), pp_resource(), RecordUploadProgress() ? bytes_sent_ : -1, RecordUploadProgress() ? total_bytes_to_be_sent_ : -1, RecordDownloadProgress() ? bytes_received_ : -1, RecordDownloadProgress() ? total_bytes_to_be_received_ : -1); } } bool PPB_URLLoader_Impl::RecordDownloadProgress() const { return request_data_.record_download_progress; } bool PPB_URLLoader_Impl::RecordUploadProgress() const { return request_data_.record_upload_progress; } } // namespace ppapi } // namespace webkit