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