Home | History | Annotate | Download | only in 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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    163 void DownloadRequestLimiter::TabDownloadState::Cancel() {
    164   SetContentSetting(CONTENT_SETTING_BLOCK);
    165   NotifyCallbacks(false);
    166 }
    167 
    168 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
    169   NotifyCallbacks(false);
    170 }
    171 
    172 void DownloadRequestLimiter::TabDownloadState::Accept() {
    173   SetContentSetting(CONTENT_SETTING_ALLOW);
    174   NotifyCallbacks(true);
    175 }
    176 
    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 
    185 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
    186   return factory_.HasWeakPtrs();
    187 }
    188 
    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 
    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 
    260 void DownloadRequestLimiter::SetContentSettingsForTesting(
    261     HostContentSettingsMap* content_settings) {
    262   content_settings_ = content_settings;
    263 }
    264 
    265 DownloadRequestLimiter::DownloadRequestLimiter()
    266     : factory_(this) {
    267 }
    268 
    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
    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 
    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*
    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 
    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 
    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 
    372 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
    373     content::WebContents* contents) {
    374   return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
    375       contents->GetBrowserContext())->GetHostContentSettingsMap();
    376 }
    377 
    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 
    454 void DownloadRequestLimiter::ScheduleNotification(const Callback& callback,
    455                                                   bool allow) {
    456   BrowserThread::PostTask(
    457       BrowserThread::IO, FROM_HERE, base::Bind(callback, allow));
    458 }
    459 
    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