Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / download / download_request_limiter.cc
blob32fb1181bfdf6dc49c29ca5bcf6eba24a76166e9
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"
7 #include "base/bind.h"
8 #include "base/stl_util.h"
9 #include "chrome/browser/content_settings/host_content_settings_map_factory.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/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/website_settings/permission_bubble_manager.h"
16 #include "components/content_settings/core/browser/host_content_settings_map.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"
29 #if defined(OS_ANDROID)
30 #include "chrome/browser/download/download_request_infobar_delegate.h"
31 #endif
33 using content::BrowserThread;
34 using content::NavigationController;
35 using content::NavigationEntry;
37 // TabDownloadState ------------------------------------------------------------
39 DownloadRequestLimiter::TabDownloadState::TabDownloadState(
40 DownloadRequestLimiter* host,
41 content::WebContents* contents,
42 content::WebContents* originating_web_contents)
43 : content::WebContentsObserver(contents),
44 web_contents_(contents),
45 host_(host),
46 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
47 download_count_(0),
48 factory_(this) {
49 content::Source<NavigationController> notification_source(
50 &contents->GetController());
51 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
52 notification_source);
53 NavigationEntry* last_entry = originating_web_contents ?
54 originating_web_contents->GetController().GetLastCommittedEntry() :
55 contents->GetController().GetLastCommittedEntry();
56 if (last_entry)
57 initial_page_host_ = last_entry->GetURL().host();
60 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
61 // We should only be destroyed after the callbacks have been notified.
62 DCHECK(callbacks_.empty());
64 // And we should have invalidated the back pointer.
65 DCHECK(!factory_.HasWeakPtrs());
68 void DownloadRequestLimiter::TabDownloadState::DidNavigateMainFrame(
69 const content::LoadCommittedDetails& details,
70 const content::FrameNavigateParams& params) {
71 switch (status_) {
72 case ALLOW_ONE_DOWNLOAD:
73 case PROMPT_BEFORE_DOWNLOAD:
74 // When the user reloads the page without responding to the infobar, they
75 // are expecting DownloadRequestLimiter to behave as if they had just
76 // initially navigated to this page. See http://crbug.com/171372
77 NotifyCallbacks(false);
78 host_->Remove(this, web_contents());
79 // WARNING: We've been deleted.
80 break;
81 case DOWNLOADS_NOT_ALLOWED:
82 case ALLOW_ALL_DOWNLOADS:
83 // Don't drop this information. The user has explicitly said that they
84 // do/don't want downloads from this host. If they accidentally Accepted
85 // or Canceled, tough luck, they don't get another chance. They can copy
86 // the URL into a new tab, which will make a new DownloadRequestLimiter.
87 // See also the initial_page_host_ logic in Observe() for
88 // NOTIFICATION_NAV_ENTRY_PENDING.
89 break;
90 default:
91 NOTREACHED();
95 void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
96 if (is_showing_prompt()) {
97 // Don't change the state if the user clicks on the page somewhere.
98 return;
101 bool promptable = (InfoBarService::FromWebContents(web_contents()) != NULL);
102 if (PermissionBubbleManager::Enabled()) {
103 promptable =
104 (PermissionBubbleManager::FromWebContents(web_contents()) != NULL);
107 // See PromptUserForDownload(): if there's no InfoBarService, then
108 // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD.
109 if ((status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS) &&
110 (!promptable ||
111 (status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED))) {
112 // Revert to default status.
113 host_->Remove(this, web_contents());
114 // WARNING: We've been deleted.
118 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
119 // Tab closed, no need to handle closing the dialog as it's owned by the
120 // WebContents.
122 NotifyCallbacks(false);
123 host_->Remove(this, web_contents());
124 // WARNING: We've been deleted.
127 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
128 const DownloadRequestLimiter::Callback& callback) {
129 callbacks_.push_back(callback);
130 DCHECK(web_contents_);
131 if (is_showing_prompt())
132 return;
134 #if defined(OS_ANDROID)
135 DownloadRequestInfoBarDelegate::Create(
136 InfoBarService::FromWebContents(web_contents_), factory_.GetWeakPtr());
137 #else
138 PermissionBubbleManager* bubble_manager =
139 PermissionBubbleManager::FromWebContents(web_contents_);
140 if (bubble_manager) {
141 bubble_manager->AddRequest(new DownloadPermissionRequest(
142 factory_.GetWeakPtr()));
143 } else {
144 Cancel();
146 #endif
149 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
150 ContentSetting setting) {
151 if (!web_contents_)
152 return;
153 HostContentSettingsMap* settings =
154 DownloadRequestLimiter::GetContentSettings(web_contents_);
155 ContentSettingsPattern pattern(
156 ContentSettingsPattern::FromURL(web_contents_->GetURL()));
157 if (!settings || !pattern.IsValid())
158 return;
159 settings->SetContentSetting(
160 pattern,
161 ContentSettingsPattern::Wildcard(),
162 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
163 std::string(),
164 setting);
167 void DownloadRequestLimiter::TabDownloadState::Cancel() {
168 SetContentSetting(CONTENT_SETTING_BLOCK);
169 NotifyCallbacks(false);
172 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
173 NotifyCallbacks(false);
176 void DownloadRequestLimiter::TabDownloadState::Accept() {
177 SetContentSetting(CONTENT_SETTING_ALLOW);
178 NotifyCallbacks(true);
181 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
182 : web_contents_(NULL),
183 host_(NULL),
184 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
185 download_count_(0),
186 factory_(this) {
189 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
190 return factory_.HasWeakPtrs();
193 void DownloadRequestLimiter::TabDownloadState::Observe(
194 int type,
195 const content::NotificationSource& source,
196 const content::NotificationDetails& details) {
197 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type);
198 content::NavigationController* controller = &web_contents()->GetController();
199 DCHECK_EQ(controller, content::Source<NavigationController>(source).ptr());
201 // NOTE: Resetting state on a pending navigate isn't ideal. In particular it
202 // is possible that queued up downloads for the page before the pending
203 // navigation will be delivered to us after we process this request. If this
204 // happens we may let a download through that we shouldn't have. But this is
205 // rather rare, and it is difficult to get 100% right, so we don't deal with
206 // it.
207 NavigationEntry* entry = controller->GetPendingEntry();
208 if (!entry)
209 return;
211 // Redirects don't count.
212 if (ui::PageTransitionIsRedirect(entry->GetTransitionType()))
213 return;
215 if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
216 status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
217 // User has either allowed all downloads or canceled all downloads. Only
218 // reset the download state if the user is navigating to a different host
219 // (or host is empty).
220 if (!initial_page_host_.empty() && !entry->GetURL().host().empty() &&
221 entry->GetURL().host() == initial_page_host_)
222 return;
225 NotifyCallbacks(false);
226 host_->Remove(this, web_contents());
229 void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
230 set_download_status(allow ?
231 DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS :
232 DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED);
233 std::vector<DownloadRequestLimiter::Callback> callbacks;
234 bool change_status = false;
236 // Selectively send first few notifications only if number of downloads exceed
237 // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
238 // don't close it. If allow is false, we send all the notifications to cancel
239 // all remaining downloads and close the infobar.
240 if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
241 // Null the generated weak pointer so we don't get notified again.
242 factory_.InvalidateWeakPtrs();
243 callbacks.swap(callbacks_);
244 } else {
245 std::vector<DownloadRequestLimiter::Callback>::iterator start, end;
246 start = callbacks_.begin();
247 end = callbacks_.begin() + kMaxDownloadsAtOnce;
248 callbacks.assign(start, end);
249 callbacks_.erase(start, end);
250 change_status = true;
253 for (const auto& callback : callbacks) {
254 // When callback runs, it can cause the WebContents to be destroyed.
255 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
256 base::Bind(callback, allow));
259 if (change_status)
260 set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD);
263 // DownloadRequestLimiter ------------------------------------------------------
265 HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
267 void DownloadRequestLimiter::SetContentSettingsForTesting(
268 HostContentSettingsMap* content_settings) {
269 content_settings_ = content_settings;
272 DownloadRequestLimiter::DownloadRequestLimiter()
273 : factory_(this) {
276 DownloadRequestLimiter::~DownloadRequestLimiter() {
277 // All the tabs should have closed before us, which sends notification and
278 // removes from state_map_. As such, there should be no pending callbacks.
279 DCHECK(state_map_.empty());
282 DownloadRequestLimiter::DownloadStatus
283 DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
284 TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
285 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
288 DownloadRequestLimiter::TabDownloadState*
289 DownloadRequestLimiter::GetDownloadState(
290 content::WebContents* web_contents,
291 content::WebContents* originating_web_contents,
292 bool create) {
293 DCHECK(web_contents);
294 StateMap::iterator i = state_map_.find(web_contents);
295 if (i != state_map_.end())
296 return i->second;
298 if (!create)
299 return NULL;
301 TabDownloadState* state =
302 new TabDownloadState(this, web_contents, originating_web_contents);
303 state_map_[web_contents] = state;
304 return state;
307 void DownloadRequestLimiter::CanDownload(int render_process_host_id,
308 int render_view_id,
309 const GURL& url,
310 const std::string& request_method,
311 const Callback& callback) {
312 DCHECK_CURRENTLY_ON(BrowserThread::UI);
314 content::WebContents* originating_contents =
315 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
316 if (!originating_contents) {
317 // The WebContents was closed, don't allow the download.
318 callback.Run(false);
319 return;
322 if (!originating_contents->GetDelegate()) {
323 callback.Run(false);
324 return;
327 // Note that because |originating_contents| might go away before
328 // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id|
329 // and |render_view_id|.
330 base::Callback<void(bool)> can_download_callback = base::Bind(
331 &DownloadRequestLimiter::OnCanDownloadDecided,
332 factory_.GetWeakPtr(),
333 render_process_host_id,
334 render_view_id,
335 request_method,
336 callback);
338 originating_contents->GetDelegate()->CanDownload(
339 url,
340 request_method,
341 can_download_callback);
344 void DownloadRequestLimiter::OnCanDownloadDecided(
345 int render_process_host_id,
346 int render_view_id,
347 const std::string& request_method,
348 const Callback& orig_callback, bool allow) {
349 DCHECK_CURRENTLY_ON(BrowserThread::UI);
350 content::WebContents* originating_contents =
351 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
352 if (!originating_contents || !allow) {
353 orig_callback.Run(false);
354 return;
357 CanDownloadImpl(originating_contents,
358 request_method,
359 orig_callback);
362 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
363 content::WebContents* contents) {
364 return content_settings_ ? content_settings_ :
365 HostContentSettingsMapFactory::GetForProfile(
366 Profile::FromBrowserContext(contents->GetBrowserContext()));
369 void DownloadRequestLimiter::CanDownloadImpl(
370 content::WebContents* originating_contents,
371 const std::string& request_method,
372 const Callback& callback) {
373 DCHECK(originating_contents);
375 TabDownloadState* state = GetDownloadState(
376 originating_contents, originating_contents, true);
377 switch (state->download_status()) {
378 case ALLOW_ALL_DOWNLOADS:
379 if (state->download_count() && !(state->download_count() %
380 DownloadRequestLimiter::kMaxDownloadsAtOnce))
381 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
382 callback.Run(true);
383 state->increment_download_count();
384 break;
386 case ALLOW_ONE_DOWNLOAD:
387 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
388 callback.Run(true);
389 state->increment_download_count();
390 break;
392 case DOWNLOADS_NOT_ALLOWED:
393 callback.Run(false);
394 break;
396 case PROMPT_BEFORE_DOWNLOAD: {
397 HostContentSettingsMap* content_settings = GetContentSettings(
398 originating_contents);
399 ContentSetting setting = CONTENT_SETTING_ASK;
400 if (content_settings)
401 setting = content_settings->GetContentSetting(
402 originating_contents->GetURL(),
403 originating_contents->GetURL(),
404 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
405 std::string());
406 switch (setting) {
407 case CONTENT_SETTING_ALLOW: {
408 TabSpecificContentSettings* settings =
409 TabSpecificContentSettings::FromWebContents(
410 originating_contents);
411 if (settings)
412 settings->SetDownloadsBlocked(false);
413 callback.Run(true);
414 state->increment_download_count();
415 return;
417 case CONTENT_SETTING_BLOCK: {
418 TabSpecificContentSettings* settings =
419 TabSpecificContentSettings::FromWebContents(
420 originating_contents);
421 if (settings)
422 settings->SetDownloadsBlocked(true);
423 callback.Run(false);
424 return;
426 case CONTENT_SETTING_DEFAULT:
427 case CONTENT_SETTING_ASK:
428 case CONTENT_SETTING_SESSION_ONLY:
429 state->PromptUserForDownload(callback);
430 state->increment_download_count();
431 break;
432 case CONTENT_SETTING_NUM_SETTINGS:
433 default:
434 NOTREACHED();
435 return;
437 break;
440 default:
441 NOTREACHED();
445 void DownloadRequestLimiter::Remove(TabDownloadState* state,
446 content::WebContents* contents) {
447 DCHECK(ContainsKey(state_map_, contents));
448 state_map_.erase(contents);
449 delete state;