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/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/cross_site_request_manager.h"
     15 #include "content/browser/frame_host/cross_site_transferring_request.h"
     16 #include "content/browser/frame_host/render_frame_host_impl.h"
     17 #include "content/browser/loader/resource_dispatcher_host_impl.h"
     18 #include "content/browser/loader/resource_request_info_impl.h"
     19 #include "content/public/browser/browser_thread.h"
     20 #include "content/public/browser/content_browser_client.h"
     21 #include "content/public/browser/global_request_id.h"
     22 #include "content/public/browser/resource_controller.h"
     23 #include "content/public/browser/site_instance.h"
     24 #include "content/public/common/content_switches.h"
     25 #include "content/public/common/resource_response.h"
     26 #include "content/public/common/url_constants.h"
     27 #include "net/http/http_response_headers.h"
     28 #include "net/url_request/url_request.h"
     29 
     30 namespace content {
     31 
     32 namespace {
     33 
     34 bool leak_requests_for_testing_ = false;
     35 
     36 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
     37 // base::Bind supports.
     38 struct CrossSiteResponseParams {
     39   CrossSiteResponseParams(
     40       int render_frame_id,
     41       const GlobalRequestID& global_request_id,
     42       bool is_transfer,
     43       const std::vector<GURL>& transfer_url_chain,
     44       const Referrer& referrer,
     45       PageTransition page_transition,
     46       bool should_replace_current_entry)
     47       : render_frame_id(render_frame_id),
     48         global_request_id(global_request_id),
     49         is_transfer(is_transfer),
     50         transfer_url_chain(transfer_url_chain),
     51         referrer(referrer),
     52         page_transition(page_transition),
     53         should_replace_current_entry(should_replace_current_entry) {
     54   }
     55 
     56   int render_frame_id;
     57   GlobalRequestID global_request_id;
     58   bool is_transfer;
     59   std::vector<GURL> transfer_url_chain;
     60   Referrer referrer;
     61   PageTransition page_transition;
     62   bool should_replace_current_entry;
     63 };
     64 
     65 void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
     66   scoped_ptr<CrossSiteTransferringRequest> cross_site_transferring_request;
     67   if (params.is_transfer) {
     68     cross_site_transferring_request.reset(new CrossSiteTransferringRequest(
     69         params.global_request_id));
     70   }
     71 
     72   RenderFrameHostImpl* rfh =
     73       RenderFrameHostImpl::FromID(params.global_request_id.child_id,
     74                                   params.render_frame_id);
     75   if (rfh) {
     76     rfh->OnCrossSiteResponse(
     77         params.global_request_id, cross_site_transferring_request.Pass(),
     78         params.transfer_url_chain, params.referrer,
     79         params.page_transition, params.should_replace_current_entry);
     80   } else if (leak_requests_for_testing_ && cross_site_transferring_request) {
     81     // Some unit tests expect requests to be leaked in this case, so they can
     82     // pass them along manually.
     83     cross_site_transferring_request->ReleaseRequest();
     84   }
     85 }
     86 
     87 bool CheckNavigationPolicyOnUI(GURL url, int process_id, int render_frame_id) {
     88   RenderFrameHostImpl* rfh =
     89       RenderFrameHostImpl::FromID(process_id, render_frame_id);
     90   if (!rfh)
     91     return false;
     92 
     93   // TODO(nasko): This check is very simplistic and is used temporarily only
     94   // for --site-per-process. It should be updated to match the check performed
     95   // by RenderFrameHostManager::UpdateStateForNavigate.
     96   return !SiteInstance::IsSameWebSite(
     97       rfh->GetSiteInstance()->GetBrowserContext(),
     98       rfh->GetSiteInstance()->GetSiteURL(), url);
     99 }
    100 
    101 }  // namespace
    102 
    103 CrossSiteResourceHandler::CrossSiteResourceHandler(
    104     scoped_ptr<ResourceHandler> next_handler,
    105     net::URLRequest* request)
    106     : LayeredResourceHandler(request, next_handler.Pass()),
    107       has_started_response_(false),
    108       in_cross_site_transition_(false),
    109       completed_during_transition_(false),
    110       did_defer_(false),
    111       weak_ptr_factory_(this) {
    112 }
    113 
    114 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
    115   // Cleanup back-pointer stored on the request info.
    116   GetRequestInfo()->set_cross_site_handler(NULL);
    117 }
    118 
    119 bool CrossSiteResourceHandler::OnRequestRedirected(
    120     const GURL& new_url,
    121     ResourceResponse* response,
    122     bool* defer) {
    123   // Top-level requests change their cookie first-party URL on redirects, while
    124   // subframes retain the parent's value.
    125   if (GetRequestInfo()->GetResourceType() == ResourceType::MAIN_FRAME)
    126     request()->set_first_party_for_cookies(new_url);
    127 
    128   // We should not have started the transition before being redirected.
    129   DCHECK(!in_cross_site_transition_);
    130   return next_handler_->OnRequestRedirected(new_url, response, defer);
    131 }
    132 
    133 bool CrossSiteResourceHandler::OnResponseStarted(
    134     ResourceResponse* response,
    135     bool* defer) {
    136   // At this point, we know that the response is safe to send back to the
    137   // renderer: it is not a download, and it has passed the SSL and safe
    138   // browsing checks.
    139   // We should not have already started the transition before now.
    140   DCHECK(!in_cross_site_transition_);
    141   has_started_response_ = true;
    142 
    143   ResourceRequestInfoImpl* info = GetRequestInfo();
    144 
    145   // We will need to swap processes if either (1) a redirect that requires a
    146   // transfer occurred before we got here, or (2) a pending cross-site request
    147   // was already in progress.  Note that a swap may no longer be needed if we
    148   // transferred back into the original process due to a redirect.
    149   bool should_transfer =
    150       GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
    151           info->GetContext(), request()->original_url(), request()->url());
    152 
    153   // When the --site-per-process flag is passed, we transfer processes for
    154   // cross-site navigations. This is skipped if a transfer is already required
    155   // or for WebUI processes for now, since pages like the NTP host multiple
    156   // cross-site WebUI iframes.
    157   if (!should_transfer &&
    158       CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess) &&
    159       !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
    160           info->GetChildID())) {
    161     return DeferForNavigationPolicyCheck(info, response, defer);
    162   }
    163 
    164   bool swap_needed = should_transfer ||
    165       CrossSiteRequestManager::GetInstance()->
    166           HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID());
    167 
    168   // If this is a download, just pass the response through without doing a
    169   // cross-site check.  The renderer will see it is a download and abort the
    170   // request.
    171   //
    172   // Similarly, HTTP 204 (No Content) responses leave us showing the previous
    173   // page.  We should allow the navigation to finish without running the unload
    174   // handler or swapping in the pending RenderFrameHost.
    175   //
    176   // In both cases, any pending RenderFrameHost (if one was created for this
    177   // navigation) will stick around until the next cross-site navigation, since
    178   // we are unable to tell when to destroy it.
    179   // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
    180   //
    181   // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
    182   // check for both and remembering about streams is error-prone.
    183   if (!swap_needed || info->IsDownload() || info->is_stream() ||
    184       (response->head.headers.get() &&
    185        response->head.headers->response_code() == 204)) {
    186     return next_handler_->OnResponseStarted(response, defer);
    187   }
    188 
    189   // Now that we know a swap is needed and we have something to commit, we
    190   // pause to let the UI thread run the unload handler of the previous page
    191   // and set up a transfer if needed.
    192   StartCrossSiteTransition(response, should_transfer);
    193 
    194   // Defer loading until after the onunload event handler has run.
    195   *defer = true;
    196   OnDidDefer();
    197   return true;
    198 }
    199 
    200 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
    201   if (is_transfer) {
    202     StartCrossSiteTransition(response_, is_transfer);
    203   } else {
    204     ResumeResponse();
    205   }
    206 }
    207 
    208 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
    209   CHECK(!in_cross_site_transition_);
    210   return next_handler_->OnReadCompleted(bytes_read, defer);
    211 }
    212 
    213 void CrossSiteResourceHandler::OnResponseCompleted(
    214     const net::URLRequestStatus& status,
    215     const std::string& security_info,
    216     bool* defer) {
    217   if (!in_cross_site_transition_) {
    218     ResourceRequestInfoImpl* info = GetRequestInfo();
    219     // If we've already completed the transition, or we're canceling the
    220     // request, or an error occurred with no cross-process navigation in
    221     // progress, then we should just pass this through.
    222     if (has_started_response_ ||
    223         status.status() != net::URLRequestStatus::FAILED ||
    224         !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
    225             info->GetChildID(), info->GetRouteID())) {
    226       next_handler_->OnResponseCompleted(status, security_info, defer);
    227       return;
    228     }
    229 
    230     // An error occurred. We should wait now for the cross-process transition,
    231     // so that the error message (e.g., 404) can be displayed to the user.
    232     // Also continue with the logic below to remember that we completed
    233     // during the cross-site transition.
    234     StartCrossSiteTransition(NULL, false);
    235   }
    236 
    237   // We have to buffer the call until after the transition completes.
    238   completed_during_transition_ = true;
    239   completed_status_ = status;
    240   completed_security_info_ = security_info;
    241 
    242   // Defer to tell RDH not to notify the world or clean up the pending request.
    243   // We will do so in ResumeResponse.
    244   *defer = true;
    245   OnDidDefer();
    246 }
    247 
    248 // We can now send the response to the new renderer, which will cause
    249 // WebContentsImpl to swap in the new renderer and destroy the old one.
    250 void CrossSiteResourceHandler::ResumeResponse() {
    251   DCHECK(request());
    252   in_cross_site_transition_ = false;
    253   ResourceRequestInfoImpl* info = GetRequestInfo();
    254 
    255   if (has_started_response_) {
    256     // Send OnResponseStarted to the new renderer.
    257     DCHECK(response_);
    258     bool defer = false;
    259     if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
    260       controller()->Cancel();
    261     } else if (!defer) {
    262       // Unpause the request to resume reading.  Any further reads will be
    263       // directed toward the new renderer.
    264       ResumeIfDeferred();
    265     }
    266   }
    267 
    268   // Remove ourselves from the ExtraRequestInfo.
    269   info->set_cross_site_handler(NULL);
    270 
    271   // If the response completed during the transition, notify the next
    272   // event handler.
    273   if (completed_during_transition_) {
    274     bool defer = false;
    275     next_handler_->OnResponseCompleted(completed_status_,
    276                                        completed_security_info_,
    277                                        &defer);
    278     if (!defer)
    279       ResumeIfDeferred();
    280   }
    281 }
    282 
    283 // static
    284 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
    285     bool leak_requests_for_testing) {
    286   leak_requests_for_testing_ = leak_requests_for_testing;
    287 }
    288 
    289 // Prepare to render the cross-site response in a new RenderFrameHost, by
    290 // telling the old RenderFrameHost to run its onunload handler.
    291 void CrossSiteResourceHandler::StartCrossSiteTransition(
    292     ResourceResponse* response,
    293     bool should_transfer) {
    294   in_cross_site_transition_ = true;
    295   response_ = response;
    296 
    297   // Store this handler on the ExtraRequestInfo, so that RDH can call our
    298   // ResumeResponse method when we are ready to resume.
    299   ResourceRequestInfoImpl* info = GetRequestInfo();
    300   info->set_cross_site_handler(this);
    301 
    302   GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
    303 
    304   // Tell the contents responsible for this request that a cross-site response
    305   // is starting, so that it can tell its old renderer to run its onunload
    306   // handler now.  We will wait until the unload is finished and (if a transfer
    307   // is needed) for the new renderer's request to arrive.
    308   // The |transfer_url_chain| contains any redirect URLs that have already
    309   // occurred, plus the destination URL at the end.
    310   std::vector<GURL> transfer_url_chain;
    311   Referrer referrer;
    312   int render_frame_id = info->GetRenderFrameID();
    313   if (should_transfer) {
    314     transfer_url_chain = request()->url_chain();
    315     referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
    316 
    317     AppCacheInterceptor::PrepareForCrossSiteTransfer(
    318         request(), global_id.child_id);
    319     ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
    320   }
    321   BrowserThread::PostTask(
    322       BrowserThread::UI,
    323       FROM_HERE,
    324       base::Bind(
    325           &OnCrossSiteResponseHelper,
    326           CrossSiteResponseParams(render_frame_id,
    327                                   global_id,
    328                                   should_transfer,
    329                                   transfer_url_chain,
    330                                   referrer,
    331                                   info->GetPageTransition(),
    332                                   info->should_replace_current_entry())));
    333 }
    334 
    335 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
    336     ResourceRequestInfoImpl* info,
    337     ResourceResponse* response,
    338     bool* defer) {
    339   // Store the response_ object internally, since the navigation is deferred
    340   // regardless of whether it will be a transfer or not.
    341   response_ = response;
    342 
    343   // Always defer the navigation to the UI thread to make a policy decision.
    344   // It will send the result back to the IO thread to either resume or
    345   // transfer it to a new renderer.
    346   // TODO(nasko): If the UI thread result is that transfer is required, the
    347   // IO thread will defer to the UI thread again through
    348   // StartCrossSiteTransition. This is unnecessary and the policy check on the
    349   // UI thread should be refactored to avoid the extra hop.
    350   BrowserThread::PostTaskAndReplyWithResult(
    351       BrowserThread::UI,
    352       FROM_HERE,
    353       base::Bind(&CheckNavigationPolicyOnUI,
    354                  request()->url(),
    355                  info->GetChildID(),
    356                  info->GetRenderFrameID()),
    357       base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
    358                  weak_ptr_factory_.GetWeakPtr()));
    359 
    360   // Defer loading until it is known whether the navigation will transfer
    361   // to a new process or continue in the existing one.
    362   *defer = true;
    363   OnDidDefer();
    364   return true;
    365 }
    366 
    367 void CrossSiteResourceHandler::ResumeIfDeferred() {
    368   if (did_defer_) {
    369     request()->LogUnblocked();
    370     did_defer_ = false;
    371     controller()->Resume();
    372   }
    373 }
    374 
    375 void CrossSiteResourceHandler::OnDidDefer() {
    376   did_defer_ = true;
    377   request()->LogBlockedBy("CrossSiteResourceHandler");
    378 }
    379 
    380 }  // namespace content
    381