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.
4
5#include "content/browser/loader/cross_site_resource_handler.h"
6
7#include <string>
8
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"
30
31namespace content {
32
33namespace {
34
35bool leak_requests_for_testing_ = false;
36
37// The parameters to OnCrossSiteResponseHelper exceed the number of arguments
38// base::Bind supports.
39struct 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) {
53  }
54
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;
61};
62
63void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
64  scoped_ptr<CrossSiteTransferringRequest> cross_site_transferring_request(
65      new CrossSiteTransferringRequest(params.global_request_id));
66
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();
79  }
80}
81
82void 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);
90}
91
92// Returns whether a transfer is needed by doing a check on the UI thread.
93bool 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;
99
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);
106}
107
108}  // namespace
109
110CrossSiteResourceHandler::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) {
119}
120
121CrossSiteResourceHandler::~CrossSiteResourceHandler() {
122  // Cleanup back-pointer stored on the request info.
123  GetRequestInfo()->set_cross_site_handler(NULL);
124}
125
126bool 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);
133}
134
135bool CrossSiteResourceHandler::OnResponseStarted(
136    ResourceResponse* response,
137    bool* defer) {
138  response_ = response;
139  has_started_response_ = true;
140
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);
145
146  TransitionLayerData transition_data;
147  bool is_navigation_transition =
148      TransitionRequestManager::GetInstance()->HasPendingTransitionRequest(
149          info->GetChildID(), info->GetRenderFrameID(), request()->url(),
150          &transition_data);
151
152  if (is_navigation_transition) {
153    if (response_.get())
154      transition_data.response_headers = response_->head.headers;
155    transition_data.request_url = request()->url();
156
157    return OnNavigationTransitionResponseStarted(response, defer,
158                                                 transition_data);
159  } else {
160    return OnNormalResponseStarted(response, defer);
161  }
162}
163
164bool 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_);
172
173  ResourceRequestInfoImpl* info = GetRequestInfo();
174
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());
183
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);
194  }
195
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.
199  //
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.
203  //
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.
208  //
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);
215  }
216
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);
220
221  // Defer loading until after the new renderer process has issued a
222  // corresponding request.
223  *defer = true;
224  OnDidDefer();
225  return true;
226}
227
228bool CrossSiteResourceHandler::OnNavigationTransitionResponseStarted(
229    ResourceResponse* response,
230    bool* defer,
231    const TransitionLayerData& transition_data) {
232  ResourceRequestInfoImpl* info = GetRequestInfo();
233
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));
244
245  *defer = true;
246  OnDidDefer();
247  return true;
248}
249
250void CrossSiteResourceHandler::ResumeResponseDeferredAtStart(int request_id) {
251  bool defer = false;
252  if (!OnNormalResponseStarted(response_.get(), &defer)) {
253    controller()->Cancel();
254  } else if (!defer) {
255    ResumeIfDeferred();
256  }
257}
258
259void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
260  if (is_transfer) {
261    StartCrossSiteTransition(response_.get());
262  } else {
263    ResumeResponse();
264  }
265}
266
267bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
268  CHECK(!in_cross_site_transition_);
269  return next_handler_->OnReadCompleted(bytes_read, defer);
270}
271
272void 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;
280  }
281
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;
286
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();
291}
292
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.
295void CrossSiteResourceHandler::ResumeResponse() {
296  TRACE_EVENT_ASYNC_END0(
297      "navigation", "CrossSiteResourceHandler transition", this);
298  DCHECK(request());
299  in_cross_site_transition_ = false;
300  ResourceRequestInfoImpl* info = GetRequestInfo();
301
302  if (has_started_response_) {
303    // Send OnResponseStarted to the new renderer.
304    DCHECK(response_.get());
305    bool defer = false;
306    if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
307      controller()->Cancel();
308    } else if (!defer) {
309      // Unpause the request to resume reading.  Any further reads will be
310      // directed toward the new renderer.
311      ResumeIfDeferred();
312    }
313  }
314
315  // Remove ourselves from the ExtraRequestInfo.
316  info->set_cross_site_handler(NULL);
317
318  // If the response completed during the transition, notify the next
319  // event handler.
320  if (completed_during_transition_) {
321    bool defer = false;
322    next_handler_->OnResponseCompleted(completed_status_,
323                                       completed_security_info_,
324                                       &defer);
325    if (!defer)
326      ResumeIfDeferred();
327  }
328}
329
330// static
331void CrossSiteResourceHandler::SetLeakRequestsForTesting(
332    bool leak_requests_for_testing) {
333  leak_requests_for_testing_ = leak_requests_for_testing;
334}
335
336// Prepare to transfer the response to a new RenderFrameHost.
337void CrossSiteResourceHandler::StartCrossSiteTransition(
338    ResourceResponse* response) {
339  TRACE_EVENT_ASYNC_BEGIN0(
340      "navigation", "CrossSiteResourceHandler transition", this);
341  in_cross_site_transition_ = true;
342  response_ = response;
343
344  // Store this handler on the ExtraRequestInfo, so that RDH can call our
345  // ResumeResponse method when we are ready to resume.
346  ResourceRequestInfoImpl* info = GetRequestInfo();
347  info->set_cross_site_handler(this);
348
349  GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
350
351  // Tell the contents responsible for this request that a cross-site response
352  // is starting, so that it can tell its old renderer to run its onunload
353  // handler now.  We will wait until the unload is finished and (if a transfer
354  // is needed) for the new renderer's request to arrive.
355  // The |transfer_url_chain| contains any redirect URLs that have already
356  // occurred, plus the destination URL at the end.
357  std::vector<GURL> transfer_url_chain;
358  Referrer referrer;
359  int render_frame_id = info->GetRenderFrameID();
360  transfer_url_chain = request()->url_chain();
361  referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
362
363  AppCacheInterceptor::PrepareForCrossSiteTransfer(
364      request(), global_id.child_id);
365  ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
366
367  BrowserThread::PostTask(
368      BrowserThread::UI,
369      FROM_HERE,
370      base::Bind(
371          &OnCrossSiteResponseHelper,
372          CrossSiteResponseParams(render_frame_id,
373                                  global_id,
374                                  transfer_url_chain,
375                                  referrer,
376                                  info->GetPageTransition(),
377                                  info->should_replace_current_entry())));
378}
379
380bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
381    ResourceRequestInfoImpl* info,
382    ResourceResponse* response,
383    bool* defer) {
384  // Store the response_ object internally, since the navigation is deferred
385  // regardless of whether it will be a transfer or not.
386  response_ = response;
387
388  // Always defer the navigation to the UI thread to make a policy decision.
389  // It will send the result back to the IO thread to either resume or
390  // transfer it to a new renderer.
391  // TODO(nasko): If the UI thread result is that transfer is required, the
392  // IO thread will defer to the UI thread again through
393  // StartCrossSiteTransition. This is unnecessary and the policy check on the
394  // UI thread should be refactored to avoid the extra hop.
395  BrowserThread::PostTaskAndReplyWithResult(
396      BrowserThread::UI,
397      FROM_HERE,
398      base::Bind(&CheckNavigationPolicyOnUI,
399                 request()->url(),
400                 info->GetChildID(),
401                 info->GetRenderFrameID()),
402      base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
403                 weak_ptr_factory_.GetWeakPtr()));
404
405  // Defer loading until it is known whether the navigation will transfer
406  // to a new process or continue in the existing one.
407  *defer = true;
408  OnDidDefer();
409  return true;
410}
411
412void CrossSiteResourceHandler::ResumeIfDeferred() {
413  if (did_defer_) {
414    request()->LogUnblocked();
415    did_defer_ = false;
416    controller()->Resume();
417  }
418}
419
420void CrossSiteResourceHandler::OnDidDefer() {
421  did_defer_ = true;
422  request()->LogBlockedBy("CrossSiteResourceHandler");
423}
424
425}  // namespace content
426