blob: 0b5312a0da89b94e054dcedb6682fb2aa26e75b5 [file] [log] [blame]
// 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/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 "content/public/common/url_constants.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.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->set_load_flags(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 |child_id|, |route_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 child_id,
int route_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;
bool MaybeDeferRequest(bool* defer);
void OnIoThreadClientReady(int new_child_id, int new_route_id);
bool MaybeBlockRequest();
bool ShouldBlockRequest();
int get_child_id() const { return child_id_; }
int get_route_id() const { return route_id_; }
private:
int child_id_;
int route_id_;
net::URLRequest* request_;
};
IoThreadClientThrottle::IoThreadClientThrottle(int child_id,
int route_id,
net::URLRequest* request)
: child_id_(child_id),
route_id_(route_id),
request_(request) { }
IoThreadClientThrottle::~IoThreadClientThrottle() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
g_webview_resource_dispatcher_host_delegate.Get().
RemovePendingThrottleOnIoThread(this);
}
void IoThreadClientThrottle::WillStartRequest(bool* defer) {
// TODO(sgurun): This block can be removed when crbug.com/277937 is fixed.
if (route_id_ < 1) {
// OPTIONS is used for preflighted requests which are generated internally.
DCHECK_EQ("OPTIONS", request_->method());
return;
}
DCHECK(child_id_);
if (!MaybeDeferRequest(defer)) {
MaybeBlockRequest();
}
}
void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url,
bool* defer) {
WillStartRequest(defer);
}
bool IoThreadClientThrottle::MaybeDeferRequest(bool* defer) {
*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(child_id_, route_id_);
if (io_client && io_client->PendingAssociation()) {
*defer = true;
AwResourceDispatcherHostDelegate::AddPendingThrottle(
child_id_, route_id_, this);
}
return *defer;
}
void IoThreadClientThrottle::OnIoThreadClientReady(int new_child_id,
int new_route_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(child_id_, route_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(chrome::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) {
// If io_client is NULL, then the browser side objects have already been
// destroyed, so do not do anything to the request. Conversely if the
// request relates to a not-yet-created popup window, then the client will
// be non-NULL but PopupPendingAssociation() will be set.
scoped_ptr<AwContentsIoThreadClient> io_client =
AwContentsIoThreadClient::FromID(child_id, route_id);
if (!io_client)
return;
throttles->push_back(new IoThreadClientThrottle(
child_id, route_id, request));
bool allow_intercepting =
// We allow intercepting navigations within subframes, but only if the
// scheme other than http or https. This is because the embedder
// can't distinguish main frame and subframe callbacks (which could lead
// to broken content if the embedder decides to not ignore the main frame
// navigation, but ignores the subframe navigation).
// The reason this is supported at all is that certain JavaScript-based
// frameworks use iframe navigation as a form of communication with the
// embedder.
(resource_type == ResourceType::MAIN_FRAME ||
(resource_type == ResourceType::SUB_FRAME &&
!request->url().SchemeIs(content::kHttpScheme) &&
!request->url().SchemeIs(content::kHttpsScheme)));
if (allow_intercepting) {
throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor(
request));
}
}
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();
scoped_ptr<AwContentsIoThreadClient> io_client =
AwContentsIoThreadClient::FromID(child_id, route_id);
// 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);
}
}
bool AwResourceDispatcherHostDelegate::AcceptAuthRequest(
net::URLRequest* request,
net::AuthChallengeInfo* auth_info) {
return true;
}
bool AwResourceDispatcherHostDelegate::AcceptSSLClientCertificateRequest(
net::URLRequest* request,
net::SSLCertRequestInfo* cert_info) {
// WebView does not support client certificate selection, however it does
// send a no-certificate response to the server to allow it decide how to
// proceed. The base class returns false here, which causes the entire
// resource request to be abort. We don't want that, so we must return true
// here (and subsequently complete the request in
// AwContentBrowserClient::SelectClientCertificate) to get the intended
// behavior.
return true;
}
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) {
// 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->GetRouteID());
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(
ChildRouteIDPair(throttle->get_child_id(), throttle->get_route_id()));
if (it != pending_throttles_.end()) {
pending_throttles_.erase(it);
}
}
// static
void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
int new_child_id,
int new_route_id) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(
&AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal,
base::Unretained(
g_webview_resource_dispatcher_host_delegate.Pointer()),
new_child_id, new_route_id));
}
// static
void AwResourceDispatcherHostDelegate::AddPendingThrottle(
int child_id,
int route_id,
IoThreadClientThrottle* pending_throttle) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(
&AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread,
base::Unretained(
g_webview_resource_dispatcher_host_delegate.Pointer()),
child_id, route_id, pending_throttle));
}
void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
int child_id,
int route_id,
IoThreadClientThrottle* pending_throttle) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
pending_throttles_.insert(
std::pair<ChildRouteIDPair, IoThreadClientThrottle*>(
ChildRouteIDPair(child_id, route_id), pending_throttle));
}
void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
int new_child_id,
int new_route_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
PendingThrottleMap::iterator it = pending_throttles_.find(
ChildRouteIDPair(new_child_id, new_route_id));
if (it != pending_throttles_.end()) {
IoThreadClientThrottle* throttle = it->second;
throttle->OnIoThreadClientReady(new_child_id, new_route_id);
pending_throttles_.erase(it);
}
}
} // namespace android_webview