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