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/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 "url/url_constants.h"
28
29using android_webview::AwContentsIoThreadClient;
30using content::BrowserThread;
31using content::ResourceType;
32using navigation_interception::InterceptNavigationDelegate;
33
34namespace {
35
36base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate>
37    g_webview_resource_dispatcher_host_delegate = LAZY_INSTANCE_INITIALIZER;
38
39void SetCacheControlFlag(
40    net::URLRequest* request, int flag) {
41  const int all_cache_control_flags = net::LOAD_BYPASS_CACHE |
42      net::LOAD_VALIDATE_CACHE |
43      net::LOAD_PREFERRING_CACHE |
44      net::LOAD_ONLY_FROM_CACHE;
45  DCHECK_EQ((flag & all_cache_control_flags), flag);
46  int load_flags = request->load_flags();
47  load_flags &= ~all_cache_control_flags;
48  load_flags |= flag;
49  request->SetLoadFlags(load_flags);
50}
51
52}  // namespace
53
54namespace android_webview {
55
56// Calls through the IoThreadClient to check the embedders settings to determine
57// if the request should be cancelled. There may not always be an IoThreadClient
58// available for the |render_process_id|, |render_frame_id| pair (in the case of
59// newly created pop up windows, for example) and in that case the request and
60// the client callbacks will be deferred the request until a client is ready.
61class IoThreadClientThrottle : public content::ResourceThrottle {
62 public:
63  IoThreadClientThrottle(int render_process_id,
64                         int render_frame_id,
65                         net::URLRequest* request);
66  virtual ~IoThreadClientThrottle();
67
68  // From content::ResourceThrottle
69  virtual void WillStartRequest(bool* defer) OVERRIDE;
70  virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE;
71  virtual const char* GetNameForLogging() const OVERRIDE;
72
73  void OnIoThreadClientReady(int new_render_process_id,
74                             int new_render_frame_id);
75  bool MaybeBlockRequest();
76  bool ShouldBlockRequest();
77  int render_process_id() const { return render_process_id_; }
78  int render_frame_id() const { return render_frame_id_; }
79
80 private:
81  int render_process_id_;
82  int render_frame_id_;
83  net::URLRequest* request_;
84};
85
86IoThreadClientThrottle::IoThreadClientThrottle(int render_process_id,
87                                               int render_frame_id,
88                                               net::URLRequest* request)
89    : render_process_id_(render_process_id),
90      render_frame_id_(render_frame_id),
91      request_(request) { }
92
93IoThreadClientThrottle::~IoThreadClientThrottle() {
94  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
95  g_webview_resource_dispatcher_host_delegate.Get().
96      RemovePendingThrottleOnIoThread(this);
97}
98
99const char* IoThreadClientThrottle::GetNameForLogging() const {
100  return "IoThreadClientThrottle";
101}
102
103void IoThreadClientThrottle::WillStartRequest(bool* defer) {
104  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
105  // TODO(sgurun): This block can be removed when crbug.com/277937 is fixed.
106  if (render_frame_id_ < 1) {
107    // OPTIONS is used for preflighted requests which are generated internally.
108    DCHECK_EQ("OPTIONS", request_->method());
109    return;
110  }
111  DCHECK(render_process_id_);
112  *defer = false;
113
114  // Defer all requests of a pop up that is still not associated with Java
115  // client so that the client will get a chance to override requests.
116  scoped_ptr<AwContentsIoThreadClient> io_client =
117      AwContentsIoThreadClient::FromID(render_process_id_, render_frame_id_);
118  if (io_client && io_client->PendingAssociation()) {
119    *defer = true;
120    AwResourceDispatcherHostDelegate::AddPendingThrottle(
121        render_process_id_, render_frame_id_, this);
122  } else {
123    MaybeBlockRequest();
124  }
125}
126
127void IoThreadClientThrottle::WillRedirectRequest(const GURL& new_url,
128                                                 bool* defer) {
129  WillStartRequest(defer);
130}
131
132void IoThreadClientThrottle::OnIoThreadClientReady(int new_render_process_id,
133                                                   int new_render_frame_id) {
134  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
135
136  if (!MaybeBlockRequest()) {
137    controller()->Resume();
138  }
139}
140
141bool IoThreadClientThrottle::MaybeBlockRequest() {
142  if (ShouldBlockRequest()) {
143    controller()->CancelWithError(net::ERR_ACCESS_DENIED);
144    return true;
145  }
146  return false;
147}
148
149bool IoThreadClientThrottle::ShouldBlockRequest() {
150  scoped_ptr<AwContentsIoThreadClient> io_client =
151      AwContentsIoThreadClient::FromID(render_process_id_, render_frame_id_);
152  if (!io_client)
153    return false;
154
155  // Part of implementation of WebSettings.allowContentAccess.
156  if (request_->url().SchemeIs(android_webview::kContentScheme) &&
157      io_client->ShouldBlockContentUrls()) {
158    return true;
159  }
160
161  // Part of implementation of WebSettings.allowFileAccess.
162  if (request_->url().SchemeIsFile() &&
163      io_client->ShouldBlockFileUrls()) {
164    const GURL& url = request_->url();
165    if (!url.has_path() ||
166        // Application's assets and resources are always available.
167        (url.path().find(android_webview::kAndroidResourcePath) != 0 &&
168         url.path().find(android_webview::kAndroidAssetPath) != 0)) {
169      return true;
170    }
171  }
172
173  if (io_client->ShouldBlockNetworkLoads()) {
174    if (request_->url().SchemeIs(url::kFtpScheme)) {
175      return true;
176    }
177    SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
178  } else {
179    AwContentsIoThreadClient::CacheMode cache_mode = io_client->GetCacheMode();
180    switch(cache_mode) {
181      case AwContentsIoThreadClient::LOAD_CACHE_ELSE_NETWORK:
182        SetCacheControlFlag(request_, net::LOAD_PREFERRING_CACHE);
183        break;
184      case AwContentsIoThreadClient::LOAD_NO_CACHE:
185        SetCacheControlFlag(request_, net::LOAD_BYPASS_CACHE);
186        break;
187      case AwContentsIoThreadClient::LOAD_CACHE_ONLY:
188        SetCacheControlFlag(request_, net::LOAD_ONLY_FROM_CACHE);
189        break;
190      default:
191        break;
192    }
193  }
194  return false;
195}
196
197// static
198void AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated() {
199  content::ResourceDispatcherHost::Get()->SetDelegate(
200      &g_webview_resource_dispatcher_host_delegate.Get());
201}
202
203AwResourceDispatcherHostDelegate::AwResourceDispatcherHostDelegate()
204    : content::ResourceDispatcherHostDelegate() {
205}
206
207AwResourceDispatcherHostDelegate::~AwResourceDispatcherHostDelegate() {
208}
209
210void AwResourceDispatcherHostDelegate::RequestBeginning(
211    net::URLRequest* request,
212    content::ResourceContext* resource_context,
213    content::AppCacheService* appcache_service,
214    ResourceType resource_type,
215    ScopedVector<content::ResourceThrottle>* throttles) {
216
217  AddExtraHeadersIfNeeded(request, resource_context);
218
219  const content::ResourceRequestInfo* request_info =
220      content::ResourceRequestInfo::ForRequest(request);
221
222  // We always push the throttles here. Checking the existence of io_client
223  // is racy when a popup window is created. That is because RequestBeginning
224  // is called whether or not requests are blocked via BlockRequestForRoute()
225  // however io_client may or may not be ready at the time depending on whether
226  // webcontents is created.
227  throttles->push_back(new IoThreadClientThrottle(
228      request_info->GetChildID(), request_info->GetRenderFrameID(), request));
229
230  // We allow intercepting only navigations within main frames. This
231  // is used to post onPageStarted. We handle shouldOverrideUrlLoading
232  // via a sync IPC.
233  if (resource_type == content::RESOURCE_TYPE_MAIN_FRAME)
234    throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor(
235        request));
236}
237
238void AwResourceDispatcherHostDelegate::OnRequestRedirected(
239    const GURL& redirect_url,
240    net::URLRequest* request,
241    content::ResourceContext* resource_context,
242    content::ResourceResponse* response) {
243  AddExtraHeadersIfNeeded(request, resource_context);
244}
245
246
247void AwResourceDispatcherHostDelegate::DownloadStarting(
248    net::URLRequest* request,
249    content::ResourceContext* resource_context,
250    int child_id,
251    int route_id,
252    int request_id,
253    bool is_content_initiated,
254    bool must_download,
255    ScopedVector<content::ResourceThrottle>* throttles) {
256  GURL url(request->url());
257  std::string user_agent;
258  std::string content_disposition;
259  std::string mime_type;
260  int64 content_length = request->GetExpectedContentSize();
261
262  request->extra_request_headers().GetHeader(
263      net::HttpRequestHeaders::kUserAgent, &user_agent);
264
265
266  net::HttpResponseHeaders* response_headers = request->response_headers();
267  if (response_headers) {
268    response_headers->GetNormalizedHeader("content-disposition",
269        &content_disposition);
270    response_headers->GetMimeType(&mime_type);
271  }
272
273  request->Cancel();
274
275  const content::ResourceRequestInfo* request_info =
276      content::ResourceRequestInfo::ForRequest(request);
277
278  scoped_ptr<AwContentsIoThreadClient> io_client =
279      AwContentsIoThreadClient::FromID(
280          child_id, request_info->GetRenderFrameID());
281
282  // POST request cannot be repeated in general, so prevent client from
283  // retrying the same request, even if it is with a GET.
284  if ("GET" == request->method() && io_client) {
285    io_client->NewDownload(url,
286                           user_agent,
287                           content_disposition,
288                           mime_type,
289                           content_length);
290  }
291}
292
293content::ResourceDispatcherHostLoginDelegate*
294    AwResourceDispatcherHostDelegate::CreateLoginDelegate(
295        net::AuthChallengeInfo* auth_info,
296        net::URLRequest* request) {
297  return new AwLoginDelegate(auth_info, request);
298}
299
300bool AwResourceDispatcherHostDelegate::HandleExternalProtocol(const GURL& url,
301                                                              int child_id,
302                                                              int route_id) {
303  // The AwURLRequestJobFactory implementation should ensure this method never
304  // gets called.
305  NOTREACHED();
306  return false;
307}
308
309void AwResourceDispatcherHostDelegate::OnResponseStarted(
310    net::URLRequest* request,
311    content::ResourceContext* resource_context,
312    content::ResourceResponse* response,
313    IPC::Sender* sender) {
314  const content::ResourceRequestInfo* request_info =
315      content::ResourceRequestInfo::ForRequest(request);
316  if (!request_info) {
317    DLOG(FATAL) << "Started request without associated info: " <<
318        request->url();
319    return;
320  }
321
322  if (request_info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME) {
323    // Check for x-auto-login header.
324    auto_login_parser::HeaderData header_data;
325    if (auto_login_parser::ParserHeaderInResponse(
326            request, auto_login_parser::ALLOW_ANY_REALM, &header_data)) {
327      scoped_ptr<AwContentsIoThreadClient> io_client =
328          AwContentsIoThreadClient::FromID(request_info->GetChildID(),
329                                           request_info->GetRenderFrameID());
330      if (io_client) {
331        io_client->NewLoginRequest(
332            header_data.realm, header_data.account, header_data.args);
333      }
334    }
335  }
336}
337
338void AwResourceDispatcherHostDelegate::RemovePendingThrottleOnIoThread(
339    IoThreadClientThrottle* throttle) {
340  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
341  PendingThrottleMap::iterator it = pending_throttles_.find(
342      FrameRouteIDPair(throttle->render_process_id(),
343                       throttle->render_frame_id()));
344  if (it != pending_throttles_.end()) {
345    pending_throttles_.erase(it);
346  }
347}
348
349// static
350void AwResourceDispatcherHostDelegate::OnIoThreadClientReady(
351    int new_render_process_id,
352    int new_render_frame_id) {
353  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
354      base::Bind(
355          &AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal,
356          base::Unretained(
357              g_webview_resource_dispatcher_host_delegate.Pointer()),
358          new_render_process_id, new_render_frame_id));
359}
360
361// static
362void AwResourceDispatcherHostDelegate::AddPendingThrottle(
363    int render_process_id,
364    int render_frame_id,
365    IoThreadClientThrottle* pending_throttle) {
366  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
367      base::Bind(
368          &AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread,
369          base::Unretained(
370              g_webview_resource_dispatcher_host_delegate.Pointer()),
371          render_process_id, render_frame_id, pending_throttle));
372}
373
374void AwResourceDispatcherHostDelegate::AddPendingThrottleOnIoThread(
375    int render_process_id,
376    int render_frame_id_id,
377    IoThreadClientThrottle* pending_throttle) {
378  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
379  pending_throttles_.insert(
380      std::pair<FrameRouteIDPair, IoThreadClientThrottle*>(
381          FrameRouteIDPair(render_process_id, render_frame_id_id),
382          pending_throttle));
383}
384
385void AwResourceDispatcherHostDelegate::OnIoThreadClientReadyInternal(
386    int new_render_process_id,
387    int new_render_frame_id) {
388  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
389  PendingThrottleMap::iterator it = pending_throttles_.find(
390      FrameRouteIDPair(new_render_process_id, new_render_frame_id));
391
392  if (it != pending_throttles_.end()) {
393    IoThreadClientThrottle* throttle = it->second;
394    throttle->OnIoThreadClientReady(new_render_process_id, new_render_frame_id);
395    pending_throttles_.erase(it);
396  }
397}
398
399void AwResourceDispatcherHostDelegate::AddExtraHeadersIfNeeded(
400    net::URLRequest* request,
401    content::ResourceContext* resource_context) {
402  const content::ResourceRequestInfo* request_info =
403      content::ResourceRequestInfo::ForRequest(request);
404  if (!request_info)
405    return;
406  if (request_info->GetResourceType() != content::RESOURCE_TYPE_MAIN_FRAME)
407    return;
408
409  const ui::PageTransition transition = request_info->GetPageTransition();
410  const bool is_load_url =
411      transition & ui::PAGE_TRANSITION_FROM_API;
412  const bool is_go_back_forward =
413      transition & ui::PAGE_TRANSITION_FORWARD_BACK;
414  const bool is_reload = ui::PageTransitionCoreTypeIs(
415      transition, ui::PAGE_TRANSITION_RELOAD);
416  if (is_load_url || is_go_back_forward || is_reload) {
417    AwResourceContext* awrc = static_cast<AwResourceContext*>(resource_context);
418    std::string extra_headers = awrc->GetExtraHeaders(request->url());
419    if (!extra_headers.empty()) {
420      net::HttpRequestHeaders headers;
421      headers.AddHeadersFromString(extra_headers);
422      for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext(); ) {
423        request->SetExtraRequestHeaderByName(it.name(), it.value(), false);
424      }
425    }
426  }
427}
428
429}  // namespace android_webview
430