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 "chrome/browser/download/download_request_limiter.h"
6
7#include "base/bind.h"
8#include "base/stl_util.h"
9#include "chrome/browser/content_settings/host_content_settings_map.h"
10#include "chrome/browser/content_settings/tab_specific_content_settings.h"
11#include "chrome/browser/download/download_permission_request.h"
12#include "chrome/browser/download/download_request_infobar_delegate.h"
13#include "chrome/browser/infobars/infobar_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/tab_contents/tab_util.h"
16#include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
17#include "content/public/browser/browser_context.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/navigation_controller.h"
20#include "content/public/browser/navigation_entry.h"
21#include "content/public/browser/notification_source.h"
22#include "content/public/browser/notification_types.h"
23#include "content/public/browser/render_process_host.h"
24#include "content/public/browser/resource_dispatcher_host.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/browser/web_contents_delegate.h"
27#include "url/gurl.h"
28
29using content::BrowserThread;
30using content::NavigationController;
31using content::NavigationEntry;
32
33// TabDownloadState ------------------------------------------------------------
34
35DownloadRequestLimiter::TabDownloadState::TabDownloadState(
36    DownloadRequestLimiter* host,
37    content::WebContents* contents,
38    content::WebContents* originating_web_contents)
39    : content::WebContentsObserver(contents),
40      web_contents_(contents),
41      host_(host),
42      status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
43      download_count_(0),
44      factory_(this) {
45  content::Source<NavigationController> notification_source(
46      &contents->GetController());
47  registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
48                 notification_source);
49  NavigationEntry* active_entry = originating_web_contents ?
50      originating_web_contents->GetController().GetActiveEntry() :
51      contents->GetController().GetActiveEntry();
52  if (active_entry)
53    initial_page_host_ = active_entry->GetURL().host();
54}
55
56DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
57  // We should only be destroyed after the callbacks have been notified.
58  DCHECK(callbacks_.empty());
59
60  // And we should have invalidated the back pointer.
61  DCHECK(!factory_.HasWeakPtrs());
62}
63
64void DownloadRequestLimiter::TabDownloadState::AboutToNavigateRenderView(
65    content::RenderViewHost* render_view_host) {
66  switch (status_) {
67    case ALLOW_ONE_DOWNLOAD:
68    case PROMPT_BEFORE_DOWNLOAD:
69      // When the user reloads the page without responding to the infobar, they
70      // are expecting DownloadRequestLimiter to behave as if they had just
71      // initially navigated to this page. See http://crbug.com/171372
72      NotifyCallbacks(false);
73      host_->Remove(this, web_contents());
74      // WARNING: We've been deleted.
75      break;
76    case DOWNLOADS_NOT_ALLOWED:
77    case ALLOW_ALL_DOWNLOADS:
78      // Don't drop this information. The user has explicitly said that they
79      // do/don't want downloads from this host.  If they accidentally Accepted
80      // or Canceled, tough luck, they don't get another chance. They can copy
81      // the URL into a new tab, which will make a new DownloadRequestLimiter.
82      // See also the initial_page_host_ logic in Observe() for
83      // NOTIFICATION_NAV_ENTRY_PENDING.
84      break;
85    default:
86      NOTREACHED();
87  }
88}
89
90void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
91  if (is_showing_prompt()) {
92    // Don't change the state if the user clicks on the page somewhere.
93    return;
94  }
95
96  bool promptable = (InfoBarService::FromWebContents(web_contents()) != NULL);
97  if (PermissionBubbleManager::Enabled()) {
98    promptable =
99        (PermissionBubbleManager::FromWebContents(web_contents()) != NULL);
100  }
101
102  // See PromptUserForDownload(): if there's no InfoBarService, then
103  // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD.
104  if ((status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS) &&
105      (!promptable ||
106       (status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED))) {
107    // Revert to default status.
108    host_->Remove(this, web_contents());
109    // WARNING: We've been deleted.
110  }
111}
112
113void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
114  // Tab closed, no need to handle closing the dialog as it's owned by the
115  // WebContents.
116
117  NotifyCallbacks(false);
118  host_->Remove(this, web_contents());
119  // WARNING: We've been deleted.
120}
121
122void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
123    const DownloadRequestLimiter::Callback& callback) {
124  callbacks_.push_back(callback);
125  DCHECK(web_contents_);
126  if (is_showing_prompt())
127    return;
128
129  if (PermissionBubbleManager::Enabled()) {
130    PermissionBubbleManager* bubble_manager =
131        PermissionBubbleManager::FromWebContents(web_contents_);
132    if (bubble_manager) {
133      bubble_manager->AddRequest(new DownloadPermissionRequest(
134          factory_.GetWeakPtr()));
135    } else {
136      Cancel();
137    }
138    return;
139  }
140
141  DownloadRequestInfoBarDelegate::Create(
142      InfoBarService::FromWebContents(web_contents_), factory_.GetWeakPtr());
143}
144
145void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
146    ContentSetting setting) {
147  if (!web_contents_)
148    return;
149  HostContentSettingsMap* settings =
150    DownloadRequestLimiter::GetContentSettings(web_contents_);
151  ContentSettingsPattern pattern(
152      ContentSettingsPattern::FromURL(web_contents_->GetURL()));
153  if (!settings || !pattern.IsValid())
154    return;
155  settings->SetContentSetting(
156      pattern,
157      ContentSettingsPattern::Wildcard(),
158      CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
159      std::string(),
160      setting);
161}
162
163void DownloadRequestLimiter::TabDownloadState::Cancel() {
164  SetContentSetting(CONTENT_SETTING_BLOCK);
165  NotifyCallbacks(false);
166}
167
168void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
169  NotifyCallbacks(false);
170}
171
172void DownloadRequestLimiter::TabDownloadState::Accept() {
173  SetContentSetting(CONTENT_SETTING_ALLOW);
174  NotifyCallbacks(true);
175}
176
177DownloadRequestLimiter::TabDownloadState::TabDownloadState()
178    : web_contents_(NULL),
179      host_(NULL),
180      status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
181      download_count_(0),
182      factory_(this) {
183}
184
185bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
186  return factory_.HasWeakPtrs();
187}
188
189void DownloadRequestLimiter::TabDownloadState::Observe(
190    int type,
191    const content::NotificationSource& source,
192    const content::NotificationDetails& details) {
193  DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type);
194  content::NavigationController* controller = &web_contents()->GetController();
195  DCHECK_EQ(controller, content::Source<NavigationController>(source).ptr());
196
197  // NOTE: Resetting state on a pending navigate isn't ideal. In particular it
198  // is possible that queued up downloads for the page before the pending
199  // navigation will be delivered to us after we process this request. If this
200  // happens we may let a download through that we shouldn't have. But this is
201  // rather rare, and it is difficult to get 100% right, so we don't deal with
202  // it.
203  NavigationEntry* entry = controller->GetPendingEntry();
204  if (!entry)
205    return;
206
207  // Redirects don't count.
208  if (ui::PageTransitionIsRedirect(entry->GetTransitionType()))
209    return;
210
211  if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
212      status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
213    // User has either allowed all downloads or canceled all downloads. Only
214    // reset the download state if the user is navigating to a different host
215    // (or host is empty).
216    if (!initial_page_host_.empty() && !entry->GetURL().host().empty() &&
217        entry->GetURL().host() == initial_page_host_)
218      return;
219  }
220
221  NotifyCallbacks(false);
222  host_->Remove(this, web_contents());
223}
224
225void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
226  set_download_status(allow ?
227      DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS :
228      DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED);
229  std::vector<DownloadRequestLimiter::Callback> callbacks;
230  bool change_status = false;
231
232  // Selectively send first few notifications only if number of downloads exceed
233  // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
234  // don't close it. If allow is false, we send all the notifications to cancel
235  // all remaining downloads and close the infobar.
236  if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
237    // Null the generated weak pointer so we don't get notified again.
238    factory_.InvalidateWeakPtrs();
239    callbacks.swap(callbacks_);
240  } else {
241    std::vector<DownloadRequestLimiter::Callback>::iterator start, end;
242    start = callbacks_.begin();
243    end = callbacks_.begin() + kMaxDownloadsAtOnce;
244    callbacks.assign(start, end);
245    callbacks_.erase(start, end);
246    change_status = true;
247  }
248
249  for (size_t i = 0; i < callbacks.size(); ++i)
250    host_->ScheduleNotification(callbacks[i], allow);
251
252  if (change_status)
253    set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD);
254}
255
256// DownloadRequestLimiter ------------------------------------------------------
257
258HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
259
260void DownloadRequestLimiter::SetContentSettingsForTesting(
261    HostContentSettingsMap* content_settings) {
262  content_settings_ = content_settings;
263}
264
265DownloadRequestLimiter::DownloadRequestLimiter()
266    : factory_(this) {
267}
268
269DownloadRequestLimiter::~DownloadRequestLimiter() {
270  // All the tabs should have closed before us, which sends notification and
271  // removes from state_map_. As such, there should be no pending callbacks.
272  DCHECK(state_map_.empty());
273}
274
275DownloadRequestLimiter::DownloadStatus
276DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
277  TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
278  return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
279}
280
281void DownloadRequestLimiter::CanDownloadOnIOThread(
282    int render_process_host_id,
283    int render_view_id,
284    const GURL& url,
285    const std::string& request_method,
286    const Callback& callback) {
287  // This is invoked on the IO thread. Schedule the task to run on the UI
288  // thread so that we can query UI state.
289  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
290  BrowserThread::PostTask(
291      BrowserThread::UI, FROM_HERE,
292      base::Bind(&DownloadRequestLimiter::CanDownload, this,
293                 render_process_host_id, render_view_id, url,
294                 request_method, callback));
295}
296
297DownloadRequestLimiter::TabDownloadState*
298DownloadRequestLimiter::GetDownloadState(
299    content::WebContents* web_contents,
300    content::WebContents* originating_web_contents,
301    bool create) {
302  DCHECK(web_contents);
303  StateMap::iterator i = state_map_.find(web_contents);
304  if (i != state_map_.end())
305    return i->second;
306
307  if (!create)
308    return NULL;
309
310  TabDownloadState* state =
311      new TabDownloadState(this, web_contents, originating_web_contents);
312  state_map_[web_contents] = state;
313  return state;
314}
315
316void DownloadRequestLimiter::CanDownload(int render_process_host_id,
317                                         int render_view_id,
318                                         const GURL& url,
319                                         const std::string& request_method,
320                                         const Callback& callback) {
321  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322
323  content::WebContents* originating_contents =
324      tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
325  if (!originating_contents) {
326    // The WebContents was closed, don't allow the download.
327    ScheduleNotification(callback, false);
328    return;
329  }
330
331  if (!originating_contents->GetDelegate()) {
332    ScheduleNotification(callback, false);
333    return;
334  }
335
336  // Note that because |originating_contents| might go away before
337  // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id|
338  // and |render_view_id|.
339  base::Callback<void(bool)> can_download_callback = base::Bind(
340      &DownloadRequestLimiter::OnCanDownloadDecided,
341      factory_.GetWeakPtr(),
342      render_process_host_id,
343      render_view_id,
344      request_method,
345      callback);
346
347  originating_contents->GetDelegate()->CanDownload(
348      originating_contents->GetRenderViewHost(),
349      url,
350      request_method,
351      can_download_callback);
352}
353
354void DownloadRequestLimiter::OnCanDownloadDecided(
355    int render_process_host_id,
356    int render_view_id,
357    const std::string& request_method,
358    const Callback& orig_callback, bool allow) {
359  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360  content::WebContents* originating_contents =
361      tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
362  if (!originating_contents || !allow) {
363    ScheduleNotification(orig_callback, false);
364    return;
365  }
366
367  CanDownloadImpl(originating_contents,
368                  request_method,
369                  orig_callback);
370}
371
372HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
373    content::WebContents* contents) {
374  return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
375      contents->GetBrowserContext())->GetHostContentSettingsMap();
376}
377
378void DownloadRequestLimiter::CanDownloadImpl(
379    content::WebContents* originating_contents,
380    const std::string& request_method,
381    const Callback& callback) {
382  DCHECK(originating_contents);
383
384  TabDownloadState* state = GetDownloadState(
385      originating_contents, originating_contents, true);
386  switch (state->download_status()) {
387    case ALLOW_ALL_DOWNLOADS:
388      if (state->download_count() && !(state->download_count() %
389            DownloadRequestLimiter::kMaxDownloadsAtOnce))
390        state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
391      ScheduleNotification(callback, true);
392      state->increment_download_count();
393      break;
394
395    case ALLOW_ONE_DOWNLOAD:
396      state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
397      ScheduleNotification(callback, true);
398      state->increment_download_count();
399      break;
400
401    case DOWNLOADS_NOT_ALLOWED:
402      ScheduleNotification(callback, false);
403      break;
404
405    case PROMPT_BEFORE_DOWNLOAD: {
406      HostContentSettingsMap* content_settings = GetContentSettings(
407          originating_contents);
408      ContentSetting setting = CONTENT_SETTING_ASK;
409      if (content_settings)
410        setting = content_settings->GetContentSetting(
411            originating_contents->GetURL(),
412            originating_contents->GetURL(),
413            CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
414            std::string());
415      switch (setting) {
416        case CONTENT_SETTING_ALLOW: {
417          TabSpecificContentSettings* settings =
418              TabSpecificContentSettings::FromWebContents(
419                  originating_contents);
420          if (settings)
421            settings->SetDownloadsBlocked(false);
422          ScheduleNotification(callback, true);
423          state->increment_download_count();
424          return;
425        }
426        case CONTENT_SETTING_BLOCK: {
427          TabSpecificContentSettings* settings =
428              TabSpecificContentSettings::FromWebContents(
429                  originating_contents);
430          if (settings)
431            settings->SetDownloadsBlocked(true);
432          ScheduleNotification(callback, false);
433          return;
434        }
435        case CONTENT_SETTING_DEFAULT:
436        case CONTENT_SETTING_ASK:
437        case CONTENT_SETTING_SESSION_ONLY:
438          state->PromptUserForDownload(callback);
439          state->increment_download_count();
440          break;
441        case CONTENT_SETTING_NUM_SETTINGS:
442        default:
443          NOTREACHED();
444          return;
445      }
446      break;
447    }
448
449    default:
450      NOTREACHED();
451  }
452}
453
454void DownloadRequestLimiter::ScheduleNotification(const Callback& callback,
455                                                  bool allow) {
456  BrowserThread::PostTask(
457      BrowserThread::IO, FROM_HERE, base::Bind(callback, allow));
458}
459
460void DownloadRequestLimiter::Remove(TabDownloadState* state,
461                                    content::WebContents* contents) {
462  DCHECK(ContainsKey(state_map_, contents));
463  state_map_.erase(contents);
464  delete state;
465}
466