// 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 "ppapi/native_client/src/trusted/plugin/file_downloader.h" #include #include #include #include "native_client/src/include/portability_io.h" #include "native_client/src/shared/platform/nacl_check.h" #include "native_client/src/shared/platform/nacl_time.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppb_file_io.h" #include "ppapi/cpp/file_io.h" #include "ppapi/cpp/file_ref.h" #include "ppapi/cpp/url_request_info.h" #include "ppapi/cpp/url_response_info.h" #include "ppapi/native_client/src/trusted/plugin/callback_source.h" #include "ppapi/native_client/src/trusted/plugin/plugin.h" #include "ppapi/native_client/src/trusted/plugin/utility.h" namespace { const int32_t kExtensionUrlRequestStatusOk = 200; const int32_t kDataUriRequestStatusOk = 0; struct NaClFileInfo NoFileInfo() { struct NaClFileInfo info; memset(&info, 0, sizeof(info)); info.desc = -1; return info; } // Converts a PP_FileHandle to a POSIX file descriptor. int32_t ConvertFileDescriptor(PP_FileHandle handle) { PLUGIN_PRINTF(("ConvertFileDescriptor, handle=%d\n", handle)); #if NACL_WINDOWS int32_t file_desc = NACL_NO_FILE_DESC; // On Windows, valid handles are 32 bit unsigned integers so this is safe. file_desc = reinterpret_cast(handle); // Convert the Windows HANDLE from Pepper to a POSIX file descriptor. int32_t posix_desc = _open_osfhandle(file_desc, _O_RDWR | _O_BINARY); if (posix_desc == -1) { // Close the Windows HANDLE if it can't be converted. CloseHandle(reinterpret_cast(file_desc)); return -1; } return posix_desc; #else return handle; #endif } } // namespace namespace plugin { void FileDownloader::Initialize(Plugin* instance) { PLUGIN_PRINTF(("FileDownloader::FileDownloader (this=%p)\n", static_cast(this))); CHECK(instance != NULL); CHECK(instance_ == NULL); // Can only initialize once. instance_ = instance; callback_factory_.Initialize(this); file_io_private_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_FILEIO_PRIVATE_INTERFACE)); url_loader_trusted_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE)); temp_buffer_.resize(kTempBufferSize); cached_file_info_ = NoFileInfo(); } bool FileDownloader::OpenStream( const nacl::string& url, const pp::CompletionCallback& callback, StreamCallbackSource* stream_callback_source) { open_and_stream_ = false; data_stream_callback_source_ = stream_callback_source; return Open(url, DOWNLOAD_STREAM, callback, true, NULL); } bool FileDownloader::Open( const nacl::string& url, DownloadMode mode, const pp::CompletionCallback& callback, bool record_progress, PP_URLLoaderTrusted_StatusCallback progress_callback) { PLUGIN_PRINTF(("FileDownloader::Open (url=%s)\n", url.c_str())); if (callback.pp_completion_callback().func == NULL || instance_ == NULL || file_io_private_interface_ == NULL) return false; CHECK(instance_ != NULL); open_time_ = NaClGetTimeOfDayMicroseconds(); status_code_ = -1; url_to_open_ = url; url_ = url; file_open_notify_callback_ = callback; mode_ = mode; buffer_.clear(); cached_file_info_ = NoFileInfo(); pp::URLRequestInfo url_request(instance_); // Allow CORS. // Note that "SetAllowCrossOriginRequests" (currently) has the side effect of // preventing credentials from being sent on same-origin requests. We // therefore avoid setting this flag unless we know for sure it is a // cross-origin request, resulting in behavior similar to XMLHttpRequest. if (!instance_->DocumentCanRequest(url)) url_request.SetAllowCrossOriginRequests(true); if (!extra_request_headers_.empty()) url_request.SetHeaders(extra_request_headers_); do { // Reset the url loader and file reader. // Note that we have the only reference to the underlying objects, so // this will implicitly close any pending IO and destroy them. url_loader_ = pp::URLLoader(instance_); url_scheme_ = instance_->GetUrlScheme(url); bool grant_universal_access = false; if (url_scheme_ == SCHEME_DATA) { // TODO(elijahtaylor) Remove this when data URIs can be read without // universal access. // https://bugs.webkit.org/show_bug.cgi?id=17352 if (streaming_to_buffer()) { grant_universal_access = true; } else { // Open is to invoke a callback on success or failure. Schedule // it asynchronously to follow PPAPI's convention and avoid reentrancy. pp::Core* core = pp::Module::Get()->core(); core->CallOnMainThread(0, callback, PP_ERROR_NOACCESS); PLUGIN_PRINTF(("FileDownloader::Open (pp_error=PP_ERROR_NOACCESS)\n")); return true; } } url_request.SetRecordDownloadProgress(record_progress); if (url_loader_trusted_interface_ != NULL) { if (grant_universal_access) { // TODO(sehr,jvoung): See if we can remove this -- currently // only used for data URIs. url_loader_trusted_interface_->GrantUniversalAccess( url_loader_.pp_resource()); } if (progress_callback != NULL) { url_loader_trusted_interface_->RegisterStatusCallback( url_loader_.pp_resource(), progress_callback); } } // Prepare the url request. url_request.SetURL(url_); if (streaming_to_file()) { file_reader_ = pp::FileIO(instance_); url_request.SetStreamToFile(true); } } while (0); // Request asynchronous download of the url providing an on-load callback. // As long as this step is guaranteed to be asynchronous, we can call // synchronously all other internal callbacks that eventually result in the // invocation of the user callback. The user code will not be reentered. pp::CompletionCallback onload_callback = callback_factory_.NewCallback(&FileDownloader::URLLoadStartNotify); int32_t pp_error = url_loader_.Open(url_request, onload_callback); PLUGIN_PRINTF(("FileDownloader::Open (pp_error=%" NACL_PRId32 ")\n", pp_error)); CHECK(pp_error == PP_OK_COMPLETIONPENDING); return true; } void FileDownloader::OpenFast(const nacl::string& url, PP_FileHandle file_handle, uint64_t file_token_lo, uint64_t file_token_hi) { PLUGIN_PRINTF(("FileDownloader::OpenFast (url=%s)\n", url.c_str())); cached_file_info_ = NoFileInfo(); CHECK(instance_ != NULL); open_time_ = NaClGetTimeOfDayMicroseconds(); status_code_ = NACL_HTTP_STATUS_OK; url_to_open_ = url; url_ = url; mode_ = DOWNLOAD_NONE; file_handle_ = file_handle; file_token_.lo = file_token_lo; file_token_.hi = file_token_hi; } struct NaClFileInfo FileDownloader::GetFileInfo() { PLUGIN_PRINTF(("FileDownloader::GetFileInfo\n")); if (cached_file_info_.desc != -1) { return cached_file_info_; } else if (not_streaming() && file_handle_ != PP_kInvalidFileHandle) { cached_file_info_.desc = ConvertFileDescriptor(file_handle_); if (cached_file_info_.desc != -1) cached_file_info_.file_token = file_token_; return cached_file_info_; } return NoFileInfo(); } int64_t FileDownloader::TimeSinceOpenMilliseconds() const { int64_t now = NaClGetTimeOfDayMicroseconds(); // If Open() wasn't called or we somehow return an earlier time now, just // return the 0 rather than worse nonsense values. if (open_time_ < 0 || now < open_time_) return 0; return (now - open_time_) / NACL_MICROS_PER_MILLI; } bool FileDownloader::InitialResponseIsValid() { // Process the response, validating the headers to confirm successful loading. url_response_ = url_loader_.GetResponseInfo(); if (url_response_.is_null()) { PLUGIN_PRINTF(( "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n")); return false; } pp::Var full_url = url_response_.GetURL(); if (!full_url.is_string()) { PLUGIN_PRINTF(( "FileDownloader::InitialResponseIsValid (url is not a string)\n")); return false; } url_ = full_url.AsString(); // Note that URLs in the data-URI scheme produce different error // codes than other schemes. This is because data-URI are really a // special kind of file scheme, and therefore do not produce HTTP // status codes. bool status_ok = false; status_code_ = url_response_.GetStatusCode(); switch (url_scheme_) { case SCHEME_CHROME_EXTENSION: PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (chrome-extension " "response status_code=%" NACL_PRId32 ")\n", status_code_)); status_ok = (status_code_ == kExtensionUrlRequestStatusOk); break; case SCHEME_DATA: PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI " "response status_code=%" NACL_PRId32 ")\n", status_code_)); status_ok = (status_code_ == kDataUriRequestStatusOk); break; case SCHEME_OTHER: PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (HTTP response " "status_code=%" NACL_PRId32 ")\n", status_code_)); status_ok = (status_code_ == NACL_HTTP_STATUS_OK); break; } return status_ok; } void FileDownloader::URLLoadStartNotify(int32_t pp_error) { PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%" NACL_PRId32")\n", pp_error)); if (pp_error != PP_OK) { file_open_notify_callback_.RunAndClear(pp_error); return; } if (!InitialResponseIsValid()) { file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED); return; } if (open_and_stream_) { FinishStreaming(file_open_notify_callback_); return; } file_open_notify_callback_.RunAndClear(PP_OK); } void FileDownloader::FinishStreaming( const pp::CompletionCallback& callback) { stream_finish_callback_ = callback; // Finish streaming the body providing an optional callback. if (streaming_to_file()) { pp::CompletionCallback onload_callback = callback_factory_.NewOptionalCallback( &FileDownloader::URLLoadFinishNotify); int32_t pp_error = url_loader_.FinishStreamingToFile(onload_callback); bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); PLUGIN_PRINTF(("FileDownloader::FinishStreaming (async_notify_ok=%d)\n", async_notify_ok)); if (!async_notify_ok) { // Call manually to free allocated memory and report errors. This calls // |stream_finish_callback_| with |pp_error| as the parameter. onload_callback.RunAndClear(pp_error); } } else { pp::CompletionCallback onread_callback = callback_factory_.NewOptionalCallback( &FileDownloader::URLReadBodyNotify); int32_t temp_size = static_cast(temp_buffer_.size()); int32_t pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0], temp_size, onread_callback); bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); PLUGIN_PRINTF(( "FileDownloader::FinishStreaming (async_notify_ok=%d)\n", async_notify_ok)); if (!async_notify_ok) { onread_callback.RunAndClear(pp_error); } } } void FileDownloader::URLLoadFinishNotify(int32_t pp_error) { PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (pp_error=%" NACL_PRId32")\n", pp_error)); if (pp_error != PP_OK) { // Streaming failed. stream_finish_callback_.RunAndClear(pp_error); return; } // Validate response again on load (though it should be the same // as it was during InitialResponseIsValid?). url_response_ = url_loader_.GetResponseInfo(); CHECK(url_response_.GetStatusCode() == NACL_HTTP_STATUS_OK || url_response_.GetStatusCode() == kExtensionUrlRequestStatusOk); // Record the full url from the response. pp::Var full_url = url_response_.GetURL(); PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (full_url=%s)\n", full_url.DebugString().c_str())); if (!full_url.is_string()) { stream_finish_callback_.RunAndClear(PP_ERROR_FAILED); return; } url_ = full_url.AsString(); // The file is now fully downloaded. pp::FileRef file(url_response_.GetBodyAsFileRef()); if (file.is_null()) { PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (file=NULL)\n")); stream_finish_callback_.RunAndClear(PP_ERROR_FAILED); return; } // Open the file providing an optional callback. pp::CompletionCallback onopen_callback = callback_factory_.NewOptionalCallback( &FileDownloader::StreamFinishNotify); pp_error = file_reader_.Open(file, PP_FILEOPENFLAG_READ, onopen_callback); bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (async_notify_ok=%d)\n", async_notify_ok)); if (!async_notify_ok) { // Call manually to free allocated memory and report errors. This calls // |stream_finish_callback_| with |pp_error| as the parameter. onopen_callback.RunAndClear(pp_error); } } void FileDownloader::URLReadBodyNotify(int32_t pp_error) { PLUGIN_PRINTF(("FileDownloader::URLReadBodyNotify (pp_error=%" NACL_PRId32")\n", pp_error)); if (pp_error < PP_OK) { stream_finish_callback_.RunAndClear(pp_error); } else if (pp_error == PP_OK) { if (streaming_to_user()) { data_stream_callback_source_->GetCallback().RunAndClear(PP_OK); } StreamFinishNotify(PP_OK); } else { if (streaming_to_buffer()) { buffer_.insert(buffer_.end(), &temp_buffer_[0], &temp_buffer_[pp_error]); } else if (streaming_to_user()) { PLUGIN_PRINTF(("Running data_stream_callback, temp_buffer_=%p\n", &temp_buffer_[0])); StreamCallback cb = data_stream_callback_source_->GetCallback(); *(cb.output()) = &temp_buffer_; cb.RunAndClear(pp_error); } pp::CompletionCallback onread_callback = callback_factory_.NewOptionalCallback( &FileDownloader::URLReadBodyNotify); int32_t temp_size = static_cast(temp_buffer_.size()); pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0], temp_size, onread_callback); bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); if (!async_notify_ok) { onread_callback.RunAndClear(pp_error); } } } bool FileDownloader::GetDownloadProgress( int64_t* bytes_received, int64_t* total_bytes_to_be_received) const { return url_loader_.GetDownloadProgress(bytes_received, total_bytes_to_be_received); } nacl::string FileDownloader::GetResponseHeaders() const { pp::Var headers = url_response_.GetHeaders(); if (!headers.is_string()) { PLUGIN_PRINTF(( "FileDownloader::GetResponseHeaders (headers are not a string)\n")); return nacl::string(); } return headers.AsString(); } void FileDownloader::StreamFinishNotify(int32_t pp_error) { PLUGIN_PRINTF(( "FileDownloader::StreamFinishNotify (pp_error=%" NACL_PRId32 ")\n", pp_error)); // Run the callback if we have an error, or if we don't have a file_reader_ // to get a file handle for. if (pp_error != PP_OK || file_reader_.pp_resource() == 0) { stream_finish_callback_.RunAndClear(pp_error); return; } pp::CompletionCallbackWithOutput cb = callback_factory_.NewCallbackWithOutput( &FileDownloader::GotFileHandleNotify); file_io_private_interface_->RequestOSFileHandle( file_reader_.pp_resource(), cb.output(), cb.pp_completion_callback()); } bool FileDownloader::streaming_to_file() const { return mode_ == DOWNLOAD_TO_FILE; } bool FileDownloader::streaming_to_buffer() const { return mode_ == DOWNLOAD_TO_BUFFER; } bool FileDownloader::streaming_to_user() const { return mode_ == DOWNLOAD_STREAM; } bool FileDownloader::not_streaming() const { return mode_ == DOWNLOAD_NONE; } void FileDownloader::GotFileHandleNotify(int32_t pp_error, PP_FileHandle handle) { PLUGIN_PRINTF(( "FileDownloader::GotFileHandleNotify (pp_error=%" NACL_PRId32 ")\n", pp_error)); if (pp_error == PP_OK) cached_file_info_.desc = ConvertFileDescriptor(handle); stream_finish_callback_.RunAndClear(pp_error); } } // namespace plugin