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
21 using ppapi::thunk::EnterResourceNoLock;
22 using ppapi::thunk::PPB_URLLoader_API;
23 using 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
30 namespace ppapi {
31 namespace proxy {
32
URLLoaderResource(Connection connection,PP_Instance instance)33 URLLoaderResource::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
URLLoaderResource(Connection connection,PP_Instance instance,int pending_main_document_loader_id,const ppapi::URLResponseInfoData & data)50 URLLoaderResource::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
~URLLoaderResource()70 URLLoaderResource::~URLLoaderResource() {
71 }
72
AsPPB_URLLoader_API()73 PPB_URLLoader_API* URLLoaderResource::AsPPB_URLLoader_API() {
74 return this;
75 }
76
Open(PP_Resource request_id,scoped_refptr<TrackedCallback> callback)77 int32_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
Open(const::ppapi::URLRequestInfoData & request_data,int requestor_pid,scoped_refptr<TrackedCallback> callback)90 int32_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
FollowRedirect(scoped_refptr<TrackedCallback> callback)110 int32_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
GetUploadProgress(int64_t * bytes_sent,int64_t * total_bytes_to_be_sent)123 PP_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
GetDownloadProgress(int64_t * bytes_received,int64_t * total_bytes_to_be_received)135 PP_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
GetResponseInfo()148 PP_Resource URLLoaderResource::GetResponseInfo() {
149 if (response_info_.get())
150 return response_info_->GetReference();
151 return 0;
152 }
153
ReadResponseBody(void * buffer,int32_t bytes_to_read,scoped_refptr<TrackedCallback> callback)154 int32_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
FinishStreamingToFile(scoped_refptr<TrackedCallback> callback)189 int32_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
Close()214 void 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
GrantUniversalAccess()226 void URLLoaderResource::GrantUniversalAccess() {
227 Post(RENDERER, PpapiHostMsg_URLLoader_GrantUniversalAccess());
228 }
229
RegisterStatusCallback(PP_URLLoaderTrusted_StatusCallback callback)230 void URLLoaderResource::RegisterStatusCallback(
231 PP_URLLoaderTrusted_StatusCallback callback) {
232 status_callback_ = callback;
233 }
234
OnReplyReceived(const ResourceMessageReplyParams & params,const IPC::Message & msg)235 void URLLoaderResource::OnReplyReceived(
236 const ResourceMessageReplyParams& params,
237 const IPC::Message& msg) {
238 IPC_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 IPC_END_MESSAGE_MAP()
255 }
256
OnPluginMsgReceivedResponse(const ResourceMessageReplyParams & params,const URLResponseInfoData & data)257 void URLLoaderResource::OnPluginMsgReceivedResponse(
258 const ResourceMessageReplyParams& params,
259 const URLResponseInfoData& data) {
260 SaveResponseInfo(data);
261 RunCallback(PP_OK);
262 }
263
OnPluginMsgSendData(const ResourceMessageReplyParams & params,const IPC::Message & message)264 void 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
OnPluginMsgFinishedLoading(const ResourceMessageReplyParams & params,int32_t result)298 void 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
OnPluginMsgUpdateProgress(const ResourceMessageReplyParams & params,int64_t bytes_sent,int64_t total_bytes_to_be_sent,int64_t bytes_received,int64_t total_bytes_to_be_received)313 void 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
SetDefersLoading(bool defers_loading)330 void URLLoaderResource::SetDefersLoading(bool defers_loading) {
331 Post(RENDERER, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading));
332 }
333
ValidateCallback(scoped_refptr<TrackedCallback> callback)334 int32_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
RegisterCallback(scoped_refptr<TrackedCallback> callback)342 void URLLoaderResource::RegisterCallback(
343 scoped_refptr<TrackedCallback> callback) {
344 DCHECK(!TrackedCallback::IsPending(pending_callback_));
345 pending_callback_ = callback;
346 }
347
RunCallback(int32_t result)348 void 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
SaveResponseInfo(const URLResponseInfoData & data)365 void 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
FillUserBuffer()377 size_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