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.
5 #include "webkit/plugins/ppapi/ppb_url_loader_impl.h"
7 #include "base/logging.h"
8 #include "net/base/net_errors.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/c/trusted/ppb_url_loader_trusted.h"
13 #include "ppapi/shared_impl/ppapi_globals.h"
14 #include "ppapi/shared_impl/url_response_info_data.h"
15 #include "ppapi/thunk/enter.h"
16 #include "ppapi/thunk/ppb_url_request_info_api.h"
17 #include "third_party/WebKit/Source/Platform/chromium/public/WebURLError.h"
18 #include "third_party/WebKit/Source/Platform/chromium/public/WebURLLoader.h"
19 #include "third_party/WebKit/Source/Platform/chromium/public/WebURLRequest.h"
20 #include "third_party/WebKit/Source/Platform/chromium/public/WebURLResponse.h"
21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
22 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
23 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
24 #include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebPluginContainer.h"
26 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
27 #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h"
28 #include "webkit/appcache/web_application_cache_host_impl.h"
29 #include "webkit/plugins/ppapi/common.h"
30 #include "webkit/plugins/ppapi/plugin_module.h"
31 #include "webkit/plugins/ppapi/ppapi_plugin_instance.h"
32 #include "webkit/plugins/ppapi/resource_helper.h"
33 #include "webkit/plugins/ppapi/url_request_info_util.h"
34 #include "webkit/plugins/ppapi/url_response_info_util.h"
36 using appcache::WebApplicationCacheHostImpl
;
37 using ppapi::Resource
;
38 using ppapi::thunk::EnterResourceNoLock
;
39 using ppapi::thunk::PPB_URLLoader_API
;
40 using ppapi::thunk::PPB_URLRequestInfo_API
;
41 using ppapi::TrackedCallback
;
42 using WebKit::WebFrame
;
43 using WebKit::WebString
;
45 using WebKit::WebURLError
;
46 using WebKit::WebURLLoader
;
47 using WebKit::WebURLLoaderOptions
;
48 using WebKit::WebURLRequest
;
49 using WebKit::WebURLResponse
;
52 // Do not warn about use of std::copy with raw pointers.
53 #pragma warning(disable : 4996)
61 WebFrame
* GetFrameForResource(const Resource
* resource
) {
62 PluginInstance
* plugin_instance
= ResourceHelper::GetPluginInstance(resource
);
65 return plugin_instance
->container()->element().document().frame();
70 PPB_URLLoader_Impl::PPB_URLLoader_Impl(PP_Instance instance
,
71 bool main_document_loader
)
72 : Resource(::ppapi::OBJECT_IS_IMPL
, instance
),
73 main_document_loader_(main_document_loader
),
76 total_bytes_to_be_sent_(-1),
78 total_bytes_to_be_received_(-1),
81 done_status_(PP_OK_COMPLETIONPENDING
),
82 is_streaming_to_file_(false),
83 is_asynchronous_load_suspended_(false),
84 has_universal_access_(false),
85 status_callback_(NULL
) {
88 PPB_URLLoader_Impl::~PPB_URLLoader_Impl() {
89 // There is a path whereby the destructor for the loader_ member can
90 // invoke InstanceWasDeleted() upon this PPB_URLLoader_Impl, thereby
91 // re-entering the scoped_ptr destructor with the same scoped_ptr object
92 // via loader_.reset(). Be sure that loader_ is first NULL then destroy
93 // the scoped_ptr. See http://crbug.com/159429.
94 scoped_ptr
<WebKit::WebURLLoader
> for_destruction_only(loader_
.release());
97 PPB_URLLoader_API
* PPB_URLLoader_Impl::AsPPB_URLLoader_API() {
101 void PPB_URLLoader_Impl::InstanceWasDeleted() {
105 int32_t PPB_URLLoader_Impl::Open(PP_Resource request_id
,
106 scoped_refptr
<TrackedCallback
> callback
) {
107 EnterResourceNoLock
<PPB_URLRequestInfo_API
> enter_request(request_id
, true);
108 if (enter_request
.failed()) {
109 Log(PP_LOGLEVEL_ERROR
,
110 "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper"
111 " users: use the ResourceRequest constructor that takes an instance or"
112 " else the request will be null.)");
113 return PP_ERROR_BADARGUMENT
;
115 return Open(enter_request
.object()->GetData(), 0, callback
);
118 int32_t PPB_URLLoader_Impl::Open(
119 const ::ppapi::URLRequestInfoData
& request_data
,
121 scoped_refptr
<TrackedCallback
> callback
) {
122 // Main document loads are already open, so don't allow people to open them
124 if (main_document_loader_
)
125 return PP_ERROR_INPROGRESS
;
127 int32_t rv
= ValidateCallback(callback
);
131 // Create a copy of the request data since CreateWebURLRequest will populate
133 ::ppapi::URLRequestInfoData filled_in_request_data
= request_data
;
135 if (URLRequestRequiresUniversalAccess(filled_in_request_data
) &&
136 !has_universal_access_
) {
137 Log(PP_LOGLEVEL_ERROR
, "PPB_URLLoader.Open: The URL you're requesting is "
138 " on a different security origin than your plugin. To request "
139 " cross-origin resources, see "
140 " PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS.");
141 return PP_ERROR_NOACCESS
;
145 return PP_ERROR_INPROGRESS
;
147 WebFrame
* frame
= GetFrameForResource(this);
149 return PP_ERROR_FAILED
;
150 WebURLRequest web_request
;
151 if (!CreateWebURLRequest(&filled_in_request_data
, frame
, &web_request
))
152 return PP_ERROR_FAILED
;
153 web_request
.setRequestorProcessID(requestor_pid
);
155 // Save a copy of the request info so the plugin can continue to use and
156 // change it while we're doing the request without affecting us. We must do
157 // this after CreateWebURLRequest since that fills out the file refs.
158 request_data_
= filled_in_request_data
;
160 WebURLLoaderOptions options
;
161 if (has_universal_access_
) {
162 options
.allowCredentials
= true;
163 options
.crossOriginRequestPolicy
=
164 WebURLLoaderOptions::CrossOriginRequestPolicyAllow
;
166 // All other HTTP requests are untrusted.
167 options
.untrustedHTTP
= true;
168 if (request_data_
.allow_cross_origin_requests
) {
169 // Allow cross-origin requests with access control. The request specifies
170 // if credentials are to be sent.
171 options
.allowCredentials
= request_data_
.allow_credentials
;
172 options
.crossOriginRequestPolicy
=
173 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl
;
175 // Same-origin requests can always send credentials.
176 options
.allowCredentials
= true;
180 is_asynchronous_load_suspended_
= false;
181 loader_
.reset(frame
->createAssociatedURLLoader(options
));
183 return PP_ERROR_FAILED
;
185 loader_
->loadAsynchronously(web_request
, this);
187 // Notify completion when we receive a redirect or response headers.
188 RegisterCallback(callback
);
189 return PP_OK_COMPLETIONPENDING
;
192 int32_t PPB_URLLoader_Impl::FollowRedirect(
193 scoped_refptr
<TrackedCallback
> callback
) {
194 int32_t rv
= ValidateCallback(callback
);
198 SetDefersLoading(false); // Allow the redirect to continue.
199 RegisterCallback(callback
);
200 return PP_OK_COMPLETIONPENDING
;
203 PP_Bool
PPB_URLLoader_Impl::GetUploadProgress(int64_t* bytes_sent
,
204 int64_t* total_bytes_to_be_sent
) {
205 if (!RecordUploadProgress()) {
207 *total_bytes_to_be_sent
= 0;
210 *bytes_sent
= bytes_sent_
;
211 *total_bytes_to_be_sent
= total_bytes_to_be_sent_
;
215 PP_Bool
PPB_URLLoader_Impl::GetDownloadProgress(
216 int64_t* bytes_received
,
217 int64_t* total_bytes_to_be_received
) {
218 if (!RecordDownloadProgress()) {
220 *total_bytes_to_be_received
= 0;
223 *bytes_received
= bytes_received_
;
224 *total_bytes_to_be_received
= total_bytes_to_be_received_
;
228 PP_Resource
PPB_URLLoader_Impl::GetResponseInfo() {
229 ::ppapi::thunk::EnterResourceCreationNoLock
enter(pp_instance());
230 if (enter
.failed() || !response_info_
.get())
233 // Since we're the "host" the process-local resource for the file ref is
234 // the same as the host resource. We pass a ref to the file ref.
235 if (!response_info_
->body_as_file_ref
.resource
.is_null()) {
236 ::ppapi::PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(
237 response_info_
->body_as_file_ref
.resource
.host_resource());
239 return enter
.functions()->CreateURLResponseInfo(
242 response_info_
->body_as_file_ref
.resource
.host_resource());
245 int32_t PPB_URLLoader_Impl::ReadResponseBody(
247 int32_t bytes_to_read
,
248 scoped_refptr
<TrackedCallback
> callback
) {
249 int32_t rv
= ValidateCallback(callback
);
252 if (!response_info_
.get() ||
253 !response_info_
->body_as_file_ref
.resource
.is_null())
254 return PP_ERROR_FAILED
;
255 if (bytes_to_read
<= 0 || !buffer
)
256 return PP_ERROR_BADARGUMENT
;
258 user_buffer_
= static_cast<char*>(buffer
);
259 user_buffer_size_
= bytes_to_read
;
261 if (!buffer_
.empty())
262 return FillUserBuffer();
264 // We may have already reached EOF.
265 if (done_status_
!= PP_OK_COMPLETIONPENDING
) {
267 user_buffer_size_
= 0;
271 RegisterCallback(callback
);
272 return PP_OK_COMPLETIONPENDING
;
275 int32_t PPB_URLLoader_Impl::FinishStreamingToFile(
276 scoped_refptr
<TrackedCallback
> callback
) {
277 int32_t rv
= ValidateCallback(callback
);
280 if (!response_info_
.get() ||
281 response_info_
->body_as_file_ref
.resource
.is_null())
282 return PP_ERROR_FAILED
;
284 // We may have already reached EOF.
285 if (done_status_
!= PP_OK_COMPLETIONPENDING
)
288 is_streaming_to_file_
= true;
289 if (is_asynchronous_load_suspended_
)
290 SetDefersLoading(false);
292 // Wait for didFinishLoading / didFail.
293 RegisterCallback(callback
);
294 return PP_OK_COMPLETIONPENDING
;
297 void PPB_URLLoader_Impl::Close() {
300 else if (main_document_loader_
)
301 GetFrameForResource(this)->stopLoading();
303 // We must not access the buffer provided by the caller from this point on.
305 user_buffer_size_
= 0;
306 if (TrackedCallback::IsPending(pending_callback_
))
307 pending_callback_
->PostAbort();
310 void PPB_URLLoader_Impl::GrantUniversalAccess() {
311 has_universal_access_
= true;
314 void PPB_URLLoader_Impl::SetStatusCallback(
315 PP_URLLoaderTrusted_StatusCallback cb
) {
316 status_callback_
= cb
;
319 bool PPB_URLLoader_Impl::GetResponseInfoData(
320 ::ppapi::URLResponseInfoData
* data
) {
321 if (!response_info_
.get())
324 *data
= *response_info_
;
326 // We transfer one plugin reference to the FileRef to the caller.
327 if (!response_info_
->body_as_file_ref
.resource
.is_null()) {
328 ::ppapi::PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(
329 response_info_
->body_as_file_ref
.resource
.host_resource());
334 void PPB_URLLoader_Impl::willSendRequest(
335 WebURLLoader
* loader
,
336 WebURLRequest
& new_request
,
337 const WebURLResponse
& redirect_response
) {
338 if (!request_data_
.follow_redirects
) {
339 SaveResponse(redirect_response
);
340 SetDefersLoading(true);
345 void PPB_URLLoader_Impl::didSendData(
346 WebURLLoader
* loader
,
347 unsigned long long bytes_sent
,
348 unsigned long long total_bytes_to_be_sent
) {
349 // TODO(darin): Bounds check input?
350 bytes_sent_
= static_cast<int64_t>(bytes_sent
);
351 total_bytes_to_be_sent_
= static_cast<int64_t>(total_bytes_to_be_sent
);
355 void PPB_URLLoader_Impl::didReceiveResponse(WebURLLoader
* loader
,
356 const WebURLResponse
& response
) {
357 SaveResponse(response
);
359 // Sets -1 if the content length is unknown.
360 total_bytes_to_be_received_
= response
.expectedContentLength();
366 void PPB_URLLoader_Impl::didDownloadData(WebURLLoader
* loader
,
368 bytes_received_
+= data_length
;
372 void PPB_URLLoader_Impl::didReceiveData(WebURLLoader
* loader
,
375 int encoded_data_length
) {
376 // Note that |loader| will be NULL for document loads.
377 bytes_received_
+= data_length
;
380 buffer_
.insert(buffer_
.end(), data
, data
+ data_length
);
382 // To avoid letting the network stack download an entire stream all at once,
383 // defer loading when we have enough buffer.
384 // Check for this before we run the callback, even though that could move
385 // data out of the buffer. Doing anything after the callback is unsafe.
386 DCHECK(request_data_
.prefetch_buffer_lower_threshold
<
387 request_data_
.prefetch_buffer_upper_threshold
);
388 if (!is_streaming_to_file_
&&
389 !is_asynchronous_load_suspended_
&&
390 (buffer_
.size() >= static_cast<size_t>(
391 request_data_
.prefetch_buffer_upper_threshold
))) {
392 DVLOG(1) << "Suspending async load - buffer size: " << buffer_
.size();
393 SetDefersLoading(true);
397 RunCallback(FillUserBuffer());
399 DCHECK(!TrackedCallback::IsPending(pending_callback_
));
403 void PPB_URLLoader_Impl::didFinishLoading(WebURLLoader
* loader
,
404 double finish_time
) {
405 FinishLoading(PP_OK
);
408 void PPB_URLLoader_Impl::didFail(WebURLLoader
* loader
,
409 const WebURLError
& error
) {
410 int32_t pp_error
= PP_ERROR_FAILED
;
411 if (error
.domain
.equals(WebString::fromUTF8(net::kErrorDomain
))) {
412 // TODO(bbudge): Extend pp_errors.h to cover interesting network errors
413 // from the net error domain.
414 switch (error
.reason
) {
415 case net::ERR_ABORTED
:
416 pp_error
= PP_ERROR_ABORTED
;
418 case net::ERR_ACCESS_DENIED
:
419 case net::ERR_NETWORK_ACCESS_DENIED
:
420 pp_error
= PP_ERROR_NOACCESS
;
424 // It's a WebKit error.
425 pp_error
= PP_ERROR_NOACCESS
;
428 FinishLoading(pp_error
);
431 void PPB_URLLoader_Impl::SetDefersLoading(bool defers_loading
) {
433 loader_
->setDefersLoading(defers_loading
);
434 is_asynchronous_load_suspended_
= defers_loading
;
437 // TODO(brettw) bug 96770: We need a way to set the defers loading flag on
438 // main document loads (when the loader_ is null).
441 void PPB_URLLoader_Impl::FinishLoading(int32_t done_status
) {
442 done_status_
= done_status
;
444 user_buffer_size_
= 0;
445 // If the client hasn't called any function that takes a callback since
446 // the initial call to Open, or called ReadResponseBody and got a
447 // synchronous return, then the callback will be NULL.
448 if (TrackedCallback::IsPending(pending_callback_
))
449 RunCallback(done_status_
);
452 int32_t PPB_URLLoader_Impl::ValidateCallback(
453 scoped_refptr
<TrackedCallback
> callback
) {
456 if (TrackedCallback::IsPending(pending_callback_
))
457 return PP_ERROR_INPROGRESS
;
462 void PPB_URLLoader_Impl::RegisterCallback(
463 scoped_refptr
<TrackedCallback
> callback
) {
464 DCHECK(!TrackedCallback::IsPending(pending_callback_
));
466 PluginModule
* plugin_module
= ResourceHelper::GetPluginModule(this);
470 pending_callback_
= callback
;
473 void PPB_URLLoader_Impl::RunCallback(int32_t result
) {
474 // This may be null only when this is a main document loader.
475 if (!pending_callback_
.get()) {
476 CHECK(main_document_loader_
);
480 // If |user_buffer_| was set as part of registering a callback, the paths
481 // which trigger that callack must have cleared it since the callback is now
482 // free to delete it.
483 DCHECK(!user_buffer_
);
485 // As a second line of defense, clear the |user_buffer_| in case the
486 // callbacks get called in an unexpected order.
488 user_buffer_size_
= 0;
489 pending_callback_
->Run(result
);
492 size_t PPB_URLLoader_Impl::FillUserBuffer() {
493 DCHECK(user_buffer_
);
494 DCHECK(user_buffer_size_
);
496 size_t bytes_to_copy
= std::min(buffer_
.size(), user_buffer_size_
);
497 std::copy(buffer_
.begin(), buffer_
.begin() + bytes_to_copy
, user_buffer_
);
498 buffer_
.erase(buffer_
.begin(), buffer_
.begin() + bytes_to_copy
);
500 // If the buffer is getting too empty, resume asynchronous loading.
501 if (is_asynchronous_load_suspended_
&&
502 buffer_
.size() <= static_cast<size_t>(
503 request_data_
.prefetch_buffer_lower_threshold
)) {
504 DVLOG(1) << "Resuming async load - buffer size: " << buffer_
.size();
505 SetDefersLoading(false);
508 // Reset for next time.
510 user_buffer_size_
= 0;
511 return bytes_to_copy
;
514 void PPB_URLLoader_Impl::SaveResponse(const WebURLResponse
& response
) {
515 // DataFromWebURLResponse returns a file ref with one reference to it, which
516 // we take over via our ScopedPPResource.
517 response_info_
.reset(new ::ppapi::URLResponseInfoData(
518 DataFromWebURLResponse(pp_instance(), response
)));
519 response_info_file_ref_
= ::ppapi::ScopedPPResource(
520 ::ppapi::ScopedPPResource::PassRef(),
521 response_info_
->body_as_file_ref
.resource
.host_resource());
524 void PPB_URLLoader_Impl::UpdateStatus() {
525 if (status_callback_
&&
526 (RecordDownloadProgress() || RecordUploadProgress())) {
527 // Here we go through some effort to only send the exact information that
528 // the requestor wanted in the request flags. It would be just as
529 // efficient to send all of it, but we don't want people to rely on
530 // getting download progress when they happen to set the upload progress
533 pp_instance(), pp_resource(),
534 RecordUploadProgress() ? bytes_sent_
: -1,
535 RecordUploadProgress() ? total_bytes_to_be_sent_
: -1,
536 RecordDownloadProgress() ? bytes_received_
: -1,
537 RecordDownloadProgress() ? total_bytes_to_be_received_
: -1);
541 bool PPB_URLLoader_Impl::RecordDownloadProgress() const {
542 return request_data_
.record_download_progress
;
545 bool PPB_URLLoader_Impl::RecordUploadProgress() const {
546 return request_data_
.record_upload_progress
;
550 } // namespace webkit