Home | History | Annotate | Download | only in renderer_host
      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 "android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h"
      6 
      7 #include <string>
      8 
      9 #include "android_webview/browser/aw_contents_io_thread_client.h"
     10 #include "android_webview/browser/aw_login_delegate.h"
     11 #include "android_webview/browser/aw_resource_context.h"
     12 #include "android_webview/common/url_constants.h"
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/memory/scoped_vector.h"
     15 #include "components/auto_login_parser/auto_login_parser.h"
     16 #include "components/navigation_interception/intercept_navigation_delegate.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "content/public/browser/resource_controller.h"
     19 #include "content/public/browser/resource_dispatcher_host.h"
     20 #include "content/public/browser/resource_dispatcher_host_login_delegate.h"
     21 #include "content/public/browser/resource_request_info.h"
     22 #include "content/public/browser/resource_throttle.h"
     23 #include "net/base/load_flags.h"
     24 #include "net/base/net_errors.h"
     25 #include "net/http/http_response_headers.h"
     26 #include "net/url_request/url_request.h"
     27 #include "url/url_constants.h"
     28 
     29 using android_webview::AwContentsIoThreadClient;
     30 using content::BrowserThread;
     31 using navigation_interception::InterceptNavigationDelegate;
     32 
     33 namespace {
     34 
     35 base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate>
     36     g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER;
     37 
     38 void SetCacheControlFlag(
     39     net::URLRequest* request, int flag) {
     40   const int all_cache_control_flags = net::LOAD_BYPASS_CACHE |
     41       net::LOAD_VALIDATE_CACHE |
     42       net::LOAD_PREFERRING_CACHE |
     43       net::LOAD_ONLY_FROM_CACHE;
     44   DCHECK((flag & all_cache_control_flags) == flag);
     45   int load_flags = request->load_flags();
     46   load_flags &= ~all_cache_control_flags;
     47   load_flags |= flag;
     48   request->SetLoadFlags(load_flags);
     49 }
     50 
     51 }  // namespace
     52 
     53 namespace android_webview {
     54 
     55 // Calls through the IoThreadClient to check the embedders settings to determine
     56 // if the request should be cancelled. There may not always be an IoThreadClient
     57 // available for the |render_process_id|, |render_frame_id| pair (in the case of
     58 // newly created pop up windows, for example) and in that case the request and
     59 // the client callbacks will be deferred the request until a client is ready.
     60 class IoThreadClientThrottle : public content::ResourceThrottle {
     61  public:
     62   IoThreadClientThrottle(int render_process_id,
     63                          int render_frame_id,
     64                          net::URLRequest* request);
     65   virtual ~IoThreadClientThrottle();
     66 
     67   // From content::ResourceThrottle
     68   virtual void WillStartRequest(bool* defer) OVERRIDE;
     69   virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE;
     70   virtual const char* GetNameForLogging() const OVERRIDE;
     71 
     72   void OnIoThreadClientReady(int new_render_process_id,
     73                              int new_render_frame_id);
     74   bool MaybeBlockRequest();
     75   bool ShouldBlockRequest();
     76   int render_process_id() const { return render_process_id_; }
     77   int render_frame_id() const { return render_frame_id_; }
     78 
     79  private:
     80   int render_process_id_;
     81   int render_frame_id_;
     82   net::URLRequest* request_;
     83 };
     84 
     85 IoThreadClientThrottle::IoThreadClientThrottle(int render_process_id,
     86                                                int render_frame_id,
     87                                                net::URLRequest* request)
     88     : render_process_id_(render_process_id),
     89       render_frame_id_(render_frame_id),
     90       request_(request) { }
     91 
     92 IoThreadClientThrottle::~IoThreadClientThrottle() {
     93   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     94   g_webview_resource_dispatcher_host_delegate.Get().
     95       RemovePendingThrottleOnIoThread(this);
     96 }
     97 
     98 const char* IoThreadClientThrottle::GetNameForLogging() const {
     99   return "IoThreadClientThrottle";
    100 }
    101 
    102 void IoThreadClientThrottle::WillStartRequest(bool* defer) {
    103   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    104   // TODO(sgurun): This block can be removed when crbug.com/277937 is fixed.
    105   if (render_frame_id_ < 1) {
    106     // OPTIONS is used for preflighted requests which are generated internally.
    107     DCHECK_EQ("OPTIONS", request_->method());
    108     return;
    109   }
    110   DCHECK(render_process_id_);
    111   *defer = false;
    112 
    113   // Defer all requests of a pop up that is still not associated with Java
    114   // client so that the client will get a chance to override requests.
    115   scoped_ptr<AwContentsIoThreadClient> io_client =
    116       AwContentsIoThreadClient::FromID(render_process_id_, render_frame_id_);
    117   if (io_client && io_client->PendingAssociation()) {
    118     *defer = true;
    119     AwResourceDispatcherHostDelegate::AddPendingThrottle(
    120         render_process_id_, render_frame_id_, this);
    121   } else {
    122     MaybeBlockRequest();
    123   }
    124 }
    125 
    126 void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url,
    127                                                  bool* defer) {
    128   WillStartRequest(defer);
    129 }
    130 
    131 void IoThreadClientThrottle::OnIoThreadClientReady(int new_render_process_id,
    132                                                    int new_render_frame_id) {
    133   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    134 
    135   if (!MaybeBlockRequest()) {
    136     controller()->Resume();
    137   }
    138 }
    139 
    140 bool IoThreadClientThrottle::MaybeBlockRequest() {
    141   if (ShouldBlockRequest()) {
    142     controller()->CancelWithError(net::ERR_ACCESS_DENIED);
    143     return true;
    144   }
    145   return false;
    146 }
    147 
    148 bool IoThreadClientThrottle::ShouldBlockRequest() {
    149   scoped_ptr<AwContentsIoThreadClient> io_client =
    150       AwContentsIoThreadClient::FromID(render_process_id_, render_frame_id_);
    151   if (!io_client)
    152     return false;
    153 
    154   // Part of implementation of WebSettings.allowContentAccess.
    155   if (request_->url().SchemeIs(android_webview::kContentScheme) &&
    156       io_client->ShouldBlockContentUrls()) {
    157     return true;
    158   }
    159 
    160   // Part of implementation of WebSettings.allowFileAccess.
    161   if (request_->url().SchemeIsFile() &&
    162       io_client->ShouldBlockFileUrls()) {
    163     const GURL& url = request_->url();
    164     if (!url.has_path() ||
    165         // Application's assets and resources are always available.
    166         (url.path().find(android_webview::kAndroidResourcePath) != 0 &&
    167          url.path().find(android_webview::kAndroidAssetPath) != 0)) {
    168       return true;
    169     }
    170   }
    171 
    172   if (io_client->ShouldBlockNetworkLoads()) {
    173     if (request_->url().SchemeIs(url::kFtpScheme)) {
    174       return true;
    175     }
    176     SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
    177   } else {
    178     AwContentsIoThreadClient::CacheMode cache_mode = io_client->GetCacheMode();
    179     switch(cache_mode) {
    180       case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK:
    181         SetCacheControlFlag(request_, net::LOAD_PREFERRING_CACHE);
    182         break;
    183       case AwContentsIoThreadClient::LOAD_NO_CACHE:
    184         SetCacheControlFlag(request_, net::LOAD_BYPASS_CACHE);
    185         break;
    186       case AwContentsIoThreadClient::LOAD_CACHE_ONLY:
    187         SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
    188         break;
    189       default:
    190         break;
    191     }
    192   }
    193   return false;
    194 }
    195 
    196 // static
    197 void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() {
    198   content::ResourceDispatcherHost::Get()->SetDelegate(
    199       &g_webview_resource_dispatcher_host_delegate.Get());
    200 }
    201 
    202 AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate()
    203     : content::ResourceDispatcherHostDelegate() {
    204 }
    205 
    206 AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() {
    207 }
    208 
    209 void AwResourceDispatcherHostDelegate::RequestBeginning(
    210     net::URLRequest* request,
    211     content::ResourceContext* resource_context,
    212     appcache::AppCacheService* appcache_service,
    213     ResourceType::Type resource_type,
    214     int child_id,
    215     int route_id,
    216     ScopedVector<content::ResourceThrottle>* throttles) {
    217 
    218   AddExtraHeadersIfNeeded(request, resource_context);
    219 
    220   const content::ResourceRequestInfo* request_info =
    221       content::ResourceRequestInfo::ForRequest(request);
    222 
    223   // We always push the throttles here. Checking the existence of io_client
    224   // is racy when a popup window is created. That is because RequestBeginning
    225   // is called whether or not requests are blocked via BlockRequestForRoute()
    226   // however io_client may or may not be ready at the time depending on whether
    227   // webcontents is created.
    228   throttles->push_back(new IoThreadClientThrottle(
    229       child_id, request_info->GetRenderFrameID(), request));
    230 
    231   // We allow intercepting only navigations within main frames. This
    232   // is used to post onPageStarted. We handle shouldOverrideUrlLoading
    233   // via a sync IPC.
    234   if (resource_type == ResourceType::MAIN_FRAME)
    235     throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor(
    236         request));
    237 }
    238 
    239 void AwResourceDispatcherHostDelegate::OnRequestRedirected(
    240     const GURL& redirect_url,
    241     net::URLRequest* request,
    242     content::ResourceContext* resource_context,
    243     content::ResourceResponse* response) {
    244   AddExtraHeadersIfNeeded(request, resource_context);
    245 }
    246 
    247 
    248 void AwResourceDispatcherHostDelegate::DownloadStarting(
    249     net::URLRequest* request,
    250     content::ResourceContext* resource_context,
    251     int child_id,
    252     int route_id,
    253     int request_id,
    254     bool is_content_initiated,
    255     bool must_download,
    256     ScopedVector<content::ResourceThrottle>* throttles) {
    257   GURL url(request->url());
    258   std::string user_agent;
    259   std::string content_disposition;
    260   std::string mime_type;
    261   int64 content_length = request->GetExpectedContentSize();
    262 
    263   request->extra_request_headers().GetHeader(
    264       net::HttpRequestHeaders::kUserAgent, &user_agent);
    265 
    266 
    267   net::HttpResponseHeaders* response_headers = request->response_headers();
    268   if (response_headers) {
    269     response_headers->GetNormalizedHeader("content-disposition",
    270         &content_disposition);
    271     response_headers->GetMimeType(&mime_type);
    272   }
    273 
    274   request->Cancel();
    275 
    276   const content::ResourceRequestInfo* request_info =
    277       content::ResourceRequestInfo::ForRequest(request);
    278 
    279   scoped_ptr<AwContentsIoThreadClient> io_client =
    280       AwContentsIoThreadClient::FromID(
    281           child_id, request_info->GetRenderFrameID());
    282 
    283   // POST request cannot be repeated in general, so prevent client from
    284   // retrying the same request, even if it is with a GET.
    285   if ("GET" == request->method() && io_client) {
    286     io_client->NewDownload(url,
    287                            user_agent,
    288                            content_disposition,
    289                            mime_type,
    290                            content_length);
    291   }
    292 }
    293 
    294 content::ResourceDispatcherHostLoginDelegate*
    295     AwResourceDispatcherHostDelegate::CreateLoginDelegate(
    296         net::AuthChallengeInfo* auth_info,
    297         net::URLRequest* request) {
    298   return new AwLoginDelegate(auth_info, request);
    299 }
    300 
    301 bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(const GURL& url,
    302                                                               int child_id,
    303                                                               int route_id) {
    304   // The AwURLRequestJobFactory implementation should ensure this method never
    305   // gets called.
    306   NOTREACHED();
    307   return false;
    308 }
    309 
    310 void AwResourceDispatcherHostDelegate::OnResponseStarted(
    311     net::URLRequest* request,
    312     content::ResourceContext* resource_context,
    313     content::ResourceResponse* response,
    314     IPC::Sender* sender) {
    315   const content::ResourceRequestInfo* request_info =
    316       content::ResourceRequestInfo::ForRequest(request);
    317   if (!request_info) {
    318     DLOG(FATAL) << "Started request without associated info: " <<
    319         request->url();
    320     return;
    321   }
    322 
    323   if (request_info->GetResourceType() == ResourceType::MAIN_FRAME) {
    324     // Check for x-auto-login header.
    325     auto_login_parser::HeaderData header_data;
    326     if (auto_login_parser::ParserHeaderInResponse(
    327             request, auto_login_parser::ALLOW_ANY_REALM, &header_data)) {
    328       scoped_ptr<AwContentsIoThreadClient> io_client =
    329           AwContentsIoThreadClient::FromID(request_info->GetChildID(),
    330                                            request_info->GetRenderFrameID());
    331       if (io_client) {
    332         io_client->NewLoginRequest(
    333             header_data.realm, header_data.account, header_data.args);
    334       }
    335     }
    336   }
    337 }
    338 
    339 void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread(
    340     IoThreadClientThrottle* throttle) {
    341   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    342   PendingThrottleMap::iterator it = pending_throttles_.find(
    343       FrameRouteIDPair(throttle->render_process_id(),
    344                        throttle->render_frame_id()));
    345   if (it != pending_throttles_.end()) {
    346     pending_throttles_.erase(it);
    347   }
    348 }
    349 
    350 // static
    351 void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
    352     int new_render_process_id,
    353     int new_render_frame_id) {
    354   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
    355       base::Bind(
    356           &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal,
    357           base::Unretained(
    358               g_webview_resource_dispatcher_host_delegate.Pointer()),
    359           new_render_process_id, new_render_frame_id));
    360 }
    361 
    362 // static
    363 void AwResourceDispatcherHostDelegate::AddPendingThrottle(
    364     int render_process_id,
    365     int render_frame_id,
    366     IoThreadClientThrottle* pending_throttle) {
    367   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
    368       base::Bind(
    369           &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread,
    370           base::Unretained(
    371               g_webview_resource_dispatcher_host_delegate.Pointer()),
    372           render_process_id, render_frame_id, pending_throttle));
    373 }
    374 
    375 void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
    376     int render_process_id,
    377     int render_frame_id_id,
    378     IoThreadClientThrottle* pending_throttle) {
    379   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    380   pending_throttles_.insert(
    381       std::pair<FrameRouteIDPair, IoThreadClientThrottle*>(
    382           FrameRouteIDPair(render_process_id, render_frame_id_id),
    383           pending_throttle));
    384 }
    385 
    386 void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
    387     int new_render_process_id,
    388     int new_render_frame_id) {
    389   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    390   PendingThrottleMap::iterator it = pending_throttles_.find(
    391       FrameRouteIDPair(new_render_process_id, new_render_frame_id));
    392 
    393   if (it != pending_throttles_.end()) {
    394     IoThreadClientThrottle* throttle = it->second;
    395     throttle->OnIoThreadClientReady(new_render_process_id, new_render_frame_id);
    396     pending_throttles_.erase(it);
    397   }
    398 }
    399 
    400 void AwResourceDispatcherHostDelegate::AddExtraHeadersIfNeeded(
    401     net::URLRequest* request,
    402     content::ResourceContext* resource_context) {
    403   const content::ResourceRequestInfo* request_info =
    404       content::ResourceRequestInfo::ForRequest(request);
    405   if (!request_info) return;
    406   if (request_info->GetResourceType() != ResourceType::MAIN_FRAME) return;
    407 
    408   const content::PageTransition transition = request_info->GetPageTransition();
    409   const bool is_load_url =
    410       transition & content::PAGE_TRANSITION_FROM_API;
    411   const bool is_go_back_forward =
    412       transition & content::PAGE_TRANSITION_FORWARD_BACK;
    413   const bool is_reload = content::PageTransitionCoreTypeIs(
    414       transition, content::PAGE_TRANSITION_RELOAD);
    415   if (is_load_url || is_go_back_forward || is_reload) {
    416     AwResourceContext* awrc = static_cast<AwResourceContext*>(resource_context);
    417     std::string extra_headers = awrc->GetExtraHeaders(request->url());
    418     if (!extra_headers.empty()) {
    419       net::HttpRequestHeaders headers;
    420       headers.AddHeadersFromString(extra_headers);
    421       for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext(); ) {
    422         request->SetExtraRequestHeaderByName(it.name(), it.value(), false);
    423       }
    424     }
    425   }
    426 }
    427 
    428 }  // namespace android_webview
    429