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