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/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 "net/url_request/url_request_status.h"
28 #include "url/url_constants.h"
30 using android_webview::AwContentsIoThreadClient
;
31 using content::BrowserThread
;
32 using content::ResourceType
;
33 using navigation_interception::InterceptNavigationDelegate
;
37 base::LazyInstance
<android_webview::AwResourceDispatcherHostDelegate
>
38 g_webview_resource_dispatcher_host_delegate
= LAZY_INSTANCE_INITIALIZER
;
40 void SetCacheControlFlag(
41 net::URLRequest
* request
, int flag
) {
42 const int all_cache_control_flags
= net::LOAD_BYPASS_CACHE
|
43 net::LOAD_VALIDATE_CACHE
|
44 net::LOAD_PREFERRING_CACHE
|
45 net::LOAD_ONLY_FROM_CACHE
;
46 DCHECK_EQ((flag
& all_cache_control_flags
), flag
);
47 int load_flags
= request
->load_flags();
48 load_flags
&= ~all_cache_control_flags
;
50 request
->SetLoadFlags(load_flags
);
55 namespace android_webview
{
57 // Calls through the IoThreadClient to check the embedders settings to determine
58 // if the request should be cancelled. There may not always be an IoThreadClient
59 // available for the |render_process_id|, |render_frame_id| pair (in the case of
60 // newly created pop up windows, for example) and in that case the request and
61 // the client callbacks will be deferred the request until a client is ready.
62 class IoThreadClientThrottle
: public content::ResourceThrottle
{
64 IoThreadClientThrottle(int render_process_id
,
66 net::URLRequest
* request
);
67 ~IoThreadClientThrottle() override
;
69 // From content::ResourceThrottle
70 void WillStartRequest(bool* defer
) override
;
71 void WillRedirectRequest(const net::RedirectInfo
& redirect_info
,
72 bool* defer
) override
;
73 const char* GetNameForLogging() const override
;
75 void OnIoThreadClientReady(int new_render_process_id
,
76 int new_render_frame_id
);
77 bool MaybeBlockRequest();
78 bool ShouldBlockRequest();
79 int render_process_id() const { return render_process_id_
; }
80 int render_frame_id() const { return render_frame_id_
; }
83 int render_process_id_
;
85 net::URLRequest
* request_
;
88 IoThreadClientThrottle::IoThreadClientThrottle(int render_process_id
,
90 net::URLRequest
* request
)
91 : render_process_id_(render_process_id
),
92 render_frame_id_(render_frame_id
),
95 IoThreadClientThrottle::~IoThreadClientThrottle() {
96 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
97 g_webview_resource_dispatcher_host_delegate
.Get().
98 RemovePendingThrottleOnIoThread(this);
101 const char* IoThreadClientThrottle::GetNameForLogging() const {
102 return "IoThreadClientThrottle";
105 void IoThreadClientThrottle::WillStartRequest(bool* defer
) {
106 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
107 if (render_frame_id_
< 1)
109 DCHECK(render_process_id_
);
112 // Defer all requests of a pop up that is still not associated with Java
113 // client so that the client will get a chance to override requests.
114 scoped_ptr
<AwContentsIoThreadClient
> io_client
=
115 AwContentsIoThreadClient::FromID(render_process_id_
, render_frame_id_
);
116 if (io_client
&& io_client
->PendingAssociation()) {
118 AwResourceDispatcherHostDelegate::AddPendingThrottle(
119 render_process_id_
, render_frame_id_
, this);
125 void IoThreadClientThrottle::WillRedirectRequest(
126 const net::RedirectInfo
& redirect_info
,
128 WillStartRequest(defer
);
131 void IoThreadClientThrottle::OnIoThreadClientReady(int new_render_process_id
,
132 int new_render_frame_id
) {
133 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
135 if (!MaybeBlockRequest()) {
136 controller()->Resume();
140 bool IoThreadClientThrottle::MaybeBlockRequest() {
141 if (ShouldBlockRequest()) {
142 controller()->CancelWithError(net::ERR_ACCESS_DENIED
);
148 bool IoThreadClientThrottle::ShouldBlockRequest() {
149 scoped_ptr
<AwContentsIoThreadClient
> io_client
=
150 AwContentsIoThreadClient::FromID(render_process_id_
, render_frame_id_
);
154 // Part of implementation of WebSettings.allowContentAccess.
155 if (request_
->url().SchemeIs(url::kContentScheme
) &&
156 io_client
->ShouldBlockContentUrls()) {
160 // Part of implementation of WebSettings.allowFileAccess.
161 if (request_
->url().SchemeIsFile() &&
162 io_client
->ShouldBlockFileUrls()) {
163 // Application's assets and resources are always available.
164 return !IsAndroidSpecialFileUrl(request_
->url());
167 if (io_client
->ShouldBlockNetworkLoads()) {
168 if (request_
->url().SchemeIs(url::kFtpScheme
)) {
171 SetCacheControlFlag(request_
, net::LOAD_ONLY_FROM_CACHE
);
173 AwContentsIoThreadClient::CacheMode cache_mode
= io_client
->GetCacheMode();
175 case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK
:
176 SetCacheControlFlag(request_
, net::LOAD_PREFERRING_CACHE
);
178 case AwContentsIoThreadClient::LOAD_NO_CACHE
:
179 SetCacheControlFlag(request_
, net::LOAD_BYPASS_CACHE
);
181 case AwContentsIoThreadClient::LOAD_CACHE_ONLY
:
182 SetCacheControlFlag(request_
, net::LOAD_ONLY_FROM_CACHE
);
192 void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() {
193 content::ResourceDispatcherHost::Get()->SetDelegate(
194 &g_webview_resource_dispatcher_host_delegate
.Get());
197 AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate()
198 : content::ResourceDispatcherHostDelegate() {
201 AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() {
204 void AwResourceDispatcherHostDelegate::RequestBeginning(
205 net::URLRequest
* request
,
206 content::ResourceContext
* resource_context
,
207 content::AppCacheService
* appcache_service
,
208 ResourceType resource_type
,
209 ScopedVector
<content::ResourceThrottle
>* throttles
) {
211 AddExtraHeadersIfNeeded(request
, resource_context
);
213 const content::ResourceRequestInfo
* request_info
=
214 content::ResourceRequestInfo::ForRequest(request
);
216 // We always push the throttles here. Checking the existence of io_client
217 // is racy when a popup window is created. That is because RequestBeginning
218 // is called whether or not requests are blocked via BlockRequestForRoute()
219 // however io_client may or may not be ready at the time depending on whether
220 // webcontents is created.
221 throttles
->push_back(new IoThreadClientThrottle(
222 request_info
->GetChildID(), request_info
->GetRenderFrameID(), request
));
224 if (resource_type
!= content::RESOURCE_TYPE_MAIN_FRAME
)
225 InterceptNavigationDelegate::UpdateUserGestureCarryoverInfo(request
);
228 void AwResourceDispatcherHostDelegate::OnRequestRedirected(
229 const GURL
& redirect_url
,
230 net::URLRequest
* request
,
231 content::ResourceContext
* resource_context
,
232 content::ResourceResponse
* response
) {
233 AddExtraHeadersIfNeeded(request
, resource_context
);
236 void AwResourceDispatcherHostDelegate::RequestComplete(
237 net::URLRequest
* request
) {
238 if (request
&& !request
->status().is_success()) {
239 const content::ResourceRequestInfo
* request_info
=
240 content::ResourceRequestInfo::ForRequest(request
);
241 scoped_ptr
<AwContentsIoThreadClient
> io_client
=
242 AwContentsIoThreadClient::FromID(request_info
->GetChildID(),
243 request_info
->GetRenderFrameID());
245 io_client
->OnReceivedError(request
);
247 DLOG(WARNING
) << "io_client is null, onReceivedError dropped for " <<
254 void AwResourceDispatcherHostDelegate::DownloadStarting(
255 net::URLRequest
* request
,
256 content::ResourceContext
* resource_context
,
260 bool is_content_initiated
,
262 ScopedVector
<content::ResourceThrottle
>* throttles
) {
263 GURL
url(request
->url());
264 std::string user_agent
;
265 std::string content_disposition
;
266 std::string mime_type
;
267 int64 content_length
= request
->GetExpectedContentSize();
269 request
->extra_request_headers().GetHeader(
270 net::HttpRequestHeaders::kUserAgent
, &user_agent
);
273 net::HttpResponseHeaders
* response_headers
= request
->response_headers();
274 if (response_headers
) {
275 response_headers
->GetNormalizedHeader("content-disposition",
276 &content_disposition
);
277 response_headers
->GetMimeType(&mime_type
);
282 const content::ResourceRequestInfo
* request_info
=
283 content::ResourceRequestInfo::ForRequest(request
);
285 scoped_ptr
<AwContentsIoThreadClient
> io_client
=
286 AwContentsIoThreadClient::FromID(
287 child_id
, request_info
->GetRenderFrameID());
289 // POST request cannot be repeated in general, so prevent client from
290 // retrying the same request, even if it is with a GET.
291 if ("GET" == request
->method() && io_client
) {
292 io_client
->NewDownload(url
,
300 content::ResourceDispatcherHostLoginDelegate
*
301 AwResourceDispatcherHostDelegate::CreateLoginDelegate(
302 net::AuthChallengeInfo
* auth_info
,
303 net::URLRequest
* request
) {
304 return new AwLoginDelegate(auth_info
, request
);
307 bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(
312 ui::PageTransition page_transition
,
313 bool has_user_gesture
) {
314 // The AwURLRequestJobFactory implementation should ensure this method never
320 void AwResourceDispatcherHostDelegate::OnResponseStarted(
321 net::URLRequest
* request
,
322 content::ResourceContext
* resource_context
,
323 content::ResourceResponse
* response
,
324 IPC::Sender
* sender
) {
325 const content::ResourceRequestInfo
* request_info
=
326 content::ResourceRequestInfo::ForRequest(request
);
328 DLOG(FATAL
) << "Started request without associated info: " <<
333 if (request_info
->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME
) {
334 // Check for x-auto-login header.
335 auto_login_parser::HeaderData header_data
;
336 if (auto_login_parser::ParserHeaderInResponse(
337 request
, auto_login_parser::ALLOW_ANY_REALM
, &header_data
)) {
338 scoped_ptr
<AwContentsIoThreadClient
> io_client
=
339 AwContentsIoThreadClient::FromID(request_info
->GetChildID(),
340 request_info
->GetRenderFrameID());
342 io_client
->NewLoginRequest(
343 header_data
.realm
, header_data
.account
, header_data
.args
);
349 void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread(
350 IoThreadClientThrottle
* throttle
) {
351 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
352 PendingThrottleMap::iterator it
= pending_throttles_
.find(
353 FrameRouteIDPair(throttle
->render_process_id(),
354 throttle
->render_frame_id()));
355 if (it
!= pending_throttles_
.end()) {
356 pending_throttles_
.erase(it
);
361 void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
362 int new_render_process_id
,
363 int new_render_frame_id
) {
364 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
366 &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal
,
368 g_webview_resource_dispatcher_host_delegate
.Pointer()),
369 new_render_process_id
, new_render_frame_id
));
373 void AwResourceDispatcherHostDelegate::AddPendingThrottle(
374 int render_process_id
,
376 IoThreadClientThrottle
* pending_throttle
) {
377 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
379 &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread
,
381 g_webview_resource_dispatcher_host_delegate
.Pointer()),
382 render_process_id
, render_frame_id
, pending_throttle
));
385 void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
386 int render_process_id
,
387 int render_frame_id_id
,
388 IoThreadClientThrottle
* pending_throttle
) {
389 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
390 pending_throttles_
.insert(
391 std::pair
<FrameRouteIDPair
, IoThreadClientThrottle
*>(
392 FrameRouteIDPair(render_process_id
, render_frame_id_id
),
396 void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
397 int new_render_process_id
,
398 int new_render_frame_id
) {
399 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
400 PendingThrottleMap::iterator it
= pending_throttles_
.find(
401 FrameRouteIDPair(new_render_process_id
, new_render_frame_id
));
403 if (it
!= pending_throttles_
.end()) {
404 IoThreadClientThrottle
* throttle
= it
->second
;
405 throttle
->OnIoThreadClientReady(new_render_process_id
, new_render_frame_id
);
406 pending_throttles_
.erase(it
);
410 void AwResourceDispatcherHostDelegate::AddExtraHeadersIfNeeded(
411 net::URLRequest
* request
,
412 content::ResourceContext
* resource_context
) {
413 const content::ResourceRequestInfo
* request_info
=
414 content::ResourceRequestInfo::ForRequest(request
);
417 if (request_info
->GetResourceType() != content::RESOURCE_TYPE_MAIN_FRAME
)
420 const ui::PageTransition transition
= request_info
->GetPageTransition();
421 const bool is_load_url
=
422 transition
& ui::PAGE_TRANSITION_FROM_API
;
423 const bool is_go_back_forward
=
424 transition
& ui::PAGE_TRANSITION_FORWARD_BACK
;
425 const bool is_reload
= ui::PageTransitionCoreTypeIs(
426 transition
, ui::PAGE_TRANSITION_RELOAD
);
427 if (is_load_url
|| is_go_back_forward
|| is_reload
) {
428 AwResourceContext
* awrc
= static_cast<AwResourceContext
*>(resource_context
);
429 std::string extra_headers
= awrc
->GetExtraHeaders(request
->url());
430 if (!extra_headers
.empty()) {
431 net::HttpRequestHeaders headers
;
432 headers
.AddHeadersFromString(extra_headers
);
433 for (net::HttpRequestHeaders::Iterator
it(headers
); it
.GetNext(); ) {
434 request
->SetExtraRequestHeaderByName(it
.name(), it
.value(), false);
440 } // namespace android_webview