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 struct NaClFileInfo NoFileInfo() {
27 struct NaClFileInfo info;
28 memset(&info, 0, sizeof(info));
33 // Converts a PP_FileHandle to a POSIX file descriptor.
34 int32_t ConvertFileDescriptor(PP_FileHandle handle) {
35 PLUGIN_PRINTF(("ConvertFileDescriptor, handle=%d\n", handle));
37 int32_t file_desc = NACL_NO_FILE_DESC;
38 // On Windows, valid handles are 32 bit unsigned integers so this is safe.
39 file_desc = reinterpret_cast<uintptr_t>(handle);
40 // Convert the Windows HANDLE from Pepper to a POSIX file descriptor.
41 int32_t posix_desc = _open_osfhandle(file_desc, _O_RDWR | _O_BINARY);
42 if (posix_desc == -1) {
43 // Close the Windows HANDLE if it can't be converted.
44 CloseHandle(reinterpret_cast<HANDLE>(file_desc));
57 NaClFileInfoAutoCloser::NaClFileInfoAutoCloser()
58 : info_(NoFileInfo()) {}
60 NaClFileInfoAutoCloser::NaClFileInfoAutoCloser(NaClFileInfo* pass_ownership)
61 : info_(*pass_ownership) {
62 *pass_ownership = NoFileInfo();
65 void NaClFileInfoAutoCloser::FreeResources() {
66 if (-1 != get_desc()) {
67 PLUGIN_PRINTF(("NaClFileInfoAutoCloser::FreeResources close(%d)\n",
74 void NaClFileInfoAutoCloser::TakeOwnership(NaClFileInfo* pass_ownership) {
75 PLUGIN_PRINTF(("NaClFileInfoAutoCloser::set: taking ownership of %d\n",
76 pass_ownership->desc));
77 CHECK(pass_ownership->desc == -1 || pass_ownership->desc != get_desc());
79 info_ = *pass_ownership;
80 *pass_ownership = NoFileInfo();
83 NaClFileInfo NaClFileInfoAutoCloser::Release() {
84 NaClFileInfo info_to_return = info_;
86 return info_to_return;
89 void FileDownloader::Initialize(Plugin* instance) {
90 PLUGIN_PRINTF(("FileDownloader::FileDownloader (this=%p)\n",
91 static_cast<void*>(this)));
92 CHECK(instance != NULL);
93 CHECK(instance_ == NULL); // Can only initialize once.
95 callback_factory_.Initialize(this);
96 file_io_private_interface_ = static_cast<const PPB_FileIO_Private*>(
97 pp::Module::Get()->GetBrowserInterface(PPB_FILEIO_PRIVATE_INTERFACE));
98 url_loader_trusted_interface_ = static_cast<const PPB_URLLoaderTrusted*>(
99 pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE));
100 temp_buffer_.resize(kTempBufferSize);
101 file_info_.FreeResources();
104 bool FileDownloader::OpenStream(
105 const nacl::string& url,
106 const pp::CompletionCallback& callback,
107 StreamCallbackSource* stream_callback_source) {
108 data_stream_callback_source_ = stream_callback_source;
109 return Open(url, DOWNLOAD_TO_BUFFER_AND_STREAM, callback, true, NULL);
112 bool FileDownloader::Open(
113 const nacl::string& url,
115 const pp::CompletionCallback& callback,
116 bool record_progress,
117 PP_URLLoaderTrusted_StatusCallback progress_callback) {
118 PLUGIN_PRINTF(("FileDownloader::Open (url=%s)\n", url.c_str()));
119 if (callback.pp_completion_callback().func == NULL ||
121 file_io_private_interface_ == NULL)
124 CHECK(instance_ != NULL);
127 file_open_notify_callback_ = callback;
129 file_info_.FreeResources();
130 pp::URLRequestInfo url_request(instance_);
133 // Note that "SetAllowCrossOriginRequests" (currently) has the side effect of
134 // preventing credentials from being sent on same-origin requests. We
135 // therefore avoid setting this flag unless we know for sure it is a
136 // cross-origin request, resulting in behavior similar to XMLHttpRequest.
137 if (!instance_->DocumentCanRequest(url))
138 url_request.SetAllowCrossOriginRequests(true);
140 if (!extra_request_headers_.empty())
141 url_request.SetHeaders(extra_request_headers_);
143 // Reset the url loader and file reader.
144 // Note that we have the only reference to the underlying objects, so
145 // this will implicitly close any pending IO and destroy them.
146 url_loader_ = pp::URLLoader(instance_);
148 url_request.SetRecordDownloadProgress(record_progress);
150 if (url_loader_trusted_interface_ != NULL) {
151 if (progress_callback != NULL) {
152 url_loader_trusted_interface_->RegisterStatusCallback(
153 url_loader_.pp_resource(), progress_callback);
157 // Prepare the url request.
158 url_request.SetURL(url_);
160 if (mode_ == DOWNLOAD_TO_FILE) {
161 file_reader_ = pp::FileIO(instance_);
162 url_request.SetStreamToFile(true);
165 // Request asynchronous download of the url providing an on-load callback.
166 // As long as this step is guaranteed to be asynchronous, we can call
167 // synchronously all other internal callbacks that eventually result in the
168 // invocation of the user callback. The user code will not be reentered.
169 pp::CompletionCallback onload_callback =
170 callback_factory_.NewCallback(&FileDownloader::URLLoadStartNotify);
171 int32_t pp_error = url_loader_.Open(url_request, onload_callback);
172 PLUGIN_PRINTF(("FileDownloader::Open (pp_error=%" NACL_PRId32 ")\n",
174 CHECK(pp_error == PP_OK_COMPLETIONPENDING);
178 void FileDownloader::OpenFast(const nacl::string& url,
179 PP_FileHandle file_handle,
180 uint64_t file_token_lo, uint64_t file_token_hi) {
181 PLUGIN_PRINTF(("FileDownloader::OpenFast (url=%s)\n", url.c_str()));
183 file_info_.FreeResources();
184 CHECK(instance_ != NULL);
185 status_code_ = NACL_HTTP_STATUS_OK;
187 mode_ = DOWNLOAD_NONE;
188 if (file_handle != PP_kInvalidFileHandle) {
189 NaClFileInfo tmp_info = NoFileInfo();
190 tmp_info.desc = ConvertFileDescriptor(file_handle);
191 tmp_info.file_token.lo = file_token_lo;
192 tmp_info.file_token.hi = file_token_hi;
193 file_info_.TakeOwnership(&tmp_info);
197 NaClFileInfo FileDownloader::GetFileInfo() {
198 NaClFileInfo info_to_return = NoFileInfo();
200 PLUGIN_PRINTF(("FileDownloader::GetFileInfo, this %p\n", this));
201 if (file_info_.get_desc() != -1) {
202 info_to_return = file_info_.Release();
204 PLUGIN_PRINTF(("FileDownloader::GetFileInfo -- returning %d\n",
205 info_to_return.desc));
206 return info_to_return;
209 bool FileDownloader::InitialResponseIsValid() {
210 // Process the response, validating the headers to confirm successful loading.
211 url_response_ = url_loader_.GetResponseInfo();
212 if (url_response_.is_null()) {
214 "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n"));
218 pp::Var full_url = url_response_.GetURL();
219 if (!full_url.is_string()) {
221 "FileDownloader::InitialResponseIsValid (url is not a string)\n"));
224 full_url_ = full_url.AsString();
226 status_code_ = url_response_.GetStatusCode();
227 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid ("
228 "response status_code=%" NACL_PRId32 ")\n", status_code_));
229 return status_code_ == NACL_HTTP_STATUS_OK;
232 void FileDownloader::URLLoadStartNotify(int32_t pp_error) {
233 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%"
234 NACL_PRId32")\n", pp_error));
235 if (pp_error != PP_OK) {
236 file_open_notify_callback_.RunAndClear(pp_error);
240 if (!InitialResponseIsValid()) {
241 file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED);
245 if (mode_ != DOWNLOAD_TO_BUFFER_AND_STREAM) {
246 FinishStreaming(file_open_notify_callback_);
250 file_open_notify_callback_.RunAndClear(PP_OK);
253 void FileDownloader::FinishStreaming(
254 const pp::CompletionCallback& callback) {
255 stream_finish_callback_ = callback;
257 // Finish streaming the body providing an optional callback.
258 if (mode_ == DOWNLOAD_TO_FILE) {
259 pp::CompletionCallback onload_callback =
260 callback_factory_.NewOptionalCallback(
261 &FileDownloader::URLLoadFinishNotify);
262 int32_t pp_error = url_loader_.FinishStreamingToFile(onload_callback);
263 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
264 PLUGIN_PRINTF(("FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
266 if (!async_notify_ok) {
267 // Call manually to free allocated memory and report errors. This calls
268 // |stream_finish_callback_| with |pp_error| as the parameter.
269 onload_callback.RunAndClear(pp_error);
272 pp::CompletionCallback onread_callback =
273 callback_factory_.NewOptionalCallback(
274 &FileDownloader::URLReadBodyNotify);
275 int32_t temp_size = static_cast<int32_t>(temp_buffer_.size());
276 int32_t pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0],
279 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
281 "FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
283 if (!async_notify_ok) {
284 onread_callback.RunAndClear(pp_error);
289 void FileDownloader::URLLoadFinishNotify(int32_t pp_error) {
290 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (pp_error=%"
291 NACL_PRId32")\n", pp_error));
292 if (pp_error != PP_OK) { // Streaming failed.
293 stream_finish_callback_.RunAndClear(pp_error);
297 // Validate response again on load (though it should be the same
298 // as it was during InitialResponseIsValid?).
299 url_response_ = url_loader_.GetResponseInfo();
300 CHECK(url_response_.GetStatusCode() == NACL_HTTP_STATUS_OK);
302 // Record the full url from the response.
303 pp::Var full_url = url_response_.GetURL();
304 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (full_url=%s)\n",
305 full_url.DebugString().c_str()));
306 if (!full_url.is_string()) {
307 stream_finish_callback_.RunAndClear(PP_ERROR_FAILED);
310 full_url_ = full_url.AsString();
312 // The file is now fully downloaded.
313 pp::FileRef file(url_response_.GetBodyAsFileRef());
314 if (file.is_null()) {
315 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (file=NULL)\n"));
316 stream_finish_callback_.RunAndClear(PP_ERROR_FAILED);
320 // Open the file providing an optional callback.
321 pp::CompletionCallback onopen_callback =
322 callback_factory_.NewOptionalCallback(
323 &FileDownloader::StreamFinishNotify);
324 pp_error = file_reader_.Open(file, PP_FILEOPENFLAG_READ, onopen_callback);
325 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
326 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (async_notify_ok=%d)\n",
328 if (!async_notify_ok) {
329 // Call manually to free allocated memory and report errors. This calls
330 // |stream_finish_callback_| with |pp_error| as the parameter.
331 onopen_callback.RunAndClear(pp_error);
335 void FileDownloader::URLReadBodyNotify(int32_t pp_error) {
336 PLUGIN_PRINTF(("FileDownloader::URLReadBodyNotify (pp_error=%"
337 NACL_PRId32")\n", pp_error));
338 if (pp_error < PP_OK) {
339 stream_finish_callback_.RunAndClear(pp_error);
340 } else if (pp_error == PP_OK) {
341 if (mode_ == DOWNLOAD_TO_BUFFER_AND_STREAM) {
342 data_stream_callback_source_->GetCallback().RunAndClear(PP_OK);
344 StreamFinishNotify(PP_OK);
346 if (mode_ == DOWNLOAD_TO_BUFFER_AND_STREAM) {
347 PLUGIN_PRINTF(("Running data_stream_callback, temp_buffer_=%p\n",
349 StreamCallback cb = data_stream_callback_source_->GetCallback();
350 *(cb.output()) = &temp_buffer_;
351 cb.RunAndClear(pp_error);
353 pp::CompletionCallback onread_callback =
354 callback_factory_.NewOptionalCallback(
355 &FileDownloader::URLReadBodyNotify);
356 int32_t temp_size = static_cast<int32_t>(temp_buffer_.size());
357 pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0],
360 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
361 if (!async_notify_ok) {
362 onread_callback.RunAndClear(pp_error);
367 bool FileDownloader::GetDownloadProgress(
368 int64_t* bytes_received,
369 int64_t* total_bytes_to_be_received) const {
370 return url_loader_.GetDownloadProgress(bytes_received,
371 total_bytes_to_be_received);
374 nacl::string FileDownloader::GetResponseHeaders() const {
375 pp::Var headers = url_response_.GetHeaders();
376 if (!headers.is_string()) {
378 "FileDownloader::GetResponseHeaders (headers are not a string)\n"));
379 return nacl::string();
381 return headers.AsString();
384 void FileDownloader::StreamFinishNotify(int32_t pp_error) {
386 "FileDownloader::StreamFinishNotify (pp_error=%" NACL_PRId32 ")\n",
389 // Run the callback if we have an error, or if we don't have a file_reader_
390 // to get a file handle for.
391 if (pp_error != PP_OK || file_reader_.pp_resource() == 0) {
392 stream_finish_callback_.RunAndClear(pp_error);
396 pp::CompletionCallbackWithOutput<PP_FileHandle> cb =
397 callback_factory_.NewCallbackWithOutput(
398 &FileDownloader::GotFileHandleNotify);
399 file_io_private_interface_->RequestOSFileHandle(
400 file_reader_.pp_resource(), cb.output(), cb.pp_completion_callback());
403 void FileDownloader::GotFileHandleNotify(int32_t pp_error,
404 PP_FileHandle handle) {
406 "FileDownloader::GotFileHandleNotify (pp_error=%" NACL_PRId32 ")\n",
408 if (pp_error == PP_OK) {
409 NaClFileInfo tmp_info = NoFileInfo();
410 tmp_info.desc = ConvertFileDescriptor(handle);
411 file_info_.TakeOwnership(&tmp_info);
414 stream_finish_callback_.RunAndClear(pp_error);
417 } // namespace plugin