• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  
29  using content::BrowserThread;
30  using content::NavigationController;
31  using content::NavigationEntry;
32  
33  // TabDownloadState ------------------------------------------------------------
34  
TabDownloadState(DownloadRequestLimiter * host,content::WebContents * contents,content::WebContents * originating_web_contents)35  DownloadRequestLimiter::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  
~TabDownloadState()56  DownloadRequestLimiter::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  
AboutToNavigateRenderView(content::RenderViewHost * render_view_host)64  void 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  
DidGetUserGesture()90  void 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  
WebContentsDestroyed()113  void 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  
PromptUserForDownload(const DownloadRequestLimiter::Callback & callback)122  void 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  
SetContentSetting(ContentSetting setting)145  void 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  
Cancel()163  void DownloadRequestLimiter::TabDownloadState::Cancel() {
164    SetContentSetting(CONTENT_SETTING_BLOCK);
165    NotifyCallbacks(false);
166  }
167  
CancelOnce()168  void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
169    NotifyCallbacks(false);
170  }
171  
Accept()172  void DownloadRequestLimiter::TabDownloadState::Accept() {
173    SetContentSetting(CONTENT_SETTING_ALLOW);
174    NotifyCallbacks(true);
175  }
176  
TabDownloadState()177  DownloadRequestLimiter::TabDownloadState::TabDownloadState()
178      : web_contents_(NULL),
179        host_(NULL),
180        status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
181        download_count_(0),
182        factory_(this) {
183  }
184  
is_showing_prompt() const185  bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
186    return factory_.HasWeakPtrs();
187  }
188  
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)189  void 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  
NotifyCallbacks(bool allow)225  void 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  
258  HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
259  
SetContentSettingsForTesting(HostContentSettingsMap * content_settings)260  void DownloadRequestLimiter::SetContentSettingsForTesting(
261      HostContentSettingsMap* content_settings) {
262    content_settings_ = content_settings;
263  }
264  
DownloadRequestLimiter()265  DownloadRequestLimiter::DownloadRequestLimiter()
266      : factory_(this) {
267  }
268  
~DownloadRequestLimiter()269  DownloadRequestLimiter::~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  
275  DownloadRequestLimiter::DownloadStatus
GetDownloadStatus(content::WebContents * web_contents)276  DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
277    TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
278    return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
279  }
280  
CanDownloadOnIOThread(int render_process_host_id,int render_view_id,const GURL & url,const std::string & request_method,const Callback & callback)281  void 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  
297  DownloadRequestLimiter::TabDownloadState*
GetDownloadState(content::WebContents * web_contents,content::WebContents * originating_web_contents,bool create)298  DownloadRequestLimiter::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  
CanDownload(int render_process_host_id,int render_view_id,const GURL & url,const std::string & request_method,const Callback & callback)316  void 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  
OnCanDownloadDecided(int render_process_host_id,int render_view_id,const std::string & request_method,const Callback & orig_callback,bool allow)354  void 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  
GetContentSettings(content::WebContents * contents)372  HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
373      content::WebContents* contents) {
374    return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
375        contents->GetBrowserContext())->GetHostContentSettingsMap();
376  }
377  
CanDownloadImpl(content::WebContents * originating_contents,const std::string & request_method,const Callback & callback)378  void 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  
ScheduleNotification(const Callback & callback,bool allow)454  void DownloadRequestLimiter::ScheduleNotification(const Callback& callback,
455                                                    bool allow) {
456    BrowserThread::PostTask(
457        BrowserThread::IO, FROM_HERE, base::Bind(callback, allow));
458  }
459  
Remove(TabDownloadState * state,content::WebContents * contents)460  void DownloadRequestLimiter::Remove(TabDownloadState* state,
461                                      content::WebContents* contents) {
462    DCHECK(ContainsKey(state_map_, contents));
463    state_map_.erase(contents);
464    delete state;
465  }
466