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