| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/download/download_request_limiter.h" |
| |
| #include <iterator> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/content_settings/chrome_content_settings_utils.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/download/download_permission_request.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "components/permissions/permission_request_manager.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "url/gurl.h" |
| |
| using content::BrowserThread; |
| using content::NavigationController; |
| using content::NavigationEntry; |
| |
| namespace { |
| |
| ContentSetting GetSettingFromDownloadStatus( |
| DownloadRequestLimiter::DownloadStatus status) { |
| switch (status) { |
| case DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD: |
| case DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD: |
| return CONTENT_SETTING_ASK; |
| case DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS: |
| return CONTENT_SETTING_ALLOW; |
| case DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED: |
| return CONTENT_SETTING_BLOCK; |
| } |
| NOTREACHED(); |
| return CONTENT_SETTING_DEFAULT; |
| } |
| |
| DownloadRequestLimiter::DownloadStatus GetDownloadStatusFromSetting( |
| ContentSetting setting) { |
| switch (setting) { |
| case CONTENT_SETTING_ALLOW: |
| return DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS; |
| case CONTENT_SETTING_BLOCK: |
| return DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED; |
| case CONTENT_SETTING_DEFAULT: |
| case CONTENT_SETTING_ASK: |
| return DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD; |
| case CONTENT_SETTING_SESSION_ONLY: |
| case CONTENT_SETTING_NUM_SETTINGS: |
| case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT: |
| NOTREACHED(); |
| return DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD; |
| } |
| NOTREACHED(); |
| return DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD; |
| } |
| |
| DownloadRequestLimiter::DownloadUiStatus GetUiStatusFromDownloadStatus( |
| DownloadRequestLimiter::DownloadStatus status, |
| bool download_seen) { |
| if (!download_seen) |
| return DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT; |
| |
| switch (status) { |
| case DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS: |
| return DownloadRequestLimiter::DOWNLOAD_UI_ALLOWED; |
| case DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED: |
| return DownloadRequestLimiter::DOWNLOAD_UI_BLOCKED; |
| case DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD: |
| case DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD: |
| return DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT; |
| } |
| NOTREACHED(); |
| return DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT; |
| } |
| |
| } // namespace |
| |
| // TabDownloadState ------------------------------------------------------------ |
| |
| DownloadRequestLimiter::TabDownloadState::TabDownloadState( |
| DownloadRequestLimiter* host, |
| content::WebContents* contents) |
| : content::WebContentsObserver(contents), |
| web_contents_(contents), |
| host_(host), |
| status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD), |
| ui_status_(DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT), |
| origin_(url::Origin::Create(contents->GetVisibleURL())), |
| download_count_(0), |
| download_seen_(false) { |
| observation_.Observe(GetContentSettings(contents)); |
| } |
| |
| DownloadRequestLimiter::TabDownloadState::~TabDownloadState() { |
| // We should only be destroyed after the callbacks have been notified. |
| DCHECK(callbacks_.empty()); |
| |
| // And we should have invalidated the back pointer. |
| DCHECK(!factory_.HasWeakPtrs()); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::SetDownloadStatusAndNotify( |
| const url::Origin& request_origin, |
| DownloadStatus status) { |
| SetDownloadStatusAndNotifyImpl(request_origin, status, |
| GetSettingFromDownloadStatus(status)); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return; |
| |
| download_seen_ = false; |
| ui_status_ = DOWNLOAD_UI_DEFAULT; |
| |
| if (navigation_handle->IsRendererInitiated()) { |
| return; |
| } |
| |
| // If this is a forward/back navigation, also don't reset a prompting or |
| // blocking limiter state if an origin is limited. This prevents a page |
| // to use history forward/backward to trigger multiple downloads. |
| if (!shouldClearDownloadState(navigation_handle)) |
| return; |
| |
| NotifyCallbacks(false); |
| host_->Remove(this, web_contents()); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return; |
| |
| // If this is a forward/back navigation, also don't reset a prompting or |
| // blocking limiter state if an origin is limited. This prevents a page |
| // to use history forward/backward to trigger multiple downloads. |
| if (!shouldClearDownloadState(navigation_handle)) |
| return; |
| |
| // Treat browser-initiated navigations as user interactions as long as the |
| // navigation can clear download state. |
| if (!navigation_handle->IsRendererInitiated()) { |
| OnUserInteraction(); |
| return; |
| } |
| |
| // When the status is ALLOW_ALL_DOWNLOADS or DOWNLOADS_NOT_ALLOWED, don't drop |
| // this information. The user has explicitly said that they do/don't want |
| // downloads from this host. If they accidentally Accepted or Canceled, they |
| // can adjust the limiter state by adjusting the automatic downloads content |
| // settings. Alternatively, they can copy the URL into a new tab, which will |
| // make a new DownloadRequestLimiter. |
| if (status_ == ALLOW_ONE_DOWNLOAD) { |
| // When the user reloads the page without responding to the prompt, |
| // they are expecting DownloadRequestLimiter to behave as if they had |
| // just initially navigated to this page. See http://crbug.com/171372. |
| // However, explicitly leave the limiter in place if the navigation was |
| // renderer-initiated and we are in a prompt state. |
| NotifyCallbacks(false); |
| host_->Remove(this, web_contents()); |
| return; |
| // WARNING: We've been deleted. |
| } else if (status_ == ALLOW_ALL_DOWNLOADS) { |
| OnUserInteraction(); |
| return; |
| } |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::DidGetUserInteraction( |
| const blink::WebInputEvent& event) { |
| if (is_showing_prompt() || |
| event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) { |
| // Don't change state if a prompt is showing or if the user has scrolled. |
| return; |
| } |
| |
| OnUserInteraction(); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() { |
| // Tab closed, no need to handle closing the dialog as it's owned by the |
| // WebContents. |
| |
| NotifyCallbacks(false); |
| host_->Remove(this, web_contents()); |
| // WARNING: We've been deleted. |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload( |
| DownloadRequestLimiter::Callback callback, |
| const url::Origin& request_origin) { |
| callbacks_.push_back(std::move(callback)); |
| DCHECK(web_contents_); |
| if (is_showing_prompt()) |
| return; |
| |
| permissions::PermissionRequestManager* permission_request_manager = |
| permissions::PermissionRequestManager::FromWebContents(web_contents_); |
| if (permission_request_manager) { |
| // The RFH is used to scope the lifetime of the request and scoping it to |
| // the initiator doesn't make sense for downloads as download navigation |
| // requests are never committed and don't update the omnibox url. |
| // Download requests should only be granted by checking `request_origin`, |
| // so we use the primary main RenderFrameHost here, to avoid discarding the |
| // request in the case that the initiator RFH is already gone. |
| permission_request_manager->AddRequest( |
| web_contents_->GetPrimaryMainFrame(), |
| new DownloadPermissionRequest(factory_.GetWeakPtr(), request_origin)); |
| } else { |
| // Call CancelOnce() so we don't set the content settings. |
| CancelOnce(request_origin); |
| } |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::SetContentSetting( |
| ContentSetting setting, |
| const url::Origin& request_origin) { |
| if (!web_contents_) |
| return; |
| if (request_origin.opaque()) |
| return; |
| HostContentSettingsMap* settings = |
| DownloadRequestLimiter::GetContentSettings(web_contents_); |
| if (!settings) |
| return; |
| settings->SetContentSettingDefaultScope( |
| request_origin.GetURL(), GURL(), ContentSettingsType::AUTOMATIC_DOWNLOADS, |
| setting); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::Cancel( |
| const url::Origin& request_origin) { |
| SetContentSetting(CONTENT_SETTING_BLOCK, request_origin); |
| bool throttled = NotifyCallbacks(false); |
| SetDownloadStatusAndNotify(request_origin, throttled ? PROMPT_BEFORE_DOWNLOAD |
| : DOWNLOADS_NOT_ALLOWED); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::CancelOnce( |
| const url::Origin& request_origin) { |
| bool throttled = NotifyCallbacks(false); |
| SetDownloadStatusAndNotify(request_origin, throttled ? PROMPT_BEFORE_DOWNLOAD |
| : DOWNLOADS_NOT_ALLOWED); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::Accept( |
| const url::Origin& request_origin) { |
| SetContentSetting(CONTENT_SETTING_ALLOW, request_origin); |
| bool throttled = NotifyCallbacks(true); |
| SetDownloadStatusAndNotify( |
| request_origin, throttled ? PROMPT_BEFORE_DOWNLOAD : ALLOW_ALL_DOWNLOADS); |
| } |
| |
| DownloadRequestLimiter::DownloadStatus |
| DownloadRequestLimiter::TabDownloadState::GetDownloadStatus( |
| const url::Origin& request_origin) { |
| auto it = download_status_map_.find(request_origin); |
| if (it != download_status_map_.end()) |
| return it->second; |
| return ALLOW_ONE_DOWNLOAD; |
| } |
| |
| DownloadRequestLimiter::TabDownloadState::TabDownloadState() |
| : web_contents_(nullptr), |
| host_(nullptr), |
| status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD), |
| ui_status_(DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT), |
| download_count_(0), |
| download_seen_(false) {} |
| |
| bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const { |
| return factory_.HasWeakPtrs(); |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::OnUserInteraction() { |
| // See PromptUserForDownload(): if there's no PermissionRequestManager, then |
| // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD. |
| bool no_permission_request_manager = |
| (permissions::PermissionRequestManager::FromWebContents(web_contents()) == |
| nullptr); |
| |
| for (auto it = download_status_map_.begin(); |
| it != download_status_map_.end();) { |
| ContentSetting setting = |
| GetAutoDownloadContentSetting(web_contents(), it->first.GetURL()); |
| // If an origin has non-block content setting and does not have |
| // |DOWNLOADS_NOT_ALLOWED| or |ALLOW_ALL_DOWNLOADS| status, remove |
| // it from the map so that it is able to initiate one download |
| // without asking the user. |
| if (setting != CONTENT_SETTING_BLOCK && it->second != ALLOW_ALL_DOWNLOADS && |
| ((no_permission_request_manager && |
| it->second == DOWNLOADS_NOT_ALLOWED) || |
| it->second != DOWNLOADS_NOT_ALLOWED)) { |
| it = download_status_map_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| |
| // Reset the download count to 0 so that one download can go through. |
| download_count_ = 0; |
| |
| if (download_status_map_.empty()) { |
| host_->Remove(this, web_contents()); |
| // WARNING: We've been deleted. |
| } |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::OnContentSettingChanged( |
| const ContentSettingsPattern& primary_pattern, |
| const ContentSettingsPattern& secondary_pattern, |
| ContentSettingsTypeSet content_type_set) { |
| if (!content_type_set.Contains(ContentSettingsType::AUTOMATIC_DOWNLOADS)) |
| return; |
| |
| if (origin_.opaque()) |
| return; |
| |
| GURL origin = origin_.GetURL(); |
| |
| // Check if the settings change affects the most recent origin passed |
| // to SetDownloadStatusAndNotify(). If so, we need to update the omnibox |
| // decoration. |
| if (!primary_pattern.Matches(origin)) |
| return; |
| |
| // Content settings have been updated for our web contents, e.g. via the OIB |
| // or the settings page. Check to see if the automatic downloads setting is |
| // different to our internal state, and update the internal state to match if |
| // necessary. If there is no content setting persisted, then retain the |
| // current state and do nothing. |
| // |
| // NotifyCallbacks is not called as this notification should be triggered when |
| // a download is not pending. |
| // |
| // Fetch the content settings map for this web contents, and extract the |
| // automatic downloads permission value. |
| HostContentSettingsMap* content_settings = GetContentSettings(web_contents()); |
| if (!content_settings) |
| return; |
| |
| ContentSetting setting = content_settings->GetContentSetting( |
| origin, origin, ContentSettingsType::AUTOMATIC_DOWNLOADS); |
| |
| // Update the internal state to match if necessary. |
| SetDownloadStatusAndNotifyImpl(origin_, GetDownloadStatusFromSetting(setting), |
| setting); |
| } |
| |
| bool DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) { |
| std::vector<DownloadRequestLimiter::Callback> callbacks; |
| bool throttled = false; |
| |
| // Selectively send first few notifications only if number of downloads exceed |
| // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and |
| // don't close it. If allow is false, we send all the notifications to cancel |
| // all remaining downloads and close the infobar. |
| if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) { |
| // Null the generated weak pointer so we don't get notified again. |
| factory_.InvalidateWeakPtrs(); |
| callbacks.swap(callbacks_); |
| } else { |
| std::vector<DownloadRequestLimiter::Callback>::iterator start, end; |
| start = callbacks_.begin(); |
| end = callbacks_.begin() + kMaxDownloadsAtOnce; |
| callbacks.assign(std::make_move_iterator(start), |
| std::make_move_iterator(end)); |
| callbacks_.erase(start, end); |
| throttled = true; |
| } |
| |
| for (auto& callback : callbacks) { |
| // When callback runs, it can cause the WebContents to be destroyed. |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), allow)); |
| } |
| |
| return throttled; |
| } |
| |
| void DownloadRequestLimiter::TabDownloadState::SetDownloadStatusAndNotifyImpl( |
| const url::Origin& request_origin, |
| DownloadStatus status, |
| ContentSetting setting) { |
| DCHECK((GetSettingFromDownloadStatus(status) == setting) || |
| (GetDownloadStatusFromSetting(setting) == status)) |
| << "status " << status << " and setting " << setting |
| << " do not correspond to each other"; |
| ContentSetting last_setting = GetSettingFromDownloadStatus(status_); |
| DownloadUiStatus last_ui_status = ui_status_; |
| url::Origin last_origin = origin_; |
| |
| status_ = status; |
| ui_status_ = GetUiStatusFromDownloadStatus(status_, download_seen_); |
| origin_ = request_origin; |
| |
| if (status_ != ALLOW_ONE_DOWNLOAD) |
| download_status_map_[request_origin] = status_; |
| else |
| download_status_map_.erase(request_origin); |
| |
| if (!web_contents()) |
| return; |
| |
| // For opaque origins, the omnibox decoration cannot show the URL. As a |
| // result, don't send a notification. |
| if (origin_.opaque()) |
| return; |
| |
| // We want to send a notification if the UI status has changed to ensure that |
| // the omnibox decoration updates appropriately. This is effectively the same |
| // as other permissions which might be in an allow state, but do not show UI |
| // until they are actively used. |
| if (last_setting == setting && last_ui_status == ui_status_ && |
| origin_ == last_origin) { |
| return; |
| } |
| |
| content_settings::UpdateLocationBarUiForWebContents(web_contents()); |
| } |
| |
| bool DownloadRequestLimiter::TabDownloadState::shouldClearDownloadState( |
| content::NavigationHandle* navigation_handle) { |
| // For forward/backward navigations, don't clear download state if some |
| // origins are restricted. |
| if (navigation_handle->GetPageTransition() & |
| ui::PAGE_TRANSITION_FORWARD_BACK) { |
| for (const auto& entry : download_status_map_) { |
| if (entry.second == PROMPT_BEFORE_DOWNLOAD || |
| entry.second == DOWNLOADS_NOT_ALLOWED) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // DownloadRequestLimiter ------------------------------------------------------ |
| |
| DownloadRequestLimiter::DownloadRequestLimiter() {} |
| |
| DownloadRequestLimiter::~DownloadRequestLimiter() { |
| // All the tabs should have closed before us, which sends notification and |
| // removes from state_map_. As such, there should be no pending callbacks. |
| DCHECK(state_map_.empty()); |
| } |
| |
| DownloadRequestLimiter::DownloadStatus |
| DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) { |
| TabDownloadState* state = GetDownloadState(web_contents, false); |
| return state ? state->download_status() : ALLOW_ONE_DOWNLOAD; |
| } |
| |
| DownloadRequestLimiter::DownloadUiStatus |
| DownloadRequestLimiter::GetDownloadUiStatus( |
| content::WebContents* web_contents) { |
| TabDownloadState* state = GetDownloadState(web_contents, false); |
| return state ? state->download_ui_status() : DOWNLOAD_UI_DEFAULT; |
| } |
| |
| GURL DownloadRequestLimiter::GetDownloadOrigin( |
| content::WebContents* web_contents) { |
| TabDownloadState* state = GetDownloadState(web_contents, false); |
| if (state && !state->origin().opaque()) |
| return state->origin().GetURL(); |
| return web_contents->GetVisibleURL(); |
| } |
| |
| DownloadRequestLimiter::TabDownloadState* |
| DownloadRequestLimiter::GetDownloadState( |
| content::WebContents* web_contents, |
| bool create) { |
| DCHECK(web_contents); |
| auto i = state_map_.find(web_contents); |
| if (i != state_map_.end()) |
| return i->second; |
| |
| if (!create) |
| return nullptr; |
| |
| TabDownloadState* state = new TabDownloadState(this, web_contents); |
| state_map_[web_contents] = state; |
| return state; |
| } |
| |
| void DownloadRequestLimiter::CanDownload( |
| const content::WebContents::Getter& web_contents_getter, |
| const GURL& url, |
| const std::string& request_method, |
| std::optional<url::Origin> request_initiator, |
| bool from_download_cross_origin_redirect, |
| Callback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| content::WebContents* originating_contents = web_contents_getter.Run(); |
| if (!originating_contents) { |
| // The WebContents was closed, don't allow the download. |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (!originating_contents->GetDelegate()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Note that because |originating_contents| might go away before |
| // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id| |
| // and |render_view_id|. |
| base::OnceCallback<void(bool)> can_download_callback = base::BindOnce( |
| &DownloadRequestLimiter::OnCanDownloadDecided, factory_.GetWeakPtr(), |
| web_contents_getter, request_method, std::move(request_initiator), |
| from_download_cross_origin_redirect, std::move(callback)); |
| |
| originating_contents->GetDelegate()->CanDownload( |
| url, request_method, std::move(can_download_callback)); |
| } |
| |
| void DownloadRequestLimiter::OnCanDownloadDecided( |
| const content::WebContents::Getter& web_contents_getter, |
| const std::string& request_method, |
| std::optional<url::Origin> request_initiator, |
| bool from_download_cross_origin_redirect, |
| Callback orig_callback, |
| bool allow) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| content::WebContents* originating_contents = web_contents_getter.Run(); |
| if (!originating_contents || !allow) { |
| std::move(orig_callback).Run(false); |
| return; |
| } |
| |
| CanDownloadImpl( |
| originating_contents, request_method, std::move(request_initiator), |
| from_download_cross_origin_redirect, std::move(orig_callback)); |
| } |
| |
| HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings( |
| content::WebContents* contents) { |
| return HostContentSettingsMapFactory::GetForProfile( |
| Profile::FromBrowserContext(contents->GetBrowserContext())); |
| } |
| |
| ContentSetting DownloadRequestLimiter::GetAutoDownloadContentSetting( |
| content::WebContents* contents, |
| const GURL& request_initiator) { |
| HostContentSettingsMap* content_settings = GetContentSettings(contents); |
| ContentSetting setting = CONTENT_SETTING_ASK; |
| if (content_settings) { |
| setting = content_settings->GetContentSetting( |
| request_initiator, request_initiator, |
| ContentSettingsType::AUTOMATIC_DOWNLOADS); |
| } |
| return setting; |
| } |
| |
| void DownloadRequestLimiter::CanDownloadImpl( |
| content::WebContents* originating_contents, |
| const std::string& request_method, |
| std::optional<url::Origin> request_initiator, |
| bool from_download_cross_origin_redirect, |
| Callback callback) { |
| DCHECK(originating_contents); |
| |
| // Always allow download resulted from a cross-origin redirect from a previous |
| // download attempt, and there's no need to update any state. |
| if (from_download_cross_origin_redirect) { |
| std::move(callback).Run(true); |
| if (!on_can_download_decided_callback_.is_null()) |
| on_can_download_decided_callback_.Run(true); |
| return; |
| } |
| |
| TabDownloadState* state = GetDownloadState(originating_contents, true); |
| state->set_download_seen(); |
| bool ret = true; |
| |
| // |request_initiator| may come from another web_contents. Check the content |
| // settings first to see if the download needs to be blocked. |
| GURL initiator = request_initiator ? request_initiator->GetURL() |
| : originating_contents->GetVisibleURL(); |
| |
| // Use the origin of |originating_contents| as a back up, if it is non-opaque. |
| url::Origin origin = |
| url::Origin::Create(originating_contents->GetVisibleURL()); |
| |
| // If |request_initiator| has a non-opaque origin or if the origin from |
| // |originating_contents| is opaque, use the origin from |request_initiator| |
| // to make decisions so that it won't impact the download state of |
| // |originating_contents|. |
| if (request_initiator && (!request_initiator->opaque() || origin.opaque())) |
| origin = request_initiator.value(); |
| |
| DownloadStatus status = state->GetDownloadStatus(origin); |
| |
| bool is_opaque_initiator = request_initiator && request_initiator->opaque(); |
| |
| // Always check for the content setting first. Having an content setting |
| // observer won't work as |request_initiator| might be different from the tab |
| // URL. |
| ContentSetting setting = |
| is_opaque_initiator |
| ? CONTENT_SETTING_BLOCK |
| : GetAutoDownloadContentSetting(originating_contents, initiator); |
| // Override the status if content setting is block or allow. If the content |
| // setting is always allow, only reset the status if it is |
| // DOWNLOADS_NOT_ALLOWED so unnecessary notifications will not be triggered. |
| // If the content setting is block, allow only one download to proceed if the |
| // current status is ALLOW_ALL_DOWNLOADS. |
| if (setting == CONTENT_SETTING_BLOCK && status == ALLOW_ALL_DOWNLOADS) { |
| status = ALLOW_ONE_DOWNLOAD; |
| } else if (setting == CONTENT_SETTING_ALLOW && |
| status == DOWNLOADS_NOT_ALLOWED) { |
| status = ALLOW_ALL_DOWNLOADS; |
| } |
| |
| // Always call SetDownloadStatusAndNotify since we may need to change the |
| // omnibox UI even if the internal state stays the same. For instance, we want |
| // to hide the indicator until a download is triggered, even if we know |
| // downloads are blocked. This mirrors the behaviour of other omnibox |
| // decorations like geolocation. |
| switch (status) { |
| case ALLOW_ALL_DOWNLOADS: |
| if (state->download_count() && |
| !(state->download_count() % |
| DownloadRequestLimiter::kMaxDownloadsAtOnce)) { |
| state->SetDownloadStatusAndNotify(origin, PROMPT_BEFORE_DOWNLOAD); |
| } else { |
| state->SetDownloadStatusAndNotify(origin, ALLOW_ALL_DOWNLOADS); |
| } |
| std::move(callback).Run(true); |
| state->increment_download_count(); |
| break; |
| |
| case ALLOW_ONE_DOWNLOAD: |
| state->SetDownloadStatusAndNotify(origin, PROMPT_BEFORE_DOWNLOAD); |
| // If one download is seen for this WebContent, ALLOW_ONE_DOWNLOAD is the |
| // same as PROMPT_BEFORE_DOWNLOAD unless all downloads are allowed for the |
| // origin. This is to avoid a page using different origins to initiate |
| // multiple downloads. |
| if (state->download_count() > 0 && setting != CONTENT_SETTING_ALLOW) { |
| ret = false; |
| // If setting is CONTENT_SETTING_BLOCK, don't prompt user. |
| if (setting == CONTENT_SETTING_BLOCK) { |
| state->SetDownloadStatusAndNotify(origin, DOWNLOADS_NOT_ALLOWED); |
| std::move(callback).Run(false); |
| } else { |
| state->PromptUserForDownload(std::move(callback), origin); |
| state->increment_download_count(); |
| } |
| } else { |
| std::move(callback).Run(true); |
| state->increment_download_count(); |
| } |
| break; |
| |
| case DOWNLOADS_NOT_ALLOWED: |
| state->SetDownloadStatusAndNotify(origin, DOWNLOADS_NOT_ALLOWED); |
| ret = false; |
| std::move(callback).Run(false); |
| break; |
| |
| case PROMPT_BEFORE_DOWNLOAD: { |
| switch (setting) { |
| case CONTENT_SETTING_ALLOW: { |
| state->SetDownloadStatusAndNotify(origin, ALLOW_ALL_DOWNLOADS); |
| std::move(callback).Run(true); |
| state->increment_download_count(); |
| break; |
| } |
| case CONTENT_SETTING_BLOCK: { |
| state->SetDownloadStatusAndNotify(origin, DOWNLOADS_NOT_ALLOWED); |
| ret = false; |
| std::move(callback).Run(false); |
| break; |
| } |
| case CONTENT_SETTING_DEFAULT: |
| case CONTENT_SETTING_ASK: |
| state->PromptUserForDownload(std::move(callback), origin); |
| state->increment_download_count(); |
| ret = false; |
| break; |
| case CONTENT_SETTING_SESSION_ONLY: |
| case CONTENT_SETTING_NUM_SETTINGS: |
| default: |
| NOTREACHED(); |
| return; |
| } |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| } |
| |
| if (!on_can_download_decided_callback_.is_null()) |
| on_can_download_decided_callback_.Run(ret); |
| } |
| |
| void DownloadRequestLimiter::Remove(TabDownloadState* state, |
| content::WebContents* contents) { |
| DCHECK(base::Contains(state_map_, contents)); |
| state_map_.erase(contents); |
| delete state; |
| } |
| |
| void DownloadRequestLimiter::SetOnCanDownloadDecidedCallbackForTesting( |
| CanDownloadDecidedCallback callback) { |
| on_can_download_decided_callback_ = callback; |
| } |