Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / browser / loader / cross_site_resource_handler.cc
bloba812fd92b4d30c5cb29704c8e7dfd0faaf8ada19
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 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 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 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,
84 int render_frame_id,
85 const TransitionLayerData& transition_data) {
86 RenderFrameHostImpl* rfh =
87 RenderFrameHostImpl::FromID(global_request_id.child_id, render_frame_id);
88 if (rfh)
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);
97 if (!rfh)
98 return false;
100 // TODO(nasko): This check is very simplistic and is used temporarily only
101 // for --site-per-process. It should be updated to match the check performed
102 // by RenderFrameHostManager::UpdateStateForNavigate.
103 return !SiteInstance::IsSameWebSite(
104 rfh->GetSiteInstance()->GetBrowserContext(),
105 rfh->GetSiteInstance()->GetSiteURL(), url);
108 } // namespace
110 CrossSiteResourceHandler::CrossSiteResourceHandler(
111 scoped_ptr<ResourceHandler> next_handler,
112 net::URLRequest* request)
113 : LayeredResourceHandler(request, next_handler.Pass()),
114 has_started_response_(false),
115 in_cross_site_transition_(false),
116 completed_during_transition_(false),
117 did_defer_(false),
118 weak_ptr_factory_(this) {
121 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
122 // Cleanup back-pointer stored on the request info.
123 GetRequestInfo()->set_cross_site_handler(NULL);
126 bool CrossSiteResourceHandler::OnRequestRedirected(
127 const net::RedirectInfo& redirect_info,
128 ResourceResponse* response,
129 bool* defer) {
130 // We should not have started the transition before being redirected.
131 DCHECK(!in_cross_site_transition_);
132 return next_handler_->OnRequestRedirected(redirect_info, response, defer);
135 bool CrossSiteResourceHandler::OnResponseStarted(
136 ResourceResponse* response,
137 bool* defer) {
138 response_ = response;
139 has_started_response_ = true;
141 // Store this handler on the ExtraRequestInfo, so that RDH can call our
142 // ResumeResponse method when we are ready to resume.
143 ResourceRequestInfoImpl* info = GetRequestInfo();
144 info->set_cross_site_handler(this);
146 TransitionLayerData transition_data;
147 bool is_navigation_transition =
148 TransitionRequestManager::GetInstance()->HasPendingTransitionRequest(
149 info->GetChildID(), info->GetRenderFrameID(), request()->url(),
150 &transition_data);
152 if (is_navigation_transition) {
153 if (response_.get())
154 transition_data.response_headers = response_->head.headers;
155 transition_data.request_url = request()->url();
157 return OnNavigationTransitionResponseStarted(response, defer,
158 transition_data);
159 } else {
160 return OnNormalResponseStarted(response, defer);
164 bool CrossSiteResourceHandler::OnNormalResponseStarted(
165 ResourceResponse* response,
166 bool* defer) {
167 // At this point, we know that the response is safe to send back to the
168 // renderer: it is not a download, and it has passed the SSL and safe
169 // browsing checks.
170 // We should not have already started the transition before now.
171 DCHECK(!in_cross_site_transition_);
173 ResourceRequestInfoImpl* info = GetRequestInfo();
175 // We only need to pause the response if a transfer to a different process is
176 // required. Other cross-process navigations can proceed immediately, since
177 // we run the unload handler at commit time.
178 // Note that a process swap may no longer be necessary if we transferred back
179 // into the original process due to a redirect.
180 bool should_transfer =
181 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
182 info->GetContext(), request()->original_url(), request()->url());
184 // When the --site-per-process flag is passed, we transfer processes for
185 // cross-site navigations. This is skipped if a transfer is already required
186 // or for WebUI processes for now, since pages like the NTP host multiple
187 // cross-site WebUI iframes.
188 if (!should_transfer &&
189 base::CommandLine::ForCurrentProcess()->HasSwitch(
190 switches::kSitePerProcess) &&
191 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
192 info->GetChildID())) {
193 return DeferForNavigationPolicyCheck(info, response, defer);
196 // If this is a download, just pass the response through without doing a
197 // cross-site check. The renderer will see it is a download and abort the
198 // request.
200 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
201 // page. We should allow the navigation to finish without running the unload
202 // handler or swapping in the pending RenderFrameHost.
204 // In both cases, any pending RenderFrameHost (if one was created for this
205 // navigation) will stick around until the next cross-site navigation, since
206 // we are unable to tell when to destroy it.
207 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
209 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
210 // check for both and remembering about streams is error-prone.
211 if (!should_transfer || info->IsDownload() || info->is_stream() ||
212 (response->head.headers.get() &&
213 response->head.headers->response_code() == 204)) {
214 return next_handler_->OnResponseStarted(response, defer);
217 // Now that we know a transfer is needed and we have something to commit, we
218 // pause to let the UI thread set up the transfer.
219 StartCrossSiteTransition(response);
221 // Defer loading until after the new renderer process has issued a
222 // corresponding request.
223 *defer = true;
224 OnDidDefer();
225 return true;
228 bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted(
229 ResourceResponse* response,
230 bool* defer,
231 const TransitionLayerData& transition_data) {
232 ResourceRequestInfoImpl* info = GetRequestInfo();
234 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
235 int render_frame_id = info->GetRenderFrameID();
236 BrowserThread::PostTask(
237 BrowserThread::UI,
238 FROM_HERE,
239 base::Bind(
240 &OnDeferredAfterResponseStartedHelper,
241 global_id,
242 render_frame_id,
243 transition_data));
245 *defer = true;
246 OnDidDefer();
247 return true;
250 void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id) {
251 bool defer = false;
252 if (!OnNormalResponseStarted(response_.get(), &defer)) {
253 controller()->Cancel();
254 } else if (!defer) {
255 ResumeIfDeferred();
259 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
260 if (is_transfer) {
261 StartCrossSiteTransition(response_.get());
262 } else {
263 ResumeResponse();
267 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
268 CHECK(!in_cross_site_transition_);
269 return next_handler_->OnReadCompleted(bytes_read, defer);
272 void CrossSiteResourceHandler::OnResponseCompleted(
273 const net::URLRequestStatus& status,
274 const std::string& security_info,
275 bool* defer) {
276 if (!in_cross_site_transition_) {
277 // If we're not transferring, then we should pass this through.
278 next_handler_->OnResponseCompleted(status, security_info, defer);
279 return;
282 // We have to buffer the call until after the transition completes.
283 completed_during_transition_ = true;
284 completed_status_ = status;
285 completed_security_info_ = security_info;
287 // Defer to tell RDH not to notify the world or clean up the pending request.
288 // We will do so in ResumeResponse.
289 *defer = true;
290 OnDidDefer();
293 // We can now send the response to the new renderer, which will cause
294 // WebContentsImpl to swap in the new renderer and destroy the old one.
295 void CrossSiteResourceHandler::ResumeResponse() {
296 DCHECK(request());
297 in_cross_site_transition_ = false;
298 ResourceRequestInfoImpl* info = GetRequestInfo();
300 if (has_started_response_) {
301 // Send OnResponseStarted to the new renderer.
302 DCHECK(response_.get());
303 bool defer = false;
304 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
305 controller()->Cancel();
306 } else if (!defer) {
307 // Unpause the request to resume reading. Any further reads will be
308 // directed toward the new renderer.
309 ResumeIfDeferred();
313 // Remove ourselves from the ExtraRequestInfo.
314 info->set_cross_site_handler(NULL);
316 // If the response completed during the transition, notify the next
317 // event handler.
318 if (completed_during_transition_) {
319 bool defer = false;
320 next_handler_->OnResponseCompleted(completed_status_,
321 completed_security_info_,
322 &defer);
323 if (!defer)
324 ResumeIfDeferred();
328 // static
329 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
330 bool leak_requests_for_testing) {
331 leak_requests_for_testing_ = leak_requests_for_testing;
334 // Prepare to transfer the response to a new RenderFrameHost.
335 void CrossSiteResourceHandler::StartCrossSiteTransition(
336 ResourceResponse* response) {
337 in_cross_site_transition_ = true;
338 response_ = response;
340 // Store this handler on the ExtraRequestInfo, so that RDH can call our
341 // ResumeResponse method when we are ready to resume.
342 ResourceRequestInfoImpl* info = GetRequestInfo();
343 info->set_cross_site_handler(this);
345 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
347 // Tell the contents responsible for this request that a cross-site response
348 // is starting, so that it can tell its old renderer to run its onunload
349 // handler now. We will wait until the unload is finished and (if a transfer
350 // is needed) for the new renderer's request to arrive.
351 // The |transfer_url_chain| contains any redirect URLs that have already
352 // occurred, plus the destination URL at the end.
353 std::vector<GURL> transfer_url_chain;
354 Referrer referrer;
355 int render_frame_id = info->GetRenderFrameID();
356 transfer_url_chain = request()->url_chain();
357 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
359 AppCacheInterceptor::PrepareForCrossSiteTransfer(
360 request(), global_id.child_id);
361 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
363 BrowserThread::PostTask(
364 BrowserThread::UI,
365 FROM_HERE,
366 base::Bind(
367 &OnCrossSiteResponseHelper,
368 CrossSiteResponseParams(render_frame_id,
369 global_id,
370 transfer_url_chain,
371 referrer,
372 info->GetPageTransition(),
373 info->should_replace_current_entry())));
376 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
377 ResourceRequestInfoImpl* info,
378 ResourceResponse* response,
379 bool* defer) {
380 // Store the response_ object internally, since the navigation is deferred
381 // regardless of whether it will be a transfer or not.
382 response_ = response;
384 // Always defer the navigation to the UI thread to make a policy decision.
385 // It will send the result back to the IO thread to either resume or
386 // transfer it to a new renderer.
387 // TODO(nasko): If the UI thread result is that transfer is required, the
388 // IO thread will defer to the UI thread again through
389 // StartCrossSiteTransition. This is unnecessary and the policy check on the
390 // UI thread should be refactored to avoid the extra hop.
391 BrowserThread::PostTaskAndReplyWithResult(
392 BrowserThread::UI,
393 FROM_HERE,
394 base::Bind(&CheckNavigationPolicyOnUI,
395 request()->url(),
396 info->GetChildID(),
397 info->GetRenderFrameID()),
398 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
399 weak_ptr_factory_.GetWeakPtr()));
401 // Defer loading until it is known whether the navigation will transfer
402 // to a new process or continue in the existing one.
403 *defer = true;
404 OnDidDefer();
405 return true;
408 void CrossSiteResourceHandler::ResumeIfDeferred() {
409 if (did_defer_) {
410 request()->LogUnblocked();
411 did_defer_ = false;
412 controller()->Resume();
416 void CrossSiteResourceHandler::OnDidDefer() {
417 did_defer_ = true;
418 request()->LogBlockedBy("CrossSiteResourceHandler");
421 } // namespace content