Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / download / download_request_limiter.cc
blob532f525086eaad74a168d2a3eee6408c2544d2a6
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/tab_specific_content_settings.h"
10 #include "chrome/browser/download/download_permission_request.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/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 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),
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();
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::DidNavigateMainFrame(
65 const content::LoadCommittedDetails& details,
66 const content::FrameNavigateParams& params) {
67 switch (status_) {
68 case ALLOW_ONE_DOWNLOAD:
69 case PROMPT_BEFORE_DOWNLOAD:
70 // When the user reloads the page without responding to the infobar, they
71 // are expecting DownloadRequestLimiter to behave as if they had just
72 // initially navigated to this page. See http://crbug.com/171372
73 NotifyCallbacks(false);
74 host_->Remove(this, web_contents());
75 // WARNING: We've been deleted.
76 break;
77 case DOWNLOADS_NOT_ALLOWED:
78 case ALLOW_ALL_DOWNLOADS:
79 // Don't drop this information. The user has explicitly said that they
80 // do/don't want downloads from this host. If they accidentally Accepted
81 // or Canceled, tough luck, they don't get another chance. They can copy
82 // the URL into a new tab, which will make a new DownloadRequestLimiter.
83 // See also the initial_page_host_ logic in Observe() for
84 // NOTIFICATION_NAV_ENTRY_PENDING.
85 break;
86 default:
87 NOTREACHED();
91 void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
92 if (is_showing_prompt()) {
93 // Don't change the state if the user clicks on the page somewhere.
94 return;
97 bool promptable = (InfoBarService::FromWebContents(web_contents()) != NULL);
98 if (PermissionBubbleManager::Enabled()) {
99 promptable =
100 (PermissionBubbleManager::FromWebContents(web_contents()) != NULL);
103 // See PromptUserForDownload(): if there's no InfoBarService, then
104 // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD.
105 if ((status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS) &&
106 (!promptable ||
107 (status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED))) {
108 // Revert to default status.
109 host_->Remove(this, web_contents());
110 // WARNING: We've been deleted.
114 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
115 // Tab closed, no need to handle closing the dialog as it's owned by the
116 // WebContents.
118 NotifyCallbacks(false);
119 host_->Remove(this, web_contents());
120 // WARNING: We've been deleted.
123 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
124 const DownloadRequestLimiter::Callback& callback) {
125 callbacks_.push_back(callback);
126 DCHECK(web_contents_);
127 if (is_showing_prompt())
128 return;
130 if (PermissionBubbleManager::Enabled()) {
131 PermissionBubbleManager* bubble_manager =
132 PermissionBubbleManager::FromWebContents(web_contents_);
133 if (bubble_manager) {
134 bubble_manager->AddRequest(new DownloadPermissionRequest(
135 factory_.GetWeakPtr()));
136 } else {
137 Cancel();
139 return;
142 DownloadRequestInfoBarDelegate::Create(
143 InfoBarService::FromWebContents(web_contents_), factory_.GetWeakPtr());
146 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
147 ContentSetting setting) {
148 if (!web_contents_)
149 return;
150 HostContentSettingsMap* settings =
151 DownloadRequestLimiter::GetContentSettings(web_contents_);
152 ContentSettingsPattern pattern(
153 ContentSettingsPattern::FromURL(web_contents_->GetURL()));
154 if (!settings || !pattern.IsValid())
155 return;
156 settings->SetContentSetting(
157 pattern,
158 ContentSettingsPattern::Wildcard(),
159 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
160 std::string(),
161 setting);
164 void DownloadRequestLimiter::TabDownloadState::Cancel() {
165 SetContentSetting(CONTENT_SETTING_BLOCK);
166 NotifyCallbacks(false);
169 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
170 NotifyCallbacks(false);
173 void DownloadRequestLimiter::TabDownloadState::Accept() {
174 SetContentSetting(CONTENT_SETTING_ALLOW);
175 NotifyCallbacks(true);
178 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
179 : web_contents_(NULL),
180 host_(NULL),
181 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
182 download_count_(0),
183 factory_(this) {
186 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
187 return factory_.HasWeakPtrs();
190 void DownloadRequestLimiter::TabDownloadState::Observe(
191 int type,
192 const content::NotificationSource& source,
193 const content::NotificationDetails& details) {
194 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type);
195 content::NavigationController* controller = &web_contents()->GetController();
196 DCHECK_EQ(controller, content::Source<NavigationController>(source).ptr());
198 // NOTE: Resetting state on a pending navigate isn't ideal. In particular it
199 // is possible that queued up downloads for the page before the pending
200 // navigation will be delivered to us after we process this request. If this
201 // happens we may let a download through that we shouldn't have. But this is
202 // rather rare, and it is difficult to get 100% right, so we don't deal with
203 // it.
204 NavigationEntry* entry = controller->GetPendingEntry();
205 if (!entry)
206 return;
208 // Redirects don't count.
209 if (ui::PageTransitionIsRedirect(entry->GetTransitionType()))
210 return;
212 if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
213 status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
214 // User has either allowed all downloads or canceled all downloads. Only
215 // reset the download state if the user is navigating to a different host
216 // (or host is empty).
217 if (!initial_page_host_.empty() && !entry->GetURL().host().empty() &&
218 entry->GetURL().host() == initial_page_host_)
219 return;
222 NotifyCallbacks(false);
223 host_->Remove(this, web_contents());
226 void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
227 set_download_status(allow ?
228 DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS :
229 DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED);
230 std::vector<DownloadRequestLimiter::Callback> callbacks;
231 bool change_status = false;
233 // Selectively send first few notifications only if number of downloads exceed
234 // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
235 // don't close it. If allow is false, we send all the notifications to cancel
236 // all remaining downloads and close the infobar.
237 if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
238 // Null the generated weak pointer so we don't get notified again.
239 factory_.InvalidateWeakPtrs();
240 callbacks.swap(callbacks_);
241 } else {
242 std::vector<DownloadRequestLimiter::Callback>::iterator start, end;
243 start = callbacks_.begin();
244 end = callbacks_.begin() + kMaxDownloadsAtOnce;
245 callbacks.assign(start, end);
246 callbacks_.erase(start, end);
247 change_status = true;
250 for (const auto& callback : callbacks) {
251 // When callback runs, it can cause the WebContents to be destroyed.
252 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
253 base::Bind(callback, allow));
256 if (change_status)
257 set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD);
260 // DownloadRequestLimiter ------------------------------------------------------
262 HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
264 void DownloadRequestLimiter::SetContentSettingsForTesting(
265 HostContentSettingsMap* content_settings) {
266 content_settings_ = content_settings;
269 DownloadRequestLimiter::DownloadRequestLimiter()
270 : factory_(this) {
273 DownloadRequestLimiter::~DownloadRequestLimiter() {
274 // All the tabs should have closed before us, which sends notification and
275 // removes from state_map_. As such, there should be no pending callbacks.
276 DCHECK(state_map_.empty());
279 DownloadRequestLimiter::DownloadStatus
280 DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
281 TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
282 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
285 DownloadRequestLimiter::TabDownloadState*
286 DownloadRequestLimiter::GetDownloadState(
287 content::WebContents* web_contents,
288 content::WebContents* originating_web_contents,
289 bool create) {
290 DCHECK(web_contents);
291 StateMap::iterator i = state_map_.find(web_contents);
292 if (i != state_map_.end())
293 return i->second;
295 if (!create)
296 return NULL;
298 TabDownloadState* state =
299 new TabDownloadState(this, web_contents, originating_web_contents);
300 state_map_[web_contents] = state;
301 return state;
304 void DownloadRequestLimiter::CanDownload(int render_process_host_id,
305 int render_view_id,
306 const GURL& url,
307 const std::string& request_method,
308 const Callback& callback) {
309 DCHECK_CURRENTLY_ON(BrowserThread::UI);
311 content::WebContents* originating_contents =
312 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
313 if (!originating_contents) {
314 // The WebContents was closed, don't allow the download.
315 callback.Run(false);
316 return;
319 if (!originating_contents->GetDelegate()) {
320 callback.Run(false);
321 return;
324 // Note that because |originating_contents| might go away before
325 // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id|
326 // and |render_view_id|.
327 base::Callback<void(bool)> can_download_callback = base::Bind(
328 &DownloadRequestLimiter::OnCanDownloadDecided,
329 factory_.GetWeakPtr(),
330 render_process_host_id,
331 render_view_id,
332 request_method,
333 callback);
335 originating_contents->GetDelegate()->CanDownload(
336 url,
337 request_method,
338 can_download_callback);
341 void DownloadRequestLimiter::OnCanDownloadDecided(
342 int render_process_host_id,
343 int render_view_id,
344 const std::string& request_method,
345 const Callback& orig_callback, bool allow) {
346 DCHECK_CURRENTLY_ON(BrowserThread::UI);
347 content::WebContents* originating_contents =
348 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
349 if (!originating_contents || !allow) {
350 orig_callback.Run(false);
351 return;
354 CanDownloadImpl(originating_contents,
355 request_method,
356 orig_callback);
359 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
360 content::WebContents* contents) {
361 return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
362 contents->GetBrowserContext())->GetHostContentSettingsMap();
365 void DownloadRequestLimiter::CanDownloadImpl(
366 content::WebContents* originating_contents,
367 const std::string& request_method,
368 const Callback& callback) {
369 DCHECK(originating_contents);
371 TabDownloadState* state = GetDownloadState(
372 originating_contents, originating_contents, true);
373 switch (state->download_status()) {
374 case ALLOW_ALL_DOWNLOADS:
375 if (state->download_count() && !(state->download_count() %
376 DownloadRequestLimiter::kMaxDownloadsAtOnce))
377 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
378 callback.Run(true);
379 state->increment_download_count();
380 break;
382 case ALLOW_ONE_DOWNLOAD:
383 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
384 callback.Run(true);
385 state->increment_download_count();
386 break;
388 case DOWNLOADS_NOT_ALLOWED:
389 callback.Run(false);
390 break;
392 case PROMPT_BEFORE_DOWNLOAD: {
393 HostContentSettingsMap* content_settings = GetContentSettings(
394 originating_contents);
395 ContentSetting setting = CONTENT_SETTING_ASK;
396 if (content_settings)
397 setting = content_settings->GetContentSetting(
398 originating_contents->GetURL(),
399 originating_contents->GetURL(),
400 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
401 std::string());
402 switch (setting) {
403 case CONTENT_SETTING_ALLOW: {
404 TabSpecificContentSettings* settings =
405 TabSpecificContentSettings::FromWebContents(
406 originating_contents);
407 if (settings)
408 settings->SetDownloadsBlocked(false);
409 callback.Run(true);
410 state->increment_download_count();
411 return;
413 case CONTENT_SETTING_BLOCK: {
414 TabSpecificContentSettings* settings =
415 TabSpecificContentSettings::FromWebContents(
416 originating_contents);
417 if (settings)
418 settings->SetDownloadsBlocked(true);
419 callback.Run(false);
420 return;
422 case CONTENT_SETTING_DEFAULT:
423 case CONTENT_SETTING_ASK:
424 case CONTENT_SETTING_SESSION_ONLY:
425 state->PromptUserForDownload(callback);
426 state->increment_download_count();
427 break;
428 case CONTENT_SETTING_NUM_SETTINGS:
429 default:
430 NOTREACHED();
431 return;
433 break;
436 default:
437 NOTREACHED();
441 void DownloadRequestLimiter::Remove(TabDownloadState* state,
442 content::WebContents* contents) {
443 DCHECK(ContainsKey(state_map_, contents));
444 state_map_.erase(contents);
445 delete state;