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_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"
27 #include "url/gurl.h"
28
29 using content::BrowserThread;
30 using content::NavigationController;
31 using content::NavigationEntry;
32
33 // TabDownloadState ------------------------------------------------------------
34
TabDownloadState(DownloadRequestLimiter * host,content::WebContents * contents,content::WebContents * originating_web_contents)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();
54 }
55
~TabDownloadState()56 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
57 // We should only be destroyed after the callbacks have been notified.
58 DCHECK(callbacks_.empty());
59
60 // And we should have invalidated the back pointer.
61 DCHECK(!factory_.HasWeakPtrs());
62 }
63
AboutToNavigateRenderView(content::RenderViewHost * render_view_host)64 void DownloadRequestLimiter::TabDownloadState::AboutToNavigateRenderView(
65 content::RenderViewHost* render_view_host) {
66 switch (status_) {
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.
75 break;
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.
84 break;
85 default:
86 NOTREACHED();
87 }
88 }
89
DidGetUserGesture()90 void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
91 if (is_showing_prompt()) {
92 // Don't change the state if the user clicks on the page somewhere.
93 return;
94 }
95
96 bool promptable = (InfoBarService::FromWebContents(web_contents()) != NULL);
97 if (PermissionBubbleManager::Enabled()) {
98 promptable =
99 (PermissionBubbleManager::FromWebContents(web_contents()) != NULL);
100 }
101
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) &&
105 (!promptable ||
106 (status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED))) {
107 // Revert to default status.
108 host_->Remove(this, web_contents());
109 // WARNING: We've been deleted.
110 }
111 }
112
WebContentsDestroyed()113 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
114 // Tab closed, no need to handle closing the dialog as it's owned by the
115 // WebContents.
116
117 NotifyCallbacks(false);
118 host_->Remove(this, web_contents());
119 // WARNING: We've been deleted.
120 }
121
PromptUserForDownload(const DownloadRequestLimiter::Callback & callback)122 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
123 const DownloadRequestLimiter::Callback& callback) {
124 callbacks_.push_back(callback);
125 DCHECK(web_contents_);
126 if (is_showing_prompt())
127 return;
128
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()));
135 } else {
136 Cancel();
137 }
138 return;
139 }
140
141 DownloadRequestInfoBarDelegate::Create(
142 InfoBarService::FromWebContents(web_contents_), factory_.GetWeakPtr());
143 }
144
SetContentSetting(ContentSetting setting)145 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
146 ContentSetting setting) {
147 if (!web_contents_)
148 return;
149 HostContentSettingsMap* settings =
150 DownloadRequestLimiter::GetContentSettings(web_contents_);
151 ContentSettingsPattern pattern(
152 ContentSettingsPattern::FromURL(web_contents_->GetURL()));
153 if (!settings || !pattern.IsValid())
154 return;
155 settings->SetContentSetting(
156 pattern,
157 ContentSettingsPattern::Wildcard(),
158 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
159 std::string(),
160 setting);
161 }
162
Cancel()163 void DownloadRequestLimiter::TabDownloadState::Cancel() {
164 SetContentSetting(CONTENT_SETTING_BLOCK);
165 NotifyCallbacks(false);
166 }
167
CancelOnce()168 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
169 NotifyCallbacks(false);
170 }
171
Accept()172 void DownloadRequestLimiter::TabDownloadState::Accept() {
173 SetContentSetting(CONTENT_SETTING_ALLOW);
174 NotifyCallbacks(true);
175 }
176
TabDownloadState()177 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
178 : web_contents_(NULL),
179 host_(NULL),
180 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
181 download_count_(0),
182 factory_(this) {
183 }
184
is_showing_prompt() const185 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
186 return factory_.HasWeakPtrs();
187 }
188
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)189 void DownloadRequestLimiter::TabDownloadState::Observe(
190 int type,
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());
196
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
202 // it.
203 NavigationEntry* entry = controller->GetPendingEntry();
204 if (!entry)
205 return;
206
207 // Redirects don't count.
208 if (ui::PageTransitionIsRedirect(entry->GetTransitionType()))
209 return;
210
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_)
218 return;
219 }
220
221 NotifyCallbacks(false);
222 host_->Remove(this, web_contents());
223 }
224
NotifyCallbacks(bool allow)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;
231
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_);
240 } else {
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;
247 }
248
249 for (size_t i = 0; i < callbacks.size(); ++i)
250 host_->ScheduleNotification(callbacks[i], allow);
251
252 if (change_status)
253 set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD);
254 }
255
256 // DownloadRequestLimiter ------------------------------------------------------
257
258 HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
259
SetContentSettingsForTesting(HostContentSettingsMap * content_settings)260 void DownloadRequestLimiter::SetContentSettingsForTesting(
261 HostContentSettingsMap* content_settings) {
262 content_settings_ = content_settings;
263 }
264
DownloadRequestLimiter()265 DownloadRequestLimiter::DownloadRequestLimiter()
266 : factory_(this) {
267 }
268
~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());
273 }
274
275 DownloadRequestLimiter::DownloadStatus
GetDownloadStatus(content::WebContents * web_contents)276 DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
277 TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
278 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
279 }
280
CanDownloadOnIOThread(int render_process_host_id,int render_view_id,const GURL & url,const std::string & request_method,const Callback & callback)281 void DownloadRequestLimiter::CanDownloadOnIOThread(
282 int render_process_host_id,
283 int render_view_id,
284 const GURL& url,
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));
295 }
296
297 DownloadRequestLimiter::TabDownloadState*
GetDownloadState(content::WebContents * web_contents,content::WebContents * originating_web_contents,bool create)298 DownloadRequestLimiter::GetDownloadState(
299 content::WebContents* web_contents,
300 content::WebContents* originating_web_contents,
301 bool create) {
302 DCHECK(web_contents);
303 StateMap::iterator i = state_map_.find(web_contents);
304 if (i != state_map_.end())
305 return i->second;
306
307 if (!create)
308 return NULL;
309
310 TabDownloadState* state =
311 new TabDownloadState(this, web_contents, originating_web_contents);
312 state_map_[web_contents] = state;
313 return state;
314 }
315
CanDownload(int render_process_host_id,int render_view_id,const GURL & url,const std::string & request_method,const Callback & callback)316 void DownloadRequestLimiter::CanDownload(int render_process_host_id,
317 int render_view_id,
318 const GURL& url,
319 const std::string& request_method,
320 const Callback& callback) {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322
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);
328 return;
329 }
330
331 if (!originating_contents->GetDelegate()) {
332 ScheduleNotification(callback, false);
333 return;
334 }
335
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,
343 render_view_id,
344 request_method,
345 callback);
346
347 originating_contents->GetDelegate()->CanDownload(
348 originating_contents->GetRenderViewHost(),
349 url,
350 request_method,
351 can_download_callback);
352 }
353
OnCanDownloadDecided(int render_process_host_id,int render_view_id,const std::string & request_method,const Callback & orig_callback,bool allow)354 void DownloadRequestLimiter::OnCanDownloadDecided(
355 int render_process_host_id,
356 int render_view_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);
364 return;
365 }
366
367 CanDownloadImpl(originating_contents,
368 request_method,
369 orig_callback);
370 }
371
GetContentSettings(content::WebContents * contents)372 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
373 content::WebContents* contents) {
374 return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
375 contents->GetBrowserContext())->GetHostContentSettingsMap();
376 }
377
CanDownloadImpl(content::WebContents * originating_contents,const std::string & request_method,const Callback & callback)378 void DownloadRequestLimiter::CanDownloadImpl(
379 content::WebContents* originating_contents,
380 const std::string& request_method,
381 const Callback& callback) {
382 DCHECK(originating_contents);
383
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();
393 break;
394
395 case ALLOW_ONE_DOWNLOAD:
396 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
397 ScheduleNotification(callback, true);
398 state->increment_download_count();
399 break;
400
401 case DOWNLOADS_NOT_ALLOWED:
402 ScheduleNotification(callback, false);
403 break;
404
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,
414 std::string());
415 switch (setting) {
416 case CONTENT_SETTING_ALLOW: {
417 TabSpecificContentSettings* settings =
418 TabSpecificContentSettings::FromWebContents(
419 originating_contents);
420 if (settings)
421 settings->SetDownloadsBlocked(false);
422 ScheduleNotification(callback, true);
423 state->increment_download_count();
424 return;
425 }
426 case CONTENT_SETTING_BLOCK: {
427 TabSpecificContentSettings* settings =
428 TabSpecificContentSettings::FromWebContents(
429 originating_contents);
430 if (settings)
431 settings->SetDownloadsBlocked(true);
432 ScheduleNotification(callback, false);
433 return;
434 }
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();
440 break;
441 case CONTENT_SETTING_NUM_SETTINGS:
442 default:
443 NOTREACHED();
444 return;
445 }
446 break;
447 }
448
449 default:
450 NOTREACHED();
451 }
452 }
453
ScheduleNotification(const Callback & callback,bool allow)454 void DownloadRequestLimiter::ScheduleNotification(const Callback& callback,
455 bool allow) {
456 BrowserThread::PostTask(
457 BrowserThread::IO, FROM_HERE, base::Bind(callback, allow));
458 }
459
Remove(TabDownloadState * state,content::WebContents * contents)460 void DownloadRequestLimiter::Remove(TabDownloadState* state,
461 content::WebContents* contents) {
462 DCHECK(ContainsKey(state_map_, contents));
463 state_map_.erase(contents);
464 delete state;
465 }
466