1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ppapi/native_client/src/trusted/plugin/file_downloader.h"
11 #include "native_client/src/include/portability_io.h"
12 #include "native_client/src/shared/platform/nacl_check.h"
13 #include "native_client/src/shared/platform/nacl_time.h"
14 #include "ppapi/c/pp_errors.h"
15 #include "ppapi/c/ppb_file_io.h"
16 #include "ppapi/cpp/file_io.h"
17 #include "ppapi/cpp/file_ref.h"
18 #include "ppapi/cpp/url_request_info.h"
19 #include "ppapi/cpp/url_response_info.h"
20 #include "ppapi/native_client/src/trusted/plugin/callback_source.h"
21 #include "ppapi/native_client/src/trusted/plugin/plugin.h"
22 #include "ppapi/native_client/src/trusted/plugin/utility.h"
26 const int32_t kExtensionUrlRequestStatusOk = 200;
27 const int32_t kDataUriRequestStatusOk = 0;
29 struct NaClFileInfo NoFileInfo() {
30 struct NaClFileInfo info;
31 memset(&info, 0, sizeof(info));
36 // Converts a PP_FileHandle to a POSIX file descriptor.
37 int32_t ConvertFileDescriptor(PP_FileHandle handle) {
38 PLUGIN_PRINTF(("ConvertFileDescriptor, handle=%d\n", handle));
40 int32_t file_desc = NACL_NO_FILE_DESC;
41 // On Windows, valid handles are 32 bit unsigned integers so this is safe.
42 file_desc = reinterpret_cast<uintptr_t>(handle);
43 // Convert the Windows HANDLE from Pepper to a POSIX file descriptor.
44 int32_t posix_desc = _open_osfhandle(file_desc, _O_RDWR | _O_BINARY);
45 if (posix_desc == -1) {
46 // Close the Windows HANDLE if it can't be converted.
47 CloseHandle(reinterpret_cast<HANDLE>(file_desc));
60 NaClFileInfoAutoCloser::NaClFileInfoAutoCloser()
61 : info_(NoFileInfo()) {}
63 NaClFileInfoAutoCloser::NaClFileInfoAutoCloser(NaClFileInfo* pass_ownership)
64 : info_(*pass_ownership) {
65 *pass_ownership = NoFileInfo();
68 void NaClFileInfoAutoCloser::FreeResources() {
69 if (-1 != get_desc()) {
70 PLUGIN_PRINTF(("NaClFileInfoAutoCloser::FreeResources close(%d)\n",
77 void NaClFileInfoAutoCloser::TakeOwnership(NaClFileInfo* pass_ownership) {
78 PLUGIN_PRINTF(("NaClFileInfoAutoCloser::set: taking ownership of %d\n",
79 pass_ownership->desc));
80 CHECK(pass_ownership->desc == -1 || pass_ownership->desc != get_desc());
82 info_ = *pass_ownership;
83 *pass_ownership = NoFileInfo();
86 NaClFileInfo NaClFileInfoAutoCloser::Release() {
87 NaClFileInfo info_to_return = info_;
89 return info_to_return;
92 void FileDownloader::Initialize(Plugin* instance) {
93 PLUGIN_PRINTF(("FileDownloader::FileDownloader (this=%p)\n",
94 static_cast<void*>(this)));
95 CHECK(instance != NULL);
96 CHECK(instance_ == NULL); // Can only initialize once.
98 callback_factory_.Initialize(this);
99 file_io_private_interface_ = static_cast<const PPB_FileIO_Private*>(
100 pp::Module::Get()->GetBrowserInterface(PPB_FILEIO_PRIVATE_INTERFACE));
101 url_loader_trusted_interface_ = static_cast<const PPB_URLLoaderTrusted*>(
102 pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE));
103 temp_buffer_.resize(kTempBufferSize);
104 file_info_.FreeResources();
107 bool FileDownloader::OpenStream(
108 const nacl::string& url,
109 const pp::CompletionCallback& callback,
110 StreamCallbackSource* stream_callback_source) {
111 open_and_stream_ = false;
112 data_stream_callback_source_ = stream_callback_source;
113 return Open(url, DOWNLOAD_STREAM, callback, true, NULL);
116 bool FileDownloader::Open(
117 const nacl::string& url,
119 const pp::CompletionCallback& callback,
120 bool record_progress,
121 PP_URLLoaderTrusted_StatusCallback progress_callback) {
122 PLUGIN_PRINTF(("FileDownloader::Open (url=%s)\n", url.c_str()));
123 if (callback.pp_completion_callback().func == NULL ||
125 file_io_private_interface_ == NULL)
128 CHECK(instance_ != NULL);
129 open_time_ = NaClGetTimeOfDayMicroseconds();
133 file_open_notify_callback_ = callback;
136 file_info_.FreeResources();
137 pp::URLRequestInfo url_request(instance_);
140 // Note that "SetAllowCrossOriginRequests" (currently) has the side effect of
141 // preventing credentials from being sent on same-origin requests. We
142 // therefore avoid setting this flag unless we know for sure it is a
143 // cross-origin request, resulting in behavior similar to XMLHttpRequest.
144 if (!instance_->DocumentCanRequest(url))
145 url_request.SetAllowCrossOriginRequests(true);
147 if (!extra_request_headers_.empty())
148 url_request.SetHeaders(extra_request_headers_);
151 // Reset the url loader and file reader.
152 // Note that we have the only reference to the underlying objects, so
153 // this will implicitly close any pending IO and destroy them.
154 url_loader_ = pp::URLLoader(instance_);
155 url_scheme_ = instance_->GetUrlScheme(url);
156 bool grant_universal_access = false;
157 if (url_scheme_ == SCHEME_DATA) {
158 // TODO(elijahtaylor) Remove this when data URIs can be read without
160 // https://bugs.webkit.org/show_bug.cgi?id=17352
161 if (streaming_to_buffer()) {
162 grant_universal_access = true;
164 // Open is to invoke a callback on success or failure. Schedule
165 // it asynchronously to follow PPAPI's convention and avoid reentrancy.
166 pp::Core* core = pp::Module::Get()->core();
167 core->CallOnMainThread(0, callback, PP_ERROR_NOACCESS);
168 PLUGIN_PRINTF(("FileDownloader::Open (pp_error=PP_ERROR_NOACCESS)\n"));
173 url_request.SetRecordDownloadProgress(record_progress);
175 if (url_loader_trusted_interface_ != NULL) {
176 if (grant_universal_access) {
177 // TODO(sehr,jvoung): See if we can remove this -- currently
178 // only used for data URIs.
179 url_loader_trusted_interface_->GrantUniversalAccess(
180 url_loader_.pp_resource());
182 if (progress_callback != NULL) {
183 url_loader_trusted_interface_->RegisterStatusCallback(
184 url_loader_.pp_resource(), progress_callback);
188 // Prepare the url request.
189 url_request.SetURL(url_);
191 if (streaming_to_file()) {
192 file_reader_ = pp::FileIO(instance_);
193 url_request.SetStreamToFile(true);
197 // Request asynchronous download of the url providing an on-load callback.
198 // As long as this step is guaranteed to be asynchronous, we can call
199 // synchronously all other internal callbacks that eventually result in the
200 // invocation of the user callback. The user code will not be reentered.
201 pp::CompletionCallback onload_callback =
202 callback_factory_.NewCallback(&FileDownloader::URLLoadStartNotify);
203 int32_t pp_error = url_loader_.Open(url_request, onload_callback);
204 PLUGIN_PRINTF(("FileDownloader::Open (pp_error=%" NACL_PRId32 ")\n",
206 CHECK(pp_error == PP_OK_COMPLETIONPENDING);
210 void FileDownloader::OpenFast(const nacl::string& url,
211 PP_FileHandle file_handle,
212 uint64_t file_token_lo, uint64_t file_token_hi) {
213 PLUGIN_PRINTF(("FileDownloader::OpenFast (url=%s)\n", url.c_str()));
215 file_info_.FreeResources();
216 CHECK(instance_ != NULL);
217 open_time_ = NaClGetTimeOfDayMicroseconds();
218 status_code_ = NACL_HTTP_STATUS_OK;
221 mode_ = DOWNLOAD_NONE;
222 if (not_streaming() && file_handle != PP_kInvalidFileHandle) {
223 NaClFileInfo tmp_info = NoFileInfo();
224 tmp_info.desc = ConvertFileDescriptor(file_handle);
225 tmp_info.file_token.lo = file_token_lo;
226 tmp_info.file_token.hi = file_token_hi;
227 file_info_.TakeOwnership(&tmp_info);
231 NaClFileInfo FileDownloader::GetFileInfo() {
232 NaClFileInfo info_to_return = NoFileInfo();
234 PLUGIN_PRINTF(("FileDownloader::GetFileInfo, this %p\n", this));
235 if (file_info_.get_desc() != -1) {
236 info_to_return = file_info_.Release();
238 PLUGIN_PRINTF(("FileDownloader::GetFileInfo -- returning %d\n",
239 info_to_return.desc));
240 return info_to_return;
243 int64_t FileDownloader::TimeSinceOpenMilliseconds() const {
244 int64_t now = NaClGetTimeOfDayMicroseconds();
245 // If Open() wasn't called or we somehow return an earlier time now, just
246 // return the 0 rather than worse nonsense values.
247 if (open_time_ < 0 || now < open_time_)
249 return (now - open_time_) / NACL_MICROS_PER_MILLI;
252 bool FileDownloader::InitialResponseIsValid() {
253 // Process the response, validating the headers to confirm successful loading.
254 url_response_ = url_loader_.GetResponseInfo();
255 if (url_response_.is_null()) {
257 "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n"));
261 pp::Var full_url = url_response_.GetURL();
262 if (!full_url.is_string()) {
264 "FileDownloader::InitialResponseIsValid (url is not a string)\n"));
267 url_ = full_url.AsString();
269 // Note that URLs in the data-URI scheme produce different error
270 // codes than other schemes. This is because data-URI are really a
271 // special kind of file scheme, and therefore do not produce HTTP
273 bool status_ok = false;
274 status_code_ = url_response_.GetStatusCode();
275 switch (url_scheme_) {
276 case SCHEME_CHROME_EXTENSION:
277 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (chrome-extension "
278 "response status_code=%" NACL_PRId32 ")\n", status_code_));
279 status_ok = (status_code_ == kExtensionUrlRequestStatusOk);
282 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI "
283 "response status_code=%" NACL_PRId32 ")\n", status_code_));
284 status_ok = (status_code_ == kDataUriRequestStatusOk);
287 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (HTTP response "
288 "status_code=%" NACL_PRId32 ")\n", status_code_));
289 status_ok = (status_code_ == NACL_HTTP_STATUS_OK);
295 void FileDownloader::URLLoadStartNotify(int32_t pp_error) {
296 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%"
297 NACL_PRId32")\n", pp_error));
298 if (pp_error != PP_OK) {
299 file_open_notify_callback_.RunAndClear(pp_error);
303 if (!InitialResponseIsValid()) {
304 file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED);
308 if (open_and_stream_) {
309 FinishStreaming(file_open_notify_callback_);
313 file_open_notify_callback_.RunAndClear(PP_OK);
316 void FileDownloader::FinishStreaming(
317 const pp::CompletionCallback& callback) {
318 stream_finish_callback_ = callback;
320 // Finish streaming the body providing an optional callback.
321 if (streaming_to_file()) {
322 pp::CompletionCallback onload_callback =
323 callback_factory_.NewOptionalCallback(
324 &FileDownloader::URLLoadFinishNotify);
325 int32_t pp_error = url_loader_.FinishStreamingToFile(onload_callback);
326 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
327 PLUGIN_PRINTF(("FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
329 if (!async_notify_ok) {
330 // Call manually to free allocated memory and report errors. This calls
331 // |stream_finish_callback_| with |pp_error| as the parameter.
332 onload_callback.RunAndClear(pp_error);
335 pp::CompletionCallback onread_callback =
336 callback_factory_.NewOptionalCallback(
337 &FileDownloader::URLReadBodyNotify);
338 int32_t temp_size = static_cast<int32_t>(temp_buffer_.size());
339 int32_t pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0],
342 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
344 "FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
346 if (!async_notify_ok) {
347 onread_callback.RunAndClear(pp_error);
352 void FileDownloader::URLLoadFinishNotify(int32_t pp_error) {
353 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (pp_error=%"
354 NACL_PRId32")\n", pp_error));
355 if (pp_error != PP_OK) { // Streaming failed.
356 stream_finish_callback_.RunAndClear(pp_error);
360 // Validate response again on load (though it should be the same
361 // as it was during InitialResponseIsValid?).
362 url_response_ = url_loader_.GetResponseInfo();
363 CHECK(url_response_.GetStatusCode() == NACL_HTTP_STATUS_OK ||
364 url_response_.GetStatusCode() == kExtensionUrlRequestStatusOk);
366 // Record the full url from the response.
367 pp::Var full_url = url_response_.GetURL();
368 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (full_url=%s)\n",
369 full_url.DebugString().c_str()));
370 if (!full_url.is_string()) {
371 stream_finish_callback_.RunAndClear(PP_ERROR_FAILED);
374 url_ = full_url.AsString();
376 // The file is now fully downloaded.
377 pp::FileRef file(url_response_.GetBodyAsFileRef());
378 if (file.is_null()) {
379 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (file=NULL)\n"));
380 stream_finish_callback_.RunAndClear(PP_ERROR_FAILED);
384 // Open the file providing an optional callback.
385 pp::CompletionCallback onopen_callback =
386 callback_factory_.NewOptionalCallback(
387 &FileDownloader::StreamFinishNotify);
388 pp_error = file_reader_.Open(file, PP_FILEOPENFLAG_READ, onopen_callback);
389 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
390 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (async_notify_ok=%d)\n",
392 if (!async_notify_ok) {
393 // Call manually to free allocated memory and report errors. This calls
394 // |stream_finish_callback_| with |pp_error| as the parameter.
395 onopen_callback.RunAndClear(pp_error);
399 void FileDownloader::URLReadBodyNotify(int32_t pp_error) {
400 PLUGIN_PRINTF(("FileDownloader::URLReadBodyNotify (pp_error=%"
401 NACL_PRId32")\n", pp_error));
402 if (pp_error < PP_OK) {
403 stream_finish_callback_.RunAndClear(pp_error);
404 } else if (pp_error == PP_OK) {
405 if (streaming_to_user()) {
406 data_stream_callback_source_->GetCallback().RunAndClear(PP_OK);
408 StreamFinishNotify(PP_OK);
410 if (streaming_to_buffer()) {
411 buffer_.insert(buffer_.end(), &temp_buffer_[0], &temp_buffer_[pp_error]);
412 } else if (streaming_to_user()) {
413 PLUGIN_PRINTF(("Running data_stream_callback, temp_buffer_=%p\n",
415 StreamCallback cb = data_stream_callback_source_->GetCallback();
416 *(cb.output()) = &temp_buffer_;
417 cb.RunAndClear(pp_error);
419 pp::CompletionCallback onread_callback =
420 callback_factory_.NewOptionalCallback(
421 &FileDownloader::URLReadBodyNotify);
422 int32_t temp_size = static_cast<int32_t>(temp_buffer_.size());
423 pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0],
426 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
427 if (!async_notify_ok) {
428 onread_callback.RunAndClear(pp_error);
433 bool FileDownloader::GetDownloadProgress(
434 int64_t* bytes_received,
435 int64_t* total_bytes_to_be_received) const {
436 return url_loader_.GetDownloadProgress(bytes_received,
437 total_bytes_to_be_received);
440 nacl::string FileDownloader::GetResponseHeaders() const {
441 pp::Var headers = url_response_.GetHeaders();
442 if (!headers.is_string()) {
444 "FileDownloader::GetResponseHeaders (headers are not a string)\n"));
445 return nacl::string();
447 return headers.AsString();
450 void FileDownloader::StreamFinishNotify(int32_t pp_error) {
452 "FileDownloader::StreamFinishNotify (pp_error=%" NACL_PRId32 ")\n",
455 // Run the callback if we have an error, or if we don't have a file_reader_
456 // to get a file handle for.
457 if (pp_error != PP_OK || file_reader_.pp_resource() == 0) {
458 stream_finish_callback_.RunAndClear(pp_error);
462 pp::CompletionCallbackWithOutput<PP_FileHandle> cb =
463 callback_factory_.NewCallbackWithOutput(
464 &FileDownloader::GotFileHandleNotify);
465 file_io_private_interface_->RequestOSFileHandle(
466 file_reader_.pp_resource(), cb.output(), cb.pp_completion_callback());
469 bool FileDownloader::streaming_to_file() const {
470 return mode_ == DOWNLOAD_TO_FILE;
473 bool FileDownloader::streaming_to_buffer() const {
474 return mode_ == DOWNLOAD_TO_BUFFER;
477 bool FileDownloader::streaming_to_user() const {
478 return mode_ == DOWNLOAD_STREAM;
481 bool FileDownloader::not_streaming() const {
482 return mode_ == DOWNLOAD_NONE;
485 void FileDownloader::GotFileHandleNotify(int32_t pp_error,
486 PP_FileHandle handle) {
488 "FileDownloader::GotFileHandleNotify (pp_error=%" NACL_PRId32 ")\n",
490 if (pp_error == PP_OK) {
491 NaClFileInfo tmp_info = NoFileInfo();
492 tmp_info.desc = ConvertFileDescriptor(handle);
493 file_info_.TakeOwnership(&tmp_info);
496 stream_finish_callback_.RunAndClear(pp_error);
499 } // namespace plugin