| // 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_item_model.h" |
| |
| #include <string> |
| |
| #include "base/functional/bind.h" |
| #include "base/i18n/number_formatting.h" |
| #include "base/i18n/rtl.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/observer_list.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/supports_user_data.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/download/bubble/download_bubble_prefs.h" |
| #include "chrome/browser/download/chrome_download_manager_delegate.h" |
| #include "chrome/browser/download/download_commands.h" |
| #include "chrome/browser/download/download_core_service.h" |
| #include "chrome/browser/download/download_core_service_factory.h" |
| #include "chrome/browser/download/download_crx_util.h" |
| #include "chrome/browser/download/download_history.h" |
| #include "chrome/browser/download/download_prefs.h" |
| #include "chrome/browser/download/download_stats.h" |
| #include "chrome/browser/download/download_target_determiner.h" |
| #include "chrome/browser/download/offline_item_utils.h" |
| #include "chrome/browser/enterprise/connectors/common.h" |
| #include "chrome/browser/enterprise/connectors/connectors_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/grit/chromium_strings.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/download/public/common/download_danger_type.h" |
| #include "components/download/public/common/download_interrupt_reasons.h" |
| #include "components/download/public/common/download_item.h" |
| #include "components/safe_browsing/buildflags.h" |
| #include "components/safe_browsing/content/browser/web_ui/safe_browsing_ui.h" |
| #include "components/safe_browsing/content/common/file_type_policies.h" |
| #include "components/safe_browsing/content/common/proto/download_file_types.pb.h" |
| #include "components/safe_browsing/core/common/features.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/account_info.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/download_item_utils.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/time_format.h" |
| #include "ui/base/text/bytes_formatting.h" |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/ui/browser.h" |
| #include "ui/views/vector_icons.h" |
| #endif |
| |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| #include "chrome/browser/safe_browsing/download_protection/deep_scanning_request.h" |
| #include "chrome/browser/safe_browsing/download_protection/download_feedback_service.h" |
| #endif |
| |
| using download::DownloadItem; |
| using InsecureDownloadStatus = download::DownloadItem::InsecureDownloadStatus; |
| using safe_browsing::DownloadFileType; |
| using ReportThreatDetailsResult = |
| safe_browsing::PingManager::ReportThreatDetailsResult; |
| using TailoredVerdict = safe_browsing::ClientDownloadResponse::TailoredVerdict; |
| |
| namespace { |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // How long an ephemeral warning is displayed on the download bubble. |
| constexpr base::TimeDelta kEphemeralWarningLifetimeOnBubble = base::Minutes(5); |
| #endif |
| |
| // Per DownloadItem data used by DownloadItemModel. The model doesn't keep any |
| // state since there could be multiple models associated with a single |
| // DownloadItem, and the lifetime of the model is shorter than the DownloadItem. |
| class DownloadItemModelData : public base::SupportsUserData::Data { |
| public: |
| ~DownloadItemModelData() override {} |
| |
| // Get the DownloadItemModelData object for |download|. Returns NULL if |
| // there's no model data. |
| static const DownloadItemModelData* Get(const DownloadItem* download); |
| |
| // Get the DownloadItemModelData object for |download|. Creates a model data |
| // object if not found. Always returns a non-NULL pointer, unless OOM. |
| static DownloadItemModelData* GetOrCreate(DownloadItem* download); |
| |
| // Whether the download should be displayed in the download shelf. True by |
| // default. |
| bool should_show_in_shelf_; |
| |
| // Whether the UI has been notified about this download. |
| bool was_ui_notified_; |
| |
| // Whether the download should be opened in the browser vs. the system handler |
| // for the file type. |
| absl::optional<bool> should_prefer_opening_in_browser_; |
| |
| // Danger level of the file determined based on the file type and whether |
| // there was a user action associated with the download. |
| DownloadFileType::DangerLevel danger_level_; |
| |
| // Whether the download is currently being revived. |
| bool is_being_revived_; |
| |
| // Whether the safe browsing download warning was shown (and recorded) earlier |
| // on the UI. |
| bool was_ui_warning_shown_ = false; |
| |
| // Tracks when an ephemeral warning was first displayed on the UI. Does not |
| // persist on restart, though ephemeral warning downloads are canceled by |
| // then as all in-progress downloads are. |
| absl::optional<base::Time> ephemeral_warning_ui_shown_time_; |
| |
| // Was the UI actioned on. |
| bool actioned_on_ = false; |
| |
| private: |
| DownloadItemModelData(); |
| |
| static const char kKey[]; |
| }; |
| |
| // static |
| const char DownloadItemModelData::kKey[] = "DownloadItemModelData key"; |
| |
| // static |
| const DownloadItemModelData* DownloadItemModelData::Get( |
| const DownloadItem* download) { |
| return static_cast<const DownloadItemModelData*>(download->GetUserData(kKey)); |
| } |
| |
| // static |
| DownloadItemModelData* DownloadItemModelData::GetOrCreate( |
| DownloadItem* download) { |
| DownloadItemModelData* data = |
| static_cast<DownloadItemModelData*>(download->GetUserData(kKey)); |
| if (data == nullptr) { |
| data = new DownloadItemModelData(); |
| data->should_show_in_shelf_ = !download->IsTransient(); |
| download->SetUserData(kKey, base::WrapUnique(data)); |
| } |
| return data; |
| } |
| |
| DownloadItemModelData::DownloadItemModelData() |
| : should_show_in_shelf_(true), |
| was_ui_notified_(false), |
| danger_level_(DownloadFileType::NOT_DANGEROUS), |
| is_being_revived_(false) {} |
| |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| bool ShouldSendDownloadReport(download::DownloadDangerType danger_type) { |
| switch (danger_type) { |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: |
| case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| void MaybeSendDownloadReport(const GURL& url, |
| download::DownloadDangerType danger_type, |
| bool did_proceed, |
| Profile* profile, |
| download::DownloadItem* download) { |
| // Dangerous download delete report is gated by the new trigger flag. |
| if (!base::FeatureList::IsEnabled( |
| safe_browsing::kSafeBrowsingCsbrrNewDownloadTrigger) && |
| !did_proceed) { |
| return; |
| } |
| // Only sends dangerous download report if : |
| // 1. FULL_SAFE_BROWSING is enabled, and |
| // 2. Download verdict is one of the dangerous types, and |
| // 3. Download URL is not empty, and |
| // 4. User is not in incognito mode. |
| if (ShouldSendDownloadReport(danger_type) && !url.is_empty() && |
| !profile->IsOffTheRecord()) { |
| safe_browsing::SafeBrowsingService* sb_service = |
| g_browser_process->safe_browsing_service(); |
| if (sb_service) { |
| bool is_successful = sb_service->SendDownloadReport( |
| download, |
| safe_browsing::ClientSafeBrowsingReportRequest:: |
| DANGEROUS_DOWNLOAD_WARNING, |
| did_proceed, /*show_download_in_folder=*/absl::nullopt); |
| DCHECK(is_successful); |
| } |
| } |
| } |
| |
| // Submits download to download feedback service if the user has approved and |
| // the download is suitable for submission. |
| // If user hasn't seen SBER opt-in text before, show SBER opt-in dialog first. |
| bool MaybeSubmitDownloadToFeedbackService(DownloadCommands::Command command, |
| Profile* profile, |
| download::DownloadItem* download) { |
| if (!download->IsDangerous() || download->IsInsecure()) { |
| return false; |
| } |
| if (!safe_browsing::DownloadFeedbackService::IsEnabledForDownload( |
| *download)) { |
| return false; |
| } |
| |
| auto* const sb_service = g_browser_process->safe_browsing_service(); |
| if (!sb_service) |
| return false; |
| auto* const dp_service = sb_service->download_protection_service(); |
| if (!dp_service) |
| return false; |
| // TODO(shaktisahu): Enable feedback service for offline item. |
| return dp_service->MaybeBeginFeedbackForDownload(profile, download, command); |
| } |
| |
| #endif |
| |
| // Enum representing reasons why a download is not preferred to be opened in |
| // browser. |
| enum class NotOpenedInBrowserReason { |
| // The total number of checks. This value should be used as the denominator |
| // when calculating the percentage of a specific reason below. |
| TOTAL_DOWNLOAD_CHECKED = 0, |
| DOWNLOAD_PATH_EMPTY = 1, |
| NOT_PREFERRED_IN_DELEGATE = 2, |
| CANNOT_BE_HANDLED_SAFELY = 3, |
| |
| kMaxValue = CANNOT_BE_HANDLED_SAFELY |
| }; |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // DownloadItemModel |
| |
| // static |
| DownloadUIModel::DownloadUIModelPtr DownloadItemModel::Wrap( |
| download::DownloadItem* download) { |
| return std::make_unique<DownloadItemModel>(download); |
| } |
| |
| // static |
| DownloadUIModel::DownloadUIModelPtr DownloadItemModel::Wrap( |
| download::DownloadItem* download, |
| std::unique_ptr<DownloadUIModel::StatusTextBuilderBase> |
| status_text_builder) { |
| return std::make_unique<DownloadItemModel>(download, |
| std::move(status_text_builder)); |
| } |
| |
| DownloadItemModel::DownloadItemModel(DownloadItem* download) |
| : DownloadItemModel(download, std::make_unique<StatusTextBuilder>()) {} |
| |
| DownloadItemModel::DownloadItemModel( |
| download::DownloadItem* download, |
| std::unique_ptr<DownloadUIModel::StatusTextBuilderBase> status_text_builder) |
| : DownloadUIModel(std::move(status_text_builder)), download_(download) { |
| download_->AddObserver(this); |
| } |
| |
| DownloadItemModel::~DownloadItemModel() { |
| if (download_) |
| download_->RemoveObserver(this); |
| } |
| |
| ContentId DownloadItemModel::GetContentId() const { |
| return OfflineItemUtils::GetContentIdForDownload(download_); |
| } |
| |
| Profile* DownloadItemModel::profile() const { |
| return Profile::FromBrowserContext( |
| content::DownloadItemUtils::GetBrowserContext(download_)); |
| } |
| |
| std::u16string DownloadItemModel::GetTabProgressStatusText() const { |
| int64_t total = GetTotalBytes(); |
| int64_t size = download_->GetReceivedBytes(); |
| std::u16string received_size = ui::FormatBytes(size); |
| std::u16string amount = received_size; |
| |
| // Adjust both strings for the locale direction since we don't yet know which |
| // string we'll end up using for constructing the final progress string. |
| base::i18n::AdjustStringForLocaleDirection(&amount); |
| |
| if (total) { |
| std::u16string total_text = ui::FormatBytes(total); |
| base::i18n::AdjustStringForLocaleDirection(&total_text); |
| |
| base::i18n::AdjustStringForLocaleDirection(&received_size); |
| amount = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_TAB_PROGRESS_SIZE, |
| received_size, total_text); |
| } else { |
| amount.assign(received_size); |
| } |
| int64_t current_speed = download_->CurrentSpeed(); |
| std::u16string speed_text = ui::FormatSpeed(current_speed); |
| base::i18n::AdjustStringForLocaleDirection(&speed_text); |
| |
| base::TimeDelta remaining; |
| std::u16string time_remaining; |
| if (download_->IsPaused()) { |
| time_remaining = l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED); |
| } else if (download_->TimeRemaining(&remaining)) { |
| time_remaining = |
| ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, |
| ui::TimeFormat::LENGTH_SHORT, remaining); |
| } |
| |
| if (time_remaining.empty()) { |
| base::i18n::AdjustStringForLocaleDirection(&amount); |
| return l10n_util::GetStringFUTF16( |
| IDS_DOWNLOAD_TAB_PROGRESS_STATUS_TIME_UNKNOWN, speed_text, amount); |
| } |
| return l10n_util::GetStringFUTF16(IDS_DOWNLOAD_TAB_PROGRESS_STATUS, |
| speed_text, amount, time_remaining); |
| } |
| |
| int64_t DownloadItemModel::GetCompletedBytes() const { |
| return download_->GetReceivedBytes(); |
| } |
| |
| int64_t DownloadItemModel::GetTotalBytes() const { |
| return download_->AllDataSaved() ? download_->GetReceivedBytes() |
| : download_->GetTotalBytes(); |
| } |
| |
| // TODO(asanka,rdsmith): Once 'open' moves exclusively to the |
| // ChromeDownloadManagerDelegate, we should calculate the percentage here |
| // instead of calling into the DownloadItem. |
| int DownloadItemModel::PercentComplete() const { |
| return download_->PercentComplete(); |
| } |
| |
| bool DownloadItemModel::IsDangerous() const { |
| return download_->IsDangerous(); |
| } |
| |
| bool DownloadItemModel::MightBeMalicious() const { |
| return IsDangerous() && (download_->GetDangerType() != |
| download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE); |
| } |
| |
| // If you change this definition of malicious, also update |
| // DownloadManagerImpl::NonMaliciousInProgressCount. |
| bool DownloadItemModel::IsMalicious() const { |
| if (!MightBeMalicious()) |
| return false; |
| switch (download_->GetDangerType()) { |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: |
| case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: |
| case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE: |
| return true; |
| |
| case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: |
| case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: |
| case download::DOWNLOAD_DANGER_TYPE_ALLOWLISTED_BY_POLICY: |
| case download::DOWNLOAD_DANGER_TYPE_MAX: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: |
| // We shouldn't get any of these due to the MightBeMalicious() test above. |
| NOTREACHED(); |
| [[fallthrough]]; |
| case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING: |
| case download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED: |
| case download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE: |
| case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING: |
| case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK: |
| case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE: |
| case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING: |
| case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE: |
| return false; |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool DownloadItemModel::IsInsecure() const { |
| return download_->IsInsecure(); |
| } |
| |
| bool DownloadItemModel::ShouldRemoveFromShelfWhenComplete() const { |
| switch (download_->GetState()) { |
| case DownloadItem::IN_PROGRESS: |
| // If the download is dangerous or malicious, we should display a warning |
| // on the shelf until the user accepts the download. |
| if (IsDangerous()) |
| return false; |
| |
| // If the download is a trusted extension, temporary, or will be opened |
| // automatically, then it should be removed from the shelf on completion. |
| // TODO(crbug.com/1077929): The logic for deciding opening behavior should |
| // be in a central location. |
| return (download_crx_util::IsTrustedExtensionDownload(profile(), |
| *download_) || |
| download_->IsTemporary() || download_->GetOpenWhenComplete() || |
| download_->ShouldOpenFileBasedOnExtension()); |
| |
| case DownloadItem::COMPLETE: |
| // If the download completed, then rely on GetAutoOpened() to check for |
| // opening behavior. This should accurately reflect whether the download |
| // was successfully opened. Extensions, for example, may fail to open. |
| return download_->GetAutoOpened() || download_->IsTemporary(); |
| |
| case DownloadItem::CANCELLED: |
| case DownloadItem::INTERRUPTED: |
| // Interrupted or cancelled downloads should remain on the shelf. |
| return false; |
| |
| case DownloadItem::MAX_DOWNLOAD_STATE: |
| NOTREACHED(); |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool DownloadItemModel::ShouldShowDownloadStartedAnimation() const { |
| return !download_->IsSavePackageDownload() && |
| !download_crx_util::IsTrustedExtensionDownload(profile(), *download_); |
| } |
| |
| bool DownloadItemModel::ShouldShowInShelf() const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| if (data) |
| return data->should_show_in_shelf_; |
| |
| return !download_->IsTransient(); |
| } |
| |
| void DownloadItemModel::SetShouldShowInShelf(bool should_show) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->should_show_in_shelf_ = should_show; |
| } |
| |
| bool DownloadItemModel::ShouldNotifyUI() const { |
| if (download_->IsTransient()) |
| return false; |
| |
| // The browser is only interested in new active downloads. History downloads |
| // that are completed or interrupted are not displayed on the shelf. The |
| // downloads page independently listens for new downloads when it is active. |
| // Note that the UI will be notified of downloads even if they are not meant |
| // to be displayed on the shelf (i.e. ShouldShowInShelf() returns false). This |
| // is because: * The shelf isn't the only UI. E.g. on Android, the UI is the |
| // system |
| // DownloadManager. |
| // * There are other UI activities that need to be performed. E.g. if the |
| // download was initiated from a new tab, then that tab should be closed. |
| return download_->GetDownloadCreationType() != |
| download::DownloadItem::DownloadCreationType:: |
| TYPE_HISTORY_IMPORT || |
| download_->GetState() == download::DownloadItem::IN_PROGRESS; |
| } |
| |
| bool DownloadItemModel::WasUINotified() const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| return data && data->was_ui_notified_; |
| } |
| |
| void DownloadItemModel::SetWasUINotified(bool was_ui_notified) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->was_ui_notified_ = was_ui_notified; |
| } |
| |
| bool DownloadItemModel::WasActionedOn() const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| return data && data->actioned_on_; |
| } |
| |
| void DownloadItemModel::SetActionedOn(bool actioned_on) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->actioned_on_ = actioned_on; |
| } |
| |
| bool DownloadItemModel::WasUIWarningShown() const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| return data && data->was_ui_warning_shown_; |
| } |
| |
| void DownloadItemModel::SetWasUIWarningShown(bool was_ui_warning_shown) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->was_ui_warning_shown_ = was_ui_warning_shown; |
| } |
| |
| absl::optional<base::Time> DownloadItemModel::GetEphemeralWarningUiShownTime() |
| const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| return data ? data->ephemeral_warning_ui_shown_time_ |
| : absl::optional<base::Time>(); |
| } |
| |
| void DownloadItemModel::SetEphemeralWarningUiShownTime( |
| absl::optional<base::Time> ephemeral_warning_ui_shown_time) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->ephemeral_warning_ui_shown_time_ = ephemeral_warning_ui_shown_time; |
| } |
| |
| bool DownloadItemModel::ShouldPreferOpeningInBrowser() { |
| const DownloadItemModelData* data = |
| DownloadItemModelData::GetOrCreate(download_); |
| #if !BUILDFLAG(IS_ANDROID) |
| if (!data->should_prefer_opening_in_browser_ && IsBubbleV2Enabled()) { |
| base::FilePath path = GetTargetFilePath(); |
| std::string mime_type = GetMimeType(); |
| DetermineAndSetShouldPreferOpeningInBrowser( |
| path, |
| DownloadTargetDeterminer::DetermineIfHandledSafelyHelperSynchronous( |
| download_, path, mime_type)); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| return data->should_prefer_opening_in_browser_.value_or(false); |
| } |
| |
| void DownloadItemModel::SetShouldPreferOpeningInBrowser(bool preference) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->should_prefer_opening_in_browser_ = preference; |
| } |
| |
| DownloadFileType::DangerLevel DownloadItemModel::GetDangerLevel() const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| return data ? data->danger_level_ : DownloadFileType::NOT_DANGEROUS; |
| } |
| |
| void DownloadItemModel::SetDangerLevel( |
| DownloadFileType::DangerLevel danger_level) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->danger_level_ = danger_level; |
| } |
| |
| download::DownloadItem::InsecureDownloadStatus |
| DownloadItemModel::GetInsecureDownloadStatus() const { |
| return download_->GetInsecureDownloadStatus(); |
| } |
| |
| bool DownloadItemModel::IsBeingRevived() const { |
| const DownloadItemModelData* data = DownloadItemModelData::Get(download_); |
| return data && data->is_being_revived_; |
| } |
| |
| void DownloadItemModel::SetIsBeingRevived(bool is_being_revived) { |
| DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_); |
| data->is_being_revived_ = is_being_revived; |
| } |
| |
| const download::DownloadItem* DownloadItemModel::GetDownloadItem() const { |
| return download_; |
| } |
| |
| base::FilePath DownloadItemModel::GetFileNameToReportUser() const { |
| return download_->GetFileNameToReportUser(); |
| } |
| |
| base::FilePath DownloadItemModel::GetTargetFilePath() const { |
| return download_->GetTargetFilePath(); |
| } |
| |
| void DownloadItemModel::OpenDownload() { |
| download_->OpenDownload(); |
| } |
| |
| download::DownloadItem::DownloadState DownloadItemModel::GetState() const { |
| return download_->GetState(); |
| } |
| |
| bool DownloadItemModel::IsPaused() const { |
| return download_->IsPaused(); |
| } |
| |
| download::DownloadDangerType DownloadItemModel::GetDangerType() const { |
| return download_->GetDangerType(); |
| } |
| |
| bool DownloadItemModel::GetOpenWhenComplete() const { |
| return download_->GetOpenWhenComplete(); |
| } |
| |
| bool DownloadItemModel::IsOpenWhenCompleteByPolicy() const { |
| return download_->ShouldOpenFileByPolicyBasedOnExtension(); |
| } |
| |
| bool DownloadItemModel::TimeRemaining(base::TimeDelta* remaining) const { |
| return download_->TimeRemaining(remaining); |
| } |
| |
| base::Time DownloadItemModel::GetStartTime() const { |
| return download_->GetStartTime(); |
| } |
| |
| base::Time DownloadItemModel::GetEndTime() const { |
| return download_->GetEndTime(); |
| } |
| |
| bool DownloadItemModel::GetOpened() const { |
| return download_->GetOpened(); |
| } |
| |
| void DownloadItemModel::SetOpened(bool opened) { |
| download_->SetOpened(opened); |
| } |
| |
| bool DownloadItemModel::IsDone() const { |
| return download_->IsDone(); |
| } |
| |
| void DownloadItemModel::Pause() { |
| download_->Pause(); |
| } |
| |
| void DownloadItemModel::Resume() { |
| download_->Resume(true /* has_user_gesture */); |
| } |
| |
| void DownloadItemModel::Cancel(bool user_cancel) { |
| download_->Cancel(user_cancel); |
| } |
| |
| void DownloadItemModel::Remove() { |
| download_->Remove(); |
| } |
| |
| void DownloadItemModel::SetOpenWhenComplete(bool open) { |
| download_->SetOpenWhenComplete(open); |
| } |
| |
| base::FilePath DownloadItemModel::GetFullPath() const { |
| return download_->GetFullPath(); |
| } |
| |
| bool DownloadItemModel::CanResume() const { |
| return download_->CanResume(); |
| } |
| |
| bool DownloadItemModel::AllDataSaved() const { |
| return download_->AllDataSaved(); |
| } |
| |
| bool DownloadItemModel::GetFileExternallyRemoved() const { |
| return download_->GetFileExternallyRemoved(); |
| } |
| |
| GURL DownloadItemModel::GetURL() const { |
| return download_->GetURL(); |
| } |
| |
| bool DownloadItemModel::HasUserGesture() const { |
| return download_->HasUserGesture(); |
| } |
| |
| void DownloadItemModel::OnDownloadUpdated(DownloadItem* download) { |
| if (delegate_) |
| delegate_->OnDownloadUpdated(); |
| } |
| |
| void DownloadItemModel::OnDownloadOpened(DownloadItem* download) { |
| if (delegate_) |
| delegate_->OnDownloadOpened(); |
| } |
| |
| void DownloadItemModel::OnDownloadDestroyed(DownloadItem* download) { |
| ContentId id = GetContentId(); |
| download_ = nullptr; |
| // The object could get deleted after this. |
| if (delegate_) |
| delegate_->OnDownloadDestroyed(id); |
| } |
| |
| void DownloadItemModel::OpenUsingPlatformHandler() { |
| DownloadCoreService* download_core_service = |
| DownloadCoreServiceFactory::GetForBrowserContext( |
| content::DownloadItemUtils::GetBrowserContext(download_)); |
| if (!download_core_service) |
| return; |
| |
| ChromeDownloadManagerDelegate* delegate = |
| download_core_service->GetDownloadManagerDelegate(); |
| if (!delegate) |
| return; |
| delegate->OpenDownloadUsingPlatformHandler(download_); |
| RecordDownloadOpen(DOWNLOAD_OPEN_METHOD_USER_PLATFORM, |
| download_->GetMimeType()); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| bool DownloadItemModel::IsCommandEnabled( |
| const DownloadCommands* download_commands, |
| DownloadCommands::Command command) const { |
| switch (command) { |
| case DownloadCommands::MAX: |
| NOTREACHED(); |
| break; |
| case DownloadCommands::SHOW_IN_FOLDER: |
| return download_->CanShowInFolder(); |
| case DownloadCommands::OPEN_WHEN_COMPLETE: |
| return download_->CanOpenDownload() && |
| !download_crx_util::IsExtensionDownload(*download_); |
| case DownloadCommands::PLATFORM_OPEN: |
| return download_->CanOpenDownload() && |
| !download_crx_util::IsExtensionDownload(*download_); |
| case DownloadCommands::ALWAYS_OPEN_TYPE: |
| // For temporary downloads, the target filename might be a temporary |
| // filename. Don't base an "Always open" decision based on it. Also |
| // exclude extensions. |
| return download_->CanOpenDownload() && |
| safe_browsing::FileTypePolicies::GetInstance() |
| ->IsAllowedToOpenAutomatically( |
| download_->GetTargetFilePath()) && |
| !download_crx_util::IsExtensionDownload(*download_); |
| case DownloadCommands::PAUSE: |
| return !download_->IsSavePackageDownload() && |
| DownloadUIModel::IsCommandEnabled(download_commands, command); |
| case DownloadCommands::CANCEL: |
| case DownloadCommands::RESUME: |
| case DownloadCommands::COPY_TO_CLIPBOARD: |
| case DownloadCommands::DISCARD: |
| case DownloadCommands::KEEP: |
| case DownloadCommands::LEARN_MORE_SCANNING: |
| case DownloadCommands::LEARN_MORE_INTERRUPTED: |
| case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD: |
| case DownloadCommands::DEEP_SCAN: |
| case DownloadCommands::BYPASS_DEEP_SCANNING: |
| case DownloadCommands::REVIEW: |
| case DownloadCommands::RETRY: |
| return DownloadUIModel::IsCommandEnabled(download_commands, command); |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool DownloadItemModel::IsCommandChecked( |
| const DownloadCommands* download_commands, |
| DownloadCommands::Command command) const { |
| switch (command) { |
| case DownloadCommands::MAX: |
| NOTREACHED(); |
| break; |
| case DownloadCommands::OPEN_WHEN_COMPLETE: |
| return download_->GetOpenWhenComplete() || |
| download_crx_util::IsExtensionDownload(*download_); |
| case DownloadCommands::ALWAYS_OPEN_TYPE: |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ |
| BUILDFLAG(IS_MAC) |
| if (download_commands->CanOpenPdfInSystemViewer()) { |
| DownloadPrefs* prefs = DownloadPrefs::FromBrowserContext(profile()); |
| return prefs->ShouldOpenPdfInSystemReader(); |
| } |
| #endif |
| return download_->ShouldOpenFileBasedOnExtension(); |
| case DownloadCommands::PAUSE: |
| case DownloadCommands::RESUME: |
| return IsPaused(); |
| case DownloadCommands::SHOW_IN_FOLDER: |
| case DownloadCommands::PLATFORM_OPEN: |
| case DownloadCommands::CANCEL: |
| case DownloadCommands::DISCARD: |
| case DownloadCommands::KEEP: |
| case DownloadCommands::LEARN_MORE_SCANNING: |
| case DownloadCommands::LEARN_MORE_INTERRUPTED: |
| case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD: |
| case DownloadCommands::COPY_TO_CLIPBOARD: |
| case DownloadCommands::DEEP_SCAN: |
| case DownloadCommands::BYPASS_DEEP_SCANNING: |
| case DownloadCommands::REVIEW: |
| case DownloadCommands::RETRY: |
| return false; |
| } |
| return false; |
| } |
| |
| void DownloadItemModel::ExecuteCommand(DownloadCommands* download_commands, |
| DownloadCommands::Command command) { |
| switch (command) { |
| case DownloadCommands::SHOW_IN_FOLDER: |
| download_->ShowDownloadInShell(); |
| break; |
| case DownloadCommands::OPEN_WHEN_COMPLETE: |
| download_->OpenDownload(); |
| break; |
| case DownloadCommands::ALWAYS_OPEN_TYPE: { |
| bool is_checked = IsCommandChecked(download_commands, |
| DownloadCommands::ALWAYS_OPEN_TYPE); |
| base::UmaHistogramBoolean("Download.SetAlwaysOpenTo", !is_checked); |
| DownloadPrefs* prefs = DownloadPrefs::FromBrowserContext(profile()); |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ |
| BUILDFLAG(IS_MAC) |
| if (download_commands->CanOpenPdfInSystemViewer()) { |
| prefs->SetShouldOpenPdfInSystemReader(!is_checked); |
| SetShouldPreferOpeningInBrowser(is_checked); |
| break; |
| } |
| #endif |
| base::FilePath path = download_->GetTargetFilePath(); |
| if (is_checked) |
| prefs->DisableAutoOpenByUserBasedOnExtension(path); |
| else |
| prefs->EnableAutoOpenByUserBasedOnExtension(path); |
| break; |
| } |
| case DownloadCommands::BYPASS_DEEP_SCANNING: |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| CompleteSafeBrowsingScan(); |
| SetOpenWhenComplete(true); |
| #endif |
| [[fallthrough]]; |
| case DownloadCommands::KEEP: |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| if (command == DownloadCommands::KEEP) { |
| MaybeSubmitDownloadToFeedbackService(command, profile(), download_); |
| } |
| #endif |
| if (IsInsecure()) { |
| download_->ValidateInsecureDownload(); |
| break; |
| } |
| if (GetDangerType() == download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING) { |
| break; |
| } |
| DCHECK(IsDangerous()); |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| MaybeSendDownloadReport(GetURL(), GetDangerType(), /*did_proceed=*/true, |
| profile(), download_); |
| #endif |
| download_->ValidateDangerousDownload(); |
| break; |
| case DownloadCommands::DISCARD: |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| MaybeSendDownloadReport(GetURL(), GetDangerType(), /*did_proceed=*/false, |
| profile(), download_); |
| if (MaybeSubmitDownloadToFeedbackService(command, profile(), download_)) { |
| // Skip Remove because it is handled by download feedback service. |
| break; |
| } |
| #endif |
| DownloadUIModel::ExecuteCommand(download_commands, command); |
| break; |
| case DownloadCommands::LEARN_MORE_SCANNING: { |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| using safe_browsing::DownloadProtectionService; |
| |
| safe_browsing::SafeBrowsingService* sb_service = |
| g_browser_process->safe_browsing_service(); |
| DownloadProtectionService* protection_service = |
| (sb_service ? sb_service->download_protection_service() : nullptr); |
| if (protection_service) |
| protection_service->ShowDetailsForDownload( |
| download_, download_commands->GetBrowser()); |
| #else |
| // Should only be getting invoked if we are using safe browsing. |
| NOTREACHED(); |
| #endif |
| break; |
| } |
| case DownloadCommands::MAX: |
| NOTREACHED(); |
| break; |
| case DownloadCommands::PLATFORM_OPEN: |
| case DownloadCommands::CANCEL: |
| case DownloadCommands::LEARN_MORE_INTERRUPTED: |
| case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD: |
| case DownloadCommands::PAUSE: |
| case DownloadCommands::RESUME: |
| case DownloadCommands::COPY_TO_CLIPBOARD: |
| case DownloadCommands::REVIEW: |
| case DownloadCommands::RETRY: |
| DownloadUIModel::ExecuteCommand(download_commands, command); |
| break; |
| case DownloadCommands::DEEP_SCAN: |
| safe_browsing::SafeBrowsingService* sb_service = |
| g_browser_process->safe_browsing_service(); |
| if (!sb_service) |
| break; |
| safe_browsing::DownloadProtectionService* protection_service = |
| sb_service->download_protection_service(); |
| if (!protection_service) |
| break; |
| DownloadCoreService* download_core_service = |
| DownloadCoreServiceFactory::GetForBrowserContext( |
| content::DownloadItemUtils::GetBrowserContext(download_)); |
| DCHECK(download_core_service); |
| ChromeDownloadManagerDelegate* delegate = |
| download_core_service->GetDownloadManagerDelegate(); |
| DCHECK(delegate); |
| |
| // Create an analysis settings object for UploadForDeepScanning(). |
| // Make sure it specifies a cloud analysis is required and does not |
| // specify a DM token, which is what triggers an APP scan. |
| enterprise_connectors::AnalysisSettings settings; |
| settings.cloud_or_local_settings = |
| enterprise_connectors::CloudOrLocalAnalysisSettings( |
| enterprise_connectors::CloudAnalysisSettings()); |
| settings.tags = {{"malware", enterprise_connectors::TagSettings()}}; |
| protection_service->UploadForDeepScanning( |
| download_, |
| base::BindRepeating( |
| &ChromeDownloadManagerDelegate::CheckClientDownloadDone, |
| delegate->GetWeakPtr(), download_->GetId()), |
| safe_browsing::DeepScanningRequest::DeepScanTrigger:: |
| TRIGGER_APP_PROMPT, |
| safe_browsing::DownloadCheckResult::UNKNOWN, std::move(settings)); |
| break; |
| } |
| } |
| |
| DownloadItemModel::BubbleUIInfo |
| DownloadItemModel::GetBubbleUIInfoForTailoredWarning() const { |
| download::DownloadDangerType danger_type = GetDangerType(); |
| TailoredVerdict tailored_verdict = safe_browsing::DownloadProtectionService:: |
| GetDownloadProtectionTailoredVerdict(download_); |
| |
| // Suspicious archives |
| if (danger_type == download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT && |
| tailored_verdict.tailored_verdict_type() == |
| TailoredVerdict::SUSPICIOUS_ARCHIVE) { |
| return DownloadUIModel::BubbleUIInfo( |
| l10n_util::GetStringUTF16( |
| IDS_DOWNLOAD_BUBBLE_SUBPAGE_SUMMARY_SUSPICIOUS_ARCHIVE)) |
| .AddIconAndColor(vector_icons::kNotSecureWarningIcon, |
| ui::kColorAlertMediumSeverity) |
| .AddPrimaryButton(DownloadCommands::Command::DISCARD) |
| .AddSubpageButton(l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_DELETE), |
| DownloadCommands::Command::DISCARD, |
| /*is_prominent=*/true) |
| .AddSubpageButton( |
| l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_CONTINUE), |
| DownloadCommands::Command::KEEP, |
| /*is_prominent=*/false); |
| } |
| |
| // Cookie theft |
| if (danger_type == |
| download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE && |
| tailored_verdict.tailored_verdict_type() == |
| TailoredVerdict::COOKIE_THEFT) { |
| if (base::Contains(tailored_verdict.adjustments(), |
| TailoredVerdict::ACCOUNT_INFO_STRING)) { |
| auto* identity_manager = IdentityManagerFactory::GetForProfile(profile()); |
| std::string email = |
| identity_manager |
| ? identity_manager |
| ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin) |
| .email |
| : ""; |
| base::UmaHistogramBoolean( |
| "SBClientDownload.TailoredWarning.HasVaidEmailForAccountInfo", |
| !email.empty()); |
| if (!email.empty()) { |
| return DownloadUIModel::BubbleUIInfo( |
| l10n_util::GetStringFUTF16( |
| IDS_DOWNLOAD_BUBBLE_SUBPAGE_SUMMARY_COOKIE_THEFT_AND_ACCOUNT, |
| base::ASCIIToUTF16(email))) |
| .AddIconAndColor(vector_icons::kDangerousIcon, |
| ui::kColorAlertHighSeverity) |
| .AddPrimaryButton(DownloadCommands::Command::DISCARD) |
| .AddSubpageButton( |
| l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_DELETE), |
| DownloadCommands::Command::DISCARD, |
| /*is_prominent=*/true); |
| } |
| } |
| return DownloadUIModel::BubbleUIInfo( |
| l10n_util::GetStringUTF16( |
| IDS_DOWNLOAD_BUBBLE_SUBPAGE_SUMMARY_COOKIE_THEFT)) |
| .AddIconAndColor(vector_icons::kDangerousIcon, |
| ui::kColorAlertHighSeverity) |
| .AddPrimaryButton(DownloadCommands::Command::DISCARD) |
| .AddSubpageButton(l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_DELETE), |
| DownloadCommands::Command::DISCARD, |
| /*is_prominent=*/true); |
| } |
| |
| NOTREACHED(); |
| return DownloadUIModel::BubbleUIInfo(); |
| } |
| |
| bool DownloadItemModel::ShouldShowTailoredWarning() const { |
| if (!IsBubbleV2Enabled() || |
| !base::FeatureList::IsEnabled(safe_browsing::kDownloadTailoredWarnings)) { |
| return false; |
| } |
| |
| static const struct ValidCombination { |
| download::DownloadDangerType danger_type; |
| TailoredVerdict::TailoredVerdictType tailored_verdict_type; |
| } kValidTailoredWarningCombinations[]{ |
| {download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT, |
| TailoredVerdict::SUSPICIOUS_ARCHIVE}, |
| {download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE, |
| TailoredVerdict::COOKIE_THEFT}}; |
| |
| download::DownloadDangerType danger_type = GetDangerType(); |
| TailoredVerdict tailored_verdict = safe_browsing::DownloadProtectionService:: |
| GetDownloadProtectionTailoredVerdict(download_); |
| for (const auto& combination : kValidTailoredWarningCombinations) { |
| if (danger_type == combination.danger_type && |
| tailored_verdict.tailored_verdict_type() == |
| combination.tailored_verdict_type) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DownloadItemModel::ShouldShowInBubble() const { |
| // Downloads blocked by local policies should be notified, otherwise users |
| // won't get any feedback that the download has failed. |
| bool should_notify = |
| download_->GetLastReason() == |
| download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED && |
| download_->GetInsecureDownloadStatus() != |
| download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK; |
| |
| // Wait until the target path is determined. |
| if (download_->GetTargetFilePath().empty() && !should_notify) { |
| return false; |
| } |
| |
| if (IsEphemeralWarning()) { |
| // Ephemeral warnings become canceled if the browser shuts down (or an hour |
| // after being displayed if the user hasn't acted on them). These should no |
| // longer be shown, regardless of what the shown time is set to. |
| if (download_->GetState() == download::DownloadItem::CANCELLED) { |
| return false; |
| } |
| |
| // If the user hasn't acted on an ephemeral warning within 5 minutes, it |
| // should no longer be shown in the bubble. (IsEphemeralWarning no longer |
| // returns true once the user has acted on the warning.) |
| auto warning_shown_time = GetEphemeralWarningUiShownTime(); |
| if (warning_shown_time.has_value() && |
| base::Time::Now() - warning_shown_time.value() > |
| kEphemeralWarningLifetimeOnBubble) { |
| return false; |
| } |
| } |
| |
| return DownloadUIModel::ShouldShowInBubble(); |
| } |
| |
| bool DownloadItemModel::IsEphemeralWarning() const { |
| if (!IsBubbleV2Enabled()) { |
| return false; |
| } |
| |
| switch (GetInsecureDownloadStatus()) { |
| case download::DownloadItem::InsecureDownloadStatus::BLOCK: |
| case download::DownloadItem::InsecureDownloadStatus::WARN: |
| return true; |
| case download::DownloadItem::InsecureDownloadStatus::UNKNOWN: |
| case download::DownloadItem::InsecureDownloadStatus::SAFE: |
| case download::DownloadItem::InsecureDownloadStatus::VALIDATED: |
| case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK: |
| break; |
| } |
| |
| switch (GetDangerType()) { |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE: |
| case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: |
| case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: |
| case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING: |
| return true; |
| case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS: |
| case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: |
| case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: |
| case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: |
| case download::DOWNLOAD_DANGER_TYPE_ALLOWLISTED_BY_POLICY: |
| case download::DOWNLOAD_DANGER_TYPE_MAX: |
| case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING: |
| case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE: |
| case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING: |
| case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE: |
| case download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED: |
| case download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE: |
| case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK: |
| return false; |
| } |
| } |
| |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| offline_items_collection::FailState DownloadItemModel::GetLastFailState() |
| const { |
| return OfflineItemUtils::ConvertDownloadInterruptReasonToFailState( |
| download_->GetLastReason()); |
| } |
| |
| std::string DownloadItemModel::GetMimeType() const { |
| return download_->GetMimeType(); |
| } |
| |
| bool DownloadItemModel::IsExtensionDownload() const { |
| return download_crx_util::IsExtensionDownload(*download_); |
| } |
| |
| #if BUILDFLAG(FULL_SAFE_BROWSING) |
| void DownloadItemModel::CompleteSafeBrowsingScan() { |
| if (download_->IsSavePackageDownload()) { |
| download_->OnAsyncScanningCompleted( |
| download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED); |
| enterprise_connectors::RunSavePackageScanningCallback(download_, true); |
| } else { |
| ChromeDownloadManagerDelegate::SafeBrowsingState* state = |
| static_cast<ChromeDownloadManagerDelegate::SafeBrowsingState*>( |
| download_->GetUserData( |
| &ChromeDownloadManagerDelegate::SafeBrowsingState:: |
| kSafeBrowsingUserDataKey)); |
| state->CompleteDownload(); |
| } |
| } |
| |
| void DownloadItemModel::ReviewScanningVerdict( |
| content::WebContents* web_contents) { |
| auto command_callback = |
| [](std::unique_ptr<DownloadItemModel> model, |
| std::unique_ptr<DownloadCommands> download_commands, |
| DownloadCommands::Command command) { |
| model->ExecuteCommand(download_commands.get(), command); |
| }; |
| enterprise_connectors::ShowDownloadReviewDialog( |
| GetFileNameToReportUser().LossyDisplayName(), profile(), download_, |
| web_contents, download_->GetDangerType(), |
| base::BindOnce( |
| command_callback, std::make_unique<DownloadItemModel>(download_), |
| std::make_unique<DownloadCommands>(DownloadUIModel::GetWeakPtr()), |
| DownloadCommands::KEEP), |
| base::BindOnce( |
| command_callback, std::make_unique<DownloadItemModel>(download_), |
| std::make_unique<DownloadCommands>(DownloadUIModel::GetWeakPtr()), |
| DownloadCommands::DISCARD)); |
| } |
| #endif |
| |
| bool DownloadItemModel::ShouldShowDropdown() const { |
| // We don't show the dropdown for dangerous file types or for files |
| // blocked by enterprise policy. |
| if (IsDangerous() && GetState() != DownloadItem::CANCELLED && |
| !MightBeMalicious()) { |
| return false; |
| } |
| |
| if (GetDangerType() == |
| download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK || |
| GetDangerType() == |
| download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED || |
| GetDangerType() == download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE || |
| GetDangerType() == |
| download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void DownloadItemModel::DetermineAndSetShouldPreferOpeningInBrowser( |
| const base::FilePath& target_path, |
| bool is_filetype_handled_safely) { |
| DownloadCoreService* download_core_service = |
| DownloadCoreServiceFactory::GetForBrowserContext( |
| content::DownloadItemUtils::GetBrowserContext(download_)); |
| if (!download_core_service) |
| return; |
| |
| ChromeDownloadManagerDelegate* delegate = |
| download_core_service->GetDownloadManagerDelegate(); |
| if (!delegate) |
| return; |
| |
| // TODO(crbug.com/1372476): Remove this histogram and the associated enum |
| // after debugging. |
| base::UmaHistogramEnumeration( |
| "Download.NotPreferredOpeningInBrowserReasons", |
| NotOpenedInBrowserReason::TOTAL_DOWNLOAD_CHECKED); |
| if (target_path.empty()) { |
| base::UmaHistogramEnumeration( |
| "Download.NotPreferredOpeningInBrowserReasons", |
| NotOpenedInBrowserReason::DOWNLOAD_PATH_EMPTY); |
| } else if (!delegate->IsOpenInBrowserPreferreredForFile(target_path)) { |
| base::UmaHistogramEnumeration( |
| "Download.NotPreferredOpeningInBrowserReasons", |
| NotOpenedInBrowserReason::NOT_PREFERRED_IN_DELEGATE); |
| } else if (!is_filetype_handled_safely) { |
| base::UmaHistogramEnumeration( |
| "Download.NotPreferredOpeningInBrowserReasons", |
| NotOpenedInBrowserReason::CANNOT_BE_HANDLED_SAFELY); |
| } |
| if (!target_path.empty() && |
| delegate->IsOpenInBrowserPreferreredForFile(target_path) && |
| is_filetype_handled_safely) { |
| SetShouldPreferOpeningInBrowser(true); |
| return; |
| } |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| if (download_->GetOriginalMimeType() == "application/x-x509-user-cert") { |
| SetShouldPreferOpeningInBrowser(true); |
| return; |
| } |
| #endif |
| SetShouldPreferOpeningInBrowser(false); |
| } |