blob: 4bc1d4a673577142d6d6bac5a33add28b855a5b0 [file] [log] [blame]
// 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 <stdio.h>
#include <string.h>
#include <string>
#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<uintptr_t>(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<HANDLE>(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<void*>(this)));
CHECK(instance != NULL);
CHECK(instance_ == NULL); // Can only initialize once.
instance_ = instance;
callback_factory_.Initialize(this);
file_io_private_interface_ = static_cast<const PPB_FileIO_Private*>(
pp::Module::Get()->GetBrowserInterface(PPB_FILEIO_PRIVATE_INTERFACE));
url_loader_trusted_interface_ = static_cast<const PPB_URLLoaderTrusted*>(
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);
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<int32_t>(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<int32_t>(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<PP_FileHandle> 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