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/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 
     31 namespace content {
     32 
     33 namespace {
     34 
     35 bool leak_requests_for_testing_ = false;
     36 
     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) {
     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 
     63 void 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 
     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);
     90 }
     91 
     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(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 
    110 CrossSiteResourceHandler::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 
    121 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
    122   // Cleanup back-pointer stored on the request info.
    123   GetRequestInfo()->set_cross_site_handler(NULL);
    124 }
    125 
    126 bool 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 
    135 bool 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 
    164 bool 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 
    228 bool 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 
    250 void 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 
    259 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
    260   if (is_transfer) {
    261     StartCrossSiteTransition(response_.get());
    262   } else {
    263     ResumeResponse();
    264   }
    265 }
    266 
    267 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
    268   CHECK(!in_cross_site_transition_);
    269   return next_handler_->OnReadCompleted(bytes_read, defer);
    270 }
    271 
    272 void 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.
    295 void 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
    331 void 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.
    337 void 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 
    380 bool 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 
    412 void CrossSiteResourceHandler::ResumeIfDeferred() {
    413   if (did_defer_) {
    414     request()->LogUnblocked();
    415     did_defer_ = false;
    416     controller()->Resume();
    417   }
    418 }
    419 
    420 void CrossSiteResourceHandler::OnDidDefer() {
    421   did_defer_ = true;
    422   request()->LogBlockedBy("CrossSiteResourceHandler");
    423 }
    424 
    425 }  // namespace content
    426