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 27 using android_webview::AwContentsIoThreadClient; 28 using content::BrowserThread; 29 using navigation_interception::InterceptNavigationDelegate; 30 31 namespace { 32 33 base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate> 34 g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER; 35 36 void 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 51 namespace 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. 58 class 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 void OnIoThreadClientReady(int new_child_id, int new_route_id); 70 bool MaybeBlockRequest(); 71 bool ShouldBlockRequest(); 72 int get_child_id() const { return child_id_; } 73 int get_route_id() const { return route_id_; } 74 75 private: 76 int child_id_; 77 int route_id_; 78 net::URLRequest* request_; 79 }; 80 81 IoThreadClientThrottle::IoThreadClientThrottle(int child_id, 82 int route_id, 83 net::URLRequest* request) 84 : child_id_(child_id), 85 route_id_(route_id), 86 request_(request) { } 87 88 IoThreadClientThrottle::~IoThreadClientThrottle() { 89 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 90 g_webview_resource_dispatcher_host_delegate.Get(). 91 RemovePendingThrottleOnIoThread(this); 92 } 93 94 void IoThreadClientThrottle::WillStartRequest(bool* defer) { 95 // TODO(sgurun): This block can be removed when crbug.com/277937 is fixed. 96 if (route_id_ < 1) { 97 // OPTIONS is used for preflighted requests which are generated internally. 98 DCHECK_EQ("OPTIONS", request_->method()); 99 return; 100 } 101 DCHECK(child_id_); 102 *defer = false; 103 // Defer all requests of a pop up that is still not associated with Java 104 // client so that the client will get a chance to override requests. 105 scoped_ptr<AwContentsIoThreadClient> io_client = 106 AwContentsIoThreadClient::FromID(child_id_, route_id_); 107 if (io_client && io_client->PendingAssociation()) { 108 *defer = true; 109 AwResourceDispatcherHostDelegate::AddPendingThrottle( 110 child_id_, route_id_, this); 111 } else { 112 MaybeBlockRequest(); 113 } 114 } 115 116 void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url, 117 bool* defer) { 118 WillStartRequest(defer); 119 } 120 121 void IoThreadClientThrottle::OnIoThreadClientReady(int new_child_id, 122 int new_route_id) { 123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 124 125 if (!MaybeBlockRequest()) { 126 controller()->Resume(); 127 } 128 } 129 130 bool IoThreadClientThrottle::MaybeBlockRequest() { 131 if (ShouldBlockRequest()) { 132 controller()->CancelWithError(net::ERR_ACCESS_DENIED); 133 return true; 134 } 135 return false; 136 } 137 138 bool IoThreadClientThrottle::ShouldBlockRequest() { 139 scoped_ptr<AwContentsIoThreadClient> io_client = 140 AwContentsIoThreadClient::FromID(child_id_, route_id_); 141 if (!io_client) 142 return false; 143 144 // Part of implementation of WebSettings.allowContentAccess. 145 if (request_->url().SchemeIs(android_webview::kContentScheme) && 146 io_client->ShouldBlockContentUrls()) { 147 return true; 148 } 149 150 // Part of implementation of WebSettings.allowFileAccess. 151 if (request_->url().SchemeIsFile() && 152 io_client->ShouldBlockFileUrls()) { 153 const GURL& url = request_->url(); 154 if (!url.has_path() || 155 // Application's assets and resources are always available. 156 (url.path().find(android_webview::kAndroidResourcePath) != 0 && 157 url.path().find(android_webview::kAndroidAssetPath) != 0)) { 158 return true; 159 } 160 } 161 162 if (io_client->ShouldBlockNetworkLoads()) { 163 if (request_->url().SchemeIs(chrome::kFtpScheme)) { 164 return true; 165 } 166 SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE); 167 } else { 168 AwContentsIoThreadClient::CacheMode cache_mode = io_client->GetCacheMode(); 169 switch(cache_mode) { 170 case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK: 171 SetCacheControlFlag(request_, net::LOAD_PREFERRING_CACHE); 172 break; 173 case AwContentsIoThreadClient::LOAD_NO_CACHE: 174 SetCacheControlFlag(request_, net::LOAD_BYPASS_CACHE); 175 break; 176 case AwContentsIoThreadClient::LOAD_CACHE_ONLY: 177 SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE); 178 break; 179 default: 180 break; 181 } 182 } 183 return false; 184 } 185 186 // static 187 void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() { 188 content::ResourceDispatcherHost::Get()->SetDelegate( 189 &g_webview_resource_dispatcher_host_delegate.Get()); 190 } 191 192 AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate() 193 : content::ResourceDispatcherHostDelegate() { 194 } 195 196 AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() { 197 } 198 199 void AwResourceDispatcherHostDelegate::RequestBeginning( 200 net::URLRequest* request, 201 content::ResourceContext* resource_context, 202 appcache::AppCacheService* appcache_service, 203 ResourceType::Type resource_type, 204 int child_id, 205 int route_id, 206 bool is_continuation_of_transferred_request, 207 ScopedVector<content::ResourceThrottle>* throttles) { 208 209 // We always push the throttles here. Checking the existence of io_client 210 // is racy when a popup window is created. That is because RequestBeginning 211 // is called whether or not requests are blocked via BlockRequestForRoute() 212 // however io_client may or may not be ready at the time depending on whether 213 // webcontents is created. 214 throttles->push_back(new IoThreadClientThrottle( 215 child_id, route_id, request)); 216 217 // We only intercept navigations with main frames since this throttle is 218 // exclusively for posting onPageStarted's. 219 if (resource_type == ResourceType::MAIN_FRAME) { 220 throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor( 221 request)); 222 } 223 } 224 225 void AwResourceDispatcherHostDelegate::DownloadStarting( 226 net::URLRequest* request, 227 content::ResourceContext* resource_context, 228 int child_id, 229 int route_id, 230 int request_id, 231 bool is_content_initiated, 232 bool must_download, 233 ScopedVector<content::ResourceThrottle>* throttles) { 234 GURL url(request->url()); 235 std::string user_agent; 236 std::string content_disposition; 237 std::string mime_type; 238 int64 content_length = request->GetExpectedContentSize(); 239 240 request->extra_request_headers().GetHeader( 241 net::HttpRequestHeaders::kUserAgent, &user_agent); 242 243 244 net::HttpResponseHeaders* response_headers = request->response_headers(); 245 if (response_headers) { 246 response_headers->GetNormalizedHeader("content-disposition", 247 &content_disposition); 248 response_headers->GetMimeType(&mime_type); 249 } 250 251 request->Cancel(); 252 253 scoped_ptr<AwContentsIoThreadClient> io_client = 254 AwContentsIoThreadClient::FromID(child_id, route_id); 255 256 // POST request cannot be repeated in general, so prevent client from 257 // retrying the same request, even if it is with a GET. 258 if ("GET" == request->method() && io_client) { 259 io_client->NewDownload(url, 260 user_agent, 261 content_disposition, 262 mime_type, 263 content_length); 264 } 265 } 266 267 bool AwResourceDispatcherHostDelegate::AcceptAuthRequest( 268 net::URLRequest* request, 269 net::AuthChallengeInfo* auth_info) { 270 return true; 271 } 272 273 bool AwResourceDispatcherHostDelegate::AcceptSSLClientCertificateRequest( 274 net::URLRequest* request, 275 net::SSLCertRequestInfo* cert_info) { 276 // WebView does not support client certificate selection, however it does 277 // send a no-certificate response to the server to allow it decide how to 278 // proceed. The base class returns false here, which causes the entire 279 // resource request to be abort. We don't want that, so we must return true 280 // here (and subsequently complete the request in 281 // AwContentBrowserClient::SelectClientCertificate) to get the intended 282 // behavior. 283 return true; 284 } 285 286 content::ResourceDispatcherHostLoginDelegate* 287 AwResourceDispatcherHostDelegate::CreateLoginDelegate( 288 net::AuthChallengeInfo* auth_info, 289 net::URLRequest* request) { 290 return new AwLoginDelegate(auth_info, request); 291 } 292 293 bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(const GURL& url, 294 int child_id, 295 int route_id) { 296 // The AwURLRequestJobFactory implementation should ensure this method never 297 // gets called. 298 NOTREACHED(); 299 return false; 300 } 301 302 void AwResourceDispatcherHostDelegate::OnResponseStarted( 303 net::URLRequest* request, 304 content::ResourceContext* resource_context, 305 content::ResourceResponse* response, 306 IPC::Sender* sender) { 307 const content::ResourceRequestInfo* request_info = 308 content::ResourceRequestInfo::ForRequest(request); 309 if (!request_info) { 310 DLOG(FATAL) << "Started request without associated info: " << 311 request->url(); 312 return; 313 } 314 315 if (request_info->GetResourceType() == ResourceType::MAIN_FRAME) { 316 // Check for x-auto-login header. 317 auto_login_parser::HeaderData header_data; 318 if (auto_login_parser::ParserHeaderInResponse( 319 request, auto_login_parser::ALLOW_ANY_REALM, &header_data)) { 320 scoped_ptr<AwContentsIoThreadClient> io_client = 321 AwContentsIoThreadClient::FromID(request_info->GetChildID(), 322 request_info->GetRouteID()); 323 if (io_client) { 324 io_client->NewLoginRequest( 325 header_data.realm, header_data.account, header_data.args); 326 } 327 } 328 } 329 } 330 331 void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread( 332 IoThreadClientThrottle* throttle) { 333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 334 PendingThrottleMap::iterator it = pending_throttles_.find( 335 ChildRouteIDPair(throttle->get_child_id(), throttle->get_route_id())); 336 if (it != pending_throttles_.end()) { 337 pending_throttles_.erase(it); 338 } 339 } 340 341 // static 342 void AwResourceDispatcherHostDelegate::OnIoThreadClientReady( 343 int new_child_id, 344 int new_route_id) { 345 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 346 base::Bind( 347 &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal, 348 base::Unretained( 349 g_webview_resource_dispatcher_host_delegate.Pointer()), 350 new_child_id, new_route_id)); 351 } 352 353 // static 354 void AwResourceDispatcherHostDelegate::AddPendingThrottle( 355 int child_id, 356 int route_id, 357 IoThreadClientThrottle* pending_throttle) { 358 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 359 base::Bind( 360 &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread, 361 base::Unretained( 362 g_webview_resource_dispatcher_host_delegate.Pointer()), 363 child_id, route_id, pending_throttle)); 364 } 365 366 void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread( 367 int child_id, 368 int route_id, 369 IoThreadClientThrottle* pending_throttle) { 370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 371 pending_throttles_.insert( 372 std::pair<ChildRouteIDPair, IoThreadClientThrottle*>( 373 ChildRouteIDPair(child_id, route_id), pending_throttle)); 374 } 375 376 void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal( 377 int new_child_id, 378 int new_route_id) { 379 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 380 PendingThrottleMap::iterator it = pending_throttles_.find( 381 ChildRouteIDPair(new_child_id, new_route_id)); 382 383 if (it != pending_throttles_.end()) { 384 IoThreadClientThrottle* throttle = it->second; 385 throttle->OnIoThreadClientReady(new_child_id, new_route_id); 386 pending_throttles_.erase(it); 387 } 388 } 389 390 } // namespace android_webview 391