Add remoting and PPAPI tests to GN build
[chromium-blink-merge.git] / content / browser / loader / cross_site_resource_handler.cc
blob44d24ce8a36984fcb8afdb94f50dfe05a1a1613d
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(base::CommandLine::ForCurrentProcess()->HasSwitch(
95 switches::kSitePerProcess));
96 RenderFrameHostImpl* rfh =
97 RenderFrameHostImpl::FromID(process_id, render_frame_id);
98 if (!rfh)
99 return false;
101 // A transfer is not needed if the current SiteInstance doesn't yet have a
102 // site. This is the case for tests that use NavigateToURL.
103 if (!rfh->GetSiteInstance()->HasSite())
104 return false;
106 // TODO(nasko): This check is very simplistic and is used temporarily only
107 // for --site-per-process. It should be updated to match the check performed
108 // by RenderFrameHostManager::UpdateStateForNavigate.
109 return !SiteInstance::IsSameWebSite(
110 rfh->GetSiteInstance()->GetBrowserContext(),
111 rfh->GetSiteInstance()->GetSiteURL(), url);
114 } // namespace
116 CrossSiteResourceHandler::CrossSiteResourceHandler(
117 scoped_ptr<ResourceHandler> next_handler,
118 net::URLRequest* request)
119 : LayeredResourceHandler(request, next_handler.Pass()),
120 has_started_response_(false),
121 in_cross_site_transition_(false),
122 completed_during_transition_(false),
123 did_defer_(false),
124 weak_ptr_factory_(this) {
127 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
128 // Cleanup back-pointer stored on the request info.
129 GetRequestInfo()->set_cross_site_handler(NULL);
132 bool CrossSiteResourceHandler::OnRequestRedirected(
133 const net::RedirectInfo& redirect_info,
134 ResourceResponse* response,
135 bool* defer) {
136 // We should not have started the transition before being redirected.
137 DCHECK(!in_cross_site_transition_);
138 return next_handler_->OnRequestRedirected(redirect_info, response, defer);
141 bool CrossSiteResourceHandler::OnResponseStarted(
142 ResourceResponse* response,
143 bool* defer) {
144 response_ = response;
145 has_started_response_ = true;
147 // Store this handler on the ExtraRequestInfo, so that RDH can call our
148 // ResumeResponse method when we are ready to resume.
149 ResourceRequestInfoImpl* info = GetRequestInfo();
150 info->set_cross_site_handler(this);
152 TransitionLayerData transition_data;
153 bool is_navigation_transition =
154 TransitionRequestManager::GetInstance()->GetPendingTransitionRequest(
155 info->GetChildID(), info->GetRenderFrameID(), request()->url(),
156 &transition_data);
158 if (is_navigation_transition) {
159 if (response_.get())
160 transition_data.response_headers = response_->head.headers;
161 transition_data.request_url = request()->url();
163 return OnNavigationTransitionResponseStarted(response, defer,
164 transition_data);
165 } else {
166 return OnNormalResponseStarted(response, defer);
170 bool CrossSiteResourceHandler::OnNormalResponseStarted(
171 ResourceResponse* response,
172 bool* defer) {
173 // At this point, we know that the response is safe to send back to the
174 // renderer: it is not a download, and it has passed the SSL and safe
175 // browsing checks.
176 // We should not have already started the transition before now.
177 DCHECK(!in_cross_site_transition_);
179 ResourceRequestInfoImpl* info = GetRequestInfo();
181 // We only need to pause the response if a transfer to a different process is
182 // required. Other cross-process navigations can proceed immediately, since
183 // we run the unload handler at commit time.
184 // Note that a process swap may no longer be necessary if we transferred back
185 // into the original process due to a redirect.
186 bool should_transfer =
187 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
188 info->GetContext(), request()->original_url(), request()->url());
190 // If this is a download, just pass the response through without doing a
191 // cross-site check. The renderer will see it is a download and abort the
192 // request.
194 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
195 // page. We should allow the navigation to finish without running the unload
196 // handler or swapping in the pending RenderFrameHost.
198 // In both cases, any pending RenderFrameHost (if one was created for this
199 // navigation) will stick around until the next cross-site navigation, since
200 // we are unable to tell when to destroy it.
201 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
203 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
204 // check for both and remembering about streams is error-prone.
205 if (info->IsDownload() || info->is_stream() ||
206 (response->head.headers.get() &&
207 response->head.headers->response_code() == 204)) {
208 return next_handler_->OnResponseStarted(response, defer);
211 // When the --site-per-process flag is passed, we transfer processes for
212 // cross-site navigations. This is skipped if a transfer is already required
213 // or for WebUI processes for now, since pages like the NTP host multiple
214 // cross-site WebUI iframes.
215 if (!should_transfer &&
216 base::CommandLine::ForCurrentProcess()->HasSwitch(
217 switches::kSitePerProcess) &&
218 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
219 info->GetChildID())) {
220 return DeferForNavigationPolicyCheck(info, response, defer);
223 if (!should_transfer)
224 return next_handler_->OnResponseStarted(response, defer);
226 // Now that we know a transfer is needed and we have something to commit, we
227 // pause to let the UI thread set up the transfer.
228 StartCrossSiteTransition(response);
230 // Defer loading until after the new renderer process has issued a
231 // corresponding request.
232 *defer = true;
233 OnDidDefer();
234 return true;
237 bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted(
238 ResourceResponse* response,
239 bool* defer,
240 const TransitionLayerData& transition_data) {
241 ResourceRequestInfoImpl* info = GetRequestInfo();
243 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
244 int render_frame_id = info->GetRenderFrameID();
245 BrowserThread::PostTask(
246 BrowserThread::UI,
247 FROM_HERE,
248 base::Bind(
249 &OnDeferredAfterResponseStartedHelper,
250 global_id,
251 render_frame_id,
252 transition_data));
254 *defer = true;
255 OnDidDefer();
256 return true;
259 void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id) {
260 bool defer = false;
261 if (!OnNormalResponseStarted(response_.get(), &defer)) {
262 controller()->Cancel();
263 } else if (!defer) {
264 ResumeIfDeferred();
268 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
269 if (is_transfer) {
270 StartCrossSiteTransition(response_.get());
271 } else {
272 ResumeResponse();
276 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
277 CHECK(!in_cross_site_transition_);
278 return next_handler_->OnReadCompleted(bytes_read, defer);
281 void CrossSiteResourceHandler::OnResponseCompleted(
282 const net::URLRequestStatus& status,
283 const std::string& security_info,
284 bool* defer) {
285 if (!in_cross_site_transition_) {
286 // If we're not transferring, then we should pass this through.
287 next_handler_->OnResponseCompleted(status, security_info, defer);
288 return;
291 // We have to buffer the call until after the transition completes.
292 completed_during_transition_ = true;
293 completed_status_ = status;
294 completed_security_info_ = security_info;
296 // Defer to tell RDH not to notify the world or clean up the pending request.
297 // We will do so in ResumeResponse.
298 *defer = true;
299 OnDidDefer();
302 // We can now send the response to the new renderer, which will cause
303 // WebContentsImpl to swap in the new renderer and destroy the old one.
304 void CrossSiteResourceHandler::ResumeResponse() {
305 TRACE_EVENT_ASYNC_END0(
306 "navigation", "CrossSiteResourceHandler transition", this);
307 DCHECK(request());
308 in_cross_site_transition_ = false;
309 ResourceRequestInfoImpl* info = GetRequestInfo();
311 if (has_started_response_) {
312 // Send OnResponseStarted to the new renderer.
313 DCHECK(response_.get());
314 bool defer = false;
315 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
316 controller()->Cancel();
317 } else if (!defer) {
318 // Unpause the request to resume reading. Any further reads will be
319 // directed toward the new renderer.
320 ResumeIfDeferred();
324 // Remove ourselves from the ExtraRequestInfo.
325 info->set_cross_site_handler(NULL);
327 // If the response completed during the transition, notify the next
328 // event handler.
329 if (completed_during_transition_) {
330 bool defer = false;
331 next_handler_->OnResponseCompleted(completed_status_,
332 completed_security_info_,
333 &defer);
334 if (!defer)
335 ResumeIfDeferred();
339 // static
340 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
341 bool leak_requests_for_testing) {
342 leak_requests_for_testing_ = leak_requests_for_testing;
345 // Prepare to transfer the response to a new RenderFrameHost.
346 void CrossSiteResourceHandler::StartCrossSiteTransition(
347 ResourceResponse* response) {
348 TRACE_EVENT_ASYNC_BEGIN0(
349 "navigation", "CrossSiteResourceHandler transition", this);
350 in_cross_site_transition_ = true;
351 response_ = response;
353 // Store this handler on the ExtraRequestInfo, so that RDH can call our
354 // ResumeResponse method when we are ready to resume.
355 ResourceRequestInfoImpl* info = GetRequestInfo();
356 info->set_cross_site_handler(this);
358 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
360 // Tell the contents responsible for this request that a cross-site response
361 // is starting, so that it can tell its old renderer to run its onunload
362 // handler now. We will wait until the unload is finished and (if a transfer
363 // is needed) for the new renderer's request to arrive.
364 // The |transfer_url_chain| contains any redirect URLs that have already
365 // occurred, plus the destination URL at the end.
366 std::vector<GURL> transfer_url_chain;
367 Referrer referrer;
368 int render_frame_id = info->GetRenderFrameID();
369 transfer_url_chain = request()->url_chain();
370 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
371 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
373 BrowserThread::PostTask(
374 BrowserThread::UI,
375 FROM_HERE,
376 base::Bind(
377 &OnCrossSiteResponseHelper,
378 CrossSiteResponseParams(render_frame_id,
379 global_id,
380 transfer_url_chain,
381 referrer,
382 info->GetPageTransition(),
383 info->should_replace_current_entry())));
386 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
387 ResourceRequestInfoImpl* info,
388 ResourceResponse* response,
389 bool* defer) {
390 // Store the response_ object internally, since the navigation is deferred
391 // regardless of whether it will be a transfer or not.
392 response_ = response;
394 // Always defer the navigation to the UI thread to make a policy decision.
395 // It will send the result back to the IO thread to either resume or
396 // transfer it to a new renderer.
397 // TODO(nasko): If the UI thread result is that transfer is required, the
398 // IO thread will defer to the UI thread again through
399 // StartCrossSiteTransition. This is unnecessary and the policy check on the
400 // UI thread should be refactored to avoid the extra hop.
401 BrowserThread::PostTaskAndReplyWithResult(
402 BrowserThread::UI,
403 FROM_HERE,
404 base::Bind(&CheckNavigationPolicyOnUI,
405 request()->url(),
406 info->GetChildID(),
407 info->GetRenderFrameID()),
408 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
409 weak_ptr_factory_.GetWeakPtr()));
411 // Defer loading until it is known whether the navigation will transfer
412 // to a new process or continue in the existing one.
413 *defer = true;
414 OnDidDefer();
415 return true;
418 void CrossSiteResourceHandler::ResumeIfDeferred() {
419 if (did_defer_) {
420 request()->LogUnblocked();
421 did_defer_ = false;
422 controller()->Resume();
426 void CrossSiteResourceHandler::OnDidDefer() {
427 did_defer_ = true;
428 request()->LogBlockedBy("CrossSiteResourceHandler");
431 } // namespace content