Apply _RELATIVE relocations ahead of others.
[chromium-blink-merge.git] / content / browser / loader / cross_site_resource_handler.cc
blob596cf629f732fbb44ac7cce896a1d42d0317a1f5
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 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 // 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())
103 return false;
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);
113 } // namespace
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),
122 did_defer_(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,
134 bool* defer) {
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,
142 bool* defer) {
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(),
155 &transition_data);
157 if (is_navigation_transition) {
158 if (response_.get())
159 transition_data.response_headers = response_->head.headers;
160 transition_data.request_url = request()->url();
162 return OnNavigationTransitionResponseStarted(response, defer,
163 transition_data);
164 } else {
165 return OnNormalResponseStarted(response, defer);
169 bool CrossSiteResourceHandler::OnNormalResponseStarted(
170 ResourceResponse* response,
171 bool* defer) {
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
174 // browsing checks.
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
191 // request.
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.
231 *defer = true;
232 OnDidDefer();
233 return true;
236 bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted(
237 ResourceResponse* response,
238 bool* defer,
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(
245 BrowserThread::UI,
246 FROM_HERE,
247 base::Bind(
248 &OnDeferredAfterResponseStartedHelper,
249 global_id,
250 render_frame_id,
251 transition_data));
253 *defer = true;
254 OnDidDefer();
255 return true;
258 void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id) {
259 bool defer = false;
260 if (!OnNormalResponseStarted(response_.get(), &defer)) {
261 controller()->Cancel();
262 } else if (!defer) {
263 ResumeIfDeferred();
267 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
268 if (is_transfer) {
269 StartCrossSiteTransition(response_.get());
270 } else {
271 ResumeResponse();
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,
283 bool* defer) {
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);
287 return;
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.
297 *defer = true;
298 OnDidDefer();
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);
306 DCHECK(request());
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());
313 bool defer = false;
314 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
315 controller()->Cancel();
316 } else if (!defer) {
317 // Unpause the request to resume reading. Any further reads will be
318 // directed toward the new renderer.
319 ResumeIfDeferred();
323 // Remove ourselves from the ExtraRequestInfo.
324 info->set_cross_site_handler(NULL);
326 // If the response completed during the transition, notify the next
327 // event handler.
328 if (completed_during_transition_) {
329 bool defer = false;
330 next_handler_->OnResponseCompleted(completed_status_,
331 completed_security_info_,
332 &defer);
333 if (!defer)
334 ResumeIfDeferred();
338 // static
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;
366 Referrer referrer;
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(
376 BrowserThread::UI,
377 FROM_HERE,
378 base::Bind(
379 &OnCrossSiteResponseHelper,
380 CrossSiteResponseParams(render_frame_id,
381 global_id,
382 transfer_url_chain,
383 referrer,
384 info->GetPageTransition(),
385 info->should_replace_current_entry())));
388 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
389 ResourceRequestInfoImpl* info,
390 ResourceResponse* response,
391 bool* defer) {
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(
404 BrowserThread::UI,
405 FROM_HERE,
406 base::Bind(&CheckNavigationPolicyOnUI,
407 request()->url(),
408 info->GetChildID(),
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.
415 *defer = true;
416 OnDidDefer();
417 return true;
420 void CrossSiteResourceHandler::ResumeIfDeferred() {
421 if (did_defer_) {
422 request()->LogUnblocked();
423 did_defer_ = false;
424 controller()->Resume();
428 void CrossSiteResourceHandler::OnDidDefer() {
429 did_defer_ = true;
430 request()->LogBlockedBy("CrossSiteResourceHandler");
433 } // namespace content