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/WebKit.h" 25 #include "third_party/WebKit/public/web/WebLocalFrame.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::WebLocalFrame; 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 PPAPI_BEGIN_MESSAGE_MAP(PepperURLLoaderHost, msg) 108 PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_URLLoader_Open, 109 OnHostMsgOpen) 110 PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_URLLoader_SetDeferLoading, 111 OnHostMsgSetDeferLoading) 112 PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_URLLoader_Close, 113 OnHostMsgClose); 114 PPAPI_DISPATCH_HOST_RESOURCE_CALL_0( 115 PpapiHostMsg_URLLoader_GrantUniversalAccess, 116 OnHostMsgGrantUniversalAccess) 117 PPAPI_END_MESSAGE_MAP() 118 return PP_ERROR_FAILED; 119 } 120 121 void PepperURLLoaderHost::willSendRequest( 122 WebURLLoader* loader, 123 WebURLRequest& new_request, 124 const WebURLResponse& redirect_response) { 125 DCHECK(out_of_order_replies_.empty()); 126 if (!request_data_.follow_redirects) { 127 SaveResponse(redirect_response); 128 SetDefersLoading(true); 129 } 130 } 131 132 void PepperURLLoaderHost::didSendData( 133 WebURLLoader* loader, 134 unsigned long long bytes_sent, 135 unsigned long long total_bytes_to_be_sent) { 136 // TODO(darin): Bounds check input? 137 bytes_sent_ = static_cast<int64_t>(bytes_sent); 138 total_bytes_to_be_sent_ = static_cast<int64_t>(total_bytes_to_be_sent); 139 UpdateProgress(); 140 } 141 142 void PepperURLLoaderHost::didReceiveResponse(WebURLLoader* loader, 143 const WebURLResponse& response) { 144 // Sets -1 if the content length is unknown. Send before issuing callback. 145 total_bytes_to_be_received_ = response.expectedContentLength(); 146 UpdateProgress(); 147 148 SaveResponse(response); 149 } 150 151 void PepperURLLoaderHost::didDownloadData(WebURLLoader* loader, 152 int data_length, 153 int encoded_data_length) { 154 bytes_received_ += data_length; 155 UpdateProgress(); 156 } 157 158 void PepperURLLoaderHost::didReceiveData(WebURLLoader* loader, 159 const char* data, 160 int data_length, 161 int encoded_data_length) { 162 // Note that |loader| will be NULL for document loads. 163 bytes_received_ += data_length; 164 UpdateProgress(); 165 166 PpapiPluginMsg_URLLoader_SendData* message = 167 new PpapiPluginMsg_URLLoader_SendData; 168 message->WriteData(data, data_length); 169 SendUpdateToPlugin(message); 170 } 171 172 void PepperURLLoaderHost::didFinishLoading(WebURLLoader* loader, 173 double finish_time, 174 int64_t total_encoded_data_length) { 175 // Note that |loader| will be NULL for document loads. 176 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(PP_OK)); 177 } 178 179 void PepperURLLoaderHost::didFail(WebURLLoader* loader, 180 const WebURLError& error) { 181 // Note that |loader| will be NULL for document loads. 182 int32_t pp_error = PP_ERROR_FAILED; 183 if (error.domain.equals(WebString::fromUTF8(net::kErrorDomain))) { 184 // TODO(bbudge): Extend pp_errors.h to cover interesting network errors 185 // from the net error domain. 186 switch (error.reason) { 187 case net::ERR_ACCESS_DENIED: 188 case net::ERR_NETWORK_ACCESS_DENIED: 189 pp_error = PP_ERROR_NOACCESS; 190 break; 191 } 192 } else { 193 // It's a WebKit error. 194 pp_error = PP_ERROR_NOACCESS; 195 } 196 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(pp_error)); 197 } 198 199 void PepperURLLoaderHost::DidConnectPendingHostToResource() { 200 for (size_t i = 0; i < pending_replies_.size(); i++) 201 host()->SendUnsolicitedReply(pp_resource(), *pending_replies_[i]); 202 pending_replies_.clear(); 203 } 204 205 int32_t PepperURLLoaderHost::OnHostMsgOpen( 206 ppapi::host::HostMessageContext* context, 207 const ppapi::URLRequestInfoData& request_data) { 208 // An "Open" isn't a resource Call so has no reply, but failure to open 209 // implies a load failure. To make it harder to forget to send the load 210 // failed reply from the open handler, we instead catch errors and convert 211 // them to load failed messages. 212 int32_t ret = InternalOnHostMsgOpen(context, request_data); 213 DCHECK(ret != PP_OK_COMPLETIONPENDING); 214 215 if (ret != PP_OK) 216 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_FinishedLoading(ret)); 217 return PP_OK; 218 } 219 220 // Since this is wrapped by OnHostMsgOpen, we can return errors here and they 221 // will be translated into a FinishedLoading call automatically. 222 int32_t PepperURLLoaderHost::InternalOnHostMsgOpen( 223 ppapi::host::HostMessageContext* context, 224 const ppapi::URLRequestInfoData& request_data) { 225 // Main document loads are already open, so don't allow people to open them 226 // again. 227 if (main_document_loader_) 228 return PP_ERROR_INPROGRESS; 229 230 // Create a copy of the request data since CreateWebURLRequest will populate 231 // the file refs. 232 ppapi::URLRequestInfoData filled_in_request_data = request_data; 233 234 if (URLRequestRequiresUniversalAccess(filled_in_request_data) && 235 !has_universal_access_) { 236 ppapi::PpapiGlobals::Get()->LogWithSource( 237 pp_instance(), 238 PP_LOGLEVEL_ERROR, 239 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 WebLocalFrame* frame = GetFrame(); 251 if (!frame) 252 return PP_ERROR_FAILED; 253 254 WebURLRequest web_request; 255 if (!CreateWebURLRequest( 256 pp_instance(), &filled_in_request_data, frame, &web_request)) { 257 return PP_ERROR_FAILED; 258 } 259 260 web_request.setRequestContext(WebURLRequest::RequestContextPlugin); 261 web_request.setRequestorProcessID(renderer_ppapi_host_->GetPluginPID()); 262 263 WebURLLoaderOptions options; 264 if (has_universal_access_) { 265 options.allowCredentials = true; 266 options.crossOriginRequestPolicy = 267 WebURLLoaderOptions::CrossOriginRequestPolicyAllow; 268 } else { 269 // All other HTTP requests are untrusted. 270 options.untrustedHTTP = true; 271 if (filled_in_request_data.allow_cross_origin_requests) { 272 // Allow cross-origin requests with access control. The request specifies 273 // if credentials are to be sent. 274 options.allowCredentials = filled_in_request_data.allow_credentials; 275 options.crossOriginRequestPolicy = 276 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; 277 } else { 278 // Same-origin requests can always send credentials. 279 options.allowCredentials = true; 280 } 281 } 282 283 loader_.reset(frame->createAssociatedURLLoader(options)); 284 if (!loader_.get()) 285 return PP_ERROR_FAILED; 286 287 // Don't actually save the request until we know we're going to load. 288 request_data_ = filled_in_request_data; 289 loader_->loadAsynchronously(web_request, this); 290 291 // Although the request is technically pending, this is not a "Call" message 292 // so we don't return COMPLETIONPENDING. 293 return PP_OK; 294 } 295 296 int32_t PepperURLLoaderHost::OnHostMsgSetDeferLoading( 297 ppapi::host::HostMessageContext* context, 298 bool defers_loading) { 299 SetDefersLoading(defers_loading); 300 return PP_OK; 301 } 302 303 int32_t PepperURLLoaderHost::OnHostMsgClose( 304 ppapi::host::HostMessageContext* context) { 305 Close(); 306 return PP_OK; 307 } 308 309 int32_t PepperURLLoaderHost::OnHostMsgGrantUniversalAccess( 310 ppapi::host::HostMessageContext* context) { 311 // Only plugins with private permission can bypass same origin. 312 if (!host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE)) 313 return PP_ERROR_FAILED; 314 has_universal_access_ = true; 315 return PP_OK; 316 } 317 318 void PepperURLLoaderHost::SendUpdateToPlugin(IPC::Message* message) { 319 // We must send messages to the plugin in the order that the responses are 320 // received from webkit, even when the host isn't ready to send messages or 321 // when the host performs an asynchronous operation. 322 // 323 // Only {FinishedLoading, ReceivedResponse, SendData} have ordering 324 // contraints; all other messages are immediately added to pending_replies_. 325 // 326 // Accepted orderings for {FinishedLoading, ReceivedResponse, SendData} are: 327 // - {ReceivedResponse, SendData (zero or more times), FinishedLoading} 328 // - {FinishedLoading (when status != PP_OK)} 329 if (message->type() == PpapiPluginMsg_URLLoader_SendData::ID || 330 message->type() == PpapiPluginMsg_URLLoader_FinishedLoading::ID) { 331 // Messages that must be sent after ReceivedResponse. 332 if (pending_response_) { 333 out_of_order_replies_.push_back(message); 334 } else { 335 SendOrderedUpdateToPlugin(message); 336 } 337 } else if (message->type() == PpapiPluginMsg_URLLoader_ReceivedResponse::ID) { 338 // Allow SendData and FinishedLoading into the ordered queue. 339 DCHECK(pending_response_); 340 SendOrderedUpdateToPlugin(message); 341 for (size_t i = 0; i < out_of_order_replies_.size(); i++) 342 SendOrderedUpdateToPlugin(out_of_order_replies_[i]); 343 // SendOrderedUpdateToPlugin destroys the messages for us. 344 out_of_order_replies_.weak_clear(); 345 pending_response_ = false; 346 } else { 347 // Messages without ordering constraints. 348 SendOrderedUpdateToPlugin(message); 349 } 350 } 351 352 void PepperURLLoaderHost::SendOrderedUpdateToPlugin(IPC::Message* message) { 353 if (pp_resource() == 0) { 354 pending_replies_.push_back(message); 355 } else { 356 host()->SendUnsolicitedReply(pp_resource(), *message); 357 delete message; 358 } 359 } 360 361 void PepperURLLoaderHost::Close() { 362 if (loader_.get()) { 363 loader_->cancel(); 364 } else if (main_document_loader_) { 365 // TODO(raymes): Calling WebLocalFrame::stopLoading here is incorrect as it 366 // cancels all URL loaders associated with the frame. If a client has opened 367 // other URLLoaders and then closes the main one, the others should still 368 // remain connected. Work out how to only cancel the main request: 369 // crbug.com/384197. 370 blink::WebLocalFrame* frame = GetFrame(); 371 if (frame) 372 frame->stopLoading(); 373 } 374 } 375 376 blink::WebLocalFrame* PepperURLLoaderHost::GetFrame() { 377 PepperPluginInstance* instance_object = 378 renderer_ppapi_host_->GetPluginInstance(pp_instance()); 379 if (!instance_object) 380 return NULL; 381 return instance_object->GetContainer()->element().document().frame(); 382 } 383 384 void PepperURLLoaderHost::SetDefersLoading(bool defers_loading) { 385 if (loader_.get()) 386 loader_->setDefersLoading(defers_loading); 387 388 // TODO(brettw) bug 96770: We need a way to set the defers loading flag on 389 // main document loads (when the loader_ is null). 390 } 391 392 void PepperURLLoaderHost::SaveResponse(const WebURLResponse& response) { 393 // When we're the main document loader, we send the response data up front, 394 // so we don't want to trigger any callbacks in the plugin which aren't 395 // expected. We should not be getting redirects so the response sent 396 // up-front should be valid (plugin document loads happen after all 397 // redirects are processed since WebKit has to know the MIME type). 398 if (!main_document_loader_) { 399 // We note when there's a callback in flight for a response to ensure that 400 // messages we send to the plugin are not sent out of order. See 401 // SendUpdateToPlugin() for more details. 402 DCHECK(!pending_response_); 403 pending_response_ = true; 404 405 DataFromWebURLResponse( 406 renderer_ppapi_host_, 407 pp_instance(), 408 response, 409 base::Bind(&PepperURLLoaderHost::DidDataFromWebURLResponse, 410 weak_factory_.GetWeakPtr())); 411 } 412 } 413 414 void PepperURLLoaderHost::DidDataFromWebURLResponse( 415 const ppapi::URLResponseInfoData& data) { 416 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_ReceivedResponse(data)); 417 } 418 419 void PepperURLLoaderHost::UpdateProgress() { 420 bool record_download = request_data_.record_download_progress; 421 bool record_upload = request_data_.record_upload_progress; 422 if (record_download || record_upload) { 423 // Here we go through some effort to only send the exact information that 424 // the requestor wanted in the request flags. It would be just as 425 // efficient to send all of it, but we don't want people to rely on 426 // getting download progress when they happen to set the upload progress 427 // flag. 428 ppapi::proxy::ResourceMessageReplyParams params; 429 SendUpdateToPlugin(new PpapiPluginMsg_URLLoader_UpdateProgress( 430 record_upload ? bytes_sent_ : -1, 431 record_upload ? total_bytes_to_be_sent_ : -1, 432 record_download ? bytes_received_ : -1, 433 record_download ? total_bytes_to_be_received_ : -1)); 434 } 435 } 436 437 } // namespace content 438