Home | History | Annotate | Download | only in loader
      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 
     25 namespace content {
     26 
     27 namespace {
     28 
     29 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
     30 // base::Bind supports.
     31 struct 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 
     60 void 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 
     75 CrossSiteResourceHandler::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 
     86 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
     87   // Cleanup back-pointer stored on the request info.
     88   GetRequestInfo()->set_cross_site_handler(NULL);
     89 }
     90 
     91 bool 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 
    102 bool 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 
    154 bool 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 
    161 void 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.
    200 void 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.
    238 void 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 
    287 void CrossSiteResourceHandler::ResumeIfDeferred() {
    288   if (did_defer_) {
    289     did_defer_ = false;
    290     controller()->Resume();
    291   }
    292 }
    293 
    294 }  // namespace content
    295