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/loader/resource_request_info_impl.h" 12#include "content/browser/renderer_host/render_view_host_delegate.h" 13#include "content/browser/renderer_host/render_view_host_impl.h" 14#include "content/public/browser/browser_thread.h" 15#include "content/public/browser/global_request_id.h" 16#include "content/public/browser/resource_controller.h" 17#include "content/public/common/resource_response.h" 18#include "net/http/http_response_headers.h" 19 20namespace content { 21 22namespace { 23 24void OnCrossSiteResponseHelper(int render_process_id, 25 int render_view_id, 26 int request_id) { 27 RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(render_process_id, 28 render_view_id); 29 if (rvh && rvh->GetDelegate()->GetRendererManagementDelegate()) { 30 rvh->GetDelegate()->GetRendererManagementDelegate()->OnCrossSiteResponse( 31 rvh, GlobalRequestID(render_process_id, request_id)); 32 } 33} 34 35} // namespace 36 37CrossSiteResourceHandler::CrossSiteResourceHandler( 38 scoped_ptr<ResourceHandler> next_handler, 39 int render_process_host_id, 40 int render_view_id, 41 net::URLRequest* request) 42 : LayeredResourceHandler(next_handler.Pass()), 43 render_process_host_id_(render_process_host_id), 44 render_view_id_(render_view_id), 45 request_(request), 46 has_started_response_(false), 47 in_cross_site_transition_(false), 48 request_id_(-1), 49 completed_during_transition_(false), 50 did_defer_(false), 51 completed_status_(), 52 response_(NULL) { 53} 54 55CrossSiteResourceHandler::~CrossSiteResourceHandler() { 56 // Cleanup back-pointer stored on the request info. 57 ResourceRequestInfoImpl::ForRequest(request_)->set_cross_site_handler(NULL); 58} 59 60bool CrossSiteResourceHandler::OnRequestRedirected( 61 int request_id, 62 const GURL& new_url, 63 ResourceResponse* response, 64 bool* defer) { 65 // We should not have started the transition before being redirected. 66 DCHECK(!in_cross_site_transition_); 67 return next_handler_->OnRequestRedirected( 68 request_id, new_url, response, defer); 69} 70 71bool CrossSiteResourceHandler::OnResponseStarted( 72 int request_id, 73 ResourceResponse* response, 74 bool* defer) { 75 // At this point, we know that the response is safe to send back to the 76 // renderer: it is not a download, and it has passed the SSL and safe 77 // browsing checks. 78 // We should not have already started the transition before now. 79 DCHECK(!in_cross_site_transition_); 80 has_started_response_ = true; 81 82 ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_); 83 84 // If this is a download, just pass the response through without doing a 85 // cross-site check. The renderer will see it is a download and abort the 86 // request. 87 // 88 // Similarly, HTTP 204 (No Content) responses leave us showing the previous 89 // page. We should allow the navigation to finish without running the unload 90 // handler or swapping in the pending RenderViewHost. 91 // 92 // In both cases, the pending RenderViewHost will stick around until the next 93 // cross-site navigation, since we are unable to tell when to destroy it. 94 // See RenderViewHostManager::RendererAbortedProvisionalLoad. 95 if (info->is_download() || (response->head.headers.get() && 96 response->head.headers->response_code() == 204)) { 97 return next_handler_->OnResponseStarted(request_id, response, defer); 98 } 99 100 // Tell the renderer to run the onunload event handler. 101 StartCrossSiteTransition(request_id, response); 102 103 // Defer loading until after the onunload event handler has run. 104 did_defer_ = *defer = true; 105 return true; 106} 107 108bool CrossSiteResourceHandler::OnReadCompleted(int request_id, 109 int bytes_read, 110 bool* defer) { 111 CHECK(!in_cross_site_transition_); 112 return next_handler_->OnReadCompleted(request_id, bytes_read, defer); 113} 114 115bool CrossSiteResourceHandler::OnResponseCompleted( 116 int request_id, 117 const net::URLRequestStatus& status, 118 const std::string& security_info) { 119 if (!in_cross_site_transition_) { 120 if (has_started_response_ || 121 status.status() != net::URLRequestStatus::FAILED) { 122 // We've already completed the transition or we're canceling the request, 123 // so just pass it through. 124 return next_handler_->OnResponseCompleted(request_id, status, 125 security_info); 126 } 127 128 // An error occured, we should wait now for the cross-site transition, 129 // so that the error message (e.g., 404) can be displayed to the user. 130 // Also continue with the logic below to remember that we completed 131 // during the cross-site transition. 132 StartCrossSiteTransition(request_id, NULL); 133 } 134 135 // We have to buffer the call until after the transition completes. 136 completed_during_transition_ = true; 137 completed_status_ = status; 138 completed_security_info_ = security_info; 139 140 // Return false to tell RDH not to notify the world or clean up the 141 // pending request. We will do so in ResumeResponse. 142 did_defer_ = true; 143 return false; 144} 145 146// We can now send the response to the new renderer, which will cause 147// WebContentsImpl to swap in the new renderer and destroy the old one. 148void CrossSiteResourceHandler::ResumeResponse() { 149 DCHECK(request_id_ != -1); 150 DCHECK(in_cross_site_transition_); 151 in_cross_site_transition_ = false; 152 153 if (has_started_response_) { 154 // Send OnResponseStarted to the new renderer. 155 DCHECK(response_); 156 bool defer = false; 157 if (!next_handler_->OnResponseStarted(request_id_, response_, &defer)) { 158 controller()->Cancel(); 159 } else if (!defer) { 160 // Unpause the request to resume reading. Any further reads will be 161 // directed toward the new renderer. 162 ResumeIfDeferred(); 163 } 164 } 165 166 // Remove ourselves from the ExtraRequestInfo. 167 ResourceRequestInfoImpl* info = 168 ResourceRequestInfoImpl::ForRequest(request_); 169 info->set_cross_site_handler(NULL); 170 171 // If the response completed during the transition, notify the next 172 // event handler. 173 if (completed_during_transition_) { 174 if (next_handler_->OnResponseCompleted(request_id_, completed_status_, 175 completed_security_info_)) { 176 ResumeIfDeferred(); 177 } 178 } 179} 180 181// Prepare to render the cross-site response in a new RenderViewHost, by 182// telling the old RenderViewHost to run its onunload handler. 183void CrossSiteResourceHandler::StartCrossSiteTransition( 184 int request_id, 185 ResourceResponse* response) { 186 in_cross_site_transition_ = true; 187 request_id_ = request_id; 188 response_ = response; 189 190 // Store this handler on the ExtraRequestInfo, so that RDH can call our 191 // ResumeResponse method when the close ACK is received. 192 ResourceRequestInfoImpl* info = 193 ResourceRequestInfoImpl::ForRequest(request_); 194 info->set_cross_site_handler(this); 195 196 // Tell the contents responsible for this request that a cross-site response 197 // is starting, so that it can tell its old renderer to run its onunload 198 // handler now. We will wait to hear the corresponding ClosePage_ACK. 199 BrowserThread::PostTask( 200 BrowserThread::UI, 201 FROM_HERE, 202 base::Bind( 203 &OnCrossSiteResponseHelper, 204 render_process_host_id_, 205 render_view_id_, 206 request_id)); 207 208 // TODO(creis): If the above call should fail, then we need to notify the IO 209 // thread to proceed anyway, using ResourceDispatcherHost::OnClosePageACK. 210} 211 212void CrossSiteResourceHandler::ResumeIfDeferred() { 213 if (did_defer_) { 214 did_defer_ = false; 215 controller()->Resume(); 216 } 217} 218 219} // namespace content 220