| // Copyright 2022 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_warning_data.h" |
| |
| #include <functional> |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "components/download/public/common/download_item.h" |
| |
| using download::DownloadItem; |
| using WarningSurface = DownloadItemWarningData::WarningSurface; |
| using WarningAction = DownloadItemWarningData::WarningAction; |
| using WarningActionEvent = DownloadItemWarningData::WarningActionEvent; |
| using ClientSafeBrowsingReportRequest = |
| safe_browsing::ClientSafeBrowsingReportRequest; |
| using DeepScanTrigger = DownloadItemWarningData::DeepScanTrigger; |
| |
| namespace { |
| constexpr int kWarningActionEventMaxLength = 20; |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class AddWarningActionEventOutcome { |
| // `download` was nullptr. This should never happen. |
| NOT_ADDED_MISSING_DOWNLOAD = 0, |
| // The first warning shown event is already logged so it is not logged this |
| // time. |
| NOT_ADDED_WARNING_SHOWN_ALREADY_LOGGED = 1, |
| // The warning action event is not added because the first warning shown event |
| // was not logged before. |
| NOT_ADDED_MISSING_FIRST_WARNING = 2, |
| // The warning action event is not added because it exceeds the max length. |
| NOT_ADDED_EXCEED_MAX_LENGTH = 3, |
| // The first warning shown event is successfully added. |
| ADDED_WARNING_FIRST_SHOWN = 4, |
| // The warning action event is successfully added. |
| ADDED_WARNING_ACTION = 5, |
| // The warning action event is not added because the download is not |
| // dangerous. |
| NOT_ADDED_DOWNLOAD_NOT_DANGEROUS = 6, |
| kMaxValue = NOT_ADDED_DOWNLOAD_NOT_DANGEROUS |
| }; |
| |
| void RecordAddWarningActionEventOutcome(AddWarningActionEventOutcome outcome) { |
| base::UmaHistogramEnumeration( |
| "Download.WarningData.AddWarningActionEventOutcome2", outcome); |
| } |
| |
| void RecordSurfaceWithoutWarningShown(WarningSurface surface) { |
| base::UmaHistogramEnumeration( |
| "Download.WarningData.SurfaceWithoutWarningShown", surface); |
| } |
| |
| void RecordWarningActionAdded(WarningAction action) { |
| base::UmaHistogramEnumeration("Download.WarningData.ActionAdded", action); |
| } |
| |
| } // namespace |
| |
| // static |
| const char DownloadItemWarningData::kKey[] = "DownloadItemWarningData key"; |
| |
| // static |
| template <typename F, typename V> |
| V DownloadItemWarningData::GetWithDefault(const DownloadItem* download, |
| F&& f, |
| V&& default_value) { |
| if (!download) { |
| return default_value; |
| } |
| DownloadItemWarningData* data = |
| static_cast<DownloadItemWarningData*>(download->GetUserData(kKey)); |
| if (!data) { |
| return default_value; |
| } |
| return std::invoke(std::forward<F>(f), *data); |
| } |
| |
| // static |
| DownloadItemWarningData* DownloadItemWarningData::GetOrCreate( |
| DownloadItem* download) { |
| DownloadItemWarningData* data = |
| static_cast<DownloadItemWarningData*>(download->GetUserData(kKey)); |
| if (!data) { |
| data = new DownloadItemWarningData(); |
| download->SetUserData(kKey, base::WrapUnique(data)); |
| } |
| |
| return data; |
| } |
| |
| // static |
| std::vector<WarningActionEvent> DownloadItemWarningData::GetWarningActionEvents( |
| const DownloadItem* download) { |
| return GetWithDefault(download, &DownloadItemWarningData::ActionEvents, |
| std::vector<WarningActionEvent>()); |
| } |
| |
| // static |
| void DownloadItemWarningData::AddWarningActionEvent(DownloadItem* download, |
| WarningSurface surface, |
| WarningAction action) { |
| if (!download) { |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::NOT_ADDED_MISSING_DOWNLOAD); |
| return; |
| } |
| if (!download->IsDangerous()) { |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::NOT_ADDED_DOWNLOAD_NOT_DANGEROUS); |
| return; |
| } |
| DownloadItemWarningData* data = GetOrCreate(download); |
| if (action == WarningAction::SHOWN) { |
| if (!data->logged_downloads_page_shown_ && |
| surface == WarningSurface::DOWNLOADS_PAGE) { |
| base::UmaHistogramEnumeration( |
| "Download.ShowedDownloadWarning.DownloadsPage", |
| download->GetDangerType(), download::DOWNLOAD_DANGER_TYPE_MAX); |
| data->logged_downloads_page_shown_ = true; |
| } |
| if (data->warning_first_shown_time_.is_null()) { |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::ADDED_WARNING_FIRST_SHOWN); |
| RecordWarningActionAdded(action); |
| data->warning_first_shown_time_ = base::Time::Now(); |
| data->warning_first_shown_surface_ = surface; |
| } else { |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::NOT_ADDED_WARNING_SHOWN_ALREADY_LOGGED); |
| } |
| return; |
| } |
| if (data->warning_first_shown_time_.is_null()) { |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::NOT_ADDED_MISSING_FIRST_WARNING); |
| RecordSurfaceWithoutWarningShown(surface); |
| return; |
| } |
| if (data->action_events_.size() >= kWarningActionEventMaxLength) { |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::NOT_ADDED_EXCEED_MAX_LENGTH); |
| return; |
| } |
| int64_t action_latency = |
| (base::Time::Now() - data->warning_first_shown_time_).InMilliseconds(); |
| bool is_terminal_action = action == WarningAction::PROCEED || |
| action == WarningAction::DISCARD || |
| action == WarningAction::PROCEED_DEEP_SCAN; |
| DCHECK_NE(WarningAction::SHOWN, action); |
| data->action_events_.emplace_back(surface, action, action_latency, |
| is_terminal_action); |
| RecordAddWarningActionEventOutcome( |
| AddWarningActionEventOutcome::ADDED_WARNING_ACTION); |
| RecordWarningActionAdded(action); |
| } |
| |
| // static |
| bool DownloadItemWarningData::IsTopLevelEncryptedArchive( |
| const download::DownloadItem* download) { |
| return GetWithDefault( |
| download, &DownloadItemWarningData::is_top_level_encrypted_archive_, |
| false); |
| } |
| |
| // static |
| void DownloadItemWarningData::SetIsTopLevelEncryptedArchive( |
| download::DownloadItem* download, |
| bool is_top_level_encrypted_archive) { |
| if (!download) { |
| return; |
| } |
| |
| GetOrCreate(download)->is_top_level_encrypted_archive_ = |
| is_top_level_encrypted_archive; |
| } |
| |
| // static |
| bool DownloadItemWarningData::HasIncorrectPassword( |
| const download::DownloadItem* download) { |
| return GetWithDefault( |
| download, &DownloadItemWarningData::has_incorrect_password_, false); |
| } |
| |
| // static |
| void DownloadItemWarningData::SetHasIncorrectPassword( |
| download::DownloadItem* download, |
| bool has_incorrect_password) { |
| if (!download) { |
| return; |
| } |
| |
| GetOrCreate(download)->has_incorrect_password_ = has_incorrect_password; |
| } |
| |
| // static |
| ClientSafeBrowsingReportRequest::DownloadWarningAction |
| DownloadItemWarningData::ConstructCsbrrDownloadWarningAction( |
| const WarningActionEvent& event) { |
| ClientSafeBrowsingReportRequest::DownloadWarningAction action; |
| switch (event.surface) { |
| case DownloadItemWarningData::WarningSurface::BUBBLE_MAINPAGE: |
| action.set_surface(ClientSafeBrowsingReportRequest:: |
| DownloadWarningAction::BUBBLE_MAINPAGE); |
| break; |
| case DownloadItemWarningData::WarningSurface::BUBBLE_SUBPAGE: |
| action.set_surface(ClientSafeBrowsingReportRequest:: |
| DownloadWarningAction::BUBBLE_SUBPAGE); |
| break; |
| case DownloadItemWarningData::WarningSurface::DOWNLOADS_PAGE: |
| action.set_surface(ClientSafeBrowsingReportRequest:: |
| DownloadWarningAction::DOWNLOADS_PAGE); |
| break; |
| case DownloadItemWarningData::WarningSurface::DOWNLOAD_PROMPT: |
| action.set_surface(ClientSafeBrowsingReportRequest:: |
| DownloadWarningAction::DOWNLOAD_PROMPT); |
| break; |
| case DownloadItemWarningData::WarningSurface::DOWNLOAD_NOTIFICATION: |
| action.set_surface(ClientSafeBrowsingReportRequest:: |
| DownloadWarningAction::DOWNLOAD_NOTIFICATION); |
| break; |
| } |
| switch (event.action) { |
| case DownloadItemWarningData::WarningAction::PROCEED: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::PROCEED); |
| break; |
| case DownloadItemWarningData::WarningAction::DISCARD: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::DISCARD); |
| break; |
| case DownloadItemWarningData::WarningAction::KEEP: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::KEEP); |
| break; |
| case DownloadItemWarningData::WarningAction::CLOSE: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::CLOSE); |
| break; |
| case DownloadItemWarningData::WarningAction::CANCEL: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::CANCEL); |
| break; |
| case DownloadItemWarningData::WarningAction::DISMISS: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::DISMISS); |
| break; |
| case DownloadItemWarningData::WarningAction::BACK: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::BACK); |
| break; |
| case DownloadItemWarningData::WarningAction::OPEN_SUBPAGE: |
| action.set_action( |
| ClientSafeBrowsingReportRequest::DownloadWarningAction::OPEN_SUBPAGE); |
| break; |
| case DownloadItemWarningData::WarningAction::PROCEED_DEEP_SCAN: |
| action.set_action(ClientSafeBrowsingReportRequest::DownloadWarningAction:: |
| PROCEED_DEEP_SCAN); |
| break; |
| case DownloadItemWarningData::WarningAction::OPEN_LEARN_MORE_LINK: |
| action.set_action(ClientSafeBrowsingReportRequest::DownloadWarningAction:: |
| OPEN_LEARN_MORE_LINK); |
| break; |
| case DownloadItemWarningData::WarningAction::SHOWN: |
| NOTREACHED(); |
| case DownloadItemWarningData::WarningAction::ACCEPT_DEEP_SCAN: |
| action.set_action(ClientSafeBrowsingReportRequest::DownloadWarningAction:: |
| ACCEPT_DEEP_SCAN); |
| } |
| action.set_is_terminal_action(event.is_terminal_action); |
| action.set_interval_msec(event.action_latency_msec); |
| return action; |
| } |
| |
| // static |
| bool DownloadItemWarningData::HasShownLocalDecryptionPrompt( |
| const download::DownloadItem* download) { |
| return GetWithDefault( |
| download, &DownloadItemWarningData::has_shown_local_decryption_prompt_, |
| false); |
| } |
| |
| // static |
| void DownloadItemWarningData::SetHasShownLocalDecryptionPrompt( |
| download::DownloadItem* download, |
| bool has_shown) { |
| if (!download) { |
| return; |
| } |
| |
| GetOrCreate(download)->has_shown_local_decryption_prompt_ = has_shown; |
| } |
| |
| // static |
| bool DownloadItemWarningData::IsFullyExtractedArchive( |
| const download::DownloadItem* download) { |
| return GetWithDefault( |
| download, &DownloadItemWarningData::fully_extracted_archive_, false); |
| } |
| |
| // static |
| void DownloadItemWarningData::SetIsFullyExtractedArchive( |
| download::DownloadItem* download, |
| bool extracted) { |
| if (!download) { |
| return; |
| } |
| |
| GetOrCreate(download)->fully_extracted_archive_ = extracted; |
| } |
| |
| // static |
| DeepScanTrigger DownloadItemWarningData::DownloadDeepScanTrigger( |
| const download::DownloadItem* download) { |
| return GetWithDefault(download, &DownloadItemWarningData::deep_scan_trigger_, |
| DeepScanTrigger::TRIGGER_UNKNOWN); |
| } |
| |
| // static |
| void DownloadItemWarningData::SetDeepScanTrigger( |
| download::DownloadItem* download, |
| DeepScanTrigger trigger) { |
| if (!download) { |
| return; |
| } |
| |
| GetOrCreate(download)->deep_scan_trigger_ = trigger; |
| } |
| |
| // static |
| base::Time DownloadItemWarningData::WarningFirstShownTime( |
| const download::DownloadItem* download) { |
| return GetWithDefault(download, |
| &DownloadItemWarningData::warning_first_shown_time_, |
| base::Time()); |
| } |
| |
| // static |
| std::optional<DownloadItemWarningData::WarningSurface> |
| DownloadItemWarningData::WarningFirstShownSurface( |
| const download::DownloadItem* download) { |
| return GetWithDefault(download, |
| &DownloadItemWarningData::warning_first_shown_surface_, |
| std::optional<WarningSurface>()); |
| } |
| |
| DownloadItemWarningData::DownloadItemWarningData() = default; |
| |
| DownloadItemWarningData::~DownloadItemWarningData() = default; |
| |
| std::vector<WarningActionEvent> DownloadItemWarningData::ActionEvents() const { |
| if (warning_first_shown_time_.is_null()) { |
| return {}; |
| } |
| return action_events_; |
| } |
| |
| WarningActionEvent::WarningActionEvent(WarningSurface surface, |
| WarningAction action, |
| int64_t action_latency_msec, |
| bool is_terminal_action) |
| : surface(surface), |
| action(action), |
| action_latency_msec(action_latency_msec), |
| is_terminal_action(is_terminal_action) {} |
| |
| std::string DownloadItemWarningData::WarningActionEvent::ToString() const { |
| std::string surface_string, action_string; |
| switch (surface) { |
| case WarningSurface::BUBBLE_MAINPAGE: |
| surface_string = "BUBBLE_MAINPAGE"; |
| break; |
| case WarningSurface::BUBBLE_SUBPAGE: |
| surface_string = "BUBBLE_SUBPAGE"; |
| break; |
| case WarningSurface::DOWNLOADS_PAGE: |
| surface_string = "DOWNLOADS_PAGE"; |
| break; |
| case WarningSurface::DOWNLOAD_PROMPT: |
| surface_string = "DOWNLOAD_PROMPT"; |
| break; |
| case WarningSurface::DOWNLOAD_NOTIFICATION: |
| surface_string = "DOWNLOAD_NOTIFICATION"; |
| break; |
| } |
| switch (action) { |
| case WarningAction::SHOWN: |
| action_string = "SHOWN"; |
| break; |
| case WarningAction::PROCEED: |
| action_string = "PROCEED"; |
| break; |
| case WarningAction::DISCARD: |
| action_string = "DISCARD"; |
| break; |
| case WarningAction::KEEP: |
| action_string = "KEEP"; |
| break; |
| case WarningAction::CLOSE: |
| action_string = "CLOSE"; |
| break; |
| case WarningAction::CANCEL: |
| action_string = "CANCEL"; |
| break; |
| case WarningAction::DISMISS: |
| action_string = "DISMISS"; |
| break; |
| case WarningAction::BACK: |
| action_string = "BACK"; |
| break; |
| case WarningAction::OPEN_SUBPAGE: |
| action_string = "OPEN_SUBPAGE"; |
| break; |
| case WarningAction::PROCEED_DEEP_SCAN: |
| action_string = "PROCEED_DEEP_SCAN"; |
| break; |
| case WarningAction::OPEN_LEARN_MORE_LINK: |
| action_string = "OPEN_LEARN_MORE_LINK"; |
| break; |
| case WarningAction::ACCEPT_DEEP_SCAN: |
| action_string = "ACCEPT_DEEP_SCAN"; |
| break; |
| } |
| return base::JoinString({surface_string, action_string, |
| base::NumberToString(action_latency_msec)}, |
| ":"); |
| } |