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/logging.h" 11 #include "content/browser/cross_site_request_manager.h" 12 #include "content/browser/loader/resource_dispatcher_host_impl.h" 13 #include "content/browser/loader/resource_request_info_impl.h" 14 #include "content/browser/renderer_host/render_view_host_delegate.h" 15 #include "content/browser/renderer_host/render_view_host_impl.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/content_browser_client.h" 18 #include "content/public/browser/global_request_id.h" 19 #include "content/public/browser/resource_controller.h" 20 #include "content/public/common/resource_response.h" 21 #include "net/http/http_response_headers.h" 22 #include "net/url_request/url_request.h" 23 #include "webkit/browser/appcache/appcache_interceptor.h" 24 25 namespace content { 26 27 namespace { 28 29 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments 30 // base::Bind supports. 31 struct CrossSiteResponseParams { 32 CrossSiteResponseParams(int render_view_id, 33 const GlobalRequestID& global_request_id, 34 bool is_transfer, 35 const std::vector<GURL>& transfer_url_chain, 36 const Referrer& referrer, 37 PageTransition page_transition, 38 int64 frame_id, 39 bool should_replace_current_entry) 40 : render_view_id(render_view_id), 41 global_request_id(global_request_id), 42 is_transfer(is_transfer), 43 transfer_url_chain(transfer_url_chain), 44 referrer(referrer), 45 page_transition(page_transition), 46 frame_id(frame_id), 47 should_replace_current_entry(should_replace_current_entry) { 48 } 49 50 int render_view_id; 51 GlobalRequestID global_request_id; 52 bool is_transfer; 53 std::vector<GURL> transfer_url_chain; 54 Referrer referrer; 55 PageTransition page_transition; 56 int64 frame_id; 57 bool should_replace_current_entry; 58 }; 59 60 void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) { 61 RenderViewHostImpl* rvh = 62 RenderViewHostImpl::FromID(params.global_request_id.child_id, 63 params.render_view_id); 64 if (rvh) { 65 rvh->OnCrossSiteResponse( 66 params.global_request_id, params.is_transfer, 67 params.transfer_url_chain, params.referrer, 68 params.page_transition, params.frame_id, 69 params.should_replace_current_entry); 70 } 71 } 72 73 } // namespace 74 75 CrossSiteResourceHandler::CrossSiteResourceHandler( 76 scoped_ptr<ResourceHandler> next_handler, 77 net::URLRequest* request) 78 : LayeredResourceHandler(request, next_handler.Pass()), 79 has_started_response_(false), 80 in_cross_site_transition_(false), 81 completed_during_transition_(false), 82 did_defer_(false), 83 completed_status_() { 84 } 85 86 CrossSiteResourceHandler::~CrossSiteResourceHandler() { 87 // Cleanup back-pointer stored on the request info. 88 GetRequestInfo()->set_cross_site_handler(NULL); 89 } 90 91 bool CrossSiteResourceHandler::OnRequestRedirected( 92 int request_id, 93 const GURL& new_url, 94 ResourceResponse* response, 95 bool* defer) { 96 // We should not have started the transition before being redirected. 97 DCHECK(!in_cross_site_transition_); 98 return next_handler_->OnRequestRedirected( 99 request_id, new_url, response, defer); 100 } 101 102 bool CrossSiteResourceHandler::OnResponseStarted( 103 int request_id, 104 ResourceResponse* response, 105 bool* defer) { 106 // At this point, we know that the response is safe to send back to the 107 // renderer: it is not a download, and it has passed the SSL and safe 108 // browsing checks. 109 // We should not have already started the transition before now. 110 DCHECK(!in_cross_site_transition_); 111 has_started_response_ = true; 112 113 ResourceRequestInfoImpl* info = GetRequestInfo(); 114 115 // We will need to swap processes if either (1) a redirect that requires a 116 // transfer occurred before we got here, or (2) a pending cross-site request 117 // was already in progress. Note that a swap may no longer be needed if we 118 // transferred back into the original process due to a redirect. 119 bool should_transfer = 120 GetContentClient()->browser()->ShouldSwapProcessesForRedirect( 121 info->GetContext(), request()->original_url(), request()->url()); 122 bool swap_needed = should_transfer || 123 CrossSiteRequestManager::GetInstance()-> 124 HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID()); 125 126 // If this is a download, just pass the response through without doing a 127 // cross-site check. The renderer will see it is a download and abort the 128 // request. 129 // 130 // Similarly, HTTP 204 (No Content) responses leave us showing the previous 131 // page. We should allow the navigation to finish without running the unload 132 // handler or swapping in the pending RenderViewHost. 133 // 134 // In both cases, any pending RenderViewHost (if one was created for this 135 // navigation) will stick around until the next cross-site navigation, since 136 // we are unable to tell when to destroy it. 137 // See RenderFrameHostManager::RendererAbortedProvisionalLoad. 138 if (!swap_needed || info->IsDownload() || 139 (response->head.headers.get() && 140 response->head.headers->response_code() == 204)) { 141 return next_handler_->OnResponseStarted(request_id, response, defer); 142 } 143 144 // Now that we know a swap is needed and we have something to commit, we 145 // pause to let the UI thread run the unload handler of the previous page 146 // and set up a transfer if needed. 147 StartCrossSiteTransition(request_id, response, should_transfer); 148 149 // Defer loading until after the onunload event handler has run. 150 did_defer_ = *defer = true; 151 return true; 152 } 153 154 bool CrossSiteResourceHandler::OnReadCompleted(int request_id, 155 int bytes_read, 156 bool* defer) { 157 CHECK(!in_cross_site_transition_); 158 return next_handler_->OnReadCompleted(request_id, bytes_read, defer); 159 } 160 161 void CrossSiteResourceHandler::OnResponseCompleted( 162 int request_id, 163 const net::URLRequestStatus& status, 164 const std::string& security_info, 165 bool* defer) { 166 if (!in_cross_site_transition_) { 167 ResourceRequestInfoImpl* info = GetRequestInfo(); 168 // If we've already completed the transition, or we're canceling the 169 // request, or an error occurred with no cross-process navigation in 170 // progress, then we should just pass this through. 171 if (has_started_response_ || 172 status.status() != net::URLRequestStatus::FAILED || 173 !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest( 174 info->GetChildID(), info->GetRouteID())) { 175 next_handler_->OnResponseCompleted(request_id, status, 176 security_info, defer); 177 return; 178 } 179 180 // An error occurred. We should wait now for the cross-process transition, 181 // so that the error message (e.g., 404) can be displayed to the user. 182 // Also continue with the logic below to remember that we completed 183 // during the cross-site transition. 184 StartCrossSiteTransition(request_id, NULL, false); 185 } 186 187 // We have to buffer the call until after the transition completes. 188 completed_during_transition_ = true; 189 completed_status_ = status; 190 completed_security_info_ = security_info; 191 192 // Defer to tell RDH not to notify the world or clean up the pending request. 193 // We will do so in ResumeResponse. 194 did_defer_ = true; 195 *defer = true; 196 } 197 198 // We can now send the response to the new renderer, which will cause 199 // WebContentsImpl to swap in the new renderer and destroy the old one. 200 void CrossSiteResourceHandler::ResumeResponse() { 201 DCHECK(request()); 202 DCHECK(in_cross_site_transition_); 203 in_cross_site_transition_ = false; 204 ResourceRequestInfoImpl* info = GetRequestInfo(); 205 206 if (has_started_response_) { 207 // Send OnResponseStarted to the new renderer. 208 DCHECK(response_); 209 bool defer = false; 210 if (!next_handler_->OnResponseStarted(info->GetRequestID(), response_.get(), 211 &defer)) { 212 controller()->Cancel(); 213 } else if (!defer) { 214 // Unpause the request to resume reading. Any further reads will be 215 // directed toward the new renderer. 216 ResumeIfDeferred(); 217 } 218 } 219 220 // Remove ourselves from the ExtraRequestInfo. 221 info->set_cross_site_handler(NULL); 222 223 // If the response completed during the transition, notify the next 224 // event handler. 225 if (completed_during_transition_) { 226 bool defer = false; 227 next_handler_->OnResponseCompleted(info->GetRequestID(), 228 completed_status_, 229 completed_security_info_, 230 &defer); 231 if (!defer) 232 ResumeIfDeferred(); 233 } 234 } 235 236 // Prepare to render the cross-site response in a new RenderViewHost, by 237 // telling the old RenderViewHost to run its onunload handler. 238 void CrossSiteResourceHandler::StartCrossSiteTransition( 239 int request_id, 240 ResourceResponse* response, 241 bool should_transfer) { 242 in_cross_site_transition_ = true; 243 response_ = response; 244 245 // Store this handler on the ExtraRequestInfo, so that RDH can call our 246 // ResumeResponse method when we are ready to resume. 247 ResourceRequestInfoImpl* info = GetRequestInfo(); 248 info->set_cross_site_handler(this); 249 250 DCHECK_EQ(request_id, info->GetRequestID()); 251 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID()); 252 253 // Tell the contents responsible for this request that a cross-site response 254 // is starting, so that it can tell its old renderer to run its onunload 255 // handler now. We will wait until the unload is finished and (if a transfer 256 // is needed) for the new renderer's request to arrive. 257 // The |transfer_url_chain| contains any redirect URLs that have already 258 // occurred, plus the destination URL at the end. 259 std::vector<GURL> transfer_url_chain; 260 Referrer referrer; 261 int frame_id = -1; 262 if (should_transfer) { 263 transfer_url_chain = request()->url_chain(); 264 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy()); 265 frame_id = info->GetFrameID(); 266 267 appcache::AppCacheInterceptor::PrepareForCrossSiteTransfer( 268 request(), global_id.child_id); 269 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation( 270 global_id, transfer_url_chain.front()); 271 } 272 BrowserThread::PostTask( 273 BrowserThread::UI, 274 FROM_HERE, 275 base::Bind( 276 &OnCrossSiteResponseHelper, 277 CrossSiteResponseParams(info->GetRouteID(), 278 global_id, 279 should_transfer, 280 transfer_url_chain, 281 referrer, 282 info->GetPageTransition(), 283 frame_id, 284 info->should_replace_current_entry()))); 285 } 286 287 void CrossSiteResourceHandler::ResumeIfDeferred() { 288 if (did_defer_) { 289 did_defer_ = false; 290 controller()->Resume(); 291 } 292 } 293 294 } // namespace content 295