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 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 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 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 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 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 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 154 void PepperURLLoaderHost::didDownloadData(WebURLLoader* loader, 155 int data_length, 156 int encoded_data_length) { 157 bytes_received_ += data_length; 158 UpdateProgress(); 159 } 160 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 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 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 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 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. 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 298 int32_t PepperURLLoaderHost::OnHostMsgSetDeferLoading( 299 ppapi::host::HostMessageContext* context, 300 bool defers_loading) { 301 SetDefersLoading(defers_loading); 302 return PP_OK; 303 } 304 305 int32_t PepperURLLoaderHost::OnHostMsgClose( 306 ppapi::host::HostMessageContext* context) { 307 Close(); 308 return PP_OK; 309 } 310 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 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 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 363 void PepperURLLoaderHost::Close() { 364 if (loader_.get()) 365 loader_->cancel(); 366 else if (main_document_loader_) 367 GetFrame()->stopLoading(); 368 } 369 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 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 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 408 void PepperURLLoaderHost::DidDataFromWebURLResponse( 409 const ppapi::URLResponseInfoData& data) { 410 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_ReceivedResponse(data)); 411 } 412 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