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
));
40 void FileDownloader::Initialize(Plugin
* instance
) {
41 PLUGIN_PRINTF(("FileDownloader::FileDownloader (this=%p)\n",
42 static_cast<void*>(this)));
43 CHECK(instance
!= NULL
);
44 CHECK(instance_
== NULL
); // Can only initialize once.
46 callback_factory_
.Initialize(this);
47 file_io_trusted_interface_
= static_cast<const PPB_FileIOTrusted
*>(
48 pp::Module::Get()->GetBrowserInterface(PPB_FILEIOTRUSTED_INTERFACE
));
49 url_loader_trusted_interface_
= static_cast<const PPB_URLLoaderTrusted
*>(
50 pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE
));
51 temp_buffer_
.resize(kTempBufferSize
);
54 bool FileDownloader::OpenStream(
55 const nacl::string
& url
,
56 const pp::CompletionCallback
& callback
,
57 StreamCallbackSource
* stream_callback_source
) {
58 open_and_stream_
= false;
59 data_stream_callback_source_
= stream_callback_source
;
60 return Open(url
, DOWNLOAD_STREAM
, callback
, true, NULL
);
63 bool FileDownloader::Open(
64 const nacl::string
& url
,
66 const pp::CompletionCallback
& callback
,
68 PP_URLLoaderTrusted_StatusCallback progress_callback
) {
69 PLUGIN_PRINTF(("FileDownloader::Open (url=%s)\n", url
.c_str()));
70 if (callback
.pp_completion_callback().func
== NULL
||
72 file_io_trusted_interface_
== NULL
)
75 CHECK(instance_
!= NULL
);
76 open_time_
= NaClGetTimeOfDayMicroseconds();
80 file_open_notify_callback_
= callback
;
83 pp::URLRequestInfo
url_request(instance_
);
86 // Note that "SetAllowCrossOriginRequests" (currently) has the side effect of
87 // preventing credentials from being sent on same-origin requests. We
88 // therefore avoid setting this flag unless we know for sure it is a
89 // cross-origin request, resulting in behavior similar to XMLHttpRequest.
90 if (!instance_
->DocumentCanRequest(url
))
91 url_request
.SetAllowCrossOriginRequests(true);
94 // Reset the url loader and file reader.
95 // Note that we have the only reference to the underlying objects, so
96 // this will implicitly close any pending IO and destroy them.
97 url_loader_
= pp::URLLoader(instance_
);
98 url_scheme_
= instance_
->GetUrlScheme(url
);
99 bool grant_universal_access
= false;
100 if (url_scheme_
== SCHEME_DATA
) {
101 // TODO(elijahtaylor) Remove this when data URIs can be read without
103 // https://bugs.webkit.org/show_bug.cgi?id=17352
104 if (streaming_to_buffer()) {
105 grant_universal_access
= true;
107 // Open is to invoke a callback on success or failure. Schedule
108 // it asynchronously to follow PPAPI's convention and avoid reentrancy.
109 pp::Core
* core
= pp::Module::Get()->core();
110 core
->CallOnMainThread(0, callback
, PP_ERROR_NOACCESS
);
111 PLUGIN_PRINTF(("FileDownloader::Open (pp_error=PP_ERROR_NOACCESS)\n"));
116 url_request
.SetRecordDownloadProgress(record_progress
);
118 if (url_loader_trusted_interface_
!= NULL
) {
119 if (grant_universal_access
) {
120 // TODO(sehr,jvoung): See if we can remove this -- currently
121 // only used for data URIs.
122 url_loader_trusted_interface_
->GrantUniversalAccess(
123 url_loader_
.pp_resource());
125 if (progress_callback
!= NULL
) {
126 url_loader_trusted_interface_
->RegisterStatusCallback(
127 url_loader_
.pp_resource(), progress_callback
);
131 // Prepare the url request.
132 url_request
.SetURL(url_
);
134 if (streaming_to_file()) {
135 file_reader_
= pp::FileIO(instance_
);
136 url_request
.SetStreamToFile(true);
140 void (FileDownloader::*start_notify
)(int32_t);
141 if (streaming_to_file())
142 start_notify
= &FileDownloader::URLLoadStartNotify
;
144 start_notify
= &FileDownloader::URLBufferStartNotify
;
146 // Request asynchronous download of the url providing an on-load callback.
147 // As long as this step is guaranteed to be asynchronous, we can call
148 // synchronously all other internal callbacks that eventually result in the
149 // invocation of the user callback. The user code will not be reentered.
150 pp::CompletionCallback onload_callback
=
151 callback_factory_
.NewCallback(start_notify
);
152 int32_t pp_error
= url_loader_
.Open(url_request
, onload_callback
);
153 PLUGIN_PRINTF(("FileDownloader::Open (pp_error=%" NACL_PRId32
")\n",
155 CHECK(pp_error
== PP_OK_COMPLETIONPENDING
);
159 void FileDownloader::OpenFast(const nacl::string
& url
,
160 PP_FileHandle file_handle
,
161 uint64_t file_token_lo
, uint64_t file_token_hi
) {
162 PLUGIN_PRINTF(("FileDownloader::OpenFast (url=%s)\n", url
.c_str()));
163 CHECK(instance_
!= NULL
);
164 open_time_
= NaClGetTimeOfDayMicroseconds();
165 status_code_
= NACL_HTTP_STATUS_OK
;
168 mode_
= DOWNLOAD_NONE
;
169 file_handle_
= file_handle
;
170 file_token_
.lo
= file_token_lo
;
171 file_token_
.hi
= file_token_hi
;
174 struct NaClFileInfo
FileDownloader::GetFileInfo() {
175 struct NaClFileInfo info
= NoFileInfo();
176 int32_t file_desc
= NACL_NO_FILE_DESC
;
177 if (not_streaming() && file_handle_
!= PP_kInvalidFileHandle
) {
179 // On Windows, valid handles are 32 bit unsigned integers so this is safe.
180 file_desc
= reinterpret_cast<uintptr_t>(file_handle_
);
182 file_desc
= file_handle_
;
184 info
.file_token
= file_token_
;
186 if (!streaming_to_file()) {
189 // Use the trusted interface to get the file descriptor.
190 if (file_io_trusted_interface_
== NULL
) {
193 file_desc
= file_io_trusted_interface_
->GetOSFileDescriptor(
194 file_reader_
.pp_resource());
198 // Convert the Windows HANDLE from Pepper to a POSIX file descriptor.
199 int32_t posix_desc
= _open_osfhandle(file_desc
, _O_RDWR
| _O_BINARY
);
200 if (posix_desc
== -1) {
201 // Close the Windows HANDLE if it can't be converted.
202 CloseHandle(reinterpret_cast<HANDLE
>(file_desc
));
205 file_desc
= posix_desc
;
208 info
.desc
= file_desc
;
212 int64_t FileDownloader::TimeSinceOpenMilliseconds() const {
213 int64_t now
= NaClGetTimeOfDayMicroseconds();
214 // If Open() wasn't called or we somehow return an earlier time now, just
215 // return the 0 rather than worse nonsense values.
216 if (open_time_
< 0 || now
< open_time_
)
218 return (now
- open_time_
) / NACL_MICROS_PER_MILLI
;
221 bool FileDownloader::InitialResponseIsValid(int32_t pp_error
) {
222 if (pp_error
!= PP_OK
) { // Url loading failed.
223 file_open_notify_callback_
.RunAndClear(pp_error
);
227 // Process the response, validating the headers to confirm successful loading.
228 url_response_
= url_loader_
.GetResponseInfo();
229 if (url_response_
.is_null()) {
231 "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n"));
232 file_open_notify_callback_
.RunAndClear(PP_ERROR_FAILED
);
236 pp::Var full_url
= url_response_
.GetURL();
237 if (!full_url
.is_string()) {
239 "FileDownloader::InitialResponseIsValid (url is not a string)\n"));
240 file_open_notify_callback_
.RunAndClear(PP_ERROR_FAILED
);
243 url_
= full_url
.AsString();
245 // Note that URLs in the data-URI scheme produce different error
246 // codes than other schemes. This is because data-URI are really a
247 // special kind of file scheme, and therefore do not produce HTTP
249 bool status_ok
= false;
250 status_code_
= url_response_
.GetStatusCode();
251 switch (url_scheme_
) {
252 case SCHEME_CHROME_EXTENSION
:
253 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (chrome-extension "
254 "response status_code=%" NACL_PRId32
")\n", status_code_
));
255 status_ok
= (status_code_
== kExtensionUrlRequestStatusOk
);
258 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI "
259 "response status_code=%" NACL_PRId32
")\n", status_code_
));
260 status_ok
= (status_code_
== kDataUriRequestStatusOk
);
263 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (HTTP response "
264 "status_code=%" NACL_PRId32
")\n", status_code_
));
265 status_ok
= (status_code_
== NACL_HTTP_STATUS_OK
);
270 file_open_notify_callback_
.RunAndClear(PP_ERROR_FAILED
);
277 void FileDownloader::URLLoadStartNotify(int32_t pp_error
) {
278 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%"
279 NACL_PRId32
")\n", pp_error
));
281 if (!InitialResponseIsValid(pp_error
)) {
282 // InitialResponseIsValid() calls file_open_notify_callback_ on errors.
286 if (open_and_stream_
)
287 return FinishStreaming(file_open_notify_callback_
);
289 file_open_notify_callback_
.RunAndClear(pp_error
);
292 void FileDownloader::URLBufferStartNotify(int32_t pp_error
) {
293 PLUGIN_PRINTF(("FileDownloader::URLBufferStartNotify (pp_error=%"
294 NACL_PRId32
")\n", pp_error
));
296 if (!InitialResponseIsValid(pp_error
)) {
297 // InitialResponseIsValid() calls file_open_notify_callback_ on errors.
301 if (open_and_stream_
)
302 return FinishStreaming(file_open_notify_callback_
);
304 file_open_notify_callback_
.RunAndClear(pp_error
);
307 void FileDownloader::FinishStreaming(
308 const pp::CompletionCallback
& callback
) {
309 stream_finish_callback_
= callback
;
311 // Finish streaming the body providing an optional callback.
312 if (streaming_to_file()) {
313 pp::CompletionCallback onload_callback
=
314 callback_factory_
.NewOptionalCallback(
315 &FileDownloader::URLLoadFinishNotify
);
316 int32_t pp_error
= url_loader_
.FinishStreamingToFile(onload_callback
);
317 bool async_notify_ok
= (pp_error
== PP_OK_COMPLETIONPENDING
);
318 PLUGIN_PRINTF(("FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
320 if (!async_notify_ok
) {
321 // Call manually to free allocated memory and report errors. This calls
322 // |stream_finish_callback_| with |pp_error| as the parameter.
323 onload_callback
.RunAndClear(pp_error
);
326 pp::CompletionCallback onread_callback
=
327 callback_factory_
.NewOptionalCallback(
328 &FileDownloader::URLReadBodyNotify
);
329 int32_t temp_size
= static_cast<int32_t>(temp_buffer_
.size());
330 int32_t pp_error
= url_loader_
.ReadResponseBody(&temp_buffer_
[0],
333 bool async_notify_ok
= (pp_error
== PP_OK_COMPLETIONPENDING
);
335 "FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
337 if (!async_notify_ok
) {
338 onread_callback
.RunAndClear(pp_error
);
343 void FileDownloader::URLLoadFinishNotify(int32_t pp_error
) {
344 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (pp_error=%"
345 NACL_PRId32
")\n", pp_error
));
346 if (pp_error
!= PP_OK
) { // Streaming failed.
347 stream_finish_callback_
.RunAndClear(pp_error
);
351 // Validate response again on load (though it should be the same
352 // as it was during InitialResponseIsValid?).
353 url_response_
= url_loader_
.GetResponseInfo();
354 CHECK(url_response_
.GetStatusCode() == NACL_HTTP_STATUS_OK
||
355 url_response_
.GetStatusCode() == kExtensionUrlRequestStatusOk
);
357 // Record the full url from the response.
358 pp::Var full_url
= url_response_
.GetURL();
359 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (full_url=%s)\n",
360 full_url
.DebugString().c_str()));
361 if (!full_url
.is_string()) {
362 stream_finish_callback_
.RunAndClear(PP_ERROR_FAILED
);
365 url_
= full_url
.AsString();
367 // The file is now fully downloaded.
368 pp::FileRef
file(url_response_
.GetBodyAsFileRef());
369 if (file
.is_null()) {
370 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (file=NULL)\n"));
371 stream_finish_callback_
.RunAndClear(PP_ERROR_FAILED
);
375 // Open the file providing an optional callback.
376 pp::CompletionCallback onopen_callback
=
377 callback_factory_
.NewOptionalCallback(
378 &FileDownloader::StreamFinishNotify
);
379 pp_error
= file_reader_
.Open(file
, PP_FILEOPENFLAG_READ
, onopen_callback
);
380 bool async_notify_ok
= (pp_error
== PP_OK_COMPLETIONPENDING
);
381 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (async_notify_ok=%d)\n",
383 if (!async_notify_ok
) {
384 // Call manually to free allocated memory and report errors. This calls
385 // |stream_finish_callback_| with |pp_error| as the parameter.
386 onopen_callback
.RunAndClear(pp_error
);
390 void FileDownloader::URLReadBodyNotify(int32_t pp_error
) {
391 PLUGIN_PRINTF(("FileDownloader::URLReadBodyNotify (pp_error=%"
392 NACL_PRId32
")\n", pp_error
));
393 if (pp_error
< PP_OK
) {
394 stream_finish_callback_
.RunAndClear(pp_error
);
395 } else if (pp_error
== PP_OK
) {
396 if (streaming_to_user()) {
397 data_stream_callback_source_
->GetCallback().RunAndClear(PP_OK
);
399 StreamFinishNotify(PP_OK
);
401 if (streaming_to_buffer()) {
402 buffer_
.insert(buffer_
.end(), &temp_buffer_
[0], &temp_buffer_
[pp_error
]);
403 } else if (streaming_to_user()) {
404 PLUGIN_PRINTF(("Running data_stream_callback, temp_buffer_=%p\n",
406 StreamCallback cb
= data_stream_callback_source_
->GetCallback();
407 *(cb
.output()) = &temp_buffer_
;
408 cb
.RunAndClear(pp_error
);
410 pp::CompletionCallback onread_callback
=
411 callback_factory_
.NewOptionalCallback(
412 &FileDownloader::URLReadBodyNotify
);
413 int32_t temp_size
= static_cast<int32_t>(temp_buffer_
.size());
414 pp_error
= url_loader_
.ReadResponseBody(&temp_buffer_
[0],
417 bool async_notify_ok
= (pp_error
== PP_OK_COMPLETIONPENDING
);
418 if (!async_notify_ok
) {
419 onread_callback
.RunAndClear(pp_error
);
424 bool FileDownloader::GetDownloadProgress(
425 int64_t* bytes_received
,
426 int64_t* total_bytes_to_be_received
) const {
427 return url_loader_
.GetDownloadProgress(bytes_received
,
428 total_bytes_to_be_received
);
431 nacl::string
FileDownloader::GetResponseHeaders() const {
432 pp::Var headers
= url_response_
.GetHeaders();
433 if (!headers
.is_string()) {
435 "FileDownloader::GetResponseHeaders (headers are not a string)\n"));
436 return nacl::string();
438 return headers
.AsString();
441 void FileDownloader::StreamFinishNotify(int32_t pp_error
) {
443 "FileDownloader::StreamFinishNotify (pp_error=%" NACL_PRId32
")\n",
445 stream_finish_callback_
.RunAndClear(pp_error
);
448 bool FileDownloader::streaming_to_file() const {
449 return mode_
== DOWNLOAD_TO_FILE
;
452 bool FileDownloader::streaming_to_buffer() const {
453 return mode_
== DOWNLOAD_TO_BUFFER
;
456 bool FileDownloader::streaming_to_user() const {
457 return mode_
== DOWNLOAD_STREAM
;
460 bool FileDownloader::not_streaming() const {
461 return mode_
== DOWNLOAD_NONE
;
464 } // namespace plugin