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/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"
35 bool leak_requests_for_testing_
= false;
37 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
38 // base::Bind supports.
39 struct CrossSiteResponseParams
{
40 CrossSiteResponseParams(
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
),
51 page_transition(page_transition
),
52 should_replace_current_entry(should_replace_current_entry
) {
56 GlobalRequestID global_request_id
;
57 std::vector
<GURL
> transfer_url_chain
;
59 ui::PageTransition page_transition
;
60 bool should_replace_current_entry
;
63 void OnCrossSiteResponseHelper(const CrossSiteResponseParams
& params
) {
64 scoped_ptr
<CrossSiteTransferringRequest
> cross_site_transferring_request(
65 new CrossSiteTransferringRequest(params
.global_request_id
));
67 RenderFrameHostImpl
* rfh
=
68 RenderFrameHostImpl::FromID(params
.global_request_id
.child_id
,
69 params
.render_frame_id
);
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();
82 void OnDeferredAfterResponseStartedHelper(
83 const GlobalRequestID
& global_request_id
,
85 const TransitionLayerData
& transition_data
) {
86 RenderFrameHostImpl
* rfh
=
87 RenderFrameHostImpl::FromID(global_request_id
.child_id
, render_frame_id
);
89 rfh
->OnDeferredAfterResponseStarted(global_request_id
, transition_data
);
92 // Returns whether a transfer is needed by doing a check on the UI thread.
93 bool 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
);
100 // A transfer is not needed if the current SiteInstance doesn't yet have a
101 // site. This is the case for tests that use NavigateToURL.
102 if (!rfh
->GetSiteInstance()->HasSite())
105 // TODO(nasko): This check is very simplistic and is used temporarily only
106 // for --site-per-process. It should be updated to match the check performed
107 // by RenderFrameHostManager::UpdateStateForNavigate.
108 return !SiteInstance::IsSameWebSite(
109 rfh
->GetSiteInstance()->GetBrowserContext(),
110 rfh
->GetSiteInstance()->GetSiteURL(), url
);
115 CrossSiteResourceHandler::CrossSiteResourceHandler(
116 scoped_ptr
<ResourceHandler
> next_handler
,
117 net::URLRequest
* request
)
118 : LayeredResourceHandler(request
, next_handler
.Pass()),
119 has_started_response_(false),
120 in_cross_site_transition_(false),
121 completed_during_transition_(false),
123 weak_ptr_factory_(this) {
126 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
127 // Cleanup back-pointer stored on the request info.
128 GetRequestInfo()->set_cross_site_handler(NULL
);
131 bool CrossSiteResourceHandler::OnRequestRedirected(
132 const net::RedirectInfo
& redirect_info
,
133 ResourceResponse
* response
,
135 // We should not have started the transition before being redirected.
136 DCHECK(!in_cross_site_transition_
);
137 return next_handler_
->OnRequestRedirected(redirect_info
, response
, defer
);
140 bool CrossSiteResourceHandler::OnResponseStarted(
141 ResourceResponse
* response
,
143 response_
= response
;
144 has_started_response_
= true;
146 // Store this handler on the ExtraRequestInfo, so that RDH can call our
147 // ResumeResponse method when we are ready to resume.
148 ResourceRequestInfoImpl
* info
= GetRequestInfo();
149 info
->set_cross_site_handler(this);
151 TransitionLayerData transition_data
;
152 bool is_navigation_transition
=
153 TransitionRequestManager::GetInstance()->HasPendingTransitionRequest(
154 info
->GetChildID(), info
->GetRenderFrameID(), request()->url(),
157 if (is_navigation_transition
) {
159 transition_data
.response_headers
= response_
->head
.headers
;
160 transition_data
.request_url
= request()->url();
162 return OnNavigationTransitionResponseStarted(response
, defer
,
165 return OnNormalResponseStarted(response
, defer
);
169 bool CrossSiteResourceHandler::OnNormalResponseStarted(
170 ResourceResponse
* response
,
172 // At this point, we know that the response is safe to send back to the
173 // renderer: it is not a download, and it has passed the SSL and safe
175 // We should not have already started the transition before now.
176 DCHECK(!in_cross_site_transition_
);
178 ResourceRequestInfoImpl
* info
= GetRequestInfo();
180 // We only need to pause the response if a transfer to a different process is
181 // required. Other cross-process navigations can proceed immediately, since
182 // we run the unload handler at commit time.
183 // Note that a process swap may no longer be necessary if we transferred back
184 // into the original process due to a redirect.
185 bool should_transfer
=
186 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
187 info
->GetContext(), request()->original_url(), request()->url());
189 // If this is a download, just pass the response through without doing a
190 // cross-site check. The renderer will see it is a download and abort the
193 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
194 // page. We should allow the navigation to finish without running the unload
195 // handler or swapping in the pending RenderFrameHost.
197 // In both cases, any pending RenderFrameHost (if one was created for this
198 // navigation) will stick around until the next cross-site navigation, since
199 // we are unable to tell when to destroy it.
200 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
202 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
203 // check for both and remembering about streams is error-prone.
204 if (info
->IsDownload() || info
->is_stream() ||
205 (response
->head
.headers
.get() &&
206 response
->head
.headers
->response_code() == 204)) {
207 return next_handler_
->OnResponseStarted(response
, defer
);
210 // When the --site-per-process flag is passed, we transfer processes for
211 // cross-site navigations. This is skipped if a transfer is already required
212 // or for WebUI processes for now, since pages like the NTP host multiple
213 // cross-site WebUI iframes.
214 if (!should_transfer
&&
215 base::CommandLine::ForCurrentProcess()->HasSwitch(
216 switches::kSitePerProcess
) &&
217 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
218 info
->GetChildID())) {
219 return DeferForNavigationPolicyCheck(info
, response
, defer
);
222 if (!should_transfer
)
223 return next_handler_
->OnResponseStarted(response
, defer
);
225 // Now that we know a transfer is needed and we have something to commit, we
226 // pause to let the UI thread set up the transfer.
227 StartCrossSiteTransition(response
);
229 // Defer loading until after the new renderer process has issued a
230 // corresponding request.
236 bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted(
237 ResourceResponse
* response
,
239 const TransitionLayerData
& transition_data
) {
240 ResourceRequestInfoImpl
* info
= GetRequestInfo();
242 GlobalRequestID
global_id(info
->GetChildID(), info
->GetRequestID());
243 int render_frame_id
= info
->GetRenderFrameID();
244 BrowserThread::PostTask(
248 &OnDeferredAfterResponseStartedHelper
,
258 void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id
) {
260 if (!OnNormalResponseStarted(response_
.get(), &defer
)) {
261 controller()->Cancel();
267 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer
) {
269 StartCrossSiteTransition(response_
.get());
275 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read
, bool* defer
) {
276 CHECK(!in_cross_site_transition_
);
277 return next_handler_
->OnReadCompleted(bytes_read
, defer
);
280 void CrossSiteResourceHandler::OnResponseCompleted(
281 const net::URLRequestStatus
& status
,
282 const std::string
& security_info
,
284 if (!in_cross_site_transition_
) {
285 // If we're not transferring, then we should pass this through.
286 next_handler_
->OnResponseCompleted(status
, security_info
, defer
);
290 // We have to buffer the call until after the transition completes.
291 completed_during_transition_
= true;
292 completed_status_
= status
;
293 completed_security_info_
= security_info
;
295 // Defer to tell RDH not to notify the world or clean up the pending request.
296 // We will do so in ResumeResponse.
301 // We can now send the response to the new renderer, which will cause
302 // WebContentsImpl to swap in the new renderer and destroy the old one.
303 void CrossSiteResourceHandler::ResumeResponse() {
304 TRACE_EVENT_ASYNC_END0(
305 "navigation", "CrossSiteResourceHandler transition", this);
307 in_cross_site_transition_
= false;
308 ResourceRequestInfoImpl
* info
= GetRequestInfo();
310 if (has_started_response_
) {
311 // Send OnResponseStarted to the new renderer.
312 DCHECK(response_
.get());
314 if (!next_handler_
->OnResponseStarted(response_
.get(), &defer
)) {
315 controller()->Cancel();
317 // Unpause the request to resume reading. Any further reads will be
318 // directed toward the new renderer.
323 // Remove ourselves from the ExtraRequestInfo.
324 info
->set_cross_site_handler(NULL
);
326 // If the response completed during the transition, notify the next
328 if (completed_during_transition_
) {
330 next_handler_
->OnResponseCompleted(completed_status_
,
331 completed_security_info_
,
339 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
340 bool leak_requests_for_testing
) {
341 leak_requests_for_testing_
= leak_requests_for_testing
;
344 // Prepare to transfer the response to a new RenderFrameHost.
345 void CrossSiteResourceHandler::StartCrossSiteTransition(
346 ResourceResponse
* response
) {
347 TRACE_EVENT_ASYNC_BEGIN0(
348 "navigation", "CrossSiteResourceHandler transition", this);
349 in_cross_site_transition_
= true;
350 response_
= response
;
352 // Store this handler on the ExtraRequestInfo, so that RDH can call our
353 // ResumeResponse method when we are ready to resume.
354 ResourceRequestInfoImpl
* info
= GetRequestInfo();
355 info
->set_cross_site_handler(this);
357 GlobalRequestID
global_id(info
->GetChildID(), info
->GetRequestID());
359 // Tell the contents responsible for this request that a cross-site response
360 // is starting, so that it can tell its old renderer to run its onunload
361 // handler now. We will wait until the unload is finished and (if a transfer
362 // is needed) for the new renderer's request to arrive.
363 // The |transfer_url_chain| contains any redirect URLs that have already
364 // occurred, plus the destination URL at the end.
365 std::vector
<GURL
> transfer_url_chain
;
367 int render_frame_id
= info
->GetRenderFrameID();
368 transfer_url_chain
= request()->url_chain();
369 referrer
= Referrer(GURL(request()->referrer()), info
->GetReferrerPolicy());
371 AppCacheInterceptor::PrepareForCrossSiteTransfer(
372 request(), global_id
.child_id
);
373 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id
);
375 BrowserThread::PostTask(
379 &OnCrossSiteResponseHelper
,
380 CrossSiteResponseParams(render_frame_id
,
384 info
->GetPageTransition(),
385 info
->should_replace_current_entry())));
388 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
389 ResourceRequestInfoImpl
* info
,
390 ResourceResponse
* response
,
392 // Store the response_ object internally, since the navigation is deferred
393 // regardless of whether it will be a transfer or not.
394 response_
= response
;
396 // Always defer the navigation to the UI thread to make a policy decision.
397 // It will send the result back to the IO thread to either resume or
398 // transfer it to a new renderer.
399 // TODO(nasko): If the UI thread result is that transfer is required, the
400 // IO thread will defer to the UI thread again through
401 // StartCrossSiteTransition. This is unnecessary and the policy check on the
402 // UI thread should be refactored to avoid the extra hop.
403 BrowserThread::PostTaskAndReplyWithResult(
406 base::Bind(&CheckNavigationPolicyOnUI
,
409 info
->GetRenderFrameID()),
410 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer
,
411 weak_ptr_factory_
.GetWeakPtr()));
413 // Defer loading until it is known whether the navigation will transfer
414 // to a new process or continue in the existing one.
420 void CrossSiteResourceHandler::ResumeIfDeferred() {
422 request()->LogUnblocked();
424 controller()->Resume();
428 void CrossSiteResourceHandler::OnDidDefer() {
430 request()->LogBlockedBy("CrossSiteResourceHandler");
433 } // namespace content