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. 4 5 #include "ppapi/native_client/src/trusted/plugin/file_downloader.h" 6 7 #include <stdio.h> 8 #include <string.h> 9 #include <string> 10 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" 23 24 namespace { 25 26 const int32_t kExtensionUrlRequestStatusOk = 200; 27 const int32_t kDataUriRequestStatusOk = 0; 28 29 struct NaClFileInfo NoFileInfo() { 30 struct NaClFileInfo info; 31 memset(&info, 0, sizeof(info)); 32 info.desc = -1; 33 return info; 34 } 35 36 } 37 38 namespace plugin { 39 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. 45 instance_ = instance; 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); 52 } 53 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); 61 } 62 63 bool FileDownloader::Open( 64 const nacl::string& url, 65 DownloadMode mode, 66 const pp::CompletionCallback& callback, 67 bool record_progress, 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 || 71 instance_ == NULL || 72 file_io_trusted_interface_ == NULL) 73 return false; 74 75 CHECK(instance_ != NULL); 76 open_time_ = NaClGetTimeOfDayMicroseconds(); 77 status_code_ = -1; 78 url_to_open_ = url; 79 url_ = url; 80 file_open_notify_callback_ = callback; 81 mode_ = mode; 82 buffer_.clear(); 83 pp::URLRequestInfo url_request(instance_); 84 85 // Allow CORS. 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); 92 93 do { 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 102 // universal access. 103 // https://bugs.webkit.org/show_bug.cgi?id=17352 104 if (streaming_to_buffer()) { 105 grant_universal_access = true; 106 } else { 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")); 112 return true; 113 } 114 } 115 116 url_request.SetRecordDownloadProgress(record_progress); 117 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()); 124 } 125 if (progress_callback != NULL) { 126 url_loader_trusted_interface_->RegisterStatusCallback( 127 url_loader_.pp_resource(), progress_callback); 128 } 129 } 130 131 // Prepare the url request. 132 url_request.SetURL(url_); 133 134 if (streaming_to_file()) { 135 file_reader_ = pp::FileIO(instance_); 136 url_request.SetStreamToFile(true); 137 } 138 } while (0); 139 140 void (FileDownloader::*start_notify)(int32_t); 141 if (streaming_to_file()) 142 start_notify = &FileDownloader::URLLoadStartNotify; 143 else 144 start_notify = &FileDownloader::URLBufferStartNotify; 145 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", 154 pp_error)); 155 CHECK(pp_error == PP_OK_COMPLETIONPENDING); 156 return true; 157 } 158 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; 166 url_to_open_ = url; 167 url_ = url; 168 mode_ = DOWNLOAD_NONE; 169 file_handle_ = file_handle; 170 file_token_.lo = file_token_lo; 171 file_token_.hi = file_token_hi; 172 } 173 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) { 178 #if NACL_WINDOWS 179 // On Windows, valid handles are 32 bit unsigned integers so this is safe. 180 file_desc = reinterpret_cast<uintptr_t>(file_handle_); 181 #else 182 file_desc = file_handle_; 183 #endif 184 info.file_token = file_token_; 185 } else { 186 if (!streaming_to_file()) { 187 return NoFileInfo(); 188 } 189 // Use the trusted interface to get the file descriptor. 190 if (file_io_trusted_interface_ == NULL) { 191 return NoFileInfo(); 192 } 193 file_desc = file_io_trusted_interface_->GetOSFileDescriptor( 194 file_reader_.pp_resource()); 195 } 196 197 #if NACL_WINDOWS 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)); 203 return NoFileInfo(); 204 } 205 file_desc = posix_desc; 206 #endif 207 208 info.desc = file_desc; 209 return info; 210 } 211 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_) 217 return 0; 218 return (now - open_time_) / NACL_MICROS_PER_MILLI; 219 } 220 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); 224 return false; 225 } 226 227 // Process the response, validating the headers to confirm successful loading. 228 url_response_ = url_loader_.GetResponseInfo(); 229 if (url_response_.is_null()) { 230 PLUGIN_PRINTF(( 231 "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n")); 232 file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED); 233 return false; 234 } 235 236 pp::Var full_url = url_response_.GetURL(); 237 if (!full_url.is_string()) { 238 PLUGIN_PRINTF(( 239 "FileDownloader::InitialResponseIsValid (url is not a string)\n")); 240 file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED); 241 return false; 242 } 243 url_ = full_url.AsString(); 244 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 248 // status codes. 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); 256 break; 257 case SCHEME_DATA: 258 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI " 259 "response status_code=%" NACL_PRId32 ")\n", status_code_)); 260 status_ok = (status_code_ == kDataUriRequestStatusOk); 261 break; 262 case SCHEME_OTHER: 263 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (HTTP response " 264 "status_code=%" NACL_PRId32 ")\n", status_code_)); 265 status_ok = (status_code_ == NACL_HTTP_STATUS_OK); 266 break; 267 } 268 269 if (!status_ok) { 270 file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED); 271 return false; 272 } 273 274 return true; 275 } 276 277 void FileDownloader::URLLoadStartNotify(int32_t pp_error) { 278 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%" 279 NACL_PRId32")\n", pp_error)); 280 281 if (!InitialResponseIsValid(pp_error)) { 282 // InitialResponseIsValid() calls file_open_notify_callback_ on errors. 283 return; 284 } 285 286 if (open_and_stream_) 287 return FinishStreaming(file_open_notify_callback_); 288 289 file_open_notify_callback_.RunAndClear(pp_error); 290 } 291 292 void FileDownloader::URLBufferStartNotify(int32_t pp_error) { 293 PLUGIN_PRINTF(("FileDownloader::URLBufferStartNotify (pp_error=%" 294 NACL_PRId32")\n", pp_error)); 295 296 if (!InitialResponseIsValid(pp_error)) { 297 // InitialResponseIsValid() calls file_open_notify_callback_ on errors. 298 return; 299 } 300 301 if (open_and_stream_) 302 return FinishStreaming(file_open_notify_callback_); 303 304 file_open_notify_callback_.RunAndClear(pp_error); 305 } 306 307 void FileDownloader::FinishStreaming( 308 const pp::CompletionCallback& callback) { 309 stream_finish_callback_ = callback; 310 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", 319 async_notify_ok)); 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); 324 } 325 } else { 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], 331 temp_size, 332 onread_callback); 333 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); 334 PLUGIN_PRINTF(( 335 "FileDownloader::FinishStreaming (async_notify_ok=%d)\n", 336 async_notify_ok)); 337 if (!async_notify_ok) { 338 onread_callback.RunAndClear(pp_error); 339 } 340 } 341 } 342 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); 348 return; 349 } 350 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); 356 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); 363 return; 364 } 365 url_ = full_url.AsString(); 366 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); 372 return; 373 } 374 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", 382 async_notify_ok)); 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); 387 } 388 } 389 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); 398 } 399 StreamFinishNotify(PP_OK); 400 } else { 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", 405 &temp_buffer_[0])); 406 StreamCallback cb = data_stream_callback_source_->GetCallback(); 407 *(cb.output()) = &temp_buffer_; 408 cb.RunAndClear(pp_error); 409 } 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], 415 temp_size, 416 onread_callback); 417 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); 418 if (!async_notify_ok) { 419 onread_callback.RunAndClear(pp_error); 420 } 421 } 422 } 423 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); 429 } 430 431 nacl::string FileDownloader::GetResponseHeaders() const { 432 pp::Var headers = url_response_.GetHeaders(); 433 if (!headers.is_string()) { 434 PLUGIN_PRINTF(( 435 "FileDownloader::GetResponseHeaders (headers are not a string)\n")); 436 return nacl::string(); 437 } 438 return headers.AsString(); 439 } 440 441 void FileDownloader::StreamFinishNotify(int32_t pp_error) { 442 PLUGIN_PRINTF(( 443 "FileDownloader::StreamFinishNotify (pp_error=%" NACL_PRId32 ")\n", 444 pp_error)); 445 stream_finish_callback_.RunAndClear(pp_error); 446 } 447 448 bool FileDownloader::streaming_to_file() const { 449 return mode_ == DOWNLOAD_TO_FILE; 450 } 451 452 bool FileDownloader::streaming_to_buffer() const { 453 return mode_ == DOWNLOAD_TO_BUFFER; 454 } 455 456 bool FileDownloader::streaming_to_user() const { 457 return mode_ == DOWNLOAD_STREAM; 458 } 459 460 bool FileDownloader::not_streaming() const { 461 return mode_ == DOWNLOAD_NONE; 462 } 463 464 } // namespace plugin 465