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
27using android_webview::AwContentsIoThreadClient;
28using content::BrowserThread;
29using navigation_interception::InterceptNavigationDelegate;
30
31namespace {
32
33base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate>
34    g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER;
35
36void 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
51namespace 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.
58class 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
76private:
77  int child_id_;
78  int route_id_;
79  net::URLRequest* request_;
80};
81
82IoThreadClientThrottle::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
89IoThreadClientThrottle::~IoThreadClientThrottle() {
90  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
91  g_webview_resource_dispatcher_host_delegate.Get().
92      RemovePendingThrottleOnIoThread(this);
93}
94
95void 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
108void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url,
109                                                 bool* defer) {
110  WillStartRequest(defer);
111}
112
113bool 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
128void 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
137bool IoThreadClientThrottle::MaybeBlockRequest() {
138  if (ShouldBlockRequest()) {
139    controller()->CancelWithError(net::ERR_ACCESS_DENIED);
140    return true;
141  }
142  return false;
143}
144
145bool 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
194void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() {
195  content::ResourceDispatcherHost::Get()->SetDelegate(
196      &g_webview_resource_dispatcher_host_delegate.Get());
197}
198
199AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate()
200    : content::ResourceDispatcherHostDelegate() {
201}
202
203AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() {
204}
205
206void 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    bool is_continuation_of_transferred_request,
214    ScopedVector<content::ResourceThrottle>* throttles) {
215  // If io_client is NULL, then the browser side objects have already been
216  // destroyed, so do not do anything to the request. Conversely if the
217  // request relates to a not-yet-created popup window, then the client will
218  // be non-NULL but PopupPendingAssociation() will be set.
219  scoped_ptr<AwContentsIoThreadClient> io_client =
220      AwContentsIoThreadClient::FromID(child_id, route_id);
221  if (!io_client)
222    return;
223
224  throttles->push_back(new IoThreadClientThrottle(
225      child_id, route_id, request));
226
227  // We only intercept navigations with main frames since this throttle is
228  // exclusively for posting onPageStarted's.
229  if (resource_type == ResourceType::MAIN_FRAME) {
230    throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor(
231        request));
232  }
233}
234
235void AwResourceDispatcherHostDelegate::DownloadStarting(
236    net::URLRequest* request,
237    content::ResourceContext* resource_context,
238    int child_id,
239    int route_id,
240    int request_id,
241    bool is_content_initiated,
242    bool must_download,
243    ScopedVector<content::ResourceThrottle>* throttles) {
244  GURL url(request->url());
245  std::string user_agent;
246  std::string content_disposition;
247  std::string mime_type;
248  int64 content_length = request->GetExpectedContentSize();
249
250  request->extra_request_headers().GetHeader(
251      net::HttpRequestHeaders::kUserAgent, &user_agent);
252
253
254  net::HttpResponseHeaders* response_headers = request->response_headers();
255  if (response_headers) {
256    response_headers->GetNormalizedHeader("content-disposition",
257        &content_disposition);
258    response_headers->GetMimeType(&mime_type);
259  }
260
261  request->Cancel();
262
263  scoped_ptr<AwContentsIoThreadClient> io_client =
264      AwContentsIoThreadClient::FromID(child_id, route_id);
265
266  // POST request cannot be repeated in general, so prevent client from
267  // retrying the same request, even if it is with a GET.
268  if ("GET" == request->method() && io_client) {
269    io_client->NewDownload(url,
270                           user_agent,
271                           content_disposition,
272                           mime_type,
273                           content_length);
274  }
275}
276
277bool AwResourceDispatcherHostDelegate::AcceptAuthRequest(
278    net::URLRequest* request,
279    net::AuthChallengeInfo* auth_info) {
280  return true;
281}
282
283content::ResourceDispatcherHostLoginDelegate*
284    AwResourceDispatcherHostDelegate::CreateLoginDelegate(
285        net::AuthChallengeInfo* auth_info,
286        net::URLRequest* request) {
287  return new AwLoginDelegate(auth_info, request);
288}
289
290bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(const GURL& url,
291                                                              int child_id,
292                                                              int route_id) {
293  // The AwURLRequestJobFactory implementation should ensure this method never
294  // gets called.
295  NOTREACHED();
296  return false;
297}
298
299void AwResourceDispatcherHostDelegate::OnResponseStarted(
300    net::URLRequest* request,
301    content::ResourceContext* resource_context,
302    content::ResourceResponse* response,
303    IPC::Sender* sender) {
304  const content::ResourceRequestInfo* request_info =
305      content::ResourceRequestInfo::ForRequest(request);
306  if (!request_info) {
307    DLOG(FATAL) << "Started request without associated info: " <<
308        request->url();
309    return;
310  }
311
312  if (request_info->GetResourceType() == ResourceType::MAIN_FRAME) {
313    // Check for x-auto-login header.
314    auto_login_parser::HeaderData header_data;
315    if (auto_login_parser::ParserHeaderInResponse(
316            request, auto_login_parser::ALLOW_ANY_REALM, &header_data)) {
317      scoped_ptr<AwContentsIoThreadClient> io_client =
318          AwContentsIoThreadClient::FromID(request_info->GetChildID(),
319                                           request_info->GetRouteID());
320      if (io_client) {
321        io_client->NewLoginRequest(
322            header_data.realm, header_data.account, header_data.args);
323      }
324    }
325  }
326}
327
328void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread(
329    IoThreadClientThrottle* throttle) {
330  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
331  PendingThrottleMap::iterator it = pending_throttles_.find(
332      ChildRouteIDPair(throttle->get_child_id(), throttle->get_route_id()));
333  if (it != pending_throttles_.end()) {
334    pending_throttles_.erase(it);
335  }
336}
337
338// static
339void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
340    int new_child_id,
341    int new_route_id) {
342  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
343      base::Bind(
344          &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal,
345          base::Unretained(
346              g_webview_resource_dispatcher_host_delegate.Pointer()),
347          new_child_id, new_route_id));
348}
349
350// static
351void AwResourceDispatcherHostDelegate::AddPendingThrottle(
352    int child_id,
353    int route_id,
354    IoThreadClientThrottle* pending_throttle) {
355  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
356      base::Bind(
357          &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread,
358          base::Unretained(
359              g_webview_resource_dispatcher_host_delegate.Pointer()),
360          child_id, route_id, pending_throttle));
361}
362
363void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
364    int child_id,
365    int route_id,
366    IoThreadClientThrottle* pending_throttle) {
367  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
368  pending_throttles_.insert(
369      std::pair<ChildRouteIDPair, IoThreadClientThrottle*>(
370          ChildRouteIDPair(child_id, route_id), pending_throttle));
371}
372
373void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
374    int new_child_id,
375    int new_route_id) {
376  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
377  PendingThrottleMap::iterator it = pending_throttles_.find(
378      ChildRouteIDPair(new_child_id, new_route_id));
379
380  if (it != pending_throttles_.end()) {
381    IoThreadClientThrottle* throttle = it->second;
382    throttle->OnIoThreadClientReady(new_child_id, new_route_id);
383    pending_throttles_.erase(it);
384  }
385}
386
387}  // namespace android_webview
388