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