| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h" |
| |
| #include <string> |
| |
| #include "android_webview/browser/aw_contents_io_thread_client.h" |
| #include "android_webview/browser/aw_login_delegate.h" |
| #include "android_webview/browser/aw_resource_context.h" |
| #include "android_webview/common/url_constants.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/scoped_vector.h" |
| #include "components/auto_login_parser/auto_login_parser.h" |
| #include "components/navigation_interception/intercept_navigation_delegate.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/resource_controller.h" |
| #include "content/public/browser/resource_dispatcher_host.h" |
| #include "content/public/browser/resource_dispatcher_host_login_delegate.h" |
| #include "content/public/browser/resource_request_info.h" |
| #include "content/public/browser/resource_throttle.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/url_request.h" |
| #include "url/url_constants.h" |
| |
| using android_webview::AwContentsIoThreadClient; |
| using content::BrowserThread; |
| using navigation_interception::InterceptNavigationDelegate; |
| |
| namespace { |
| |
| base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate> |
| g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER; |
| |
| void SetCacheControlFlag( |
| net::URLRequest* request, int flag) { |
| const int all_cache_control_flags = net::LOAD_BYPASS_CACHE | |
| net::LOAD_VALIDATE_CACHE | |
| net::LOAD_PREFERRING_CACHE | |
| net::LOAD_ONLY_FROM_CACHE; |
| DCHECK((flag & all_cache_control_flags) == flag); |
| int load_flags = request->load_flags(); |
| load_flags &= ~all_cache_control_flags; |
| load_flags |= flag; |
| request->SetLoadFlags(load_flags); |
| } |
| |
| } // namespace |
| |
| namespace android_webview { |
| |
| // Calls through the IoThreadClient to check the embedders settings to determine |
| // if the request should be cancelled. There may not always be an IoThreadClient |
| // available for the |render_process_id|, |render_frame_id| pair (in the case of |
| // newly created pop up windows, for example) and in that case the request and |
| // the client callbacks will be deferred the request until a client is ready. |
| class IoThreadClientThrottle : public content::ResourceThrottle { |
| public: |
| IoThreadClientThrottle(int render_process_id, |
| int render_frame_id, |
| net::URLRequest* request); |
| virtual ~IoThreadClientThrottle(); |
| |
| // From content::ResourceThrottle |
| virtual void WillStartRequest(bool* defer) OVERRIDE; |
| virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE; |
| virtual const char* GetNameForLogging() const OVERRIDE; |
| |
| void OnIoThreadClientReady(int new_render_process_id, |
| int new_render_frame_id); |
| bool MaybeBlockRequest(); |
| bool ShouldBlockRequest(); |
| int render_process_id() const { return render_process_id_; } |
| int render_frame_id() const { return render_frame_id_; } |
| |
| private: |
| int render_process_id_; |
| int render_frame_id_; |
| net::URLRequest* request_; |
| }; |
| |
| IoThreadClientThrottle::IoThreadClientThrottle(int render_process_id, |
| int render_frame_id, |
| net::URLRequest* request) |
| : render_process_id_(render_process_id), |
| render_frame_id_(render_frame_id), |
| request_(request) { } |
| |
| IoThreadClientThrottle::~IoThreadClientThrottle() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| g_webview_resource_dispatcher_host_delegate.Get(). |
| RemovePendingThrottleOnIoThread(this); |
| } |
| |
| const char* IoThreadClientThrottle::GetNameForLogging() const { |
| return "IoThreadClientThrottle"; |
| } |
| |
| void IoThreadClientThrottle::WillStartRequest(bool* defer) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| // TODO(sgurun): This block can be removed when crbug.com/277937 is fixed. |
| if (render_frame_id_ < 1) { |
| // OPTIONS is used for preflighted requests which are generated internally. |
| DCHECK_EQ("OPTIONS", request_->method()); |
| return; |
| } |
| DCHECK(render_process_id_); |
| *defer = false; |
| |
| // Defer all requests of a pop up that is still not associated with Java |
| // client so that the client will get a chance to override requests. |
| scoped_ptr<AwContentsIoThreadClient> io_client = |
| AwContentsIoThreadClient::FromID(render_process_id_, render_frame_id_); |
| if (io_client && io_client->PendingAssociation()) { |
| *defer = true; |
| AwResourceDispatcherHostDelegate::AddPendingThrottle( |
| render_process_id_, render_frame_id_, this); |
| } else { |
| MaybeBlockRequest(); |
| } |
| } |
| |
| void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url, |
| bool* defer) { |
| WillStartRequest(defer); |
| } |
| |
| void IoThreadClientThrottle::OnIoThreadClientReady(int new_render_process_id, |
| int new_render_frame_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| if (!MaybeBlockRequest()) { |
| controller()->Resume(); |
| } |
| } |
| |
| bool IoThreadClientThrottle::MaybeBlockRequest() { |
| if (ShouldBlockRequest()) { |
| controller()->CancelWithError(net::ERR_ACCESS_DENIED); |
| return true; |
| } |
| return false; |
| } |
| |
| bool IoThreadClientThrottle::ShouldBlockRequest() { |
| scoped_ptr<AwContentsIoThreadClient> io_client = |
| AwContentsIoThreadClient::FromID(render_process_id_, render_frame_id_); |
| if (!io_client) |
| return false; |
| |
| // Part of implementation of WebSettings.allowContentAccess. |
| if (request_->url().SchemeIs(android_webview::kContentScheme) && |
| io_client->ShouldBlockContentUrls()) { |
| return true; |
| } |
| |
| // Part of implementation of WebSettings.allowFileAccess. |
| if (request_->url().SchemeIsFile() && |
| io_client->ShouldBlockFileUrls()) { |
| const GURL& url = request_->url(); |
| if (!url.has_path() || |
| // Application's assets and resources are always available. |
| (url.path().find(android_webview::kAndroidResourcePath) != 0 && |
| url.path().find(android_webview::kAndroidAssetPath) != 0)) { |
| return true; |
| } |
| } |
| |
| if (io_client->ShouldBlockNetworkLoads()) { |
| if (request_->url().SchemeIs(url::kFtpScheme)) { |
| return true; |
| } |
| SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE); |
| } else { |
| AwContentsIoThreadClient::CacheMode cache_mode = io_client->GetCacheMode(); |
| switch(cache_mode) { |
| case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK: |
| SetCacheControlFlag(request_, net::LOAD_PREFERRING_CACHE); |
| break; |
| case AwContentsIoThreadClient::LOAD_NO_CACHE: |
| SetCacheControlFlag(request_, net::LOAD_BYPASS_CACHE); |
| break; |
| case AwContentsIoThreadClient::LOAD_CACHE_ONLY: |
| SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE); |
| break; |
| default: |
| break; |
| } |
| } |
| return false; |
| } |
| |
| // static |
| void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() { |
| content::ResourceDispatcherHost::Get()->SetDelegate( |
| &g_webview_resource_dispatcher_host_delegate.Get()); |
| } |
| |
| AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate() |
| : content::ResourceDispatcherHostDelegate() { |
| } |
| |
| AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() { |
| } |
| |
| void AwResourceDispatcherHostDelegate::RequestBeginning( |
| net::URLRequest* request, |
| content::ResourceContext* resource_context, |
| appcache::AppCacheService* appcache_service, |
| ResourceType::Type resource_type, |
| int child_id, |
| int route_id, |
| ScopedVector<content::ResourceThrottle>* throttles) { |
| |
| AddExtraHeadersIfNeeded(request, resource_context); |
| |
| const content::ResourceRequestInfo* request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| |
| // We always push the throttles here. Checking the existence of io_client |
| // is racy when a popup window is created. That is because RequestBeginning |
| // is called whether or not requests are blocked via BlockRequestForRoute() |
| // however io_client may or may not be ready at the time depending on whether |
| // webcontents is created. |
| throttles->push_back(new IoThreadClientThrottle( |
| child_id, request_info->GetRenderFrameID(), request)); |
| |
| // We allow intercepting only navigations within main frames. This |
| // is used to post onPageStarted. We handle shouldOverrideUrlLoading |
| // via a sync IPC. |
| if (resource_type == ResourceType::MAIN_FRAME) |
| throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor( |
| request)); |
| } |
| |
| void AwResourceDispatcherHostDelegate::OnRequestRedirected( |
| const GURL& redirect_url, |
| net::URLRequest* request, |
| content::ResourceContext* resource_context, |
| content::ResourceResponse* response) { |
| AddExtraHeadersIfNeeded(request, resource_context); |
| } |
| |
| |
| void AwResourceDispatcherHostDelegate::DownloadStarting( |
| net::URLRequest* request, |
| content::ResourceContext* resource_context, |
| int child_id, |
| int route_id, |
| int request_id, |
| bool is_content_initiated, |
| bool must_download, |
| ScopedVector<content::ResourceThrottle>* throttles) { |
| GURL url(request->url()); |
| std::string user_agent; |
| std::string content_disposition; |
| std::string mime_type; |
| int64 content_length = request->GetExpectedContentSize(); |
| |
| request->extra_request_headers().GetHeader( |
| net::HttpRequestHeaders::kUserAgent, &user_agent); |
| |
| |
| net::HttpResponseHeaders* response_headers = request->response_headers(); |
| if (response_headers) { |
| response_headers->GetNormalizedHeader("content-disposition", |
| &content_disposition); |
| response_headers->GetMimeType(&mime_type); |
| } |
| |
| request->Cancel(); |
| |
| const content::ResourceRequestInfo* request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| |
| scoped_ptr<AwContentsIoThreadClient> io_client = |
| AwContentsIoThreadClient::FromID( |
| child_id, request_info->GetRenderFrameID()); |
| |
| // POST request cannot be repeated in general, so prevent client from |
| // retrying the same request, even if it is with a GET. |
| if ("GET" == request->method() && io_client) { |
| io_client->NewDownload(url, |
| user_agent, |
| content_disposition, |
| mime_type, |
| content_length); |
| } |
| } |
| |
| content::ResourceDispatcherHostLoginDelegate* |
| AwResourceDispatcherHostDelegate::CreateLoginDelegate( |
| net::AuthChallengeInfo* auth_info, |
| net::URLRequest* request) { |
| return new AwLoginDelegate(auth_info, request); |
| } |
| |
| bool AwResourceDispatcherHostDelegate::HandleExternalProtocol( |
| const GURL& url, |
| int child_id, |
| int route_id, |
| bool initiated_by_user_gesture) { |
| // The AwURLRequestJobFactory implementation should ensure this method never |
| // gets called. |
| NOTREACHED(); |
| return false; |
| } |
| |
| void AwResourceDispatcherHostDelegate::OnResponseStarted( |
| net::URLRequest* request, |
| content::ResourceContext* resource_context, |
| content::ResourceResponse* response, |
| IPC::Sender* sender) { |
| const content::ResourceRequestInfo* request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| if (!request_info) { |
| DLOG(FATAL) << "Started request without associated info: " << |
| request->url(); |
| return; |
| } |
| |
| if (request_info->GetResourceType() == ResourceType::MAIN_FRAME) { |
| // Check for x-auto-login header. |
| auto_login_parser::HeaderData header_data; |
| if (auto_login_parser::ParserHeaderInResponse( |
| request, auto_login_parser::ALLOW_ANY_REALM, &header_data)) { |
| scoped_ptr<AwContentsIoThreadClient> io_client = |
| AwContentsIoThreadClient::FromID(request_info->GetChildID(), |
| request_info->GetRenderFrameID()); |
| if (io_client) { |
| io_client->NewLoginRequest( |
| header_data.realm, header_data.account, header_data.args); |
| } |
| } |
| } |
| } |
| |
| void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread( |
| IoThreadClientThrottle* throttle) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| PendingThrottleMap::iterator it = pending_throttles_.find( |
| FrameRouteIDPair(throttle->render_process_id(), |
| throttle->render_frame_id())); |
| if (it != pending_throttles_.end()) { |
| pending_throttles_.erase(it); |
| } |
| } |
| |
| // static |
| void AwResourceDispatcherHostDelegate::OnIoThreadClientReady( |
| int new_render_process_id, |
| int new_render_frame_id) { |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind( |
| &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal, |
| base::Unretained( |
| g_webview_resource_dispatcher_host_delegate.Pointer()), |
| new_render_process_id, new_render_frame_id)); |
| } |
| |
| // static |
| void AwResourceDispatcherHostDelegate::AddPendingThrottle( |
| int render_process_id, |
| int render_frame_id, |
| IoThreadClientThrottle* pending_throttle) { |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind( |
| &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread, |
| base::Unretained( |
| g_webview_resource_dispatcher_host_delegate.Pointer()), |
| render_process_id, render_frame_id, pending_throttle)); |
| } |
| |
| void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread( |
| int render_process_id, |
| int render_frame_id_id, |
| IoThreadClientThrottle* pending_throttle) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| pending_throttles_.insert( |
| std::pair<FrameRouteIDPair, IoThreadClientThrottle*>( |
| FrameRouteIDPair(render_process_id, render_frame_id_id), |
| pending_throttle)); |
| } |
| |
| void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal( |
| int new_render_process_id, |
| int new_render_frame_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| PendingThrottleMap::iterator it = pending_throttles_.find( |
| FrameRouteIDPair(new_render_process_id, new_render_frame_id)); |
| |
| if (it != pending_throttles_.end()) { |
| IoThreadClientThrottle* throttle = it->second; |
| throttle->OnIoThreadClientReady(new_render_process_id, new_render_frame_id); |
| pending_throttles_.erase(it); |
| } |
| } |
| |
| void AwResourceDispatcherHostDelegate::AddExtraHeadersIfNeeded( |
| net::URLRequest* request, |
| content::ResourceContext* resource_context) { |
| const content::ResourceRequestInfo* request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| if (!request_info) return; |
| if (request_info->GetResourceType() != ResourceType::MAIN_FRAME) return; |
| |
| const content::PageTransition transition = request_info->GetPageTransition(); |
| const bool is_load_url = |
| transition & content::PAGE_TRANSITION_FROM_API; |
| const bool is_go_back_forward = |
| transition & content::PAGE_TRANSITION_FORWARD_BACK; |
| const bool is_reload = content::PageTransitionCoreTypeIs( |
| transition, content::PAGE_TRANSITION_RELOAD); |
| if (is_load_url || is_go_back_forward || is_reload) { |
| AwResourceContext* awrc = static_cast<AwResourceContext*>(resource_context); |
| std::string extra_headers = awrc->GetExtraHeaders(request->url()); |
| if (!extra_headers.empty()) { |
| net::HttpRequestHeaders headers; |
| headers.AddHeadersFromString(extra_headers); |
| for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext(); ) { |
| request->SetExtraRequestHeaderByName(it.name(), it.value(), false); |
| } |
| } |
| } |
| } |
| |
| } // namespace android_webview |