- add sources.
[platform/framework/web/crosswalk.git] / src / android_webview / browser / renderer_host / aw_resource_dispatcher_host_delegate.cc
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/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"
26
27 using android_webview::AwContentsIoThreadClient;
28 using content::BrowserThread;
29 using navigation_interception::InterceptNavigationDelegate;
30
31 namespace {
32
33 base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate>
34     g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER;
35
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;
45   load_flags |= flag;
46   request->set_load_flags(load_flags);
47 }
48
49 }  // namespace
50
51 namespace android_webview {
52
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 {
59  public:
60   IoThreadClientThrottle(int child_id,
61                          int route_id,
62                          net::URLRequest* request);
63   virtual ~IoThreadClientThrottle();
64
65   // From content::ResourceThrottle
66   virtual void WillStartRequest(bool* defer) OVERRIDE;
67   virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE;
68
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_; }
75
76 private:
77   int child_id_;
78   int route_id_;
79   net::URLRequest* request_;
80 };
81
82 IoThreadClientThrottle::IoThreadClientThrottle(int child_id,
83                                                int route_id,
84                                                net::URLRequest* request)
85     : child_id_(child_id),
86       route_id_(route_id),
87       request_(request) { }
88
89 IoThreadClientThrottle::~IoThreadClientThrottle() {
90   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
91   g_webview_resource_dispatcher_host_delegate.Get().
92       RemovePendingThrottleOnIoThread(this);
93 }
94
95 void IoThreadClientThrottle::WillStartRequest(bool* defer) {
96   // TODO(sgurun): This block can be removed when crbug.com/277937 is fixed.
97   if (route_id_ < 1) {
98     // OPTIONS is used for preflighted requests which are generated internally.
99     DCHECK_EQ("OPTIONS", request_->method());
100     return;
101   }
102   DCHECK(child_id_);
103   if (!MaybeDeferRequest(defer)) {
104     MaybeBlockRequest();
105   }
106 }
107
108 void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url,
109                                                  bool* defer) {
110   WillStartRequest(defer);
111 }
112
113 bool IoThreadClientThrottle::MaybeDeferRequest(bool* defer) {
114   *defer = false;
115
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()) {
121     *defer = true;
122     AwResourceDispatcherHostDelegate::AddPendingThrottle(
123         child_id_, route_id_, this);
124   }
125   return *defer;
126 }
127
128 void IoThreadClientThrottle::OnIoThreadClientReady(int new_child_id,
129                                                    int new_route_id) {
130   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
131
132   if (!MaybeBlockRequest()) {
133     controller()->Resume();
134   }
135 }
136
137 bool IoThreadClientThrottle::MaybeBlockRequest() {
138   if (ShouldBlockRequest()) {
139     controller()->CancelWithError(net::ERR_ACCESS_DENIED);
140     return true;
141   }
142   return false;
143 }
144
145 bool IoThreadClientThrottle::ShouldBlockRequest() {
146   scoped_ptr<AwContentsIoThreadClient> io_client =
147       AwContentsIoThreadClient::FromID(child_id_, route_id_);
148   if (!io_client)
149     return false;
150
151   // Part of implementation of WebSettings.allowContentAccess.
152   if (request_->url().SchemeIs(android_webview::kContentScheme) &&
153       io_client->ShouldBlockContentUrls()) {
154     return true;
155   }
156
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)) {
165       return true;
166     }
167   }
168
169   if (io_client->ShouldBlockNetworkLoads()) {
170     if (request_->url().SchemeIs(chrome::kFtpScheme)) {
171       return true;
172     }
173     SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
174   } else {
175     AwContentsIoThreadClient::CacheMode cache_mode = io_client->GetCacheMode();
176     switch(cache_mode) {
177       case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK:
178         SetCacheControlFlag(request_, net::LOAD_PREFERRING_CACHE);
179         break;
180       case AwContentsIoThreadClient::LOAD_NO_CACHE:
181         SetCacheControlFlag(request_, net::LOAD_BYPASS_CACHE);
182         break;
183       case AwContentsIoThreadClient::LOAD_CACHE_ONLY:
184         SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
185         break;
186       default:
187         break;
188     }
189   }
190   return false;
191 }
192
193 // static
194 void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() {
195   content::ResourceDispatcherHost::Get()->SetDelegate(
196       &g_webview_resource_dispatcher_host_delegate.Get());
197 }
198
199 AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate()
200     : content::ResourceDispatcherHostDelegate() {
201 }
202
203 AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() {
204 }
205
206 void AwResourceDispatcherHostDelegate::RequestBeginning(
207     net::URLRequest* request,
208     content::ResourceContext* resource_context,
209     appcache::AppCacheService* appcache_service,
210     ResourceType::Type resource_type,
211     int child_id,
212     int route_id,
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);
220   if (!io_client)
221     return;
222
223   throttles->push_back(new IoThreadClientThrottle(
224       child_id, route_id, request));
225
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
234       // embedder.
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(
241         request));
242   }
243 }
244
245 void AwResourceDispatcherHostDelegate::DownloadStarting(
246     net::URLRequest* request,
247     content::ResourceContext* resource_context,
248     int child_id,
249     int route_id,
250     int request_id,
251     bool is_content_initiated,
252     bool must_download,
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();
259
260   request->extra_request_headers().GetHeader(
261       net::HttpRequestHeaders::kUserAgent, &user_agent);
262
263
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);
269   }
270
271   request->Cancel();
272
273   scoped_ptr<AwContentsIoThreadClient> io_client =
274       AwContentsIoThreadClient::FromID(child_id, route_id);
275
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,
280                            user_agent,
281                            content_disposition,
282                            mime_type,
283                            content_length);
284   }
285 }
286
287 bool AwResourceDispatcherHostDelegate::AcceptAuthRequest(
288     net::URLRequest* request,
289     net::AuthChallengeInfo* auth_info) {
290   return true;
291 }
292
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
302   // behavior.
303   return true;
304 }
305
306 content::ResourceDispatcherHostLoginDelegate*
307     AwResourceDispatcherHostDelegate::CreateLoginDelegate(
308         net::AuthChallengeInfo* auth_info,
309         net::URLRequest* request) {
310   return new AwLoginDelegate(auth_info, request);
311 }
312
313 bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(const GURL& url,
314                                                               int child_id,
315                                                               int route_id) {
316   // The AwURLRequestJobFactory implementation should ensure this method never
317   // gets called.
318   NOTREACHED();
319   return false;
320 }
321
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);
329   if (!request_info) {
330     DLOG(FATAL) << "Started request without associated info: " <<
331         request->url();
332     return;
333   }
334
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());
343       if (io_client) {
344         io_client->NewLoginRequest(
345             header_data.realm, header_data.account, header_data.args);
346       }
347     }
348   }
349 }
350
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);
358   }
359 }
360
361 // static
362 void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
363     int new_child_id,
364     int new_route_id) {
365   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
366       base::Bind(
367           &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal,
368           base::Unretained(
369               g_webview_resource_dispatcher_host_delegate.Pointer()),
370           new_child_id, new_route_id));
371 }
372
373 // static
374 void AwResourceDispatcherHostDelegate::AddPendingThrottle(
375     int child_id,
376     int route_id,
377     IoThreadClientThrottle* pending_throttle) {
378   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
379       base::Bind(
380           &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread,
381           base::Unretained(
382               g_webview_resource_dispatcher_host_delegate.Pointer()),
383           child_id, route_id, pending_throttle));
384 }
385
386 void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
387     int child_id,
388     int route_id,
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));
394 }
395
396 void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
397     int new_child_id,
398     int new_route_id) {
399   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
400   PendingThrottleMap::iterator it = pending_throttles_.find(
401       ChildRouteIDPair(new_child_id, new_route_id));
402
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);
407   }
408 }
409
410 }  // namespace android_webview