blob: 6e5d7f0bfd89ae448b88cfb356e640dba830594b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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 "base/i18n/number_formatting.h"
#include "base/i18n/rtl.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string16.h"
#include "base/strings/sys_string_conversions.h"
#include "base/supports_user_data.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/chrome_download_manager_delegate.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/offline_item_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/download_protection/download_feedback_service.h"
#include "chrome/common/safe_browsing/download_file_types.pb.h"
#include "chrome/common/safe_browsing/file_type_policies.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 "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"
using base::TimeDelta;
using download::DownloadItem;
using safe_browsing::DownloadFileType;
namespace {
// 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.
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_;
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 == NULL) {
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),
should_prefer_opening_in_browser_(false),
danger_level_(DownloadFileType::NOT_DANGEROUS),
is_being_revived_(false) {}
} // namespace
// -----------------------------------------------------------------------------
// DownloadItemModel
// static
DownloadUIModel::DownloadUIModelPtr DownloadItemModel::Wrap(
download::DownloadItem* download) {
DownloadUIModel::DownloadUIModelPtr model(
new DownloadItemModel(download),
base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
return model;
}
DownloadItemModel::DownloadItemModel(DownloadItem* download)
: download_(download) {
download_->AddObserver(this);
}
DownloadItemModel::~DownloadItemModel() {
if (download_)
download_->RemoveObserver(this);
}
ContentId DownloadItemModel::GetContentId() const {
bool off_the_record = content::DownloadItemUtils::GetBrowserContext(download_)
->IsOffTheRecord();
return ContentId(OfflineItemUtils::GetDownloadNamespacePrefix(off_the_record),
download_->GetGuid());
}
Profile* DownloadItemModel::profile() const {
return Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(download_));
}
base::string16 DownloadItemModel::GetTabProgressStatusText() const {
int64_t total = GetTotalBytes();
int64_t size = download_->GetReceivedBytes();
base::string16 received_size = ui::FormatBytes(size);
base::string16 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) {
base::string16 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();
base::string16 speed_text = ui::FormatSpeed(current_speed);
base::i18n::AdjustStringForLocaleDirection(&speed_text);
base::TimeDelta remaining;
base::string16 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 {
if (!IsDangerous())
return false;
switch (download_->GetDangerType()) {
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:
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_WHITELISTED_BY_POLICY:
case download::DOWNLOAD_DANGER_TYPE_MAX:
// We shouldn't get any of these due to the IsDangerous() test above.
NOTREACHED();
FALLTHROUGH;
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
return false;
}
NOTREACHED();
return false;
}
// 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:
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_WHITELISTED_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:
return false;
}
NOTREACHED();
return false;
}
bool DownloadItemModel::ShouldAllowDownloadFeedback() const {
#if defined(FULL_SAFE_BROWSING)
if (!IsDangerous())
return false;
return safe_browsing::DownloadFeedbackService::IsEnabledForDownload(
*download_);
#else
return false;
#endif
}
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 an extension, temporary, or will be opened
// automatically, then it should be removed from the shelf on completion.
// TODO(asanka): The logic for deciding opening behavior should be in a
// central location. http://crbug.com/167702
return (download_crx_util::IsExtensionDownload(*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::IsExtensionDownload(*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::ShouldPreferOpeningInBrowser() const {
const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
return data && data->should_prefer_opening_in_browser_;
}
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;
}
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;
}
download::DownloadItem* DownloadItemModel::download() {
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::TimeRemaining(base::TimeDelta* remaining) const {
return download_->TimeRemaining(remaining);
}
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();
}
void DownloadItemModel::OnDownloadUpdated(DownloadItem* download) {
for (auto& obs : observers_)
obs.OnDownloadUpdated();
}
void DownloadItemModel::OnDownloadOpened(DownloadItem* download) {
for (auto& obs : observers_)
obs.OnDownloadOpened();
}
void DownloadItemModel::OnDownloadDestroyed(DownloadItem* download) {
for (auto& obs : observers_)
obs.OnDownloadDestroyed();
download_ = nullptr;
}
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_);
RecordDownloadOpenMethod(DOWNLOAD_OPEN_METHOD_USER_PLATFORM);
}
#if !defined(OS_ANDROID)
bool DownloadItemModel::IsCommandEnabled(
const DownloadCommands* download_commands,
DownloadCommands::Command command) const {
switch (command) {
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::ANNOTATE:
case DownloadCommands::DISCARD:
case DownloadCommands::KEEP:
case DownloadCommands::LEARN_MORE_SCANNING:
case DownloadCommands::LEARN_MORE_INTERRUPTED:
return DownloadUIModel::IsCommandEnabled(download_commands, command);
}
NOTREACHED();
return false;
}
bool DownloadItemModel::IsCommandChecked(
const DownloadCommands* download_commands,
DownloadCommands::Command command) const {
switch (command) {
case DownloadCommands::OPEN_WHEN_COMPLETE:
return download_->GetOpenWhenComplete() ||
download_crx_util::IsExtensionDownload(*download_);
case DownloadCommands::ALWAYS_OPEN_TYPE:
#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
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::COPY_TO_CLIPBOARD:
case DownloadCommands::ANNOTATE:
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);
DownloadPrefs* prefs = DownloadPrefs::FromBrowserContext(profile());
#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
if (download_commands->CanOpenPdfInSystemViewer()) {
prefs->SetShouldOpenPdfInSystemReader(!is_checked);
SetShouldPreferOpeningInBrowser(is_checked);
break;
}
#endif
base::FilePath path = download_->GetTargetFilePath();
if (is_checked)
prefs->DisableAutoOpenBasedOnExtension(path);
else
prefs->EnableAutoOpenBasedOnExtension(path);
break;
}
case DownloadCommands::KEEP:
// Only sends uncommon download accept report if :
// 1. FULL_SAFE_BROWSING is enabled, and
// 2. Download verdict is uncommon, and
// 3. Download URL is not empty, and
// 4. User is not in incognito mode.
#if defined(FULL_SAFE_BROWSING)
if (GetDangerType() == download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT &&
!GetURL().is_empty() && !profile()->IsOffTheRecord()) {
safe_browsing::SafeBrowsingService* sb_service =
g_browser_process->safe_browsing_service();
// Compiles the uncommon download warning report.
safe_browsing::ClientSafeBrowsingReportRequest report;
report.set_type(safe_browsing::ClientSafeBrowsingReportRequest::
DANGEROUS_DOWNLOAD_WARNING);
report.set_download_verdict(
safe_browsing::ClientDownloadResponse::UNCOMMON);
report.set_url(GetURL().spec());
report.set_did_proceed(true);
std::string token =
safe_browsing::DownloadProtectionService::GetDownloadPingToken(
download_);
if (!token.empty())
report.set_token(token);
std::string serialized_report;
if (report.SerializeToString(&serialized_report)) {
sb_service->SendSerializedDownloadReport(serialized_report);
} else {
DCHECK(false)
<< "Unable to serialize the uncommon download warning report.";
}
}
#endif
download_->ValidateDangerousDownload();
break;
case DownloadCommands::LEARN_MORE_SCANNING: {
#if defined(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::PLATFORM_OPEN:
case DownloadCommands::CANCEL:
case DownloadCommands::DISCARD:
case DownloadCommands::LEARN_MORE_INTERRUPTED:
case DownloadCommands::PAUSE:
case DownloadCommands::RESUME:
case DownloadCommands::COPY_TO_CLIPBOARD:
case DownloadCommands::ANNOTATE:
DownloadUIModel::ExecuteCommand(download_commands, command);
break;
}
}
#endif
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_);
}