Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / download / download_request_limiter.cc
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
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
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
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
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
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
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
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
163 void DownloadRequestLimiter::TabDownloadState::Cancel() {
164   SetContentSetting(CONTENT_SETTING_BLOCK);
165   NotifyCallbacks(false);
166 }
167
168 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
169   NotifyCallbacks(false);
170 }
171
172 void DownloadRequestLimiter::TabDownloadState::Accept() {
173   SetContentSetting(CONTENT_SETTING_ALLOW);
174   NotifyCallbacks(true);
175 }
176
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
185 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
186   return factory_.HasWeakPtrs();
187 }
188
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
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
260 void DownloadRequestLimiter::SetContentSettingsForTesting(
261     HostContentSettingsMap* content_settings) {
262   content_settings_ = content_settings;
263 }
264
265 DownloadRequestLimiter::DownloadRequestLimiter()
266     : factory_(this) {
267 }
268
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
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
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*
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
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
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
372 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
373     content::WebContents* contents) {
374   return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
375       contents->GetBrowserContext())->GetHostContentSettingsMap();
376 }
377
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
454 void DownloadRequestLimiter::ScheduleNotification(const Callback& callback,
455                                                   bool allow) {
456   BrowserThread::PostTask(
457       BrowserThread::IO, FROM_HERE, base::Bind(callback, allow));
458 }
459
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 }