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.
5 #include "ppapi/proxy/url_loader_resource.h"
7 #include "base/logging.h"
8 #include "base/numerics/safe_conversions.h"
9 #include "ppapi/c/pp_completion_callback.h"
10 #include "ppapi/c/pp_errors.h"
11 #include "ppapi/c/ppb_url_loader.h"
12 #include "ppapi/proxy/dispatch_reply_message.h"
13 #include "ppapi/proxy/file_ref_resource.h"
14 #include "ppapi/proxy/ppapi_messages.h"
15 #include "ppapi/proxy/url_request_info_resource.h"
16 #include "ppapi/proxy/url_response_info_resource.h"
17 #include "ppapi/shared_impl/ppapi_globals.h"
18 #include "ppapi/shared_impl/url_response_info_data.h"
19 #include "ppapi/thunk/enter.h"
20 #include "ppapi/thunk/resource_creation_api.h"
22 using ppapi::thunk::EnterResourceNoLock
;
23 using ppapi::thunk::PPB_URLLoader_API
;
24 using ppapi::thunk::PPB_URLRequestInfo_API
;
27 // Do not warn about use of std::copy with raw pointers.
28 #pragma warning(disable : 4996)
34 URLLoaderResource::URLLoaderResource(Connection connection
,
36 : PluginResource(connection
, instance
),
37 mode_(MODE_WAITING_TO_OPEN
),
38 status_callback_(NULL
),
40 total_bytes_to_be_sent_(-1),
42 total_bytes_to_be_received_(-1),
45 done_status_(PP_OK_COMPLETIONPENDING
),
46 is_streaming_to_file_(false),
47 is_asynchronous_load_suspended_(false) {
48 SendCreate(RENDERER
, PpapiHostMsg_URLLoader_Create());
51 URLLoaderResource::URLLoaderResource(Connection connection
,
53 int pending_main_document_loader_id
,
54 const ppapi::URLResponseInfoData
& data
)
55 : PluginResource(connection
, instance
),
57 status_callback_(NULL
),
59 total_bytes_to_be_sent_(-1),
61 total_bytes_to_be_received_(-1),
64 done_status_(PP_OK_COMPLETIONPENDING
),
65 is_streaming_to_file_(false),
66 is_asynchronous_load_suspended_(false) {
67 AttachToPendingHost(RENDERER
, pending_main_document_loader_id
);
68 SaveResponseInfo(data
);
71 URLLoaderResource::~URLLoaderResource() {
74 PPB_URLLoader_API
* URLLoaderResource::AsPPB_URLLoader_API() {
78 int32_t URLLoaderResource::Open(PP_Resource request_id
,
79 scoped_refptr
<TrackedCallback
> callback
) {
80 EnterResourceNoLock
<PPB_URLRequestInfo_API
> enter_request(request_id
, true);
81 if (enter_request
.failed()) {
82 Log(PP_LOGLEVEL_ERROR
,
83 "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper"
84 " users: use the ResourceRequest constructor that takes an instance or"
85 " else the request will be null.)");
86 return PP_ERROR_BADARGUMENT
;
88 return Open(enter_request
.object()->GetData(), 0, callback
);
91 int32_t URLLoaderResource::Open(
92 const ::ppapi::URLRequestInfoData
& request_data
,
94 scoped_refptr
<TrackedCallback
> callback
) {
95 int32_t rv
= ValidateCallback(callback
);
98 if (mode_
!= MODE_WAITING_TO_OPEN
)
99 return PP_ERROR_INPROGRESS
;
101 request_data_
= request_data
;
103 mode_
= MODE_OPENING
;
104 is_asynchronous_load_suspended_
= false;
106 RegisterCallback(callback
);
107 Post(RENDERER
, PpapiHostMsg_URLLoader_Open(request_data
));
108 return PP_OK_COMPLETIONPENDING
;
111 int32_t URLLoaderResource::FollowRedirect(
112 scoped_refptr
<TrackedCallback
> callback
) {
113 int32_t rv
= ValidateCallback(callback
);
116 if (mode_
!= MODE_OPENING
)
117 return PP_ERROR_INPROGRESS
;
119 SetDefersLoading(false); // Allow the redirect to continue.
120 RegisterCallback(callback
);
121 return PP_OK_COMPLETIONPENDING
;
124 PP_Bool
URLLoaderResource::GetUploadProgress(int64_t* bytes_sent
,
125 int64_t* total_bytes_to_be_sent
) {
126 if (!request_data_
.record_upload_progress
) {
128 *total_bytes_to_be_sent
= 0;
131 *bytes_sent
= bytes_sent_
;
132 *total_bytes_to_be_sent
= total_bytes_to_be_sent_
;
136 PP_Bool
URLLoaderResource::GetDownloadProgress(
137 int64_t* bytes_received
,
138 int64_t* total_bytes_to_be_received
) {
139 if (!request_data_
.record_download_progress
) {
141 *total_bytes_to_be_received
= 0;
144 *bytes_received
= bytes_received_
;
145 *total_bytes_to_be_received
= total_bytes_to_be_received_
;
149 PP_Resource
URLLoaderResource::GetResponseInfo() {
150 if (response_info_
.get())
151 return response_info_
->GetReference();
155 int32_t URLLoaderResource::ReadResponseBody(
157 int32_t bytes_to_read
,
158 scoped_refptr
<TrackedCallback
> callback
) {
159 int32_t rv
= ValidateCallback(callback
);
162 if (!response_info_
.get())
163 return PP_ERROR_FAILED
;
165 // Fail if we have a valid file ref.
166 // ReadResponseBody() is for reading to a user-provided buffer.
167 if (response_info_
->data().body_as_file_ref
.IsValid())
168 return PP_ERROR_FAILED
;
170 if (bytes_to_read
<= 0 || !buffer
)
171 return PP_ERROR_BADARGUMENT
;
173 user_buffer_
= static_cast<char*>(buffer
);
174 user_buffer_size_
= bytes_to_read
;
176 if (!buffer_
.empty())
177 return FillUserBuffer();
179 // We may have already reached EOF.
180 if (done_status_
!= PP_OK_COMPLETIONPENDING
) {
182 user_buffer_size_
= 0;
186 RegisterCallback(callback
);
187 return PP_OK_COMPLETIONPENDING
;
190 int32_t URLLoaderResource::FinishStreamingToFile(
191 scoped_refptr
<TrackedCallback
> callback
) {
192 int32_t rv
= ValidateCallback(callback
);
195 if (!response_info_
.get())
196 return PP_ERROR_FAILED
;
198 // Fail if we do not have a valid file ref.
199 if (!response_info_
->data().body_as_file_ref
.IsValid())
200 return PP_ERROR_FAILED
;
202 // We may have already reached EOF.
203 if (done_status_
!= PP_OK_COMPLETIONPENDING
)
206 is_streaming_to_file_
= true;
207 if (is_asynchronous_load_suspended_
)
208 SetDefersLoading(false);
210 // Wait for didFinishLoading / didFail.
211 RegisterCallback(callback
);
212 return PP_OK_COMPLETIONPENDING
;
215 void URLLoaderResource::Close() {
216 mode_
= MODE_LOAD_COMPLETE
;
217 done_status_
= PP_ERROR_ABORTED
;
219 Post(RENDERER
, PpapiHostMsg_URLLoader_Close());
221 // Abort the callbacks, the plugin doesn't want to be called back after this.
222 // TODO(brettw) this should fix bug 69457, mark it fixed. ============
223 if (TrackedCallback::IsPending(pending_callback_
))
224 pending_callback_
->PostAbort();
227 void URLLoaderResource::GrantUniversalAccess() {
228 Post(RENDERER
, PpapiHostMsg_URLLoader_GrantUniversalAccess());
231 void URLLoaderResource::RegisterStatusCallback(
232 PP_URLLoaderTrusted_StatusCallback callback
) {
233 status_callback_
= callback
;
236 void URLLoaderResource::OnReplyReceived(
237 const ResourceMessageReplyParams
& params
,
238 const IPC::Message
& msg
) {
239 PPAPI_BEGIN_MESSAGE_MAP(URLLoaderResource
, msg
)
240 case PpapiPluginMsg_URLLoader_SendData::ID
:
241 // Special message, manually dispatch since we don't want the automatic
243 OnPluginMsgSendData(params
, msg
);
246 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
247 PpapiPluginMsg_URLLoader_ReceivedResponse
,
248 OnPluginMsgReceivedResponse
)
249 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
250 PpapiPluginMsg_URLLoader_FinishedLoading
,
251 OnPluginMsgFinishedLoading
)
252 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
253 PpapiPluginMsg_URLLoader_UpdateProgress
,
254 OnPluginMsgUpdateProgress
)
255 PPAPI_END_MESSAGE_MAP()
258 void URLLoaderResource::OnPluginMsgReceivedResponse(
259 const ResourceMessageReplyParams
& params
,
260 const URLResponseInfoData
& data
) {
261 SaveResponseInfo(data
);
265 void URLLoaderResource::OnPluginMsgSendData(
266 const ResourceMessageReplyParams
& params
,
267 const IPC::Message
& message
) {
268 PickleIterator
iter(message
);
271 if (!iter
.ReadData(&data
, &data_length
)) {
272 NOTREACHED() << "Expecting data";
276 mode_
= MODE_STREAMING_DATA
;
277 buffer_
.insert(buffer_
.end(), data
, data
+ data_length
);
279 // To avoid letting the network stack download an entire stream all at once,
280 // defer loading when we have enough buffer.
281 // Check for this before we run the callback, even though that could move
282 // data out of the buffer. Doing anything after the callback is unsafe.
283 DCHECK(request_data_
.prefetch_buffer_lower_threshold
<
284 request_data_
.prefetch_buffer_upper_threshold
);
285 if (!is_streaming_to_file_
&&
286 !is_asynchronous_load_suspended_
&&
287 (buffer_
.size() >= static_cast<size_t>(
288 request_data_
.prefetch_buffer_upper_threshold
))) {
289 DVLOG(1) << "Suspending async load - buffer size: " << buffer_
.size();
290 SetDefersLoading(true);
294 RunCallback(FillUserBuffer());
296 DCHECK(!TrackedCallback::IsPending(pending_callback_
));
299 void URLLoaderResource::OnPluginMsgFinishedLoading(
300 const ResourceMessageReplyParams
& params
,
302 mode_
= MODE_LOAD_COMPLETE
;
303 done_status_
= result
;
305 user_buffer_size_
= 0;
307 // If the client hasn't called any function that takes a callback since
308 // the initial call to Open, or called ReadResponseBody and got a
309 // synchronous return, then the callback will be NULL.
310 if (TrackedCallback::IsPending(pending_callback_
))
311 RunCallback(done_status_
);
314 void URLLoaderResource::OnPluginMsgUpdateProgress(
315 const ResourceMessageReplyParams
& params
,
317 int64_t total_bytes_to_be_sent
,
318 int64_t bytes_received
,
319 int64_t total_bytes_to_be_received
) {
320 bytes_sent_
= bytes_sent
;
321 total_bytes_to_be_sent_
= total_bytes_to_be_sent
;
322 bytes_received_
= bytes_received
;
323 total_bytes_to_be_received_
= total_bytes_to_be_received
;
325 if (status_callback_
)
326 status_callback_(pp_instance(), pp_resource(),
327 bytes_sent_
, total_bytes_to_be_sent_
,
328 bytes_received_
, total_bytes_to_be_received_
);
331 void URLLoaderResource::SetDefersLoading(bool defers_loading
) {
332 Post(RENDERER
, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading
));
335 int32_t URLLoaderResource::ValidateCallback(
336 scoped_refptr
<TrackedCallback
> callback
) {
337 DCHECK(callback
.get());
338 if (TrackedCallback::IsPending(pending_callback_
))
339 return PP_ERROR_INPROGRESS
;
343 void URLLoaderResource::RegisterCallback(
344 scoped_refptr
<TrackedCallback
> callback
) {
345 DCHECK(!TrackedCallback::IsPending(pending_callback_
));
346 pending_callback_
= callback
;
349 void URLLoaderResource::RunCallback(int32_t result
) {
350 // This may be null when this is a main document loader.
351 if (!pending_callback_
.get())
354 // If |user_buffer_| was set as part of registering a callback, the paths
355 // which trigger that callack must have cleared it since the callback is now
356 // free to delete it.
357 DCHECK(!user_buffer_
);
359 // As a second line of defense, clear the |user_buffer_| in case the
360 // callbacks get called in an unexpected order.
362 user_buffer_size_
= 0;
363 pending_callback_
->Run(result
);
366 void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData
& data
) {
367 // Create a proxy resource for the the file ref host resource if needed.
368 PP_Resource body_as_file_ref
= 0;
369 if (data
.body_as_file_ref
.IsValid()) {
370 body_as_file_ref
= FileRefResource::CreateFileRef(connection(),
372 data
.body_as_file_ref
);
374 response_info_
= new URLResponseInfoResource(
375 connection(), pp_instance(), data
, body_as_file_ref
);
378 int32_t URLLoaderResource::FillUserBuffer() {
379 DCHECK(user_buffer_
);
380 DCHECK(user_buffer_size_
);
382 size_t bytes_to_copy
= std::min(buffer_
.size(), user_buffer_size_
);
383 std::copy(buffer_
.begin(), buffer_
.begin() + bytes_to_copy
, user_buffer_
);
384 buffer_
.erase(buffer_
.begin(), buffer_
.begin() + bytes_to_copy
);
386 // If the buffer is getting too empty, resume asynchronous loading.
387 if (is_asynchronous_load_suspended_
&&
388 buffer_
.size() <= static_cast<size_t>(
389 request_data_
.prefetch_buffer_lower_threshold
)) {
390 DVLOG(1) << "Resuming async load - buffer size: " << buffer_
.size();
391 SetDefersLoading(false);
394 // Reset for next time.
396 user_buffer_size_
= 0;
397 return base::checked_cast
<int32_t>(bytes_to_copy
);