Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / ppapi / native_client / src / trusted / plugin / file_downloader.cc
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 // Converts a PP_FileHandle to a POSIX file descriptor.
37 int32_t ConvertFileDescriptor(PP_FileHandle handle) {
38   PLUGIN_PRINTF(("ConvertFileDescriptor, handle=%d\n", handle));
39 #if NACL_WINDOWS
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));
48     return -1;
49   }
50   return posix_desc;
51 #else
52   return handle;
53 #endif
54 }
55
56 }  // namespace
57
58 namespace plugin {
59
60 NaClFileInfoAutoCloser::NaClFileInfoAutoCloser()
61     : info_(NoFileInfo()) {}
62
63 NaClFileInfoAutoCloser::NaClFileInfoAutoCloser(NaClFileInfo* pass_ownership)
64     : info_(*pass_ownership) {
65   *pass_ownership = NoFileInfo();
66 }
67
68 void NaClFileInfoAutoCloser::FreeResources() {
69   if (-1 != get_desc()) {
70     PLUGIN_PRINTF(("NaClFileInfoAutoCloser::FreeResources close(%d)\n",
71                    get_desc()));
72     close(get_desc());
73   }
74   info_.desc = -1;
75 }
76
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());
81   FreeResources();
82   info_ = *pass_ownership;
83   *pass_ownership = NoFileInfo();
84 }
85
86 NaClFileInfo NaClFileInfoAutoCloser::Release() {
87   NaClFileInfo info_to_return = info_;
88   info_ = NoFileInfo();
89   return info_to_return;
90 }
91
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.
97   instance_ = instance;
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();
105 }
106
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);
114 }
115
116 bool FileDownloader::Open(
117     const nacl::string& url,
118     DownloadMode mode,
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 ||
124       instance_ == NULL ||
125       file_io_private_interface_ == NULL)
126     return false;
127
128   CHECK(instance_ != NULL);
129   open_time_ = NaClGetTimeOfDayMicroseconds();
130   status_code_ = -1;
131   url_to_open_ = url;
132   url_ = url;
133   file_open_notify_callback_ = callback;
134   mode_ = mode;
135   buffer_.clear();
136   file_info_.FreeResources();
137   pp::URLRequestInfo url_request(instance_);
138
139   // Allow CORS.
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);
146
147   if (!extra_request_headers_.empty())
148     url_request.SetHeaders(extra_request_headers_);
149
150   do {
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
159       // universal access.
160       // https://bugs.webkit.org/show_bug.cgi?id=17352
161       if (streaming_to_buffer()) {
162         grant_universal_access = true;
163       } else {
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"));
169         return true;
170       }
171     }
172
173     url_request.SetRecordDownloadProgress(record_progress);
174
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());
181       }
182       if (progress_callback != NULL) {
183         url_loader_trusted_interface_->RegisterStatusCallback(
184             url_loader_.pp_resource(), progress_callback);
185       }
186     }
187
188     // Prepare the url request.
189     url_request.SetURL(url_);
190
191     if (streaming_to_file()) {
192       file_reader_ = pp::FileIO(instance_);
193       url_request.SetStreamToFile(true);
194     }
195   } while (0);
196
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",
205                  pp_error));
206   CHECK(pp_error == PP_OK_COMPLETIONPENDING);
207   return true;
208 }
209
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()));
214
215   file_info_.FreeResources();
216   CHECK(instance_ != NULL);
217   open_time_ = NaClGetTimeOfDayMicroseconds();
218   status_code_ = NACL_HTTP_STATUS_OK;
219   url_to_open_ = url;
220   url_ = url;
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);
228   }
229 }
230
231 NaClFileInfo FileDownloader::GetFileInfo() {
232   NaClFileInfo info_to_return = NoFileInfo();
233
234   PLUGIN_PRINTF(("FileDownloader::GetFileInfo, this %p\n", this));
235   if (file_info_.get_desc() != -1) {
236     info_to_return = file_info_.Release();
237   }
238   PLUGIN_PRINTF(("FileDownloader::GetFileInfo -- returning %d\n",
239                  info_to_return.desc));
240   return info_to_return;
241 }
242
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_)
248     return 0;
249   return (now - open_time_) / NACL_MICROS_PER_MILLI;
250 }
251
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()) {
256     PLUGIN_PRINTF((
257         "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n"));
258     return false;
259   }
260
261   pp::Var full_url = url_response_.GetURL();
262   if (!full_url.is_string()) {
263     PLUGIN_PRINTF((
264         "FileDownloader::InitialResponseIsValid (url is not a string)\n"));
265     return false;
266   }
267   url_ = full_url.AsString();
268
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
272   // status codes.
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);
280       break;
281     case SCHEME_DATA:
282       PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI "
283                      "response status_code=%" NACL_PRId32 ")\n", status_code_));
284       status_ok = (status_code_ == kDataUriRequestStatusOk);
285       break;
286     case SCHEME_OTHER:
287       PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (HTTP response "
288                      "status_code=%" NACL_PRId32 ")\n", status_code_));
289       status_ok = (status_code_ == NACL_HTTP_STATUS_OK);
290       break;
291   }
292   return status_ok;
293 }
294
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);
300     return;
301   }
302
303   if (!InitialResponseIsValid()) {
304     file_open_notify_callback_.RunAndClear(PP_ERROR_FAILED);
305     return;
306   }
307
308   if (open_and_stream_) {
309     FinishStreaming(file_open_notify_callback_);
310     return;
311   }
312
313   file_open_notify_callback_.RunAndClear(PP_OK);
314 }
315
316 void FileDownloader::FinishStreaming(
317     const pp::CompletionCallback& callback) {
318   stream_finish_callback_ = callback;
319
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",
328                    async_notify_ok));
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);
333     }
334   } else {
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],
340                                                     temp_size,
341                                                     onread_callback);
342     bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
343     PLUGIN_PRINTF((
344         "FileDownloader::FinishStreaming (async_notify_ok=%d)\n",
345         async_notify_ok));
346     if (!async_notify_ok) {
347       onread_callback.RunAndClear(pp_error);
348     }
349   }
350 }
351
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);
357     return;
358   }
359
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);
365
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);
372     return;
373   }
374   url_ = full_url.AsString();
375
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);
381     return;
382   }
383
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",
391                  async_notify_ok));
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);
396   }
397 }
398
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);
407     }
408     StreamFinishNotify(PP_OK);
409   } else {
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",
414                      &temp_buffer_[0]));
415       StreamCallback cb = data_stream_callback_source_->GetCallback();
416       *(cb.output()) = &temp_buffer_;
417       cb.RunAndClear(pp_error);
418     }
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],
424                                             temp_size,
425                                             onread_callback);
426     bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING);
427     if (!async_notify_ok) {
428       onread_callback.RunAndClear(pp_error);
429     }
430   }
431 }
432
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);
438 }
439
440 nacl::string FileDownloader::GetResponseHeaders() const {
441   pp::Var headers = url_response_.GetHeaders();
442   if (!headers.is_string()) {
443     PLUGIN_PRINTF((
444         "FileDownloader::GetResponseHeaders (headers are not a string)\n"));
445     return nacl::string();
446   }
447   return headers.AsString();
448 }
449
450 void FileDownloader::StreamFinishNotify(int32_t pp_error) {
451   PLUGIN_PRINTF((
452       "FileDownloader::StreamFinishNotify (pp_error=%" NACL_PRId32 ")\n",
453       pp_error));
454
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);
459     return;
460   }
461
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());
467 }
468
469 bool FileDownloader::streaming_to_file() const {
470   return mode_ == DOWNLOAD_TO_FILE;
471 }
472
473 bool FileDownloader::streaming_to_buffer() const {
474   return mode_ == DOWNLOAD_TO_BUFFER;
475 }
476
477 bool FileDownloader::streaming_to_user() const {
478   return mode_ == DOWNLOAD_STREAM;
479 }
480
481 bool FileDownloader::not_streaming() const {
482   return mode_ == DOWNLOAD_NONE;
483 }
484
485 void FileDownloader::GotFileHandleNotify(int32_t pp_error,
486                                          PP_FileHandle handle) {
487   PLUGIN_PRINTF((
488       "FileDownloader::GotFileHandleNotify (pp_error=%" NACL_PRId32 ")\n",
489       pp_error));
490   if (pp_error == PP_OK) {
491     NaClFileInfo tmp_info = NoFileInfo();
492     tmp_info.desc = ConvertFileDescriptor(handle);
493     file_info_.TakeOwnership(&tmp_info);
494   }
495
496   stream_finish_callback_.RunAndClear(pp_error);
497 }
498
499 }  // namespace plugin