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 <iterator>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/stl_util.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
14 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
15 #include "chrome/browser/download/download_permission_request.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/tab_contents/tab_util.h"
18 #include "components/content_settings/core/browser/content_settings_details.h"
19 #include "components/permissions/permission_request_manager.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/browser_task_traits.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/navigation_controller.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/navigation_handle.h"
26 #include "content/public/browser/render_process_host.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_delegate.h"
29 #include "url/gurl.h"
30 
31 using content::BrowserThread;
32 using content::NavigationController;
33 using content::NavigationEntry;
34 
35 namespace {
36 
GetSettingFromDownloadStatus(DownloadRequestLimiter::DownloadStatus status)37 ContentSetting GetSettingFromDownloadStatus(
38     DownloadRequestLimiter::DownloadStatus status) {
39   switch (status) {
40     case DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD:
41     case DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD:
42       return CONTENT_SETTING_ASK;
43     case DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS:
44       return CONTENT_SETTING_ALLOW;
45     case DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED:
46       return CONTENT_SETTING_BLOCK;
47   }
48   NOTREACHED();
49   return CONTENT_SETTING_DEFAULT;
50 }
51 
GetDownloadStatusFromSetting(ContentSetting setting)52 DownloadRequestLimiter::DownloadStatus GetDownloadStatusFromSetting(
53     ContentSetting setting) {
54   switch (setting) {
55     case CONTENT_SETTING_ALLOW:
56       return DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS;
57     case CONTENT_SETTING_BLOCK:
58       return DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED;
59     case CONTENT_SETTING_DEFAULT:
60     case CONTENT_SETTING_ASK:
61       return DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD;
62     case CONTENT_SETTING_SESSION_ONLY:
63     case CONTENT_SETTING_NUM_SETTINGS:
64     case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT:
65       NOTREACHED();
66       return DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD;
67   }
68   NOTREACHED();
69   return DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD;
70 }
71 
GetUiStatusFromDownloadStatus(DownloadRequestLimiter::DownloadStatus status,bool download_seen)72 DownloadRequestLimiter::DownloadUiStatus GetUiStatusFromDownloadStatus(
73     DownloadRequestLimiter::DownloadStatus status,
74     bool download_seen) {
75   if (!download_seen)
76     return DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT;
77 
78   switch (status) {
79     case DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS:
80       return DownloadRequestLimiter::DOWNLOAD_UI_ALLOWED;
81     case DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED:
82       return DownloadRequestLimiter::DOWNLOAD_UI_BLOCKED;
83     case DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD:
84     case DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD:
85       return DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT;
86   }
87   NOTREACHED();
88   return DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT;
89 }
90 
91 }  // namespace
92 
93 // TabDownloadState ------------------------------------------------------------
94 
TabDownloadState(DownloadRequestLimiter * host,content::WebContents * contents)95 DownloadRequestLimiter::TabDownloadState::TabDownloadState(
96     DownloadRequestLimiter* host,
97     content::WebContents* contents)
98     : content::WebContentsObserver(contents),
99       web_contents_(contents),
100       host_(host),
101       status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
102       ui_status_(DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT),
103       origin_(url::Origin::Create(contents->GetVisibleURL())),
104       download_count_(0),
105       download_seen_(false) {
106   observer_.Add(GetContentSettings(contents));
107   NavigationEntry* last_entry =
108       contents->GetController().GetLastCommittedEntry();
109   if (last_entry)
110     initial_page_host_ = last_entry->GetURL().host();
111 }
112 
~TabDownloadState()113 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
114   // We should only be destroyed after the callbacks have been notified.
115   DCHECK(callbacks_.empty());
116 
117   // And we should have invalidated the back pointer.
118   DCHECK(!factory_.HasWeakPtrs());
119 }
120 
SetDownloadStatusAndNotify(const url::Origin & request_origin,DownloadStatus status)121 void DownloadRequestLimiter::TabDownloadState::SetDownloadStatusAndNotify(
122     const url::Origin& request_origin,
123     DownloadStatus status) {
124   SetDownloadStatusAndNotifyImpl(request_origin, status,
125                                  GetSettingFromDownloadStatus(status));
126 }
127 
DidStartNavigation(content::NavigationHandle * navigation_handle)128 void DownloadRequestLimiter::TabDownloadState::DidStartNavigation(
129     content::NavigationHandle* navigation_handle) {
130   if (!navigation_handle->IsInMainFrame())
131     return;
132 
133   download_seen_ = false;
134   ui_status_ = DOWNLOAD_UI_DEFAULT;
135 
136   if (status_ == PROMPT_BEFORE_DOWNLOAD || status_ == DOWNLOADS_NOT_ALLOWED) {
137     // If the navigation is renderer-initiated (but not user-initiated), ensure
138     // that a prompting or blocking limiter state is not reset, so
139     // window.location.href or meta refresh can't be abused to avoid the
140     // limiter.
141     if (navigation_handle->IsRendererInitiated()) {
142       GURL url = navigation_handle->GetURL();
143       // Mark the origin as restricted. If the origin does not exist in
144       // |download_status_map_|, give it a default value of
145       // PROMPT_BEFORE_DOWNLOAD and content setting will be checked later once
146       // CanDownloadImpl() is called.
147       if (!url.is_empty())
148         download_status_map_.emplace(url::Origin::Create(url),
149                                      PROMPT_BEFORE_DOWNLOAD);
150       return;
151     }
152 
153     // If this is a forward/back navigation, also don't reset a prompting or
154     // blocking limiter state unless a new host is encounted. This prevents a
155     // page to use history forward/backward to trigger multiple downloads.
156     if (IsNavigationRestricted(navigation_handle))
157       return;
158   }
159 
160   if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
161       status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
162     // User has either allowed all downloads or blocked all downloads. Only
163     // reset the download state if the user is navigating to a different host
164     // (or host is empty).
165     if (!initial_page_host_.empty() &&
166         navigation_handle->GetURL().host_piece() == initial_page_host_) {
167       return;
168     }
169   }
170 
171   NotifyCallbacks(false);
172   host_->Remove(this, web_contents());
173 }
174 
DidFinishNavigation(content::NavigationHandle * navigation_handle)175 void DownloadRequestLimiter::TabDownloadState::DidFinishNavigation(
176     content::NavigationHandle* navigation_handle) {
177   if (!navigation_handle->IsInMainFrame())
178     return;
179 
180   // Treat browser-initiated navigations as user interactions as long as the
181   // navigation isn't restricted.
182   if (!navigation_handle->IsRendererInitiated() &&
183       !IsNavigationRestricted(navigation_handle)) {
184     OnUserInteraction();
185     return;
186   }
187 
188   // When the status is ALLOW_ALL_DOWNLOADS or DOWNLOADS_NOT_ALLOWED, don't drop
189   // this information. The user has explicitly said that they do/don't want
190   // downloads from this host. If they accidentally Accepted or Canceled, they
191   // can adjust the limiter state by adjusting the automatic downloads content
192   // settings. Alternatively, they can copy the URL into a new tab, which will
193   // make a new DownloadRequestLimiter. See also the initial_page_host_ logic in
194   // DidStartNavigation.
195   if (status_ == ALLOW_ONE_DOWNLOAD) {
196     // When the user reloads the page without responding to the prompt,
197     // they are expecting DownloadRequestLimiter to behave as if they had
198     // just initially navigated to this page. See http://crbug.com/171372.
199     // However, explicitly leave the limiter in place if the navigation was
200     // renderer-initiated and we are in a prompt state.
201     NotifyCallbacks(false);
202     host_->Remove(this, web_contents());
203     // WARNING: We've been deleted.
204   }
205 }
206 
DidGetUserInteraction(const blink::WebInputEvent & event)207 void DownloadRequestLimiter::TabDownloadState::DidGetUserInteraction(
208     const blink::WebInputEvent& event) {
209   if (is_showing_prompt() ||
210       event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
211     // Don't change state if a prompt is showing or if the user has scrolled.
212     return;
213   }
214 
215   OnUserInteraction();
216 }
217 
WebContentsDestroyed()218 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
219   // Tab closed, no need to handle closing the dialog as it's owned by the
220   // WebContents.
221 
222   NotifyCallbacks(false);
223   host_->Remove(this, web_contents());
224   // WARNING: We've been deleted.
225 }
226 
PromptUserForDownload(DownloadRequestLimiter::Callback callback,const url::Origin & request_origin)227 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
228     DownloadRequestLimiter::Callback callback,
229     const url::Origin& request_origin) {
230   callbacks_.push_back(std::move(callback));
231   DCHECK(web_contents_);
232   if (is_showing_prompt())
233     return;
234 
235   permissions::PermissionRequestManager* permission_request_manager =
236       permissions::PermissionRequestManager::FromWebContents(web_contents_);
237   if (permission_request_manager) {
238     // TODO(https://crbug.com/1061899): We should pass the frame which initiated
239     // the action instead of assuming that it was the current main frame.
240     permission_request_manager->AddRequest(
241         web_contents_->GetMainFrame(),
242         new DownloadPermissionRequest(factory_.GetWeakPtr(), request_origin));
243   } else {
244     // Call CancelOnce() so we don't set the content settings.
245     CancelOnce(request_origin);
246   }
247 }
248 
SetContentSetting(ContentSetting setting,const url::Origin & request_origin)249 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
250     ContentSetting setting,
251     const url::Origin& request_origin) {
252   if (!web_contents_)
253     return;
254   if (request_origin.opaque())
255     return;
256   HostContentSettingsMap* settings =
257       DownloadRequestLimiter::GetContentSettings(web_contents_);
258   if (!settings)
259     return;
260   settings->SetContentSettingDefaultScope(
261       request_origin.GetURL(), GURL(), ContentSettingsType::AUTOMATIC_DOWNLOADS,
262       setting);
263 }
264 
Cancel(const url::Origin & request_origin)265 void DownloadRequestLimiter::TabDownloadState::Cancel(
266     const url::Origin& request_origin) {
267   SetContentSetting(CONTENT_SETTING_BLOCK, request_origin);
268   bool throttled = NotifyCallbacks(false);
269   SetDownloadStatusAndNotify(request_origin, throttled ? PROMPT_BEFORE_DOWNLOAD
270                                                        : DOWNLOADS_NOT_ALLOWED);
271 }
272 
CancelOnce(const url::Origin & request_origin)273 void DownloadRequestLimiter::TabDownloadState::CancelOnce(
274     const url::Origin& request_origin) {
275   bool throttled = NotifyCallbacks(false);
276   SetDownloadStatusAndNotify(request_origin, throttled ? PROMPT_BEFORE_DOWNLOAD
277                                                        : DOWNLOADS_NOT_ALLOWED);
278 }
279 
Accept(const url::Origin & request_origin)280 void DownloadRequestLimiter::TabDownloadState::Accept(
281     const url::Origin& request_origin) {
282   SetContentSetting(CONTENT_SETTING_ALLOW, request_origin);
283   bool throttled = NotifyCallbacks(true);
284   SetDownloadStatusAndNotify(
285       request_origin, throttled ? PROMPT_BEFORE_DOWNLOAD : ALLOW_ALL_DOWNLOADS);
286 }
287 
288 DownloadRequestLimiter::DownloadStatus
GetDownloadStatus(const url::Origin & request_origin)289 DownloadRequestLimiter::TabDownloadState::GetDownloadStatus(
290     const url::Origin& request_origin) {
291   auto it = download_status_map_.find(request_origin);
292   if (it != download_status_map_.end())
293     return it->second;
294   return ALLOW_ONE_DOWNLOAD;
295 }
296 
TabDownloadState()297 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
298     : web_contents_(nullptr),
299       host_(nullptr),
300       status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
301       ui_status_(DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT),
302       download_count_(0),
303       download_seen_(false),
304       observer_(this) {}
305 
is_showing_prompt() const306 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
307   return factory_.HasWeakPtrs();
308 }
309 
OnUserInteraction()310 void DownloadRequestLimiter::TabDownloadState::OnUserInteraction() {
311   // See PromptUserForDownload(): if there's no PermissionRequestManager, then
312   // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD.
313   bool need_prompt = (permissions::PermissionRequestManager::FromWebContents(
314                           web_contents()) == nullptr &&
315                       status_ == DOWNLOADS_NOT_ALLOWED) ||
316                      status_ == PROMPT_BEFORE_DOWNLOAD;
317 
318   // If content setting blocks automatic downloads, don't reset the
319   // PROMPT_BEFORE_DOWNLOAD status for the current page because doing
320   // that will default the download status to ALLOW_ONE_DOWNLOAD. That
321   // will allow an extra download when CanDownloadImpl() is called.
322   ContentSetting setting = GetAutoDownloadContentSetting(
323       web_contents(), web_contents()->GetVisibleURL());
324   if (status_ == ALLOW_ONE_DOWNLOAD ||
325       (need_prompt && setting != CONTENT_SETTING_BLOCK)) {
326     url::Origin origin = url::Origin::Create(web_contents()->GetVisibleURL());
327 
328     // Revert to default status and notify if needed.
329     download_status_map_.erase(origin);
330     if (download_status_map_.empty()) {
331       host_->Remove(this, web_contents());
332       // WARNING: We've been deleted.
333     }
334   }
335 }
336 
OnContentSettingChanged(const ContentSettingsPattern & primary_pattern,const ContentSettingsPattern & secondary_pattern,ContentSettingsType content_type)337 void DownloadRequestLimiter::TabDownloadState::OnContentSettingChanged(
338     const ContentSettingsPattern& primary_pattern,
339     const ContentSettingsPattern& secondary_pattern,
340     ContentSettingsType content_type) {
341   if (content_type != ContentSettingsType::AUTOMATIC_DOWNLOADS)
342     return;
343 
344   if (origin_.opaque())
345     return;
346 
347   GURL origin = origin_.GetURL();
348   // Analogous to PageSpecificContentSettings::OnContentSettingChanged:
349   const ContentSettingsDetails details(primary_pattern, secondary_pattern,
350                                        content_type);
351 
352   // Check if the settings change affects the most recent origin passed
353   // to SetDownloadStatusAndNotify(). If so, we need to update the omnibox
354   // decoration.
355   if (!details.update_all() && !details.primary_pattern().Matches(origin))
356     return;
357 
358   // Content settings have been updated for our web contents, e.g. via the OIB
359   // or the settings page. Check to see if the automatic downloads setting is
360   // different to our internal state, and update the internal state to match if
361   // necessary. If there is no content setting persisted, then retain the
362   // current state and do nothing.
363   //
364   // NotifyCallbacks is not called as this notification should be triggered when
365   // a download is not pending.
366   //
367   // Fetch the content settings map for this web contents, and extract the
368   // automatic downloads permission value.
369   HostContentSettingsMap* content_settings = GetContentSettings(web_contents());
370   if (!content_settings)
371     return;
372 
373   ContentSetting setting = content_settings->GetContentSetting(
374       origin, origin, ContentSettingsType::AUTOMATIC_DOWNLOADS);
375 
376   // Update the internal state to match if necessary.
377   SetDownloadStatusAndNotifyImpl(origin_, GetDownloadStatusFromSetting(setting),
378                                  setting);
379 }
380 
NotifyCallbacks(bool allow)381 bool DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
382   std::vector<DownloadRequestLimiter::Callback> callbacks;
383   bool throttled = false;
384 
385   // Selectively send first few notifications only if number of downloads exceed
386   // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
387   // don't close it. If allow is false, we send all the notifications to cancel
388   // all remaining downloads and close the infobar.
389   if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
390     // Null the generated weak pointer so we don't get notified again.
391     factory_.InvalidateWeakPtrs();
392     callbacks.swap(callbacks_);
393   } else {
394     std::vector<DownloadRequestLimiter::Callback>::iterator start, end;
395     start = callbacks_.begin();
396     end = callbacks_.begin() + kMaxDownloadsAtOnce;
397     callbacks.assign(std::make_move_iterator(start),
398                      std::make_move_iterator(end));
399     callbacks_.erase(start, end);
400     throttled = true;
401   }
402 
403   for (auto& callback : callbacks) {
404     // When callback runs, it can cause the WebContents to be destroyed.
405     content::GetUIThreadTaskRunner({})->PostTask(
406         FROM_HERE, base::BindOnce(std::move(callback), allow));
407   }
408 
409   return throttled;
410 }
411 
SetDownloadStatusAndNotifyImpl(const url::Origin & request_origin,DownloadStatus status,ContentSetting setting)412 void DownloadRequestLimiter::TabDownloadState::SetDownloadStatusAndNotifyImpl(
413     const url::Origin& request_origin,
414     DownloadStatus status,
415     ContentSetting setting) {
416   DCHECK((GetSettingFromDownloadStatus(status) == setting) ||
417          (GetDownloadStatusFromSetting(setting) == status))
418       << "status " << status << " and setting " << setting
419       << " do not correspond to each other";
420   ContentSetting last_setting = GetSettingFromDownloadStatus(status_);
421   DownloadUiStatus last_ui_status = ui_status_;
422   url::Origin last_origin = origin_;
423 
424   status_ = status;
425   ui_status_ = GetUiStatusFromDownloadStatus(status_, download_seen_);
426   origin_ = request_origin;
427 
428   if (status_ != ALLOW_ONE_DOWNLOAD)
429     download_status_map_[request_origin] = status_;
430   else
431     download_status_map_.erase(request_origin);
432 
433   if (!web_contents())
434     return;
435 
436   // For opaque origins, the omnibox decoration cannot show the URL. As a
437   // result, don't send a notification.
438   if (origin_.opaque())
439     return;
440 
441   // We want to send a notification if the UI status has changed to ensure that
442   // the omnibox decoration updates appropriately. This is effectively the same
443   // as other permissions which might be in an allow state, but do not show UI
444   // until they are actively used.
445   if (last_setting == setting && last_ui_status == ui_status_ &&
446       origin_ == last_origin) {
447     return;
448   }
449 
450   content_settings::UpdateLocationBarUiForWebContents(web_contents());
451 }
452 
IsNavigationRestricted(content::NavigationHandle * navigation_handle)453 bool DownloadRequestLimiter::TabDownloadState::IsNavigationRestricted(
454     content::NavigationHandle* navigation_handle) {
455   url::Origin origin = url::Origin::Create(navigation_handle->GetURL());
456   if (navigation_handle->GetPageTransition() &
457       ui::PAGE_TRANSITION_FORWARD_BACK) {
458     auto it = download_status_map_.find(origin);
459     if (it != download_status_map_.end())
460       return it->second != ALLOW_ALL_DOWNLOADS;
461   }
462   return false;
463 }
464 
465 // DownloadRequestLimiter ------------------------------------------------------
466 
DownloadRequestLimiter()467 DownloadRequestLimiter::DownloadRequestLimiter() {}
468 
~DownloadRequestLimiter()469 DownloadRequestLimiter::~DownloadRequestLimiter() {
470   // All the tabs should have closed before us, which sends notification and
471   // removes from state_map_. As such, there should be no pending callbacks.
472   DCHECK(state_map_.empty());
473 }
474 
475 DownloadRequestLimiter::DownloadStatus
GetDownloadStatus(content::WebContents * web_contents)476 DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
477   TabDownloadState* state = GetDownloadState(web_contents, false);
478   return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
479 }
480 
481 DownloadRequestLimiter::DownloadUiStatus
GetDownloadUiStatus(content::WebContents * web_contents)482 DownloadRequestLimiter::GetDownloadUiStatus(
483     content::WebContents* web_contents) {
484   TabDownloadState* state = GetDownloadState(web_contents, false);
485   return state ? state->download_ui_status() : DOWNLOAD_UI_DEFAULT;
486 }
487 
GetDownloadOrigin(content::WebContents * web_contents)488 GURL DownloadRequestLimiter::GetDownloadOrigin(
489     content::WebContents* web_contents) {
490   TabDownloadState* state = GetDownloadState(web_contents, false);
491   if (state && !state->origin().opaque())
492     return state->origin().GetURL();
493   return web_contents->GetVisibleURL();
494 }
495 
496 DownloadRequestLimiter::TabDownloadState*
GetDownloadState(content::WebContents * web_contents,bool create)497 DownloadRequestLimiter::GetDownloadState(
498     content::WebContents* web_contents,
499     bool create) {
500   DCHECK(web_contents);
501   auto i = state_map_.find(web_contents);
502   if (i != state_map_.end())
503     return i->second;
504 
505   if (!create)
506     return nullptr;
507 
508   TabDownloadState* state = new TabDownloadState(this, web_contents);
509   state_map_[web_contents] = state;
510   return state;
511 }
512 
CanDownload(const content::WebContents::Getter & web_contents_getter,const GURL & url,const std::string & request_method,base::Optional<url::Origin> request_initiator,bool from_download_cross_origin_redirect,Callback callback)513 void DownloadRequestLimiter::CanDownload(
514     const content::WebContents::Getter& web_contents_getter,
515     const GURL& url,
516     const std::string& request_method,
517     base::Optional<url::Origin> request_initiator,
518     bool from_download_cross_origin_redirect,
519     Callback callback) {
520   DCHECK_CURRENTLY_ON(BrowserThread::UI);
521 
522   content::WebContents* originating_contents = web_contents_getter.Run();
523   if (!originating_contents) {
524     // The WebContents was closed, don't allow the download.
525     std::move(callback).Run(false);
526     return;
527   }
528 
529   if (!originating_contents->GetDelegate()) {
530     std::move(callback).Run(false);
531     return;
532   }
533 
534   // Note that because |originating_contents| might go away before
535   // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id|
536   // and |render_view_id|.
537   base::OnceCallback<void(bool)> can_download_callback = base::BindOnce(
538       &DownloadRequestLimiter::OnCanDownloadDecided, factory_.GetWeakPtr(),
539       web_contents_getter, request_method, std::move(request_initiator),
540       from_download_cross_origin_redirect, std::move(callback));
541 
542   originating_contents->GetDelegate()->CanDownload(
543       url, request_method, std::move(can_download_callback));
544 }
545 
OnCanDownloadDecided(const content::WebContents::Getter & web_contents_getter,const std::string & request_method,base::Optional<url::Origin> request_initiator,bool from_download_cross_origin_redirect,Callback orig_callback,bool allow)546 void DownloadRequestLimiter::OnCanDownloadDecided(
547     const content::WebContents::Getter& web_contents_getter,
548     const std::string& request_method,
549     base::Optional<url::Origin> request_initiator,
550     bool from_download_cross_origin_redirect,
551     Callback orig_callback,
552     bool allow) {
553   DCHECK_CURRENTLY_ON(BrowserThread::UI);
554   content::WebContents* originating_contents = web_contents_getter.Run();
555   if (!originating_contents || !allow) {
556     std::move(orig_callback).Run(false);
557     return;
558   }
559 
560   CanDownloadImpl(
561       originating_contents, request_method, std::move(request_initiator),
562       from_download_cross_origin_redirect, std::move(orig_callback));
563 }
564 
GetContentSettings(content::WebContents * contents)565 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
566     content::WebContents* contents) {
567   return HostContentSettingsMapFactory::GetForProfile(
568       Profile::FromBrowserContext(contents->GetBrowserContext()));
569 }
570 
GetAutoDownloadContentSetting(content::WebContents * contents,const GURL & request_initiator)571 ContentSetting DownloadRequestLimiter::GetAutoDownloadContentSetting(
572     content::WebContents* contents,
573     const GURL& request_initiator) {
574   HostContentSettingsMap* content_settings = GetContentSettings(contents);
575   ContentSetting setting = CONTENT_SETTING_ASK;
576   if (content_settings) {
577     setting = content_settings->GetContentSetting(
578         request_initiator, request_initiator,
579         ContentSettingsType::AUTOMATIC_DOWNLOADS);
580   }
581   return setting;
582 }
583 
CanDownloadImpl(content::WebContents * originating_contents,const std::string & request_method,base::Optional<url::Origin> request_initiator,bool from_download_cross_origin_redirect,Callback callback)584 void DownloadRequestLimiter::CanDownloadImpl(
585     content::WebContents* originating_contents,
586     const std::string& request_method,
587     base::Optional<url::Origin> request_initiator,
588     bool from_download_cross_origin_redirect,
589     Callback callback) {
590   DCHECK(originating_contents);
591 
592   // Always allow download resulted from a cross-origin redirect from a previous
593   // download attempt, and there's no need to update any state.
594   if (from_download_cross_origin_redirect) {
595     std::move(callback).Run(true);
596     if (!on_can_download_decided_callback_.is_null())
597       on_can_download_decided_callback_.Run(true);
598     return;
599   }
600 
601   TabDownloadState* state = GetDownloadState(originating_contents, true);
602   state->set_download_seen();
603   bool ret = true;
604 
605   // |request_initiator| may come from another web_contents. Check the content
606   // settings first to see if the download needs to be blocked.
607   GURL initiator = request_initiator ? request_initiator->GetURL()
608                                      : originating_contents->GetVisibleURL();
609   // Use the origin of |originating_contents| as a back up, if it is non-opaque.
610   url::Origin origin =
611       url::Origin::Create(originating_contents->GetVisibleURL());
612   // If |request_initiator| has a non-opaque origin or if the origin from
613   // |originating_contents| is opaque, use the origin from |request_initiator|
614   // to make decisions so that it won't impact the download state of
615   // |originating_contents|.
616   if (request_initiator && (!request_initiator->opaque() || origin.opaque()))
617     origin = request_initiator.value();
618 
619   DownloadStatus status = state->GetDownloadStatus(origin);
620 
621   bool is_opaque_initiator = request_initiator && request_initiator->opaque();
622 
623   // Always check for the content setting first. Having an content setting
624   // observer won't work as |request_initiator| might be different from the tab
625   // URL.
626   ContentSetting setting =
627       is_opaque_initiator
628           ? CONTENT_SETTING_BLOCK
629           : GetAutoDownloadContentSetting(originating_contents, initiator);
630   // Override the status if content setting is block or allow. If the content
631   // setting is always allow, only reset the status if it is
632   // DOWNLOADS_NOT_ALLOWED so unnecessary notifications will not be triggered.
633   // If the content setting is block, allow only one download to proceed if the
634   // current status is ALLOW_ALL_DOWNLOADS.
635   if (setting == CONTENT_SETTING_BLOCK && status == ALLOW_ALL_DOWNLOADS) {
636     status = ALLOW_ONE_DOWNLOAD;
637   } else if (setting == CONTENT_SETTING_ALLOW &&
638              status == DOWNLOADS_NOT_ALLOWED) {
639     status = ALLOW_ALL_DOWNLOADS;
640   }
641 
642   // Always call SetDownloadStatusAndNotify since we may need to change the
643   // omnibox UI even if the internal state stays the same. For instance, we want
644   // to hide the indicator until a download is triggered, even if we know
645   // downloads are blocked. This mirrors the behaviour of other omnibox
646   // decorations like geolocation.
647   switch (status) {
648     case ALLOW_ALL_DOWNLOADS:
649       if (state->download_count() &&
650           !(state->download_count() %
651             DownloadRequestLimiter::kMaxDownloadsAtOnce)) {
652         state->SetDownloadStatusAndNotify(origin, PROMPT_BEFORE_DOWNLOAD);
653       } else {
654         state->SetDownloadStatusAndNotify(origin, ALLOW_ALL_DOWNLOADS);
655       }
656       std::move(callback).Run(true);
657       state->increment_download_count();
658       break;
659 
660     case ALLOW_ONE_DOWNLOAD:
661       state->SetDownloadStatusAndNotify(origin, PROMPT_BEFORE_DOWNLOAD);
662       std::move(callback).Run(true);
663       state->increment_download_count();
664       break;
665 
666     case DOWNLOADS_NOT_ALLOWED:
667       state->SetDownloadStatusAndNotify(origin, DOWNLOADS_NOT_ALLOWED);
668       ret = false;
669       std::move(callback).Run(false);
670       break;
671 
672     case PROMPT_BEFORE_DOWNLOAD: {
673       switch (setting) {
674         case CONTENT_SETTING_ALLOW: {
675           state->SetDownloadStatusAndNotify(origin, ALLOW_ALL_DOWNLOADS);
676           std::move(callback).Run(true);
677           state->increment_download_count();
678           break;
679         }
680         case CONTENT_SETTING_BLOCK: {
681           state->SetDownloadStatusAndNotify(origin, DOWNLOADS_NOT_ALLOWED);
682           ret = false;
683           std::move(callback).Run(false);
684           break;
685         }
686         case CONTENT_SETTING_DEFAULT:
687         case CONTENT_SETTING_ASK:
688           state->PromptUserForDownload(std::move(callback), origin);
689           state->increment_download_count();
690           ret = false;
691           break;
692         case CONTENT_SETTING_SESSION_ONLY:
693         case CONTENT_SETTING_NUM_SETTINGS:
694         default:
695           NOTREACHED();
696           return;
697       }
698       break;
699     }
700 
701     default:
702       NOTREACHED();
703   }
704 
705   if (!on_can_download_decided_callback_.is_null())
706     on_can_download_decided_callback_.Run(ret);
707 }
708 
Remove(TabDownloadState * state,content::WebContents * contents)709 void DownloadRequestLimiter::Remove(TabDownloadState* state,
710                                     content::WebContents* contents) {
711   DCHECK(base::Contains(state_map_, contents));
712   state_map_.erase(contents);
713   delete state;
714 }
715 
SetOnCanDownloadDecidedCallbackForTesting(CanDownloadDecidedCallback callback)716 void DownloadRequestLimiter::SetOnCanDownloadDecidedCallbackForTesting(
717     CanDownloadDecidedCallback callback) {
718   on_can_download_decided_callback_ = callback;
719 }
720