Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / download / download_request_limiter.cc
blob4c5eb9bf9d94df65f76745fffe1c9ee09e75b1e3
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.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 "content/public/browser/browser_context.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/navigation_controller.h"
18 #include "content/public/browser/navigation_entry.h"
19 #include "content/public/browser/notification_source.h"
20 #include "content/public/browser/notification_types.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/resource_dispatcher_host.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/browser/web_contents_delegate.h"
25 #include "url/gurl.h"
27 using content::BrowserThread;
28 using content::NavigationController;
29 using content::NavigationEntry;
31 // TabDownloadState ------------------------------------------------------------
33 DownloadRequestLimiter::TabDownloadState::TabDownloadState(
34 DownloadRequestLimiter* host,
35 content::WebContents* contents,
36 content::WebContents* originating_web_contents)
37 : content::WebContentsObserver(contents),
38 web_contents_(contents),
39 host_(host),
40 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
41 download_count_(0),
42 factory_(this) {
43 content::Source<NavigationController> notification_source(
44 &contents->GetController());
45 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
46 notification_source);
47 NavigationEntry* active_entry = originating_web_contents ?
48 originating_web_contents->GetController().GetActiveEntry() :
49 contents->GetController().GetActiveEntry();
50 if (active_entry)
51 initial_page_host_ = active_entry->GetURL().host();
54 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
55 // We should only be destroyed after the callbacks have been notified.
56 DCHECK(callbacks_.empty());
58 // And we should have invalidated the back pointer.
59 DCHECK(!factory_.HasWeakPtrs());
62 void DownloadRequestLimiter::TabDownloadState::AboutToNavigateRenderView(
63 content::RenderViewHost* render_view_host) {
64 switch (status_) {
65 case ALLOW_ONE_DOWNLOAD:
66 case PROMPT_BEFORE_DOWNLOAD:
67 // When the user reloads the page without responding to the infobar, they
68 // are expecting DownloadRequestLimiter to behave as if they had just
69 // initially navigated to this page. See http://crbug.com/171372
70 NotifyCallbacks(false);
71 host_->Remove(this, web_contents());
72 // WARNING: We've been deleted.
73 break;
74 case DOWNLOADS_NOT_ALLOWED:
75 case ALLOW_ALL_DOWNLOADS:
76 // Don't drop this information. The user has explicitly said that they
77 // do/don't want downloads from this host. If they accidentally Accepted
78 // or Canceled, tough luck, they don't get another chance. They can copy
79 // the URL into a new tab, which will make a new DownloadRequestLimiter.
80 // See also the initial_page_host_ logic in Observe() for
81 // NOTIFICATION_NAV_ENTRY_PENDING.
82 break;
83 default:
84 NOTREACHED();
88 void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
89 if (is_showing_prompt()) {
90 // Don't change the state if the user clicks on the page somewhere.
91 return;
94 // See PromptUserForDownload(): if there's no InfoBarService, then
95 // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD.
96 if ((status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS) &&
97 (!InfoBarService::FromWebContents(web_contents()) ||
98 (status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED))) {
99 // Revert to default status.
100 host_->Remove(this, web_contents());
101 // WARNING: We've been deleted.
105 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed(
106 content::WebContents* web_contents) {
107 // Tab closed, no need to handle closing the dialog as it's owned by the
108 // WebContents.
110 NotifyCallbacks(false);
111 // Note that web_contents() is NULL at this point.
112 host_->Remove(this, web_contents);
113 // WARNING: We've been deleted.
116 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
117 const DownloadRequestLimiter::Callback& callback) {
118 callbacks_.push_back(callback);
119 DCHECK(web_contents_);
120 if (is_showing_prompt())
121 return;
122 DownloadRequestInfoBarDelegate::Create(
123 InfoBarService::FromWebContents(web_contents_), factory_.GetWeakPtr());
126 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
127 ContentSetting setting) {
128 if (!web_contents_)
129 return;
130 HostContentSettingsMap* settings =
131 DownloadRequestLimiter::GetContentSettings(web_contents_);
132 ContentSettingsPattern pattern(
133 ContentSettingsPattern::FromURL(web_contents_->GetURL()));
134 if (!settings || !pattern.IsValid())
135 return;
136 settings->SetContentSetting(
137 pattern,
138 ContentSettingsPattern::Wildcard(),
139 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
140 std::string(),
141 setting);
144 void DownloadRequestLimiter::TabDownloadState::Cancel() {
145 SetContentSetting(CONTENT_SETTING_BLOCK);
146 NotifyCallbacks(false);
149 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
150 NotifyCallbacks(false);
153 void DownloadRequestLimiter::TabDownloadState::Accept() {
154 SetContentSetting(CONTENT_SETTING_ALLOW);
155 NotifyCallbacks(true);
158 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
159 : web_contents_(NULL),
160 host_(NULL),
161 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
162 download_count_(0),
163 factory_(this) {
166 void DownloadRequestLimiter::TabDownloadState::Observe(
167 int type,
168 const content::NotificationSource& source,
169 const content::NotificationDetails& details) {
170 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type);
171 content::NavigationController* controller = &web_contents()->GetController();
172 DCHECK_EQ(controller, content::Source<NavigationController>(source).ptr());
174 // NOTE: Resetting state on a pending navigate isn't ideal. In particular it
175 // is possible that queued up downloads for the page before the pending
176 // navigation will be delivered to us after we process this request. If this
177 // happens we may let a download through that we shouldn't have. But this is
178 // rather rare, and it is difficult to get 100% right, so we don't deal with
179 // it.
180 NavigationEntry* entry = controller->GetPendingEntry();
181 if (!entry)
182 return;
184 // Redirects don't count.
185 if (content::PageTransitionIsRedirect(entry->GetTransitionType()))
186 return;
188 if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
189 status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
190 // User has either allowed all downloads or canceled all downloads. Only
191 // reset the download state if the user is navigating to a different host
192 // (or host is empty).
193 if (!initial_page_host_.empty() && !entry->GetURL().host().empty() &&
194 entry->GetURL().host() == initial_page_host_)
195 return;
198 NotifyCallbacks(false);
199 host_->Remove(this, web_contents());
202 void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
203 set_download_status(allow ?
204 DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS :
205 DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED);
206 std::vector<DownloadRequestLimiter::Callback> callbacks;
207 bool change_status = false;
209 // Selectively send first few notifications only if number of downloads exceed
210 // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
211 // don't close it. If allow is false, we send all the notifications to cancel
212 // all remaining downloads and close the infobar.
213 if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
214 // Null the generated weak pointer so we don't get notified again.
215 factory_.InvalidateWeakPtrs();
216 callbacks.swap(callbacks_);
217 } else {
218 std::vector<DownloadRequestLimiter::Callback>::iterator start, end;
219 start = callbacks_.begin();
220 end = callbacks_.begin() + kMaxDownloadsAtOnce;
221 callbacks.assign(start, end);
222 callbacks_.erase(start, end);
223 change_status = true;
226 for (size_t i = 0; i < callbacks.size(); ++i)
227 host_->ScheduleNotification(callbacks[i], allow);
229 if (change_status)
230 set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD);
233 // DownloadRequestLimiter ------------------------------------------------------
235 HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
237 void DownloadRequestLimiter::SetContentSettingsForTesting(
238 HostContentSettingsMap* content_settings) {
239 content_settings_ = content_settings;
242 DownloadRequestLimiter::DownloadRequestLimiter()
243 : factory_(this) {
246 DownloadRequestLimiter::~DownloadRequestLimiter() {
247 // All the tabs should have closed before us, which sends notification and
248 // removes from state_map_. As such, there should be no pending callbacks.
249 DCHECK(state_map_.empty());
252 DownloadRequestLimiter::DownloadStatus
253 DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
254 TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
255 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
258 void DownloadRequestLimiter::CanDownloadOnIOThread(
259 int render_process_host_id,
260 int render_view_id,
261 int request_id,
262 const std::string& request_method,
263 const Callback& callback) {
264 // This is invoked on the IO thread. Schedule the task to run on the UI
265 // thread so that we can query UI state.
266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
267 BrowserThread::PostTask(
268 BrowserThread::UI, FROM_HERE,
269 base::Bind(&DownloadRequestLimiter::CanDownload, this,
270 render_process_host_id, render_view_id, request_id,
271 request_method, callback));
274 DownloadRequestLimiter::TabDownloadState*
275 DownloadRequestLimiter::GetDownloadState(
276 content::WebContents* web_contents,
277 content::WebContents* originating_web_contents,
278 bool create) {
279 DCHECK(web_contents);
280 StateMap::iterator i = state_map_.find(web_contents);
281 if (i != state_map_.end())
282 return i->second;
284 if (!create)
285 return NULL;
287 TabDownloadState* state =
288 new TabDownloadState(this, web_contents, originating_web_contents);
289 state_map_[web_contents] = state;
290 return state;
293 void DownloadRequestLimiter::CanDownload(int render_process_host_id,
294 int render_view_id,
295 int request_id,
296 const std::string& request_method,
297 const Callback& callback) {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300 content::WebContents* originating_contents =
301 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
302 if (!originating_contents) {
303 // The WebContents was closed, don't allow the download.
304 ScheduleNotification(callback, false);
305 return;
308 if (!originating_contents->GetDelegate()) {
309 ScheduleNotification(callback, false);
310 return;
313 // Note that because |originating_contents| might go away before
314 // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id|
315 // and |render_view_id|.
316 base::Callback<void(bool)> can_download_callback = base::Bind(
317 &DownloadRequestLimiter::OnCanDownloadDecided,
318 factory_.GetWeakPtr(),
319 render_process_host_id,
320 render_view_id,
321 request_id,
322 request_method,
323 callback);
325 originating_contents->GetDelegate()->CanDownload(
326 originating_contents->GetRenderViewHost(),
327 request_id,
328 request_method,
329 can_download_callback);
332 void DownloadRequestLimiter::OnCanDownloadDecided(
333 int render_process_host_id,
334 int render_view_id,
335 int request_id,
336 const std::string& request_method,
337 const Callback& orig_callback, bool allow) {
338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
339 content::WebContents* originating_contents =
340 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
341 if (!originating_contents || !allow) {
342 ScheduleNotification(orig_callback, false);
343 return;
346 CanDownloadImpl(originating_contents,
347 request_id,
348 request_method,
349 orig_callback);
352 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
353 content::WebContents* contents) {
354 return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
355 contents->GetBrowserContext())->GetHostContentSettingsMap();
358 void DownloadRequestLimiter::CanDownloadImpl(
359 content::WebContents* originating_contents,
360 int request_id,
361 const std::string& request_method,
362 const Callback& callback) {
363 DCHECK(originating_contents);
365 TabDownloadState* state = GetDownloadState(
366 originating_contents, originating_contents, true);
367 switch (state->download_status()) {
368 case ALLOW_ALL_DOWNLOADS:
369 if (state->download_count() && !(state->download_count() %
370 DownloadRequestLimiter::kMaxDownloadsAtOnce))
371 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
372 ScheduleNotification(callback, true);
373 state->increment_download_count();
374 break;
376 case ALLOW_ONE_DOWNLOAD:
377 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
378 ScheduleNotification(callback, true);
379 state->increment_download_count();
380 break;
382 case DOWNLOADS_NOT_ALLOWED:
383 ScheduleNotification(callback, false);
384 break;
386 case PROMPT_BEFORE_DOWNLOAD: {
387 HostContentSettingsMap* content_settings = GetContentSettings(
388 originating_contents);
389 ContentSetting setting = CONTENT_SETTING_ASK;
390 if (content_settings)
391 setting = content_settings->GetContentSetting(
392 originating_contents->GetURL(),
393 originating_contents->GetURL(),
394 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
395 std::string());
396 switch (setting) {
397 case CONTENT_SETTING_ALLOW: {
398 TabSpecificContentSettings* settings =
399 TabSpecificContentSettings::FromWebContents(
400 originating_contents);
401 if (settings)
402 settings->SetDownloadsBlocked(false);
403 ScheduleNotification(callback, true);
404 state->increment_download_count();
405 return;
407 case CONTENT_SETTING_BLOCK: {
408 TabSpecificContentSettings* settings =
409 TabSpecificContentSettings::FromWebContents(
410 originating_contents);
411 if (settings)
412 settings->SetDownloadsBlocked(true);
413 ScheduleNotification(callback, false);
414 return;
416 case CONTENT_SETTING_DEFAULT:
417 case CONTENT_SETTING_ASK:
418 case CONTENT_SETTING_SESSION_ONLY:
419 state->PromptUserForDownload(callback);
420 state->increment_download_count();
421 break;
422 case CONTENT_SETTING_NUM_SETTINGS:
423 default:
424 NOTREACHED();
425 return;
427 break;
430 default:
431 NOTREACHED();
435 void DownloadRequestLimiter::ScheduleNotification(const Callback& callback,
436 bool allow) {
437 BrowserThread::PostTask(
438 BrowserThread::IO, FROM_HERE, base::Bind(callback, allow));
441 void DownloadRequestLimiter::Remove(TabDownloadState* state,
442 content::WebContents* contents) {
443 DCHECK(ContainsKey(state_map_, contents));
444 state_map_.erase(contents);
445 delete state;