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.
5 #include "content/browser/loader/cross_site_resource_handler.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "content/browser/child_process_security_policy_impl.h"
13 #include "content/browser/cross_site_request_manager.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/public/browser/browser_thread.h"
19 #include "content/public/browser/content_browser_client.h"
20 #include "content/public/browser/global_request_id.h"
21 #include "content/public/browser/resource_controller.h"
22 #include "content/public/browser/site_instance.h"
23 #include "content/public/common/content_switches.h"
24 #include "content/public/common/resource_response.h"
25 #include "content/public/common/url_constants.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/url_request/url_request.h"
28 #include "webkit/browser/appcache/appcache_interceptor.h"
34 bool leak_requests_for_testing_ = false;
36 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
37 // base::Bind supports.
38 struct CrossSiteResponseParams {
39 CrossSiteResponseParams(
41 const GlobalRequestID& global_request_id,
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),
52 page_transition(page_transition),
53 should_replace_current_entry(should_replace_current_entry) {
57 GlobalRequestID global_request_id;
59 std::vector<GURL> transfer_url_chain;
61 PageTransition page_transition;
62 bool should_replace_current_entry;
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));
72 RenderFrameHostImpl* rfh =
73 RenderFrameHostImpl::FromID(params.global_request_id.child_id,
74 params.render_frame_id);
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();
89 CrossSiteResourceHandler::CrossSiteResourceHandler(
90 scoped_ptr<ResourceHandler> next_handler,
91 net::URLRequest* request)
92 : LayeredResourceHandler(request, next_handler.Pass()),
93 has_started_response_(false),
94 in_cross_site_transition_(false),
95 completed_during_transition_(false),
99 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
100 // Cleanup back-pointer stored on the request info.
101 GetRequestInfo()->set_cross_site_handler(NULL);
104 bool CrossSiteResourceHandler::OnRequestRedirected(
107 ResourceResponse* response,
109 // We should not have started the transition before being redirected.
110 DCHECK(!in_cross_site_transition_);
111 return next_handler_->OnRequestRedirected(
112 request_id, new_url, response, defer);
115 bool CrossSiteResourceHandler::OnResponseStarted(
117 ResourceResponse* response,
119 // At this point, we know that the response is safe to send back to the
120 // renderer: it is not a download, and it has passed the SSL and safe
122 // We should not have already started the transition before now.
123 DCHECK(!in_cross_site_transition_);
124 has_started_response_ = true;
126 ResourceRequestInfoImpl* info = GetRequestInfo();
128 // We will need to swap processes if either (1) a redirect that requires a
129 // transfer occurred before we got here, or (2) a pending cross-site request
130 // was already in progress. Note that a swap may no longer be needed if we
131 // transferred back into the original process due to a redirect.
132 bool should_transfer =
133 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
134 info->GetContext(), request()->original_url(), request()->url());
136 // When the --site-per-process flag is passed, we transfer processes for
137 // cross-site subframe navigations.
138 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess)) {
139 GURL referrer(request()->referrer());
140 // We skip this for WebUI processes for now, since pages like the NTP host
141 // cross-site WebUI iframes but don't have referrers.
142 bool is_webui_process = ChildProcessSecurityPolicyImpl::GetInstance()->
143 HasWebUIBindings(info->GetChildID());
145 // TODO(creis): This shouldn't rely on the referrer to determine the parent
146 // frame's URL. This also doesn't work for hosted apps, due to passing NULL
147 // to IsSameWebSite. It should be possible to always send the navigation to
148 // the UI thread to make a policy decision, which could let us eliminate the
149 // renderer-side check in RenderViewImpl::decidePolicyForNavigation as well.
150 if (info->GetResourceType() == ResourceType::SUB_FRAME &&
152 !SiteInstance::IsSameWebSite(NULL, request()->url(), referrer)) {
153 should_transfer = true;
157 bool swap_needed = should_transfer ||
158 CrossSiteRequestManager::GetInstance()->
159 HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID());
161 // If this is a download, just pass the response through without doing a
162 // cross-site check. The renderer will see it is a download and abort the
165 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
166 // page. We should allow the navigation to finish without running the unload
167 // handler or swapping in the pending RenderFrameHost.
169 // In both cases, any pending RenderFrameHost (if one was created for this
170 // navigation) will stick around until the next cross-site navigation, since
171 // we are unable to tell when to destroy it.
172 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
174 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
175 // check for both and remembering about streams is error-prone.
176 if (!swap_needed || info->IsDownload() || info->is_stream() ||
177 (response->head.headers.get() &&
178 response->head.headers->response_code() == 204)) {
179 return next_handler_->OnResponseStarted(request_id, response, defer);
182 // Now that we know a swap is needed and we have something to commit, we
183 // pause to let the UI thread run the unload handler of the previous page
184 // and set up a transfer if needed.
185 StartCrossSiteTransition(request_id, response, should_transfer);
187 // Defer loading until after the onunload event handler has run.
193 bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
196 CHECK(!in_cross_site_transition_);
197 return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
200 void CrossSiteResourceHandler::OnResponseCompleted(
202 const net::URLRequestStatus& status,
203 const std::string& security_info,
205 if (!in_cross_site_transition_) {
206 ResourceRequestInfoImpl* info = GetRequestInfo();
207 // If we've already completed the transition, or we're canceling the
208 // request, or an error occurred with no cross-process navigation in
209 // progress, then we should just pass this through.
210 if (has_started_response_ ||
211 status.status() != net::URLRequestStatus::FAILED ||
212 !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
213 info->GetChildID(), info->GetRouteID())) {
214 next_handler_->OnResponseCompleted(request_id, status,
215 security_info, defer);
219 // An error occurred. We should wait now for the cross-process transition,
220 // so that the error message (e.g., 404) can be displayed to the user.
221 // Also continue with the logic below to remember that we completed
222 // during the cross-site transition.
223 StartCrossSiteTransition(request_id, NULL, false);
226 // We have to buffer the call until after the transition completes.
227 completed_during_transition_ = true;
228 completed_status_ = status;
229 completed_security_info_ = security_info;
231 // Defer to tell RDH not to notify the world or clean up the pending request.
232 // We will do so in ResumeResponse.
237 // We can now send the response to the new renderer, which will cause
238 // WebContentsImpl to swap in the new renderer and destroy the old one.
239 void CrossSiteResourceHandler::ResumeResponse() {
241 DCHECK(in_cross_site_transition_);
242 in_cross_site_transition_ = false;
243 ResourceRequestInfoImpl* info = GetRequestInfo();
245 if (has_started_response_) {
246 // Send OnResponseStarted to the new renderer.
249 if (!next_handler_->OnResponseStarted(info->GetRequestID(), response_.get(),
251 controller()->Cancel();
253 // Unpause the request to resume reading. Any further reads will be
254 // directed toward the new renderer.
259 // Remove ourselves from the ExtraRequestInfo.
260 info->set_cross_site_handler(NULL);
262 // If the response completed during the transition, notify the next
264 if (completed_during_transition_) {
266 next_handler_->OnResponseCompleted(info->GetRequestID(),
268 completed_security_info_,
276 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
277 bool leak_requests_for_testing) {
278 leak_requests_for_testing_ = leak_requests_for_testing;
281 // Prepare to render the cross-site response in a new RenderFrameHost, by
282 // telling the old RenderFrameHost to run its onunload handler.
283 void CrossSiteResourceHandler::StartCrossSiteTransition(
285 ResourceResponse* response,
286 bool should_transfer) {
287 in_cross_site_transition_ = true;
288 response_ = response;
290 // Store this handler on the ExtraRequestInfo, so that RDH can call our
291 // ResumeResponse method when we are ready to resume.
292 ResourceRequestInfoImpl* info = GetRequestInfo();
293 info->set_cross_site_handler(this);
295 DCHECK_EQ(request_id, info->GetRequestID());
296 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
298 // Tell the contents responsible for this request that a cross-site response
299 // is starting, so that it can tell its old renderer to run its onunload
300 // handler now. We will wait until the unload is finished and (if a transfer
301 // is needed) for the new renderer's request to arrive.
302 // The |transfer_url_chain| contains any redirect URLs that have already
303 // occurred, plus the destination URL at the end.
304 std::vector<GURL> transfer_url_chain;
306 int render_frame_id = info->GetRenderFrameID();
307 if (should_transfer) {
308 transfer_url_chain = request()->url_chain();
309 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
311 appcache::AppCacheInterceptor::PrepareForCrossSiteTransfer(
312 request(), global_id.child_id);
313 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
315 BrowserThread::PostTask(
319 &OnCrossSiteResponseHelper,
320 CrossSiteResponseParams(render_frame_id,
325 info->GetPageTransition(),
326 info->should_replace_current_entry())));
329 void CrossSiteResourceHandler::ResumeIfDeferred() {
331 request()->LogUnblocked();
333 controller()->Resume();
337 void CrossSiteResourceHandler::OnDidDefer() {
339 request()->LogBlockedBy("CrossSiteResourceHandler");
342 } // namespace content