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.
5 #include "android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h"
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"
27 using android_webview::AwContentsIoThreadClient;
28 using content::BrowserThread;
29 using navigation_interception::InterceptNavigationDelegate;
33 base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate>
34 g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER;
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;
46 request->set_load_flags(load_flags);
51 namespace android_webview {
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 {
60 IoThreadClientThrottle(int child_id,
62 net::URLRequest* request);
63 virtual ~IoThreadClientThrottle();
65 // From content::ResourceThrottle
66 virtual void WillStartRequest(bool* defer) OVERRIDE;
67 virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE;
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_; }
79 net::URLRequest* request_;
82 IoThreadClientThrottle::IoThreadClientThrottle(int child_id,
84 net::URLRequest* request)
85 : child_id_(child_id),
89 IoThreadClientThrottle::~IoThreadClientThrottle() {
90 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
91 g_webview_resource_dispatcher_host_delegate.Get().
92 RemovePendingThrottleOnIoThread(this);
95 void IoThreadClientThrottle::WillStartRequest(bool* defer) {
96 // TODO(sgurun): This block can be removed when crbug.com/277937 is fixed.
98 // OPTIONS is used for preflighted requests which are generated internally.
99 DCHECK_EQ("OPTIONS", request_->method());
103 if (!MaybeDeferRequest(defer)) {
108 void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url,
110 WillStartRequest(defer);
113 bool IoThreadClientThrottle::MaybeDeferRequest(bool* defer) {
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()) {
122 AwResourceDispatcherHostDelegate::AddPendingThrottle(
123 child_id_, route_id_, this);
128 void IoThreadClientThrottle::OnIoThreadClientReady(int new_child_id,
130 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
132 if (!MaybeBlockRequest()) {
133 controller()->Resume();
137 bool IoThreadClientThrottle::MaybeBlockRequest() {
138 if (ShouldBlockRequest()) {
139 controller()->CancelWithError(net::ERR_ACCESS_DENIED);
145 bool IoThreadClientThrottle::ShouldBlockRequest() {
146 scoped_ptr<AwContentsIoThreadClient> io_client =
147 AwContentsIoThreadClient::FromID(child_id_, route_id_);
151 // Part of implementation of WebSettings.allowContentAccess.
152 if (request_->url().SchemeIs(android_webview::kContentScheme) &&
153 io_client->ShouldBlockContentUrls()) {
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)) {
169 if (io_client->ShouldBlockNetworkLoads()) {
170 if (request_->url().SchemeIs(chrome::kFtpScheme)) {
173 SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
175 AwContentsIoThreadClient::CacheMode cache_mode = io_client->GetCacheMode();
177 case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK:
178 SetCacheControlFlag(request_, net::LOAD_PREFERRING_CACHE);
180 case AwContentsIoThreadClient::LOAD_NO_CACHE:
181 SetCacheControlFlag(request_, net::LOAD_BYPASS_CACHE);
183 case AwContentsIoThreadClient::LOAD_CACHE_ONLY:
184 SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
194 void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() {
195 content::ResourceDispatcherHost::Get()->SetDelegate(
196 &g_webview_resource_dispatcher_host_delegate.Get());
199 AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate()
200 : content::ResourceDispatcherHostDelegate() {
203 AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() {
206 void AwResourceDispatcherHostDelegate::RequestBeginning(
207 net::URLRequest* request,
208 content::ResourceContext* resource_context,
209 appcache::AppCacheService* appcache_service,
210 ResourceType::Type resource_type,
213 ScopedVector<content::ResourceThrottle>* throttles) {
214 // If io_client is NULL, then the browser side objects have already been
215 // destroyed, so do not do anything to the request. Conversely if the
216 // request relates to a not-yet-created popup window, then the client will
217 // be non-NULL but PopupPendingAssociation() will be set.
218 scoped_ptr<AwContentsIoThreadClient> io_client =
219 AwContentsIoThreadClient::FromID(child_id, route_id);
223 throttles->push_back(new IoThreadClientThrottle(
224 child_id, route_id, request));
226 bool allow_intercepting =
227 // We allow intercepting navigations within subframes, but only if the
228 // scheme other than http or https. This is because the embedder
229 // can't distinguish main frame and subframe callbacks (which could lead
230 // to broken content if the embedder decides to not ignore the main frame
231 // navigation, but ignores the subframe navigation).
232 // The reason this is supported at all is that certain JavaScript-based
233 // frameworks use iframe navigation as a form of communication with the
235 (resource_type == ResourceType::MAIN_FRAME ||
236 (resource_type == ResourceType::SUB_FRAME &&
237 !request->url().SchemeIs(content::kHttpScheme) &&
238 !request->url().SchemeIs(content::kHttpsScheme)));
239 if (allow_intercepting) {
240 throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor(
245 void AwResourceDispatcherHostDelegate::DownloadStarting(
246 net::URLRequest* request,
247 content::ResourceContext* resource_context,
251 bool is_content_initiated,
253 ScopedVector<content::ResourceThrottle>* throttles) {
254 GURL url(request->url());
255 std::string user_agent;
256 std::string content_disposition;
257 std::string mime_type;
258 int64 content_length = request->GetExpectedContentSize();
260 request->extra_request_headers().GetHeader(
261 net::HttpRequestHeaders::kUserAgent, &user_agent);
264 net::HttpResponseHeaders* response_headers = request->response_headers();
265 if (response_headers) {
266 response_headers->GetNormalizedHeader("content-disposition",
267 &content_disposition);
268 response_headers->GetMimeType(&mime_type);
273 scoped_ptr<AwContentsIoThreadClient> io_client =
274 AwContentsIoThreadClient::FromID(child_id, route_id);
276 // POST request cannot be repeated in general, so prevent client from
277 // retrying the same request, even if it is with a GET.
278 if ("GET" == request->method() && io_client) {
279 io_client->NewDownload(url,
287 bool AwResourceDispatcherHostDelegate::AcceptAuthRequest(
288 net::URLRequest* request,
289 net::AuthChallengeInfo* auth_info) {
293 bool AwResourceDispatcherHostDelegate::AcceptSSLClientCertificateRequest(
294 net::URLRequest* request,
295 net::SSLCertRequestInfo* cert_info) {
296 // WebView does not support client certificate selection, however it does
297 // send a no-certificate response to the server to allow it decide how to
298 // proceed. The base class returns false here, which causes the entire
299 // resource request to be abort. We don't want that, so we must return true
300 // here (and subsequently complete the request in
301 // AwContentBrowserClient::SelectClientCertificate) to get the intended
306 content::ResourceDispatcherHostLoginDelegate*
307 AwResourceDispatcherHostDelegate::CreateLoginDelegate(
308 net::AuthChallengeInfo* auth_info,
309 net::URLRequest* request) {
310 return new AwLoginDelegate(auth_info, request);
313 bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(const GURL& url,
316 // The AwURLRequestJobFactory implementation should ensure this method never
322 void AwResourceDispatcherHostDelegate::OnResponseStarted(
323 net::URLRequest* request,
324 content::ResourceContext* resource_context,
325 content::ResourceResponse* response,
326 IPC::Sender* sender) {
327 const content::ResourceRequestInfo* request_info =
328 content::ResourceRequestInfo::ForRequest(request);
330 DLOG(FATAL) << "Started request without associated info: " <<
335 if (request_info->GetResourceType() == ResourceType::MAIN_FRAME) {
336 // Check for x-auto-login header.
337 auto_login_parser::HeaderData header_data;
338 if (auto_login_parser::ParserHeaderInResponse(
339 request, auto_login_parser::ALLOW_ANY_REALM, &header_data)) {
340 scoped_ptr<AwContentsIoThreadClient> io_client =
341 AwContentsIoThreadClient::FromID(request_info->GetChildID(),
342 request_info->GetRouteID());
344 io_client->NewLoginRequest(
345 header_data.realm, header_data.account, header_data.args);
351 void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread(
352 IoThreadClientThrottle* throttle) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
354 PendingThrottleMap::iterator it = pending_throttles_.find(
355 ChildRouteIDPair(throttle->get_child_id(), throttle->get_route_id()));
356 if (it != pending_throttles_.end()) {
357 pending_throttles_.erase(it);
362 void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
365 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
367 &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal,
369 g_webview_resource_dispatcher_host_delegate.Pointer()),
370 new_child_id, new_route_id));
374 void AwResourceDispatcherHostDelegate::AddPendingThrottle(
377 IoThreadClientThrottle* pending_throttle) {
378 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
380 &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread,
382 g_webview_resource_dispatcher_host_delegate.Pointer()),
383 child_id, route_id, pending_throttle));
386 void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
389 IoThreadClientThrottle* pending_throttle) {
390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
391 pending_throttles_.insert(
392 std::pair<ChildRouteIDPair, IoThreadClientThrottle*>(
393 ChildRouteIDPair(child_id, route_id), pending_throttle));
396 void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
400 PendingThrottleMap::iterator it = pending_throttles_.find(
401 ChildRouteIDPair(new_child_id, new_route_id));
403 if (it != pending_throttles_.end()) {
404 IoThreadClientThrottle* throttle = it->second;
405 throttle->OnIoThreadClientReady(new_child_id, new_route_id);
406 pending_throttles_.erase(it);
410 } // namespace android_webview