Roll src/third_party/WebKit 3a0ba1e:fcd7003 (svn 191141:191149)
[chromium-blink-merge.git] / content / browser / loader / cross_site_resource_handler.cc
blobccdd321ef3362af8fe3dcca1c08eb856a004ad93
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/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"
31 namespace content {
33 namespace {
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(
41 int render_frame_id,
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),
50 referrer(referrer),
51 page_transition(page_transition),
52 should_replace_current_entry(should_replace_current_entry) {
55 int render_frame_id;
56 GlobalRequestID global_request_id;
57 std::vector<GURL> transfer_url_chain;
58 Referrer referrer;
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);
70 if (rfh) {
71 if (rfh->GetParent()) {
72 // We should only swap processes for subframes in --site-per-process mode.
73 // CrossSiteResourceHandler is not installed on subframe requests in
74 // default Chrome.
75 CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
76 switches::kSitePerProcess));
78 rfh->OnCrossSiteResponse(
79 params.global_request_id, cross_site_transferring_request.Pass(),
80 params.transfer_url_chain, params.referrer,
81 params.page_transition, params.should_replace_current_entry);
82 } else if (leak_requests_for_testing_ && cross_site_transferring_request) {
83 // Some unit tests expect requests to be leaked in this case, so they can
84 // pass them along manually.
85 cross_site_transferring_request->ReleaseRequest();
89 void OnDeferredAfterResponseStartedHelper(
90 const GlobalRequestID& global_request_id,
91 int render_frame_id,
92 const TransitionLayerData& transition_data) {
93 RenderFrameHostImpl* rfh =
94 RenderFrameHostImpl::FromID(global_request_id.child_id, render_frame_id);
95 if (rfh)
96 rfh->OnDeferredAfterResponseStarted(global_request_id, transition_data);
99 // Returns whether a transfer is needed by doing a check on the UI thread.
100 bool CheckNavigationPolicyOnUI(GURL url, int process_id, int render_frame_id) {
101 CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
102 switches::kSitePerProcess));
103 RenderFrameHostImpl* rfh =
104 RenderFrameHostImpl::FromID(process_id, render_frame_id);
105 if (!rfh)
106 return false;
108 // A transfer is not needed if the current SiteInstance doesn't yet have a
109 // site. This is the case for tests that use NavigateToURL.
110 if (!rfh->GetSiteInstance()->HasSite())
111 return false;
113 // TODO(nasko): This check is very simplistic and is used temporarily only
114 // for --site-per-process. It should be updated to match the check performed
115 // by RenderFrameHostManager::UpdateStateForNavigate.
116 return !SiteInstance::IsSameWebSite(
117 rfh->GetSiteInstance()->GetBrowserContext(),
118 rfh->GetSiteInstance()->GetSiteURL(), url);
121 } // namespace
123 CrossSiteResourceHandler::CrossSiteResourceHandler(
124 scoped_ptr<ResourceHandler> next_handler,
125 net::URLRequest* request)
126 : LayeredResourceHandler(request, next_handler.Pass()),
127 has_started_response_(false),
128 in_cross_site_transition_(false),
129 completed_during_transition_(false),
130 did_defer_(false),
131 weak_ptr_factory_(this) {
134 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
135 // Cleanup back-pointer stored on the request info.
136 GetRequestInfo()->set_cross_site_handler(NULL);
139 bool CrossSiteResourceHandler::OnRequestRedirected(
140 const net::RedirectInfo& redirect_info,
141 ResourceResponse* response,
142 bool* defer) {
143 // We should not have started the transition before being redirected.
144 DCHECK(!in_cross_site_transition_);
145 return next_handler_->OnRequestRedirected(redirect_info, response, defer);
148 bool CrossSiteResourceHandler::OnResponseStarted(
149 ResourceResponse* response,
150 bool* defer) {
151 response_ = response;
152 has_started_response_ = true;
154 // Store this handler on the ExtraRequestInfo, so that RDH can call our
155 // ResumeResponse method when we are ready to resume.
156 ResourceRequestInfoImpl* info = GetRequestInfo();
157 info->set_cross_site_handler(this);
159 TransitionLayerData transition_data;
160 bool is_navigation_transition =
161 TransitionRequestManager::GetInstance()->GetPendingTransitionRequest(
162 info->GetChildID(), info->GetRenderFrameID(), request()->url(),
163 &transition_data);
165 if (is_navigation_transition) {
166 if (response_.get())
167 transition_data.response_headers = response_->head.headers;
168 transition_data.request_url = request()->url();
170 return OnNavigationTransitionResponseStarted(response, defer,
171 transition_data);
172 } else {
173 return OnNormalResponseStarted(response, defer);
177 bool CrossSiteResourceHandler::OnNormalResponseStarted(
178 ResourceResponse* response,
179 bool* defer) {
180 // At this point, we know that the response is safe to send back to the
181 // renderer: it is not a download, and it has passed the SSL and safe
182 // browsing checks.
183 // We should not have already started the transition before now.
184 DCHECK(!in_cross_site_transition_);
186 ResourceRequestInfoImpl* info = GetRequestInfo();
188 // We only need to pause the response if a transfer to a different process is
189 // required. Other cross-process navigations can proceed immediately, since
190 // we run the unload handler at commit time.
191 // Note that a process swap may no longer be necessary if we transferred back
192 // into the original process due to a redirect.
193 bool should_transfer =
194 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
195 info->GetContext(), request()->original_url(), request()->url());
197 // If this is a download, just pass the response through without doing a
198 // cross-site check. The renderer will see it is a download and abort the
199 // request.
201 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
202 // page. We should allow the navigation to finish without running the unload
203 // handler or swapping in the pending RenderFrameHost.
205 // In both cases, any pending RenderFrameHost (if one was created for this
206 // navigation) will stick around until the next cross-site navigation, since
207 // we are unable to tell when to destroy it.
208 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
210 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
211 // check for both and remembering about streams is error-prone.
212 if (info->IsDownload() || info->is_stream() ||
213 (response->head.headers.get() &&
214 response->head.headers->response_code() == 204)) {
215 return next_handler_->OnResponseStarted(response, defer);
218 // When the --site-per-process flag is passed, we transfer processes for
219 // cross-site navigations. This is skipped if a transfer is already required
220 // or for WebUI processes for now, since pages like the NTP host multiple
221 // cross-site WebUI iframes.
222 if (!should_transfer &&
223 base::CommandLine::ForCurrentProcess()->HasSwitch(
224 switches::kSitePerProcess) &&
225 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
226 info->GetChildID())) {
227 return DeferForNavigationPolicyCheck(info, response, defer);
230 if (!should_transfer)
231 return next_handler_->OnResponseStarted(response, defer);
233 // Now that we know a transfer is needed and we have something to commit, we
234 // pause to let the UI thread set up the transfer.
235 StartCrossSiteTransition(response);
237 // Defer loading until after the new renderer process has issued a
238 // corresponding request.
239 *defer = true;
240 OnDidDefer();
241 return true;
244 bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted(
245 ResourceResponse* response,
246 bool* defer,
247 const TransitionLayerData& transition_data) {
248 ResourceRequestInfoImpl* info = GetRequestInfo();
250 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
251 int render_frame_id = info->GetRenderFrameID();
252 BrowserThread::PostTask(
253 BrowserThread::UI,
254 FROM_HERE,
255 base::Bind(
256 &OnDeferredAfterResponseStartedHelper,
257 global_id,
258 render_frame_id,
259 transition_data));
261 *defer = true;
262 OnDidDefer();
263 return true;
266 void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id) {
267 bool defer = false;
268 if (!OnNormalResponseStarted(response_.get(), &defer)) {
269 controller()->Cancel();
270 } else if (!defer) {
271 ResumeIfDeferred();
275 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
276 if (is_transfer) {
277 StartCrossSiteTransition(response_.get());
278 } else {
279 ResumeResponse();
283 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
284 CHECK(!in_cross_site_transition_);
285 return next_handler_->OnReadCompleted(bytes_read, defer);
288 void CrossSiteResourceHandler::OnResponseCompleted(
289 const net::URLRequestStatus& status,
290 const std::string& security_info,
291 bool* defer) {
292 if (!in_cross_site_transition_) {
293 // If we're not transferring, then we should pass this through.
294 next_handler_->OnResponseCompleted(status, security_info, defer);
295 return;
298 // We have to buffer the call until after the transition completes.
299 completed_during_transition_ = true;
300 completed_status_ = status;
301 completed_security_info_ = security_info;
303 // Defer to tell RDH not to notify the world or clean up the pending request.
304 // We will do so in ResumeResponse.
305 *defer = true;
306 OnDidDefer();
309 // We can now send the response to the new renderer, which will cause
310 // WebContentsImpl to swap in the new renderer and destroy the old one.
311 void CrossSiteResourceHandler::ResumeResponse() {
312 TRACE_EVENT_ASYNC_END0(
313 "navigation", "CrossSiteResourceHandler transition", this);
314 DCHECK(request());
315 in_cross_site_transition_ = false;
316 ResourceRequestInfoImpl* info = GetRequestInfo();
318 if (has_started_response_) {
319 // Send OnResponseStarted to the new renderer.
320 DCHECK(response_.get());
321 bool defer = false;
322 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
323 controller()->Cancel();
324 } else if (!defer) {
325 // Unpause the request to resume reading. Any further reads will be
326 // directed toward the new renderer.
327 ResumeIfDeferred();
331 // Remove ourselves from the ExtraRequestInfo.
332 info->set_cross_site_handler(NULL);
334 // If the response completed during the transition, notify the next
335 // event handler.
336 if (completed_during_transition_) {
337 bool defer = false;
338 next_handler_->OnResponseCompleted(completed_status_,
339 completed_security_info_,
340 &defer);
341 if (!defer)
342 ResumeIfDeferred();
346 // static
347 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
348 bool leak_requests_for_testing) {
349 leak_requests_for_testing_ = leak_requests_for_testing;
352 // Prepare to transfer the response to a new RenderFrameHost.
353 void CrossSiteResourceHandler::StartCrossSiteTransition(
354 ResourceResponse* response) {
355 TRACE_EVENT_ASYNC_BEGIN0(
356 "navigation", "CrossSiteResourceHandler transition", this);
357 in_cross_site_transition_ = true;
358 response_ = response;
360 // Store this handler on the ExtraRequestInfo, so that RDH can call our
361 // ResumeResponse method when we are ready to resume.
362 ResourceRequestInfoImpl* info = GetRequestInfo();
363 info->set_cross_site_handler(this);
365 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
367 // Tell the contents responsible for this request that a cross-site response
368 // is starting, so that it can tell its old renderer to run its onunload
369 // handler now. We will wait until the unload is finished and (if a transfer
370 // is needed) for the new renderer's request to arrive.
371 // The |transfer_url_chain| contains any redirect URLs that have already
372 // occurred, plus the destination URL at the end.
373 std::vector<GURL> transfer_url_chain;
374 Referrer referrer;
375 int render_frame_id = info->GetRenderFrameID();
376 transfer_url_chain = request()->url_chain();
377 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
378 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
380 BrowserThread::PostTask(
381 BrowserThread::UI,
382 FROM_HERE,
383 base::Bind(
384 &OnCrossSiteResponseHelper,
385 CrossSiteResponseParams(render_frame_id,
386 global_id,
387 transfer_url_chain,
388 referrer,
389 info->GetPageTransition(),
390 info->should_replace_current_entry())));
393 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
394 ResourceRequestInfoImpl* info,
395 ResourceResponse* response,
396 bool* defer) {
397 // Store the response_ object internally, since the navigation is deferred
398 // regardless of whether it will be a transfer or not.
399 response_ = response;
401 // Always defer the navigation to the UI thread to make a policy decision.
402 // It will send the result back to the IO thread to either resume or
403 // transfer it to a new renderer.
404 // TODO(nasko): If the UI thread result is that transfer is required, the
405 // IO thread will defer to the UI thread again through
406 // StartCrossSiteTransition. This is unnecessary and the policy check on the
407 // UI thread should be refactored to avoid the extra hop.
408 BrowserThread::PostTaskAndReplyWithResult(
409 BrowserThread::UI,
410 FROM_HERE,
411 base::Bind(&CheckNavigationPolicyOnUI,
412 request()->url(),
413 info->GetChildID(),
414 info->GetRenderFrameID()),
415 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
416 weak_ptr_factory_.GetWeakPtr()));
418 // Defer loading until it is known whether the navigation will transfer
419 // to a new process or continue in the existing one.
420 *defer = true;
421 OnDidDefer();
422 return true;
425 void CrossSiteResourceHandler::ResumeIfDeferred() {
426 if (did_defer_) {
427 request()->LogUnblocked();
428 did_defer_ = false;
429 controller()->Resume();
433 void CrossSiteResourceHandler::OnDidDefer() {
434 did_defer_ = true;
435 request()->LogBlockedBy("CrossSiteResourceHandler");
438 } // namespace content