Home | History | Annotate | Download | only in plugin
      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