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