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