When enabling new profile management programmatically, make sure to set the
[chromium-blink-merge.git] / content / browser / loader / cross_site_resource_handler.cc
blob8fa7238c357086949984c5c2ad61ec585b9a83f7
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"
7 #include <string>
9 #include "base/bind.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"
30 namespace content {
32 namespace {
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(
40 int render_frame_id,
41 const GlobalRequestID& global_request_id,
42 bool is_transfer,
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),
51 referrer(referrer),
52 page_transition(page_transition),
53 should_replace_current_entry(should_replace_current_entry) {
56 int render_frame_id;
57 GlobalRequestID global_request_id;
58 bool is_transfer;
59 std::vector<GURL> transfer_url_chain;
60 Referrer referrer;
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);
75 if (rfh) {
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);
90 if (!rfh)
91 return false;
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);
101 } // namespace
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),
110 did_defer_(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(
120 const GURL& new_url,
121 ResourceResponse* response,
122 bool* defer) {
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,
135 bool* defer) {
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
138 // browsing checks.
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
170 // request.
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.
195 *defer = true;
196 OnDidDefer();
197 return true;
200 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
201 if (is_transfer) {
202 StartCrossSiteTransition(response_, is_transfer);
203 } else {
204 ResumeResponse();
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,
216 bool* defer) {
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);
227 return;
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.
244 *defer = true;
245 OnDidDefer();
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() {
251 DCHECK(request());
252 in_cross_site_transition_ = false;
253 ResourceRequestInfoImpl* info = GetRequestInfo();
255 if (has_started_response_) {
256 // Send OnResponseStarted to the new renderer.
257 DCHECK(response_);
258 bool defer = false;
259 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
260 controller()->Cancel();
261 } else if (!defer) {
262 // Unpause the request to resume reading. Any further reads will be
263 // directed toward the new renderer.
264 ResumeIfDeferred();
268 // Remove ourselves from the ExtraRequestInfo.
269 info->set_cross_site_handler(NULL);
271 // If the response completed during the transition, notify the next
272 // event handler.
273 if (completed_during_transition_) {
274 bool defer = false;
275 next_handler_->OnResponseCompleted(completed_status_,
276 completed_security_info_,
277 &defer);
278 if (!defer)
279 ResumeIfDeferred();
283 // static
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;
311 Referrer referrer;
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(
322 BrowserThread::UI,
323 FROM_HERE,
324 base::Bind(
325 &OnCrossSiteResponseHelper,
326 CrossSiteResponseParams(render_frame_id,
327 global_id,
328 should_transfer,
329 transfer_url_chain,
330 referrer,
331 info->GetPageTransition(),
332 info->should_replace_current_entry())));
335 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
336 ResourceRequestInfoImpl* info,
337 ResourceResponse* response,
338 bool* defer) {
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(
351 BrowserThread::UI,
352 FROM_HERE,
353 base::Bind(&CheckNavigationPolicyOnUI,
354 request()->url(),
355 info->GetChildID(),
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.
362 *defer = true;
363 OnDidDefer();
364 return true;
367 void CrossSiteResourceHandler::ResumeIfDeferred() {
368 if (did_defer_) {
369 request()->LogUnblocked();
370 did_defer_ = false;
371 controller()->Resume();
375 void CrossSiteResourceHandler::OnDidDefer() {
376 did_defer_ = true;
377 request()->LogBlockedBy("CrossSiteResourceHandler");
380 } // namespace content