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. 4 5#include "content/browser/loader/cross_site_resource_handler.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/command_line.h" 11#include "base/logging.h" 12#include "content/browser/appcache/appcache_interceptor.h" 13#include "content/browser/child_process_security_policy_impl.h" 14#include "content/browser/frame_host/cross_site_transferring_request.h" 15#include "content/browser/frame_host/render_frame_host_impl.h" 16#include "content/browser/loader/resource_dispatcher_host_impl.h" 17#include "content/browser/loader/resource_request_info_impl.h" 18#include "content/browser/site_instance_impl.h" 19#include "content/browser/transition_request_manager.h" 20#include "content/public/browser/browser_thread.h" 21#include "content/public/browser/content_browser_client.h" 22#include "content/public/browser/global_request_id.h" 23#include "content/public/browser/resource_controller.h" 24#include "content/public/browser/site_instance.h" 25#include "content/public/common/content_switches.h" 26#include "content/public/common/resource_response.h" 27#include "content/public/common/url_constants.h" 28#include "net/http/http_response_headers.h" 29#include "net/url_request/url_request.h" 30 31namespace content { 32 33namespace { 34 35bool leak_requests_for_testing_ = false; 36 37// The parameters to OnCrossSiteResponseHelper exceed the number of arguments 38// base::Bind supports. 39struct CrossSiteResponseParams { 40 CrossSiteResponseParams( 41 int render_frame_id, 42 const GlobalRequestID& global_request_id, 43 const std::vector<GURL>& transfer_url_chain, 44 const Referrer& referrer, 45 ui::PageTransition page_transition, 46 bool should_replace_current_entry) 47 : render_frame_id(render_frame_id), 48 global_request_id(global_request_id), 49 transfer_url_chain(transfer_url_chain), 50 referrer(referrer), 51 page_transition(page_transition), 52 should_replace_current_entry(should_replace_current_entry) { 53 } 54 55 int render_frame_id; 56 GlobalRequestID global_request_id; 57 std::vector<GURL> transfer_url_chain; 58 Referrer referrer; 59 ui::PageTransition page_transition; 60 bool should_replace_current_entry; 61}; 62 63void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) { 64 scoped_ptr<CrossSiteTransferringRequest> cross_site_transferring_request( 65 new CrossSiteTransferringRequest(params.global_request_id)); 66 67 RenderFrameHostImpl* rfh = 68 RenderFrameHostImpl::FromID(params.global_request_id.child_id, 69 params.render_frame_id); 70 if (rfh) { 71 rfh->OnCrossSiteResponse( 72 params.global_request_id, cross_site_transferring_request.Pass(), 73 params.transfer_url_chain, params.referrer, 74 params.page_transition, params.should_replace_current_entry); 75 } else if (leak_requests_for_testing_ && cross_site_transferring_request) { 76 // Some unit tests expect requests to be leaked in this case, so they can 77 // pass them along manually. 78 cross_site_transferring_request->ReleaseRequest(); 79 } 80} 81 82void OnDeferredAfterResponseStartedHelper( 83 const GlobalRequestID& global_request_id, 84 int render_frame_id, 85 const TransitionLayerData& transition_data) { 86 RenderFrameHostImpl* rfh = 87 RenderFrameHostImpl::FromID(global_request_id.child_id, render_frame_id); 88 if (rfh) 89 rfh->OnDeferredAfterResponseStarted(global_request_id, transition_data); 90} 91 92// Returns whether a transfer is needed by doing a check on the UI thread. 93bool CheckNavigationPolicyOnUI(GURL url, int process_id, int render_frame_id) { 94 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess)); 95 RenderFrameHostImpl* rfh = 96 RenderFrameHostImpl::FromID(process_id, render_frame_id); 97 if (!rfh) 98 return false; 99 100 // TODO(nasko): This check is very simplistic and is used temporarily only 101 // for --site-per-process. It should be updated to match the check performed 102 // by RenderFrameHostManager::UpdateStateForNavigate. 103 return !SiteInstance::IsSameWebSite( 104 rfh->GetSiteInstance()->GetBrowserContext(), 105 rfh->GetSiteInstance()->GetSiteURL(), url); 106} 107 108} // namespace 109 110CrossSiteResourceHandler::CrossSiteResourceHandler( 111 scoped_ptr<ResourceHandler> next_handler, 112 net::URLRequest* request) 113 : LayeredResourceHandler(request, next_handler.Pass()), 114 has_started_response_(false), 115 in_cross_site_transition_(false), 116 completed_during_transition_(false), 117 did_defer_(false), 118 weak_ptr_factory_(this) { 119} 120 121CrossSiteResourceHandler::~CrossSiteResourceHandler() { 122 // Cleanup back-pointer stored on the request info. 123 GetRequestInfo()->set_cross_site_handler(NULL); 124} 125 126bool CrossSiteResourceHandler::OnRequestRedirected( 127 const net::RedirectInfo& redirect_info, 128 ResourceResponse* response, 129 bool* defer) { 130 // We should not have started the transition before being redirected. 131 DCHECK(!in_cross_site_transition_); 132 return next_handler_->OnRequestRedirected(redirect_info, response, defer); 133} 134 135bool CrossSiteResourceHandler::OnResponseStarted( 136 ResourceResponse* response, 137 bool* defer) { 138 response_ = response; 139 has_started_response_ = true; 140 141 // Store this handler on the ExtraRequestInfo, so that RDH can call our 142 // ResumeResponse method when we are ready to resume. 143 ResourceRequestInfoImpl* info = GetRequestInfo(); 144 info->set_cross_site_handler(this); 145 146 TransitionLayerData transition_data; 147 bool is_navigation_transition = 148 TransitionRequestManager::GetInstance()->HasPendingTransitionRequest( 149 info->GetChildID(), info->GetRenderFrameID(), request()->url(), 150 &transition_data); 151 152 if (is_navigation_transition) { 153 if (response_.get()) 154 transition_data.response_headers = response_->head.headers; 155 transition_data.request_url = request()->url(); 156 157 return OnNavigationTransitionResponseStarted(response, defer, 158 transition_data); 159 } else { 160 return OnNormalResponseStarted(response, defer); 161 } 162} 163 164bool CrossSiteResourceHandler::OnNormalResponseStarted( 165 ResourceResponse* response, 166 bool* defer) { 167 // At this point, we know that the response is safe to send back to the 168 // renderer: it is not a download, and it has passed the SSL and safe 169 // browsing checks. 170 // We should not have already started the transition before now. 171 DCHECK(!in_cross_site_transition_); 172 173 ResourceRequestInfoImpl* info = GetRequestInfo(); 174 175 // We only need to pause the response if a transfer to a different process is 176 // required. Other cross-process navigations can proceed immediately, since 177 // we run the unload handler at commit time. 178 // Note that a process swap may no longer be necessary if we transferred back 179 // into the original process due to a redirect. 180 bool should_transfer = 181 GetContentClient()->browser()->ShouldSwapProcessesForRedirect( 182 info->GetContext(), request()->original_url(), request()->url()); 183 184 // When the --site-per-process flag is passed, we transfer processes for 185 // cross-site navigations. This is skipped if a transfer is already required 186 // or for WebUI processes for now, since pages like the NTP host multiple 187 // cross-site WebUI iframes. 188 if (!should_transfer && 189 base::CommandLine::ForCurrentProcess()->HasSwitch( 190 switches::kSitePerProcess) && 191 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings( 192 info->GetChildID())) { 193 return DeferForNavigationPolicyCheck(info, response, defer); 194 } 195 196 // If this is a download, just pass the response through without doing a 197 // cross-site check. The renderer will see it is a download and abort the 198 // request. 199 // 200 // Similarly, HTTP 204 (No Content) responses leave us showing the previous 201 // page. We should allow the navigation to finish without running the unload 202 // handler or swapping in the pending RenderFrameHost. 203 // 204 // In both cases, any pending RenderFrameHost (if one was created for this 205 // navigation) will stick around until the next cross-site navigation, since 206 // we are unable to tell when to destroy it. 207 // See RenderFrameHostManager::RendererAbortedProvisionalLoad. 208 // 209 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to 210 // check for both and remembering about streams is error-prone. 211 if (!should_transfer || info->IsDownload() || info->is_stream() || 212 (response->head.headers.get() && 213 response->head.headers->response_code() == 204)) { 214 return next_handler_->OnResponseStarted(response, defer); 215 } 216 217 // Now that we know a transfer is needed and we have something to commit, we 218 // pause to let the UI thread set up the transfer. 219 StartCrossSiteTransition(response); 220 221 // Defer loading until after the new renderer process has issued a 222 // corresponding request. 223 *defer = true; 224 OnDidDefer(); 225 return true; 226} 227 228bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted( 229 ResourceResponse* response, 230 bool* defer, 231 const TransitionLayerData& transition_data) { 232 ResourceRequestInfoImpl* info = GetRequestInfo(); 233 234 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID()); 235 int render_frame_id = info->GetRenderFrameID(); 236 BrowserThread::PostTask( 237 BrowserThread::UI, 238 FROM_HERE, 239 base::Bind( 240 &OnDeferredAfterResponseStartedHelper, 241 global_id, 242 render_frame_id, 243 transition_data)); 244 245 *defer = true; 246 OnDidDefer(); 247 return true; 248} 249 250void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id) { 251 bool defer = false; 252 if (!OnNormalResponseStarted(response_.get(), &defer)) { 253 controller()->Cancel(); 254 } else if (!defer) { 255 ResumeIfDeferred(); 256 } 257} 258 259void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) { 260 if (is_transfer) { 261 StartCrossSiteTransition(response_.get()); 262 } else { 263 ResumeResponse(); 264 } 265} 266 267bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) { 268 CHECK(!in_cross_site_transition_); 269 return next_handler_->OnReadCompleted(bytes_read, defer); 270} 271 272void CrossSiteResourceHandler::OnResponseCompleted( 273 const net::URLRequestStatus& status, 274 const std::string& security_info, 275 bool* defer) { 276 if (!in_cross_site_transition_) { 277 // If we're not transferring, then we should pass this through. 278 next_handler_->OnResponseCompleted(status, security_info, defer); 279 return; 280 } 281 282 // We have to buffer the call until after the transition completes. 283 completed_during_transition_ = true; 284 completed_status_ = status; 285 completed_security_info_ = security_info; 286 287 // Defer to tell RDH not to notify the world or clean up the pending request. 288 // We will do so in ResumeResponse. 289 *defer = true; 290 OnDidDefer(); 291} 292 293// We can now send the response to the new renderer, which will cause 294// WebContentsImpl to swap in the new renderer and destroy the old one. 295void CrossSiteResourceHandler::ResumeResponse() { 296 TRACE_EVENT_ASYNC_END0( 297 "navigation", "CrossSiteResourceHandler transition", this); 298 DCHECK(request()); 299 in_cross_site_transition_ = false; 300 ResourceRequestInfoImpl* info = GetRequestInfo(); 301 302 if (has_started_response_) { 303 // Send OnResponseStarted to the new renderer. 304 DCHECK(response_.get()); 305 bool defer = false; 306 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) { 307 controller()->Cancel(); 308 } else if (!defer) { 309 // Unpause the request to resume reading. Any further reads will be 310 // directed toward the new renderer. 311 ResumeIfDeferred(); 312 } 313 } 314 315 // Remove ourselves from the ExtraRequestInfo. 316 info->set_cross_site_handler(NULL); 317 318 // If the response completed during the transition, notify the next 319 // event handler. 320 if (completed_during_transition_) { 321 bool defer = false; 322 next_handler_->OnResponseCompleted(completed_status_, 323 completed_security_info_, 324 &defer); 325 if (!defer) 326 ResumeIfDeferred(); 327 } 328} 329 330// static 331void CrossSiteResourceHandler::SetLeakRequestsForTesting( 332 bool leak_requests_for_testing) { 333 leak_requests_for_testing_ = leak_requests_for_testing; 334} 335 336// Prepare to transfer the response to a new RenderFrameHost. 337void CrossSiteResourceHandler::StartCrossSiteTransition( 338 ResourceResponse* response) { 339 TRACE_EVENT_ASYNC_BEGIN0( 340 "navigation", "CrossSiteResourceHandler transition", this); 341 in_cross_site_transition_ = true; 342 response_ = response; 343 344 // Store this handler on the ExtraRequestInfo, so that RDH can call our 345 // ResumeResponse method when we are ready to resume. 346 ResourceRequestInfoImpl* info = GetRequestInfo(); 347 info->set_cross_site_handler(this); 348 349 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID()); 350 351 // Tell the contents responsible for this request that a cross-site response 352 // is starting, so that it can tell its old renderer to run its onunload 353 // handler now. We will wait until the unload is finished and (if a transfer 354 // is needed) for the new renderer's request to arrive. 355 // The |transfer_url_chain| contains any redirect URLs that have already 356 // occurred, plus the destination URL at the end. 357 std::vector<GURL> transfer_url_chain; 358 Referrer referrer; 359 int render_frame_id = info->GetRenderFrameID(); 360 transfer_url_chain = request()->url_chain(); 361 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy()); 362 363 AppCacheInterceptor::PrepareForCrossSiteTransfer( 364 request(), global_id.child_id); 365 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id); 366 367 BrowserThread::PostTask( 368 BrowserThread::UI, 369 FROM_HERE, 370 base::Bind( 371 &OnCrossSiteResponseHelper, 372 CrossSiteResponseParams(render_frame_id, 373 global_id, 374 transfer_url_chain, 375 referrer, 376 info->GetPageTransition(), 377 info->should_replace_current_entry()))); 378} 379 380bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck( 381 ResourceRequestInfoImpl* info, 382 ResourceResponse* response, 383 bool* defer) { 384 // Store the response_ object internally, since the navigation is deferred 385 // regardless of whether it will be a transfer or not. 386 response_ = response; 387 388 // Always defer the navigation to the UI thread to make a policy decision. 389 // It will send the result back to the IO thread to either resume or 390 // transfer it to a new renderer. 391 // TODO(nasko): If the UI thread result is that transfer is required, the 392 // IO thread will defer to the UI thread again through 393 // StartCrossSiteTransition. This is unnecessary and the policy check on the 394 // UI thread should be refactored to avoid the extra hop. 395 BrowserThread::PostTaskAndReplyWithResult( 396 BrowserThread::UI, 397 FROM_HERE, 398 base::Bind(&CheckNavigationPolicyOnUI, 399 request()->url(), 400 info->GetChildID(), 401 info->GetRenderFrameID()), 402 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer, 403 weak_ptr_factory_.GetWeakPtr())); 404 405 // Defer loading until it is known whether the navigation will transfer 406 // to a new process or continue in the existing one. 407 *defer = true; 408 OnDidDefer(); 409 return true; 410} 411 412void CrossSiteResourceHandler::ResumeIfDeferred() { 413 if (did_defer_) { 414 request()->LogUnblocked(); 415 did_defer_ = false; 416 controller()->Resume(); 417 } 418} 419 420void CrossSiteResourceHandler::OnDidDefer() { 421 did_defer_ = true; 422 request()->LogBlockedBy("CrossSiteResourceHandler"); 423} 424 425} // namespace content 426