blob: 21ac83cff682d12b51ecf5ad15a4ec3df0206cb8 [file] [log] [blame]
// Copyright 2023 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/ui/download/download_bubble_row_view_info.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/download/download_ui_safe_browsing_util.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/download/download_item_mode.h"
#include "chrome/grit/generated_resources.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
#include "ui/views/vector_icons.h"
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/common.h"
#endif
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
#endif
using download::DownloadItem;
using offline_items_collection::FailState;
using TailoredVerdict = safe_browsing::ClientDownloadResponse::TailoredVerdict;
using TailoredWarningType = DownloadUIModel::TailoredWarningType;
DownloadBubbleRowViewInfoObserver::DownloadBubbleRowViewInfoObserver() =
default;
DownloadBubbleRowViewInfoObserver::~DownloadBubbleRowViewInfoObserver() {
CHECK(!IsInObserverList());
}
DownloadBubbleRowViewInfo::DownloadBubbleRowViewInfo(
DownloadUIModel::DownloadUIModelPtr model)
: model_(std::move(model)), state_(model_->GetState()) {
// Ignore whether we changed anything because it's the initial setup
PopulateFromModel();
model_->SetDelegate(this);
}
DownloadBubbleRowViewInfo::~DownloadBubbleRowViewInfo() {
model_->SetDelegate(nullptr);
}
void DownloadBubbleRowViewInfo::SetQuickActionsForTesting(
const std::vector<DownloadBubbleQuickAction>& actions) {
quick_actions_ = actions;
}
void DownloadBubbleRowViewInfo::OnDownloadOpened() {
model_->SetActionedOn(true);
}
void DownloadBubbleRowViewInfo::OnDownloadUpdated() {
if (state_ != model_->GetState()) {
NotifyObservers(&DownloadBubbleRowViewInfoObserver::OnDownloadStateChanged,
state_, model_->GetState());
}
PopulateFromModel();
NotifyObservers(&DownloadBubbleRowViewInfoObserver::OnInfoChanged);
}
void DownloadBubbleRowViewInfo::OnDownloadDestroyed(
const offline_items_collection::ContentId& id) {
NotifyObservers(&DownloadBubbleRowViewInfoObserver::OnDownloadDestroyed, id);
}
void DownloadBubbleRowViewInfo::PopulateFromModel() {
Reset();
icon_and_color_ = IconAndColorForDownload(*model_);
// Add primary button/quick actions for in-progress (paused or active), and
// completed downloads
quick_actions_ = QuickActionsForDownload(*model_);
progress_bar_ = ProgressBarForDownload(*model_);
switch (model_->GetState()) {
case DownloadItem::IN_PROGRESS:
case DownloadItem::COMPLETE:
PopulateForInProgressOrComplete();
return;
case DownloadItem::INTERRUPTED: {
const FailState fail_state = model_->GetLastFailState();
if (fail_state != FailState::USER_CANCELED) {
PopulateForInterrupted(fail_state);
return;
}
}
[[fallthrough]];
case DownloadItem::CANCELLED:
case DownloadItem::MAX_DOWNLOAD_STATE:
return;
}
}
void DownloadBubbleRowViewInfo::PopulateForInProgressOrComplete() {
switch (model_->GetInsecureDownloadStatus()) {
case download::DownloadItem::InsecureDownloadStatus::BLOCK:
case download::DownloadItem::InsecureDownloadStatus::WARN:
PopulateSuspiciousUiPattern();
primary_button_command_ = DownloadCommands::Command::KEEP;
return;
case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
case download::DownloadItem::InsecureDownloadStatus::SAFE:
case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
break;
}
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
if (enterprise_connectors::ShouldPromptReviewForDownload(
model_->profile(), model_->GetDownloadItem())) {
switch (model_->GetDangerType()) {
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
primary_button_command_ = DownloadCommands::Command::REVIEW;
return;
case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
secondary_text_color_ = kColorDownloadItemTextWarning;
primary_button_command_ = DownloadCommands::Command::REVIEW;
return;
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
secondary_text_color_ = kColorDownloadItemTextWarning;
primary_button_command_ = DownloadCommands::Command::REVIEW;
return;
default:
break;
}
}
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
if (TailoredWarningType type = model_->GetTailoredWarningType();
type != TailoredWarningType::kNoTailoredWarning) {
PopulateForTailoredWarning(type);
return;
}
switch (model_->GetDangerType()) {
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
if (model_->IsExtensionDownload()) {
PopulateSuspiciousUiPattern();
return;
} else {
if (WasSafeBrowsingVerdictObtained(model_->GetDownloadItem())) {
PopulateSuspiciousUiPattern();
return;
}
if (ShouldShowWarningForNoSafeBrowsing(model_->profile())) {
PopulateForFileTypeWarningNoSafeBrowsing();
return;
}
PopulateSuspiciousUiPattern();
return;
}
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:
PopulateDangerousUiPattern();
return;
case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
bool request_ap_verdicts = false;
#if BUILDFLAG(FULL_SAFE_BROWSING)
request_ap_verdicts =
safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
model_->profile())
->IsUnderAdvancedProtection();
#endif
if (request_ap_verdicts) {
has_subpage_ = true;
secondary_text_color_ = kColorDownloadItemTextWarning;
return;
} else {
PopulateSuspiciousUiPattern();
return;
}
}
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING: {
has_subpage_ = true;
secondary_text_color_ = kColorDownloadItemTextWarning;
primary_button_command_ = DownloadCommands::Command::DISCARD;
return;
}
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING: {
secondary_text_color_ = kColorDownloadItemTextWarning;
has_subpage_ = true;
return;
}
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_LOCAL_PASSWORD_SCANNING: {
secondary_text_color_ = kColorDownloadItemTextWarning;
has_subpage_ = true;
return;
}
case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
has_subpage_ = true;
return;
case download::DOWNLOAD_DANGER_TYPE_ASYNC_LOCAL_PASSWORD_SCANNING:
has_subpage_ = true;
return;
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_FAILED:
primary_button_command_ = DownloadCommands::Command::OPEN_WHEN_COMPLETE;
secondary_text_color_ = kColorDownloadItemTextWarning;
main_button_enabled_ = false;
return;
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED:
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
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_BLOCKED_SCAN_FAILED:
case download::DOWNLOAD_DANGER_TYPE_MAX:
break;
}
}
void DownloadBubbleRowViewInfo::PopulateForInterrupted(
offline_items_collection::FailState fail_state) {
// Only handle danger types that are terminated in the interrupted state in
// this function. The other danger types are handled in
// `PopulateForInProgressOrComplete`.
switch (model_->GetDangerType()) {
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED: {
has_subpage_ = true;
return;
}
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE: {
has_subpage_ = true;
return;
}
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK: {
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
if (enterprise_connectors::ShouldPromptReviewForDownload(
model_->profile(), model_->GetDownloadItem())) {
primary_button_command_ = DownloadCommands::Command::REVIEW;
return;
}
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
has_subpage_ = true;
return;
}
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:
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_LOCAL_PASSWORD_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_ASYNC_LOCAL_PASSWORD_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_FAILED:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
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_BLOCKED_SCAN_FAILED:
case download::DOWNLOAD_DANGER_TYPE_MAX:
break;
}
switch (fail_state) {
case FailState::FILE_BLOCKED: {
has_subpage_ = true;
return;
}
case FailState::FILE_NAME_TOO_LONG:
case FailState::FILE_NO_SPACE:
case FailState::SERVER_UNAUTHORIZED: {
has_subpage_ = true;
return;
}
// No Retry in these cases.
case FailState::FILE_TOO_LARGE:
case FailState::FILE_VIRUS_INFECTED:
case FailState::FILE_SECURITY_CHECK_FAILED:
case FailState::FILE_ACCESS_DENIED:
case FailState::SERVER_FORBIDDEN:
case FailState::FILE_SAME_AS_SOURCE:
case FailState::SERVER_BAD_CONTENT: {
return;
}
// Try resume if possible or retry if not in these cases, and in the default
// case.
case FailState::NETWORK_INVALID_REQUEST:
case FailState::NETWORK_FAILED:
case FailState::NETWORK_TIMEOUT:
case FailState::NETWORK_DISCONNECTED:
case FailState::NETWORK_SERVER_DOWN:
case FailState::FILE_TRANSIENT_ERROR:
case FailState::USER_SHUTDOWN:
case FailState::CRASH:
case FailState::SERVER_CONTENT_LENGTH_MISMATCH:
case FailState::SERVER_NO_RANGE:
case FailState::SERVER_CROSS_ORIGIN_REDIRECT:
case FailState::FILE_FAILED:
case FailState::FILE_HASH_MISMATCH:
case FailState::SERVER_FAILED:
case FailState::SERVER_CERT_PROBLEM:
case FailState::SERVER_UNREACHABLE:
case FailState::FILE_TOO_SHORT:
break;
// Not possible because the USER_CANCELED fail state does not allow a call
// into this function
case FailState::USER_CANCELED:
// Deprecated
case FailState::NETWORK_INSTABILITY:
case FailState::CANNOT_DOWNLOAD:
NOTREACHED();
case FailState::NO_FAILURE:
return;
}
primary_button_command_ = model_->CanResume()
? DownloadCommands::Command::RESUME
: DownloadCommands::Command::RETRY;
}
void DownloadBubbleRowViewInfo::PopulateForTailoredWarning(
TailoredWarningType tailored_warning_type) {
CHECK(model_->GetDownloadItem());
switch (tailored_warning_type) {
case TailoredWarningType::kSuspiciousArchive:
return PopulateSuspiciousUiPattern();
case TailoredWarningType::kCookieTheft:
return PopulateDangerousUiPattern();
case TailoredWarningType::kNoTailoredWarning: {
NOTREACHED();
}
}
}
void DownloadBubbleRowViewInfo::PopulateForFileTypeWarningNoSafeBrowsing() {
PopulateSuspiciousUiPattern();
}
void DownloadBubbleRowViewInfo::PopulateSuspiciousUiPattern() {
has_subpage_ = true;
secondary_text_color_ = kColorDownloadItemTextWarning;
}
void DownloadBubbleRowViewInfo::PopulateDangerousUiPattern() {
has_subpage_ = true;
secondary_text_color_ = kColorDownloadItemTextDangerous;
}
void DownloadBubbleRowViewInfo::Reset() {
icon_and_color_ = IconAndColor{};
secondary_text_color_ = std::nullopt;
quick_actions_.clear();
main_button_enabled_ = true;
has_subpage_ = false;
primary_button_command_ = std::nullopt;
progress_bar_ = DownloadBubbleProgressBar::NoProgressBar();
}