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