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/logging.h"
11#include "content/browser/cross_site_request_manager.h"
12#include "content/browser/loader/resource_dispatcher_host_impl.h"
13#include "content/browser/loader/resource_request_info_impl.h"
14#include "content/browser/renderer_host/render_view_host_delegate.h"
15#include "content/browser/renderer_host/render_view_host_impl.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/content_browser_client.h"
18#include "content/public/browser/global_request_id.h"
19#include "content/public/browser/resource_controller.h"
20#include "content/public/common/resource_response.h"
21#include "net/http/http_response_headers.h"
22#include "net/url_request/url_request.h"
23#include "webkit/browser/appcache/appcache_interceptor.h"
24
25namespace content {
26
27namespace {
28
29// The parameters to OnCrossSiteResponseHelper exceed the number of arguments
30// base::Bind supports.
31struct CrossSiteResponseParams {
32  CrossSiteResponseParams(int render_view_id,
33                          const GlobalRequestID& global_request_id,
34                          bool is_transfer,
35                          const std::vector<GURL>& transfer_url_chain,
36                          const Referrer& referrer,
37                          PageTransition page_transition,
38                          int64 frame_id,
39                          bool should_replace_current_entry)
40      : render_view_id(render_view_id),
41        global_request_id(global_request_id),
42        is_transfer(is_transfer),
43        transfer_url_chain(transfer_url_chain),
44        referrer(referrer),
45        page_transition(page_transition),
46        frame_id(frame_id),
47        should_replace_current_entry(should_replace_current_entry) {
48  }
49
50  int render_view_id;
51  GlobalRequestID global_request_id;
52  bool is_transfer;
53  std::vector<GURL> transfer_url_chain;
54  Referrer referrer;
55  PageTransition page_transition;
56  int64 frame_id;
57  bool should_replace_current_entry;
58};
59
60void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
61  RenderViewHostImpl* rvh =
62      RenderViewHostImpl::FromID(params.global_request_id.child_id,
63                                 params.render_view_id);
64  if (rvh) {
65    rvh->OnCrossSiteResponse(
66        params.global_request_id, params.is_transfer,
67        params.transfer_url_chain, params.referrer,
68        params.page_transition, params.frame_id,
69        params.should_replace_current_entry);
70  }
71}
72
73}  // namespace
74
75CrossSiteResourceHandler::CrossSiteResourceHandler(
76    scoped_ptr<ResourceHandler> next_handler,
77    net::URLRequest* request)
78    : LayeredResourceHandler(request, next_handler.Pass()),
79      has_started_response_(false),
80      in_cross_site_transition_(false),
81      completed_during_transition_(false),
82      did_defer_(false),
83      completed_status_() {
84}
85
86CrossSiteResourceHandler::~CrossSiteResourceHandler() {
87  // Cleanup back-pointer stored on the request info.
88  GetRequestInfo()->set_cross_site_handler(NULL);
89}
90
91bool CrossSiteResourceHandler::OnRequestRedirected(
92    int request_id,
93    const GURL& new_url,
94    ResourceResponse* response,
95    bool* defer) {
96  // We should not have started the transition before being redirected.
97  DCHECK(!in_cross_site_transition_);
98  return next_handler_->OnRequestRedirected(
99      request_id, new_url, response, defer);
100}
101
102bool CrossSiteResourceHandler::OnResponseStarted(
103    int request_id,
104    ResourceResponse* response,
105    bool* defer) {
106  // At this point, we know that the response is safe to send back to the
107  // renderer: it is not a download, and it has passed the SSL and safe
108  // browsing checks.
109  // We should not have already started the transition before now.
110  DCHECK(!in_cross_site_transition_);
111  has_started_response_ = true;
112
113  ResourceRequestInfoImpl* info = GetRequestInfo();
114
115  // We will need to swap processes if either (1) a redirect that requires a
116  // transfer occurred before we got here, or (2) a pending cross-site request
117  // was already in progress.  Note that a swap may no longer be needed if we
118  // transferred back into the original process due to a redirect.
119  bool should_transfer =
120      GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
121          info->GetContext(), request()->original_url(), request()->url());
122  bool swap_needed = should_transfer ||
123      CrossSiteRequestManager::GetInstance()->
124          HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID());
125
126  // If this is a download, just pass the response through without doing a
127  // cross-site check.  The renderer will see it is a download and abort the
128  // request.
129  //
130  // Similarly, HTTP 204 (No Content) responses leave us showing the previous
131  // page.  We should allow the navigation to finish without running the unload
132  // handler or swapping in the pending RenderViewHost.
133  //
134  // In both cases, any pending RenderViewHost (if one was created for this
135  // navigation) will stick around until the next cross-site navigation, since
136  // we are unable to tell when to destroy it.
137  // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
138  if (!swap_needed || info->IsDownload() ||
139      (response->head.headers.get() &&
140       response->head.headers->response_code() == 204)) {
141    return next_handler_->OnResponseStarted(request_id, response, defer);
142  }
143
144  // Now that we know a swap is needed and we have something to commit, we
145  // pause to let the UI thread run the unload handler of the previous page
146  // and set up a transfer if needed.
147  StartCrossSiteTransition(request_id, response, should_transfer);
148
149  // Defer loading until after the onunload event handler has run.
150  did_defer_ = *defer = true;
151  return true;
152}
153
154bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
155                                               int bytes_read,
156                                               bool* defer) {
157  CHECK(!in_cross_site_transition_);
158  return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
159}
160
161void CrossSiteResourceHandler::OnResponseCompleted(
162    int request_id,
163    const net::URLRequestStatus& status,
164    const std::string& security_info,
165    bool* defer) {
166  if (!in_cross_site_transition_) {
167    ResourceRequestInfoImpl* info = GetRequestInfo();
168    // If we've already completed the transition, or we're canceling the
169    // request, or an error occurred with no cross-process navigation in
170    // progress, then we should just pass this through.
171    if (has_started_response_ ||
172        status.status() != net::URLRequestStatus::FAILED ||
173        !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
174            info->GetChildID(), info->GetRouteID())) {
175      next_handler_->OnResponseCompleted(request_id, status,
176                                         security_info, defer);
177      return;
178    }
179
180    // An error occurred. We should wait now for the cross-process transition,
181    // so that the error message (e.g., 404) can be displayed to the user.
182    // Also continue with the logic below to remember that we completed
183    // during the cross-site transition.
184    StartCrossSiteTransition(request_id, NULL, false);
185  }
186
187  // We have to buffer the call until after the transition completes.
188  completed_during_transition_ = true;
189  completed_status_ = status;
190  completed_security_info_ = security_info;
191
192  // Defer to tell RDH not to notify the world or clean up the pending request.
193  // We will do so in ResumeResponse.
194  did_defer_ = true;
195  *defer = true;
196}
197
198// We can now send the response to the new renderer, which will cause
199// WebContentsImpl to swap in the new renderer and destroy the old one.
200void CrossSiteResourceHandler::ResumeResponse() {
201  DCHECK(request());
202  DCHECK(in_cross_site_transition_);
203  in_cross_site_transition_ = false;
204  ResourceRequestInfoImpl* info = GetRequestInfo();
205
206  if (has_started_response_) {
207    // Send OnResponseStarted to the new renderer.
208    DCHECK(response_);
209    bool defer = false;
210    if (!next_handler_->OnResponseStarted(info->GetRequestID(), response_.get(),
211                                          &defer)) {
212      controller()->Cancel();
213    } else if (!defer) {
214      // Unpause the request to resume reading.  Any further reads will be
215      // directed toward the new renderer.
216      ResumeIfDeferred();
217    }
218  }
219
220  // Remove ourselves from the ExtraRequestInfo.
221  info->set_cross_site_handler(NULL);
222
223  // If the response completed during the transition, notify the next
224  // event handler.
225  if (completed_during_transition_) {
226    bool defer = false;
227    next_handler_->OnResponseCompleted(info->GetRequestID(),
228                                       completed_status_,
229                                       completed_security_info_,
230                                       &defer);
231    if (!defer)
232      ResumeIfDeferred();
233  }
234}
235
236// Prepare to render the cross-site response in a new RenderViewHost, by
237// telling the old RenderViewHost to run its onunload handler.
238void CrossSiteResourceHandler::StartCrossSiteTransition(
239    int request_id,
240    ResourceResponse* response,
241    bool should_transfer) {
242  in_cross_site_transition_ = true;
243  response_ = response;
244
245  // Store this handler on the ExtraRequestInfo, so that RDH can call our
246  // ResumeResponse method when we are ready to resume.
247  ResourceRequestInfoImpl* info = GetRequestInfo();
248  info->set_cross_site_handler(this);
249
250  DCHECK_EQ(request_id, info->GetRequestID());
251  GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
252
253  // Tell the contents responsible for this request that a cross-site response
254  // is starting, so that it can tell its old renderer to run its onunload
255  // handler now.  We will wait until the unload is finished and (if a transfer
256  // is needed) for the new renderer's request to arrive.
257  // The |transfer_url_chain| contains any redirect URLs that have already
258  // occurred, plus the destination URL at the end.
259  std::vector<GURL> transfer_url_chain;
260  Referrer referrer;
261  int frame_id = -1;
262  if (should_transfer) {
263    transfer_url_chain = request()->url_chain();
264    referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
265    frame_id = info->GetFrameID();
266
267    appcache::AppCacheInterceptor::PrepareForCrossSiteTransfer(
268        request(), global_id.child_id);
269    ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(
270        global_id, transfer_url_chain.front());
271  }
272  BrowserThread::PostTask(
273      BrowserThread::UI,
274      FROM_HERE,
275      base::Bind(
276          &OnCrossSiteResponseHelper,
277          CrossSiteResponseParams(info->GetRouteID(),
278                                  global_id,
279                                  should_transfer,
280                                  transfer_url_chain,
281                                  referrer,
282                                  info->GetPageTransition(),
283                                  frame_id,
284                                  info->should_replace_current_entry())));
285}
286
287void CrossSiteResourceHandler::ResumeIfDeferred() {
288  if (did_defer_) {
289    did_defer_ = false;
290    controller()->Resume();
291  }
292}
293
294}  // namespace content
295