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 (content::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