1// Copyright (c) 2013 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/proxy/url_loader_resource.h"
6
7#include "base/logging.h"
8#include "ppapi/c/pp_completion_callback.h"
9#include "ppapi/c/pp_errors.h"
10#include "ppapi/c/ppb_url_loader.h"
11#include "ppapi/proxy/dispatch_reply_message.h"
12#include "ppapi/proxy/file_ref_resource.h"
13#include "ppapi/proxy/ppapi_messages.h"
14#include "ppapi/proxy/url_request_info_resource.h"
15#include "ppapi/proxy/url_response_info_resource.h"
16#include "ppapi/shared_impl/ppapi_globals.h"
17#include "ppapi/shared_impl/url_response_info_data.h"
18#include "ppapi/thunk/enter.h"
19#include "ppapi/thunk/resource_creation_api.h"
20
21using ppapi::thunk::EnterResourceNoLock;
22using ppapi::thunk::PPB_URLLoader_API;
23using ppapi::thunk::PPB_URLRequestInfo_API;
24
25#ifdef _MSC_VER
26// Do not warn about use of std::copy with raw pointers.
27#pragma warning(disable : 4996)
28#endif
29
30namespace ppapi {
31namespace proxy {
32
33URLLoaderResource::URLLoaderResource(Connection connection,
34                                     PP_Instance instance)
35    : PluginResource(connection, instance),
36      mode_(MODE_WAITING_TO_OPEN),
37      status_callback_(NULL),
38      bytes_sent_(0),
39      total_bytes_to_be_sent_(-1),
40      bytes_received_(0),
41      total_bytes_to_be_received_(-1),
42      user_buffer_(NULL),
43      user_buffer_size_(0),
44      done_status_(PP_OK_COMPLETIONPENDING),
45      is_streaming_to_file_(false),
46      is_asynchronous_load_suspended_(false) {
47  SendCreate(RENDERER, PpapiHostMsg_URLLoader_Create());
48}
49
50URLLoaderResource::URLLoaderResource(Connection connection,
51                                     PP_Instance instance,
52                                     int pending_main_document_loader_id,
53                                     const ppapi::URLResponseInfoData& data)
54    : PluginResource(connection, instance),
55      mode_(MODE_OPENING),
56      status_callback_(NULL),
57      bytes_sent_(0),
58      total_bytes_to_be_sent_(-1),
59      bytes_received_(0),
60      total_bytes_to_be_received_(-1),
61      user_buffer_(NULL),
62      user_buffer_size_(0),
63      done_status_(PP_OK_COMPLETIONPENDING),
64      is_streaming_to_file_(false),
65      is_asynchronous_load_suspended_(false) {
66  AttachToPendingHost(RENDERER, pending_main_document_loader_id);
67  SaveResponseInfo(data);
68}
69
70URLLoaderResource::~URLLoaderResource() {
71}
72
73PPB_URLLoader_API* URLLoaderResource::AsPPB_URLLoader_API() {
74  return this;
75}
76
77int32_t URLLoaderResource::Open(PP_Resource request_id,
78                                scoped_refptr<TrackedCallback> callback) {
79  EnterResourceNoLock<PPB_URLRequestInfo_API> enter_request(request_id, true);
80  if (enter_request.failed()) {
81    Log(PP_LOGLEVEL_ERROR,
82        "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper"
83        " users: use the ResourceRequest constructor that takes an instance or"
84        " else the request will be null.)");
85    return PP_ERROR_BADARGUMENT;
86  }
87  return Open(enter_request.object()->GetData(), 0, callback);
88}
89
90int32_t URLLoaderResource::Open(
91    const ::ppapi::URLRequestInfoData& request_data,
92    int requestor_pid,
93    scoped_refptr<TrackedCallback> callback) {
94  int32_t rv = ValidateCallback(callback);
95  if (rv != PP_OK)
96    return rv;
97  if (mode_ != MODE_WAITING_TO_OPEN)
98    return PP_ERROR_INPROGRESS;
99
100  request_data_ = request_data;
101
102  mode_ = MODE_OPENING;
103  is_asynchronous_load_suspended_ = false;
104
105  RegisterCallback(callback);
106  Post(RENDERER, PpapiHostMsg_URLLoader_Open(request_data));
107  return PP_OK_COMPLETIONPENDING;
108}
109
110int32_t URLLoaderResource::FollowRedirect(
111    scoped_refptr<TrackedCallback> callback) {
112  int32_t rv = ValidateCallback(callback);
113  if (rv != PP_OK)
114    return rv;
115  if (mode_ != MODE_OPENING)
116    return PP_ERROR_INPROGRESS;
117
118  SetDefersLoading(false);  // Allow the redirect to continue.
119  RegisterCallback(callback);
120  return PP_OK_COMPLETIONPENDING;
121}
122
123PP_Bool URLLoaderResource::GetUploadProgress(int64_t* bytes_sent,
124                                              int64_t* total_bytes_to_be_sent) {
125  if (!request_data_.record_upload_progress) {
126    *bytes_sent = 0;
127    *total_bytes_to_be_sent = 0;
128    return PP_FALSE;
129  }
130  *bytes_sent = bytes_sent_;
131  *total_bytes_to_be_sent = total_bytes_to_be_sent_;
132  return PP_TRUE;
133}
134
135PP_Bool URLLoaderResource::GetDownloadProgress(
136    int64_t* bytes_received,
137    int64_t* total_bytes_to_be_received) {
138  if (!request_data_.record_download_progress) {
139    *bytes_received = 0;
140    *total_bytes_to_be_received = 0;
141    return PP_FALSE;
142  }
143  *bytes_received = bytes_received_;
144  *total_bytes_to_be_received = total_bytes_to_be_received_;
145  return PP_TRUE;
146}
147
148PP_Resource URLLoaderResource::GetResponseInfo() {
149  if (response_info_.get())
150    return response_info_->GetReference();
151  return 0;
152}
153
154int32_t URLLoaderResource::ReadResponseBody(
155    void* buffer,
156    int32_t bytes_to_read,
157    scoped_refptr<TrackedCallback> callback) {
158  int32_t rv = ValidateCallback(callback);
159  if (rv != PP_OK)
160    return rv;
161  if (!response_info_.get())
162    return PP_ERROR_FAILED;
163
164  // Fail if we have a valid file ref.
165  // ReadResponseBody() is for reading to a user-provided buffer.
166  if (response_info_->data().body_as_file_ref.IsValid())
167    return PP_ERROR_FAILED;
168
169  if (bytes_to_read <= 0 || !buffer)
170    return PP_ERROR_BADARGUMENT;
171
172  user_buffer_ = static_cast<char*>(buffer);
173  user_buffer_size_ = bytes_to_read;
174
175  if (!buffer_.empty())
176    return FillUserBuffer();
177
178  // We may have already reached EOF.
179  if (done_status_ != PP_OK_COMPLETIONPENDING) {
180    user_buffer_ = NULL;
181    user_buffer_size_ = 0;
182    return done_status_;
183  }
184
185  RegisterCallback(callback);
186  return PP_OK_COMPLETIONPENDING;
187}
188
189int32_t URLLoaderResource::FinishStreamingToFile(
190    scoped_refptr<TrackedCallback> callback) {
191  int32_t rv = ValidateCallback(callback);
192  if (rv != PP_OK)
193    return rv;
194  if (!response_info_.get())
195    return PP_ERROR_FAILED;
196
197  // Fail if we do not have a valid file ref.
198  if (!response_info_->data().body_as_file_ref.IsValid())
199    return PP_ERROR_FAILED;
200
201  // We may have already reached EOF.
202  if (done_status_ != PP_OK_COMPLETIONPENDING)
203    return done_status_;
204
205  is_streaming_to_file_ = true;
206  if (is_asynchronous_load_suspended_)
207    SetDefersLoading(false);
208
209  // Wait for didFinishLoading / didFail.
210  RegisterCallback(callback);
211  return PP_OK_COMPLETIONPENDING;
212}
213
214void URLLoaderResource::Close() {
215  mode_ = MODE_LOAD_COMPLETE;
216  done_status_ = PP_ERROR_ABORTED;
217
218  Post(RENDERER, PpapiHostMsg_URLLoader_Close());
219
220  // Abort the callbacks, the plugin doesn't want to be called back after this.
221  // TODO(brettw) this should fix bug 69457, mark it fixed. ============
222  if (TrackedCallback::IsPending(pending_callback_))
223    pending_callback_->PostAbort();
224}
225
226void URLLoaderResource::GrantUniversalAccess() {
227  Post(RENDERER, PpapiHostMsg_URLLoader_GrantUniversalAccess());
228}
229
230void URLLoaderResource::RegisterStatusCallback(
231    PP_URLLoaderTrusted_StatusCallback callback) {
232  status_callback_ = callback;
233}
234
235void URLLoaderResource::OnReplyReceived(
236    const ResourceMessageReplyParams& params,
237    const IPC::Message& msg) {
238  PPAPI_BEGIN_MESSAGE_MAP(URLLoaderResource, msg)
239    case PpapiPluginMsg_URLLoader_SendData::ID:
240      // Special message, manually dispatch since we don't want the automatic
241      // unpickling.
242      OnPluginMsgSendData(params, msg);
243      break;
244
245    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
246        PpapiPluginMsg_URLLoader_ReceivedResponse,
247        OnPluginMsgReceivedResponse)
248    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
249        PpapiPluginMsg_URLLoader_FinishedLoading,
250        OnPluginMsgFinishedLoading)
251    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
252        PpapiPluginMsg_URLLoader_UpdateProgress,
253        OnPluginMsgUpdateProgress)
254  PPAPI_END_MESSAGE_MAP()
255}
256
257void URLLoaderResource::OnPluginMsgReceivedResponse(
258    const ResourceMessageReplyParams& params,
259    const URLResponseInfoData& data) {
260  SaveResponseInfo(data);
261  RunCallback(PP_OK);
262}
263
264void URLLoaderResource::OnPluginMsgSendData(
265    const ResourceMessageReplyParams& params,
266    const IPC::Message& message) {
267  PickleIterator iter(message);
268  const char* data;
269  int data_length;
270  if (!iter.ReadData(&data, &data_length)) {
271    NOTREACHED() << "Expecting data";
272    return;
273  }
274
275  mode_ = MODE_STREAMING_DATA;
276  buffer_.insert(buffer_.end(), data, data + data_length);
277
278  // To avoid letting the network stack download an entire stream all at once,
279  // defer loading when we have enough buffer.
280  // Check for this before we run the callback, even though that could move
281  // data out of the buffer. Doing anything after the callback is unsafe.
282  DCHECK(request_data_.prefetch_buffer_lower_threshold <
283         request_data_.prefetch_buffer_upper_threshold);
284  if (!is_streaming_to_file_ &&
285      !is_asynchronous_load_suspended_ &&
286      (buffer_.size() >= static_cast<size_t>(
287          request_data_.prefetch_buffer_upper_threshold))) {
288    DVLOG(1) << "Suspending async load - buffer size: " << buffer_.size();
289    SetDefersLoading(true);
290  }
291
292  if (user_buffer_)
293    RunCallback(FillUserBuffer());
294  else
295    DCHECK(!TrackedCallback::IsPending(pending_callback_));
296}
297
298void URLLoaderResource::OnPluginMsgFinishedLoading(
299    const ResourceMessageReplyParams& params,
300    int32_t result) {
301  mode_ = MODE_LOAD_COMPLETE;
302  done_status_ = result;
303  user_buffer_ = NULL;
304  user_buffer_size_ = 0;
305
306  // If the client hasn't called any function that takes a callback since
307  // the initial call to Open, or called ReadResponseBody and got a
308  // synchronous return, then the callback will be NULL.
309  if (TrackedCallback::IsPending(pending_callback_))
310    RunCallback(done_status_);
311}
312
313void URLLoaderResource::OnPluginMsgUpdateProgress(
314    const ResourceMessageReplyParams& params,
315    int64_t bytes_sent,
316    int64_t total_bytes_to_be_sent,
317    int64_t bytes_received,
318    int64_t total_bytes_to_be_received) {
319  bytes_sent_ = bytes_sent;
320  total_bytes_to_be_sent_ = total_bytes_to_be_sent;
321  bytes_received_ = bytes_received;
322  total_bytes_to_be_received_ = total_bytes_to_be_received;
323
324  if (status_callback_)
325    status_callback_(pp_instance(), pp_resource(),
326                     bytes_sent_, total_bytes_to_be_sent_,
327                     bytes_received_, total_bytes_to_be_received_);
328}
329
330void URLLoaderResource::SetDefersLoading(bool defers_loading) {
331  Post(RENDERER, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading));
332}
333
334int32_t URLLoaderResource::ValidateCallback(
335    scoped_refptr<TrackedCallback> callback) {
336  DCHECK(callback.get());
337  if (TrackedCallback::IsPending(pending_callback_))
338    return PP_ERROR_INPROGRESS;
339  return PP_OK;
340}
341
342void URLLoaderResource::RegisterCallback(
343    scoped_refptr<TrackedCallback> callback) {
344  DCHECK(!TrackedCallback::IsPending(pending_callback_));
345  pending_callback_ = callback;
346}
347
348void URLLoaderResource::RunCallback(int32_t result) {
349  // This may be null when this is a main document loader.
350  if (!pending_callback_.get())
351    return;
352
353  // If |user_buffer_| was set as part of registering a callback, the paths
354  // which trigger that callack must have cleared it since the callback is now
355  // free to delete it.
356  DCHECK(!user_buffer_);
357
358  // As a second line of defense, clear the |user_buffer_| in case the
359  // callbacks get called in an unexpected order.
360  user_buffer_ = NULL;
361  user_buffer_size_ = 0;
362  pending_callback_->Run(result);
363}
364
365void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData& data) {
366  // Create a proxy resource for the the file ref host resource if needed.
367  PP_Resource body_as_file_ref = 0;
368  if (data.body_as_file_ref.IsValid()) {
369    body_as_file_ref = FileRefResource::CreateFileRef(connection(),
370                                                      pp_instance(),
371                                                      data.body_as_file_ref);
372  }
373  response_info_ = new URLResponseInfoResource(
374      connection(), pp_instance(), data, body_as_file_ref);
375}
376
377size_t URLLoaderResource::FillUserBuffer() {
378  DCHECK(user_buffer_);
379  DCHECK(user_buffer_size_);
380
381  size_t bytes_to_copy = std::min(buffer_.size(), user_buffer_size_);
382  std::copy(buffer_.begin(), buffer_.begin() + bytes_to_copy, user_buffer_);
383  buffer_.erase(buffer_.begin(), buffer_.begin() + bytes_to_copy);
384
385  // If the buffer is getting too empty, resume asynchronous loading.
386  if (is_asynchronous_load_suspended_ &&
387      buffer_.size() <= static_cast<size_t>(
388          request_data_.prefetch_buffer_lower_threshold)) {
389    DVLOG(1) << "Resuming async load - buffer size: " << buffer_.size();
390    SetDefersLoading(false);
391  }
392
393  // Reset for next time.
394  user_buffer_ = NULL;
395  user_buffer_size_ = 0;
396  return bytes_to_copy;
397}
398
399}  // namespace proxy
400}  // namespace ppapi
401