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.
5 #include "chrome/browser/download/download_request_limiter.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"
29 using content::BrowserThread
;
30 using content::NavigationController
;
31 using content::NavigationEntry
;
33 // TabDownloadState ------------------------------------------------------------
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
),
42 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD
),
45 content::Source
<NavigationController
> notification_source(
46 &contents
->GetController());
47 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING
,
49 NavigationEntry
* active_entry
= originating_web_contents
?
50 originating_web_contents
->GetController().GetActiveEntry() :
51 contents
->GetController().GetActiveEntry();
53 initial_page_host_
= active_entry
->GetURL().host();
56 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
57 // We should only be destroyed after the callbacks have been notified.
58 DCHECK(callbacks_
.empty());
60 // And we should have invalidated the back pointer.
61 DCHECK(!factory_
.HasWeakPtrs());
64 void DownloadRequestLimiter::TabDownloadState::AboutToNavigateRenderView(
65 content::RenderViewHost
* render_view_host
) {
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.
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.
90 void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
91 if (is_showing_prompt()) {
92 // Don't change the state if the user clicks on the page somewhere.
96 bool promptable
= (InfoBarService::FromWebContents(web_contents()) != NULL
);
97 if (PermissionBubbleManager::Enabled()) {
99 (PermissionBubbleManager::FromWebContents(web_contents()) != NULL
);
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
) &&
106 (status_
!= DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED
))) {
107 // Revert to default status.
108 host_
->Remove(this, web_contents());
109 // WARNING: We've been deleted.
113 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
114 // Tab closed, no need to handle closing the dialog as it's owned by the
117 NotifyCallbacks(false);
118 host_
->Remove(this, web_contents());
119 // WARNING: We've been deleted.
122 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
123 const DownloadRequestLimiter::Callback
& callback
) {
124 callbacks_
.push_back(callback
);
125 DCHECK(web_contents_
);
126 if (is_showing_prompt())
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()));
141 DownloadRequestInfoBarDelegate::Create(
142 InfoBarService::FromWebContents(web_contents_
), factory_
.GetWeakPtr());
145 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
146 ContentSetting setting
) {
149 HostContentSettingsMap
* settings
=
150 DownloadRequestLimiter::GetContentSettings(web_contents_
);
151 ContentSettingsPattern
pattern(
152 ContentSettingsPattern::FromURL(web_contents_
->GetURL()));
153 if (!settings
|| !pattern
.IsValid())
155 settings
->SetContentSetting(
157 ContentSettingsPattern::Wildcard(),
158 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS
,
163 void DownloadRequestLimiter::TabDownloadState::Cancel() {
164 SetContentSetting(CONTENT_SETTING_BLOCK
);
165 NotifyCallbacks(false);
168 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
169 NotifyCallbacks(false);
172 void DownloadRequestLimiter::TabDownloadState::Accept() {
173 SetContentSetting(CONTENT_SETTING_ALLOW
);
174 NotifyCallbacks(true);
177 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
178 : web_contents_(NULL
),
180 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD
),
185 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
186 return factory_
.HasWeakPtrs();
189 void DownloadRequestLimiter::TabDownloadState::Observe(
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());
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
203 NavigationEntry
* entry
= controller
->GetPendingEntry();
207 // Redirects don't count.
208 if (ui::PageTransitionIsRedirect(entry
->GetTransitionType()))
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_
)
221 NotifyCallbacks(false);
222 host_
->Remove(this, web_contents());
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;
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_
);
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;
249 for (size_t i
= 0; i
< callbacks
.size(); ++i
)
250 host_
->ScheduleNotification(callbacks
[i
], allow
);
253 set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD
);
256 // DownloadRequestLimiter ------------------------------------------------------
258 HostContentSettingsMap
* DownloadRequestLimiter::content_settings_
= NULL
;
260 void DownloadRequestLimiter::SetContentSettingsForTesting(
261 HostContentSettingsMap
* content_settings
) {
262 content_settings_
= content_settings
;
265 DownloadRequestLimiter::DownloadRequestLimiter()
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());
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
;
281 void DownloadRequestLimiter::CanDownloadOnIOThread(
282 int render_process_host_id
,
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
));
297 DownloadRequestLimiter::TabDownloadState
*
298 DownloadRequestLimiter::GetDownloadState(
299 content::WebContents
* web_contents
,
300 content::WebContents
* originating_web_contents
,
302 DCHECK(web_contents
);
303 StateMap::iterator i
= state_map_
.find(web_contents
);
304 if (i
!= state_map_
.end())
310 TabDownloadState
* state
=
311 new TabDownloadState(this, web_contents
, originating_web_contents
);
312 state_map_
[web_contents
] = state
;
316 void DownloadRequestLimiter::CanDownload(int render_process_host_id
,
319 const std::string
& request_method
,
320 const Callback
& callback
) {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
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);
331 if (!originating_contents
->GetDelegate()) {
332 ScheduleNotification(callback
, false);
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
,
347 originating_contents
->GetDelegate()->CanDownload(
348 originating_contents
->GetRenderViewHost(),
351 can_download_callback
);
354 void DownloadRequestLimiter::OnCanDownloadDecided(
355 int render_process_host_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);
367 CanDownloadImpl(originating_contents
,
372 HostContentSettingsMap
* DownloadRequestLimiter::GetContentSettings(
373 content::WebContents
* contents
) {
374 return content_settings_
? content_settings_
: Profile::FromBrowserContext(
375 contents
->GetBrowserContext())->GetHostContentSettingsMap();
378 void DownloadRequestLimiter::CanDownloadImpl(
379 content::WebContents
* originating_contents
,
380 const std::string
& request_method
,
381 const Callback
& callback
) {
382 DCHECK(originating_contents
);
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();
395 case ALLOW_ONE_DOWNLOAD
:
396 state
->set_download_status(PROMPT_BEFORE_DOWNLOAD
);
397 ScheduleNotification(callback
, true);
398 state
->increment_download_count();
401 case DOWNLOADS_NOT_ALLOWED
:
402 ScheduleNotification(callback
, false);
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
,
416 case CONTENT_SETTING_ALLOW
: {
417 TabSpecificContentSettings
* settings
=
418 TabSpecificContentSettings::FromWebContents(
419 originating_contents
);
421 settings
->SetDownloadsBlocked(false);
422 ScheduleNotification(callback
, true);
423 state
->increment_download_count();
426 case CONTENT_SETTING_BLOCK
: {
427 TabSpecificContentSettings
* settings
=
428 TabSpecificContentSettings::FromWebContents(
429 originating_contents
);
431 settings
->SetDownloadsBlocked(true);
432 ScheduleNotification(callback
, false);
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();
441 case CONTENT_SETTING_NUM_SETTINGS
:
454 void DownloadRequestLimiter::ScheduleNotification(const Callback
& callback
,
456 BrowserThread::PostTask(
457 BrowserThread::IO
, FROM_HERE
, base::Bind(callback
, allow
));
460 void DownloadRequestLimiter::Remove(TabDownloadState
* state
,
461 content::WebContents
* contents
) {
462 DCHECK(ContainsKey(state_map_
, contents
));
463 state_map_
.erase(contents
);