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/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"
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();
87 bool CheckNavigationPolicyOnUI(GURL url
, int process_id
, int render_frame_id
) {
88 RenderFrameHostImpl
* rfh
=
89 RenderFrameHostImpl::FromID(process_id
, render_frame_id
);
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
);
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),
111 weak_ptr_factory_(this) {
114 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
115 // Cleanup back-pointer stored on the request info.
116 GetRequestInfo()->set_cross_site_handler(NULL
);
119 bool CrossSiteResourceHandler::OnRequestRedirected(
121 ResourceResponse
* response
,
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
);
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
);
133 bool CrossSiteResourceHandler::OnResponseStarted(
134 ResourceResponse
* response
,
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
139 // We should not have already started the transition before now.
140 DCHECK(!in_cross_site_transition_
);
141 has_started_response_
= true;
143 ResourceRequestInfoImpl
* info
= GetRequestInfo();
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());
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
);
164 bool swap_needed
= should_transfer
||
165 CrossSiteRequestManager::GetInstance()->
166 HasPendingCrossSiteRequest(info
->GetChildID(), info
->GetRouteID());
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
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.
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.
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
);
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
);
194 // Defer loading until after the onunload event handler has run.
200 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer
) {
202 StartCrossSiteTransition(response_
, is_transfer
);
208 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read
, bool* defer
) {
209 CHECK(!in_cross_site_transition_
);
210 return next_handler_
->OnReadCompleted(bytes_read
, defer
);
213 void CrossSiteResourceHandler::OnResponseCompleted(
214 const net::URLRequestStatus
& status
,
215 const std::string
& security_info
,
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
);
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);
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
;
242 // Defer to tell RDH not to notify the world or clean up the pending request.
243 // We will do so in ResumeResponse.
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() {
252 in_cross_site_transition_
= false;
253 ResourceRequestInfoImpl
* info
= GetRequestInfo();
255 if (has_started_response_
) {
256 // Send OnResponseStarted to the new renderer.
259 if (!next_handler_
->OnResponseStarted(response_
.get(), &defer
)) {
260 controller()->Cancel();
262 // Unpause the request to resume reading. Any further reads will be
263 // directed toward the new renderer.
268 // Remove ourselves from the ExtraRequestInfo.
269 info
->set_cross_site_handler(NULL
);
271 // If the response completed during the transition, notify the next
273 if (completed_during_transition_
) {
275 next_handler_
->OnResponseCompleted(completed_status_
,
276 completed_security_info_
,
284 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
285 bool leak_requests_for_testing
) {
286 leak_requests_for_testing_
= leak_requests_for_testing
;
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
;
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);
302 GlobalRequestID
global_id(info
->GetChildID(), info
->GetRequestID());
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
;
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());
317 AppCacheInterceptor::PrepareForCrossSiteTransfer(
318 request(), global_id
.child_id
);
319 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id
);
321 BrowserThread::PostTask(
325 &OnCrossSiteResponseHelper
,
326 CrossSiteResponseParams(render_frame_id
,
331 info
->GetPageTransition(),
332 info
->should_replace_current_entry())));
335 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
336 ResourceRequestInfoImpl
* info
,
337 ResourceResponse
* response
,
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
;
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(
353 base::Bind(&CheckNavigationPolicyOnUI
,
356 info
->GetRenderFrameID()),
357 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer
,
358 weak_ptr_factory_
.GetWeakPtr()));
360 // Defer loading until it is known whether the navigation will transfer
361 // to a new process or continue in the existing one.
367 void CrossSiteResourceHandler::ResumeIfDeferred() {
369 request()->LogUnblocked();
371 controller()->Resume();
375 void CrossSiteResourceHandler::OnDidDefer() {
377 request()->LogBlockedBy("CrossSiteResourceHandler");
380 } // namespace content