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 "content/renderer/pepper/pepper_url_loader_host.h"
6
7 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
8 #include "content/renderer/pepper/renderer_ppapi_host_impl.h"
9 #include "content/renderer/pepper/url_request_info_util.h"
10 #include "content/renderer/pepper/url_response_info_util.h"
11 #include "net/base/net_errors.h"
12 #include "ppapi/c/pp_errors.h"
13 #include "ppapi/host/dispatch_host_message.h"
14 #include "ppapi/host/host_message_context.h"
15 #include "ppapi/host/ppapi_host.h"
16 #include "ppapi/proxy/ppapi_messages.h"
17 #include "ppapi/shared_impl/ppapi_globals.h"
18 #include "third_party/WebKit/public/platform/WebURLError.h"
19 #include "third_party/WebKit/public/platform/WebURLLoader.h"
20 #include "third_party/WebKit/public/platform/WebURLRequest.h"
21 #include "third_party/WebKit/public/platform/WebURLResponse.h"
22 #include "third_party/WebKit/public/web/WebDocument.h"
23 #include "third_party/WebKit/public/web/WebElement.h"
24 #include "third_party/WebKit/public/web/WebFrame.h"
25 #include "third_party/WebKit/public/web/WebKit.h"
26 #include "third_party/WebKit/public/web/WebPluginContainer.h"
27 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
28 #include "third_party/WebKit/public/web/WebURLLoaderOptions.h"
29
30 using blink::WebFrame;
31 using blink::WebString;
32 using blink::WebURL;
33 using blink::WebURLError;
34 using blink::WebURLLoader;
35 using blink::WebURLLoaderOptions;
36 using blink::WebURLRequest;
37 using blink::WebURLResponse;
38
39 #ifdef _MSC_VER
40 // Do not warn about use of std::copy with raw pointers.
41 #pragma warning(disable : 4996)
42 #endif
43
44 namespace content {
45
PepperURLLoaderHost(RendererPpapiHostImpl * host,bool main_document_loader,PP_Instance instance,PP_Resource resource)46 PepperURLLoaderHost::PepperURLLoaderHost(RendererPpapiHostImpl* host,
47 bool main_document_loader,
48 PP_Instance instance,
49 PP_Resource resource)
50 : ResourceHost(host->GetPpapiHost(), instance, resource),
51 renderer_ppapi_host_(host),
52 main_document_loader_(main_document_loader),
53 has_universal_access_(false),
54 bytes_sent_(0),
55 total_bytes_to_be_sent_(-1),
56 bytes_received_(0),
57 total_bytes_to_be_received_(-1),
58 pending_response_(false),
59 weak_factory_(this) {
60 DCHECK((main_document_loader && !resource) ||
61 (!main_document_loader && resource));
62 }
63
~PepperURLLoaderHost()64 PepperURLLoaderHost::~PepperURLLoaderHost() {
65 // Normally deleting this object will delete the loader which will implicitly
66 // cancel the load. But this won't happen for the main document loader. So it
67 // would be nice to issue a Close() here.
68 //
69 // However, the PDF plugin will cancel the document load and then close the
70 // resource (which is reasonable). It then makes a second request to load the
71 // document so it can set the "want progress" flags (which is unreasonable --
72 // we should probably provide download progress on document loads).
73 //
74 // But a Close() on the main document (even if the request is already
75 // canceled) will cancel all pending subresources, of which the second
76 // request is one, and the load will fail. Even if we fixed the PDF reader to
77 // change the timing or to send progress events to avoid the second request,
78 // we don't want to cancel other loads when the main one is closed.
79 //
80 // "Leaking" the main document load here by not closing it will only affect
81 // plugins handling main document loads (which are very few, mostly only PDF)
82 // that dereference without explicitly closing the main document load (which
83 // PDF doesn't do -- it explicitly closes it before issuing the second
84 // request). And the worst thing that will happen is that any remaining data
85 // will get queued inside WebKit.
86 if (main_document_loader_) {
87 // The PluginInstance has a non-owning pointer to us.
88 PepperPluginInstanceImpl* instance_object =
89 renderer_ppapi_host_->GetPluginInstanceImpl(pp_instance());
90 if (instance_object) {
91 DCHECK(instance_object->document_loader() == this);
92 instance_object->set_document_loader(NULL);
93 }
94 }
95
96 // There is a path whereby the destructor for the loader_ member can
97 // invoke InstanceWasDeleted() upon this URLLoaderResource, thereby
98 // re-entering the scoped_ptr destructor with the same scoped_ptr object
99 // via loader_.reset(). Be sure that loader_ is first NULL then destroy
100 // the scoped_ptr. See http://crbug.com/159429.
101 scoped_ptr<blink::WebURLLoader> for_destruction_only(loader_.release());
102 }
103
OnResourceMessageReceived(const IPC::Message & msg,ppapi::host::HostMessageContext * context)104 int32_t PepperURLLoaderHost::OnResourceMessageReceived(
105 const IPC::Message& msg,
106 ppapi::host::HostMessageContext* context) {
107 IPC_BEGIN_MESSAGE_MAP(PepperURLLoaderHost, msg)
108 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
109 PpapiHostMsg_URLLoader_Open,
110 OnHostMsgOpen)
111 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
112 PpapiHostMsg_URLLoader_SetDeferLoading,
113 OnHostMsgSetDeferLoading)
114 PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
115 PpapiHostMsg_URLLoader_Close,
116 OnHostMsgClose);
117 PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
118 PpapiHostMsg_URLLoader_GrantUniversalAccess,
119 OnHostMsgGrantUniversalAccess)
120 IPC_END_MESSAGE_MAP()
121 return PP_ERROR_FAILED;
122 }
123
willSendRequest(WebURLLoader * loader,WebURLRequest & new_request,const WebURLResponse & redirect_response)124 void PepperURLLoaderHost::willSendRequest(
125 WebURLLoader* loader,
126 WebURLRequest& new_request,
127 const WebURLResponse& redirect_response) {
128 DCHECK(out_of_order_replies_.empty());
129 if (!request_data_.follow_redirects) {
130 SaveResponse(redirect_response);
131 SetDefersLoading(true);
132 }
133 }
134
didSendData(WebURLLoader * loader,unsigned long long bytes_sent,unsigned long long total_bytes_to_be_sent)135 void PepperURLLoaderHost::didSendData(
136 WebURLLoader* loader,
137 unsigned long long bytes_sent,
138 unsigned long long total_bytes_to_be_sent) {
139 // TODO(darin): Bounds check input?
140 bytes_sent_ = static_cast<int64_t>(bytes_sent);
141 total_bytes_to_be_sent_ = static_cast<int64_t>(total_bytes_to_be_sent);
142 UpdateProgress();
143 }
144
didReceiveResponse(WebURLLoader * loader,const WebURLResponse & response)145 void PepperURLLoaderHost::didReceiveResponse(WebURLLoader* loader,
146 const WebURLResponse& response) {
147 // Sets -1 if the content length is unknown. Send before issuing callback.
148 total_bytes_to_be_received_ = response.expectedContentLength();
149 UpdateProgress();
150
151 SaveResponse(response);
152 }
153
didDownloadData(WebURLLoader * loader,int data_length,int encoded_data_length)154 void PepperURLLoaderHost::didDownloadData(WebURLLoader* loader,
155 int data_length,
156 int encoded_data_length) {
157 bytes_received_ += data_length;
158 UpdateProgress();
159 }
160
didReceiveData(WebURLLoader * loader,const char * data,int data_length,int encoded_data_length)161 void PepperURLLoaderHost::didReceiveData(WebURLLoader* loader,
162 const char* data,
163 int data_length,
164 int encoded_data_length) {
165 // Note that |loader| will be NULL for document loads.
166 bytes_received_ += data_length;
167 UpdateProgress();
168
169 PpapiPluginMsg_URLLoader_SendData* message =
170 new PpapiPluginMsg_URLLoader_SendData;
171 message->WriteData(data, data_length);
172 SendUpdateToPlugin(message);
173 }
174
didFinishLoading(WebURLLoader * loader,double finish_time)175 void PepperURLLoaderHost::didFinishLoading(WebURLLoader* loader,
176 double finish_time) {
177 // Note that |loader| will be NULL for document loads.
178 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(PP_OK));
179 }
180
didFail(WebURLLoader * loader,const WebURLError & error)181 void PepperURLLoaderHost::didFail(WebURLLoader* loader,
182 const WebURLError& error) {
183 // Note that |loader| will be NULL for document loads.
184 int32_t pp_error = PP_ERROR_FAILED;
185 if (error.domain.equals(WebString::fromUTF8(net::kErrorDomain))) {
186 // TODO(bbudge): Extend pp_errors.h to cover interesting network errors
187 // from the net error domain.
188 switch (error.reason) {
189 case net::ERR_ACCESS_DENIED:
190 case net::ERR_NETWORK_ACCESS_DENIED:
191 pp_error = PP_ERROR_NOACCESS;
192 break;
193 }
194 } else {
195 // It's a WebKit error.
196 pp_error = PP_ERROR_NOACCESS;
197 }
198 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(pp_error));
199 }
200
DidConnectPendingHostToResource()201 void PepperURLLoaderHost::DidConnectPendingHostToResource() {
202 for (size_t i = 0; i < pending_replies_.size(); i++)
203 host()->SendUnsolicitedReply(pp_resource(), *pending_replies_[i]);
204 pending_replies_.clear();
205 }
206
OnHostMsgOpen(ppapi::host::HostMessageContext * context,const ppapi::URLRequestInfoData & request_data)207 int32_t PepperURLLoaderHost::OnHostMsgOpen(
208 ppapi::host::HostMessageContext* context,
209 const ppapi::URLRequestInfoData& request_data) {
210 // An "Open" isn't a resource Call so has no reply, but failure to open
211 // implies a load failure. To make it harder to forget to send the load
212 // failed reply from the open handler, we instead catch errors and convert
213 // them to load failed messages.
214 int32_t ret = InternalOnHostMsgOpen(context, request_data);
215 DCHECK(ret != PP_OK_COMPLETIONPENDING);
216
217 if (ret != PP_OK)
218 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(ret));
219 return PP_OK;
220 }
221
222 // Since this is wrapped by OnHostMsgOpen, we can return errors here and they
223 // will be translated into a FinishedLoading call automatically.
InternalOnHostMsgOpen(ppapi::host::HostMessageContext * context,const ppapi::URLRequestInfoData & request_data)224 int32_t PepperURLLoaderHost::InternalOnHostMsgOpen(
225 ppapi::host::HostMessageContext* context,
226 const ppapi::URLRequestInfoData& request_data) {
227 // Main document loads are already open, so don't allow people to open them
228 // again.
229 if (main_document_loader_)
230 return PP_ERROR_INPROGRESS;
231
232 // Create a copy of the request data since CreateWebURLRequest will populate
233 // the file refs.
234 ppapi::URLRequestInfoData filled_in_request_data = request_data;
235
236 if (URLRequestRequiresUniversalAccess(filled_in_request_data) &&
237 !has_universal_access_) {
238 ppapi::PpapiGlobals::Get()->LogWithSource(
239 pp_instance(), PP_LOGLEVEL_ERROR, std::string(),
240 "PPB_URLLoader.Open: The URL you're requesting is "
241 " on a different security origin than your plugin. To request "
242 " cross-origin resources, see "
243 " PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS.");
244 return PP_ERROR_NOACCESS;
245 }
246
247 if (loader_.get())
248 return PP_ERROR_INPROGRESS;
249
250 WebFrame* frame = GetFrame();
251 if (!frame)
252 return PP_ERROR_FAILED;
253
254 WebURLRequest web_request;
255 if (!CreateWebURLRequest(pp_instance(),
256 &filled_in_request_data,
257 frame,
258 &web_request)) {
259 return PP_ERROR_FAILED;
260 }
261
262 web_request.setTargetType(WebURLRequest::TargetIsObject);
263 web_request.setRequestorProcessID(renderer_ppapi_host_->GetPluginPID());
264
265 WebURLLoaderOptions options;
266 if (has_universal_access_) {
267 options.allowCredentials = true;
268 options.crossOriginRequestPolicy =
269 WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
270 } else {
271 // All other HTTP requests are untrusted.
272 options.untrustedHTTP = true;
273 if (filled_in_request_data.allow_cross_origin_requests) {
274 // Allow cross-origin requests with access control. The request specifies
275 // if credentials are to be sent.
276 options.allowCredentials = filled_in_request_data.allow_credentials;
277 options.crossOriginRequestPolicy =
278 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
279 } else {
280 // Same-origin requests can always send credentials.
281 options.allowCredentials = true;
282 }
283 }
284
285 loader_.reset(frame->createAssociatedURLLoader(options));
286 if (!loader_.get())
287 return PP_ERROR_FAILED;
288
289 // Don't actually save the request until we know we're going to load.
290 request_data_ = filled_in_request_data;
291 loader_->loadAsynchronously(web_request, this);
292
293 // Although the request is technically pending, this is not a "Call" message
294 // so we don't return COMPLETIONPENDING.
295 return PP_OK;
296 }
297
OnHostMsgSetDeferLoading(ppapi::host::HostMessageContext * context,bool defers_loading)298 int32_t PepperURLLoaderHost::OnHostMsgSetDeferLoading(
299 ppapi::host::HostMessageContext* context,
300 bool defers_loading) {
301 SetDefersLoading(defers_loading);
302 return PP_OK;
303 }
304
OnHostMsgClose(ppapi::host::HostMessageContext * context)305 int32_t PepperURLLoaderHost::OnHostMsgClose(
306 ppapi::host::HostMessageContext* context) {
307 Close();
308 return PP_OK;
309 }
310
OnHostMsgGrantUniversalAccess(ppapi::host::HostMessageContext * context)311 int32_t PepperURLLoaderHost::OnHostMsgGrantUniversalAccess(
312 ppapi::host::HostMessageContext* context) {
313 // Only plugins with private permission can bypass same origin.
314 if (!host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE))
315 return PP_ERROR_FAILED;
316 has_universal_access_ = true;
317 return PP_OK;
318 }
319
SendUpdateToPlugin(IPC::Message * message)320 void PepperURLLoaderHost::SendUpdateToPlugin(IPC::Message* message) {
321 // We must send messages to the plugin in the order that the responses are
322 // received from webkit, even when the host isn't ready to send messages or
323 // when the host performs an asynchronous operation.
324 //
325 // Only {FinishedLoading, ReceivedResponse, SendData} have ordering
326 // contraints; all other messages are immediately added to pending_replies_.
327 //
328 // Accepted orderings for {FinishedLoading, ReceivedResponse, SendData} are:
329 // - {ReceivedResponse, SendData (zero or more times), FinishedLoading}
330 // - {FinishedLoading (when status != PP_OK)}
331 if (message->type() == PpapiPluginMsg_URLLoader_SendData::ID ||
332 message->type() == PpapiPluginMsg_URLLoader_FinishedLoading::ID) {
333 // Messages that must be sent after ReceivedResponse.
334 if (pending_response_) {
335 out_of_order_replies_.push_back(message);
336 } else {
337 SendOrderedUpdateToPlugin(message);
338 }
339 } else if (message->type() == PpapiPluginMsg_URLLoader_ReceivedResponse::ID) {
340 // Allow SendData and FinishedLoading into the ordered queue.
341 DCHECK(pending_response_);
342 SendOrderedUpdateToPlugin(message);
343 for (size_t i = 0; i < out_of_order_replies_.size(); i++)
344 SendOrderedUpdateToPlugin(out_of_order_replies_[i]);
345 // SendOrderedUpdateToPlugin destroys the messages for us.
346 out_of_order_replies_.weak_clear();
347 pending_response_ = false;
348 } else {
349 // Messages without ordering constraints.
350 SendOrderedUpdateToPlugin(message);
351 }
352 }
353
SendOrderedUpdateToPlugin(IPC::Message * message)354 void PepperURLLoaderHost::SendOrderedUpdateToPlugin(IPC::Message* message) {
355 if (pp_resource() == 0) {
356 pending_replies_.push_back(message);
357 } else {
358 host()->SendUnsolicitedReply(pp_resource(), *message);
359 delete message;
360 }
361 }
362
Close()363 void PepperURLLoaderHost::Close() {
364 if (loader_.get())
365 loader_->cancel();
366 else if (main_document_loader_)
367 GetFrame()->stopLoading();
368 }
369
GetFrame()370 blink::WebFrame* PepperURLLoaderHost::GetFrame() {
371 PepperPluginInstance* instance_object =
372 renderer_ppapi_host_->GetPluginInstance(pp_instance());
373 if (!instance_object)
374 return NULL;
375 return instance_object->GetContainer()->element().document().frame();
376 }
377
SetDefersLoading(bool defers_loading)378 void PepperURLLoaderHost::SetDefersLoading(bool defers_loading) {
379 if (loader_.get())
380 loader_->setDefersLoading(defers_loading);
381
382 // TODO(brettw) bug 96770: We need a way to set the defers loading flag on
383 // main document loads (when the loader_ is null).
384 }
385
SaveResponse(const WebURLResponse & response)386 void PepperURLLoaderHost::SaveResponse(const WebURLResponse& response) {
387 // When we're the main document loader, we send the response data up front,
388 // so we don't want to trigger any callbacks in the plugin which aren't
389 // expected. We should not be getting redirects so the response sent
390 // up-front should be valid (plugin document loads happen after all
391 // redirects are processed since WebKit has to know the MIME type).
392 if (!main_document_loader_) {
393 // We note when there's a callback in flight for a response to ensure that
394 // messages we send to the plugin are not sent out of order. See
395 // SendUpdateToPlugin() for more details.
396 DCHECK(!pending_response_);
397 pending_response_ = true;
398
399 DataFromWebURLResponse(
400 renderer_ppapi_host_,
401 pp_instance(),
402 response,
403 base::Bind(&PepperURLLoaderHost::DidDataFromWebURLResponse,
404 weak_factory_.GetWeakPtr()));
405 }
406 }
407
DidDataFromWebURLResponse(const ppapi::URLResponseInfoData & data)408 void PepperURLLoaderHost::DidDataFromWebURLResponse(
409 const ppapi::URLResponseInfoData& data) {
410 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_ReceivedResponse(data));
411 }
412
UpdateProgress()413 void PepperURLLoaderHost::UpdateProgress() {
414 bool record_download = request_data_.record_download_progress;
415 bool record_upload = request_data_.record_upload_progress;
416 if (record_download || record_upload) {
417 // Here we go through some effort to only send the exact information that
418 // the requestor wanted in the request flags. It would be just as
419 // efficient to send all of it, but we don't want people to rely on
420 // getting download progress when they happen to set the upload progress
421 // flag.
422 ppapi::proxy::ResourceMessageReplyParams params;
423 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_UpdateProgress(
424 record_upload ? bytes_sent_ : -1,
425 record_upload ? total_bytes_to_be_sent_ : -1,
426 record_download ? bytes_received_ : -1,
427 record_download ? total_bytes_to_be_received_ : -1));
428 }
429 }
430
431 } // namespace content
432