#include "chrome/browser/download/download_target_determiner.h"
#include <optional>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_confirmation_reason.h"
#include "chrome/browser/download/download_crx_util.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_stats.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/safe_browsing_metrics_collector_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_item.h"
#include "components/download/public/common/download_target_info.h"
#include "components/history/core/browser/history_service.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/buildflags.h"
#include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/common/buildflags.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "net/base/filename_util.h"
#include "net/http/http_content_disposition.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/extensions/webstore_installer.h"
#include "extensions/common/feature_switch.h"
#endif
#if BUILDFLAG(ENABLE_PLUGINS)
#include "chrome/browser/plugins/plugin_prefs.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/webplugininfo.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "ui/shell_dialogs/select_file_utils_win.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/policy/dlp/dlp_files_controller_ash.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_file_destination.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
#include "components/download/public/common/base_file.h"
#endif
#if BUILDFLAG(IS_ANDROID)
#include "components/download/public/common/download_file.h"
#include "components/safe_browsing/android/safe_browsing_api_handler_bridge.h"
#endif
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "components/safe_browsing/content/browser/download/download_stats.h"
#include "components/safe_browsing/content/common/file_type_policies.h"
#endif
using content::BrowserThread;
using download::DownloadItem;
using download::DownloadPathReservationTracker;
using safe_browsing::DownloadFileType;
namespace {
const base::FilePath::CharType kCrdownloadSuffix[] =
FILE_PATH_LITERAL(".crdownload");
void VisitCountsToVisitedBefore(base::OnceCallback<void(bool)> callback,
history::VisibleVisitCountToHostResult result) {
std::move(callback).Run(
result.success && result.count > 0 &&
(result.first_visit.LocalMidnight() < base::Time::Now().LocalMidnight()));
}
void GenerateSafeFileName(base::FilePath* new_path,
const base::FilePath::StringType& old_extension,
const std::string& mime_type) {
DCHECK(new_path);
if (new_path->Extension().empty() || new_path->Extension() == old_extension) {
net::GenerateSafeFileName(std::string() ,
false , new_path);
} else {
net::GenerateSafeFileName(mime_type, true , new_path);
}
}
}
DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() = default;
DownloadTargetDeterminer::DownloadTargetDeterminer(
DownloadItem* download,
const base::FilePath& initial_virtual_path,
DownloadPathReservationTracker::FilenameConflictAction conflict_action,
DownloadPrefs* download_prefs,
DownloadTargetDeterminerDelegate* delegate,
CompletionCallback callback)
: next_state_(STATE_GENERATE_TARGET_PATH),
confirmation_reason_(DownloadConfirmationReason::NONE),
should_notify_extensions_(false),
create_target_directory_(false),
conflict_action_(conflict_action),
danger_type_(download->GetDangerType()),
danger_level_(DownloadFileType::NOT_DANGEROUS),
virtual_path_(initial_virtual_path),
is_filetype_handled_safely_(false),
#if BUILDFLAG(IS_ANDROID)
is_checking_dialog_confirmed_path_(false),
#endif
download_(download),
is_resumption_(download_->GetLastReason() !=
download::DOWNLOAD_INTERRUPT_REASON_NONE &&
!initial_virtual_path.empty()),
download_prefs_(download_prefs),
delegate_(delegate),
completion_callback_(std::move(callback)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(download_);
DCHECK(delegate);
download_->AddObserver(this);
DoLoop();
}
DownloadTargetDeterminer::~DownloadTargetDeterminer() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(download_);
DCHECK(!completion_callback_);
download_->RemoveObserver(this);
}
void DownloadTargetDeterminer::DoLoop() {
Result result = CONTINUE;
do {
State current_state = next_state_;
next_state_ = STATE_NONE;
switch (current_state) {
case STATE_GENERATE_TARGET_PATH:
result = DoGenerateTargetPath();
break;
case STATE_SET_INSECURE_DOWNLOAD_STATUS:
result = DoSetInsecureDownloadStatus();
break;
case STATE_NOTIFY_EXTENSIONS:
result = DoNotifyExtensions();
break;
case STATE_RESERVE_VIRTUAL_PATH:
result = DoReserveVirtualPath();
break;
case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH:
result = DoRequestConfirmation();
break;
case STATE_DETERMINE_LOCAL_PATH:
result = DoDetermineLocalPath();
break;
case STATE_DETERMINE_MIME_TYPE:
result = DoDetermineMimeType();
break;
case STATE_CHECK_DOWNLOAD_URL:
result = DoCheckDownloadUrl();
break;
#if BUILDFLAG(IS_ANDROID)
case STATE_CHECK_APP_VERIFICATION:
result = DoCheckAppVerification();
break;
#endif
case STATE_CHECK_VISITED_REFERRER_BEFORE:
result = DoCheckVisitedReferrerBefore();
break;
case STATE_DETERMINE_INTERMEDIATE_PATH:
result = DoDetermineIntermediatePath();
break;
case STATE_NONE:
NOTREACHED();
}
} while (result == CONTINUE);
if (result == COMPLETE)
ScheduleCallbackAndDeleteSelf(download::DOWNLOAD_INTERRUPT_REASON_NONE);
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoGenerateTargetPath() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(local_path_.empty());
DCHECK_EQ(confirmation_reason_, DownloadConfirmationReason::NONE);
DCHECK(!should_notify_extensions_);
bool is_forced_path = !download_->GetForcedFilePath().empty();
next_state_ = STATE_SET_INSECURE_DOWNLOAD_STATUS;
if (download_->IsTransient()) {
if (is_forced_path) {
RecordDownloadPathGeneration(DownloadPathGenerationEvent::USE_FORCE_PATH,
true);
virtual_path_ = download_->GetForcedFilePath();
} else if (!virtual_path_.empty()) {
RecordDownloadPathGeneration(
DownloadPathGenerationEvent::USE_EXISTING_VIRTUAL_PATH, true);
} else {
RecordDownloadPathGeneration(DownloadPathGenerationEvent::NO_VALID_PATH,
true);
RecordDownloadCancelReason(DownloadCancelReason::kNoValidPath);
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
return QUIT_DOLOOP;
}
DCHECK(virtual_path_.IsAbsolute());
return CONTINUE;
}
bool no_prompt_needed = HasPromptedForPath();
#if BUILDFLAG(IS_ANDROID)
no_prompt_needed |= virtual_path_.IsContentUri();
#endif
if (!virtual_path_.empty() && no_prompt_needed && !is_forced_path) {
confirmation_reason_ = NeedsConfirmation(virtual_path_);
conflict_action_ = DownloadPathReservationTracker::OVERWRITE;
RecordDownloadPathGeneration(
DownloadPathGenerationEvent::USE_EXISTING_VIRTUAL_PATH, false);
} else if (!is_forced_path) {
base::FilePath generated_filename = GenerateFileName();
confirmation_reason_ = NeedsConfirmation(generated_filename);
base::FilePath target_directory;
if (confirmation_reason_ != DownloadConfirmationReason::NONE) {
if (download_prefs_->IsDownloadPathManaged())
DCHECK(confirmation_reason_ == DownloadConfirmationReason::DLP_BLOCKED);
target_directory = download_prefs_->SaveFilePath();
RecordDownloadPathGeneration(
DownloadPathGenerationEvent::USE_LAST_PROMPT_DIRECTORY, false);
} else {
target_directory = download_prefs_->DownloadPath();
RecordDownloadPathGeneration(
DownloadPathGenerationEvent::USE_DEFAULTL_DOWNLOAD_DIRECTORY, false);
}
should_notify_extensions_ = true;
virtual_path_ = target_directory.Append(generated_filename);
DCHECK(virtual_path_.IsAbsolute());
} else {
conflict_action_ = DownloadPathReservationTracker::OVERWRITE;
virtual_path_ = download_->GetForcedFilePath();
RecordDownloadPathGeneration(DownloadPathGenerationEvent::USE_FORCE_PATH,
false);
DCHECK(virtual_path_.IsAbsolute());
}
DVLOG(20) << "Generated virtual path: " << virtual_path_.AsUTF8Unsafe();
return CONTINUE;
}
base::FilePath DownloadTargetDeterminer::GenerateFileName() const {
std::string suggested_filename = download_->GetSuggestedFilename();
std::string sniffed_mime_type = download_->GetMimeType();
if (suggested_filename.empty() &&
sniffed_mime_type == "application/x-x509-user-cert") {
suggested_filename = "user.crt";
}
std::string default_filename(
l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
std::string referrer_charset =
GetProfile()->GetPrefs()->GetString(prefs::kDefaultCharset);
base::FilePath generated_filename = net::GenerateFileName(
download_->GetURL(), download_->GetContentDisposition(), referrer_charset,
suggested_filename, sniffed_mime_type, default_filename);
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
if (safe_browsing::FileTypePolicies::GetInstance()->IsCheckedBinaryFile(
generated_filename)) {
return generated_filename;
}
#endif
if (sniffed_mime_type.empty() || !suggested_filename.empty())
return generated_filename;
net::HttpContentDisposition content_disposition_header(
download_->GetContentDisposition(), referrer_charset);
if (!content_disposition_header.filename().empty())
return generated_filename;
if (sniffed_mime_type == "text/plain" &&
download_->GetOriginalMimeType() != "text/plain") {
return generated_filename;
}
generated_filename = net::GenerateFileName(
download_->GetURL(), std::string() ,
referrer_charset, std::string() ,
sniffed_mime_type, default_filename, true );
return generated_filename;
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoSetInsecureDownloadStatus() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
next_state_ = STATE_NOTIFY_EXTENSIONS;
delegate_->GetInsecureDownloadStatus(
download_, virtual_path_,
base::BindOnce(&DownloadTargetDeterminer::GetInsecureDownloadStatusDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
void DownloadTargetDeterminer::GetInsecureDownloadStatusDone(
download::DownloadItem::InsecureDownloadStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(STATE_NOTIFY_EXTENSIONS, next_state_);
insecure_download_status_ = status;
if (status == download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK) {
RecordDownloadCancelReason(DownloadCancelReason::kInsecureDownload);
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED);
return;
}
DoLoop();
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoNotifyExtensions() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
next_state_ = STATE_RESERVE_VIRTUAL_PATH;
if (!should_notify_extensions_ ||
download_->GetState() != DownloadItem::IN_PROGRESS)
return CONTINUE;
delegate_->NotifyExtensions(
download_, virtual_path_,
base::BindOnce(&DownloadTargetDeterminer::NotifyExtensionsDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
void DownloadTargetDeterminer::NotifyExtensionsDone(
const base::FilePath& suggested_path,
DownloadPathReservationTracker::FilenameConflictAction conflict_action) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << "Extension suggested path: " << suggested_path.AsUTF8Unsafe();
DCHECK_EQ(STATE_RESERVE_VIRTUAL_PATH, next_state_);
if (download_->GetURL().SchemeIsFile()) {
DoLoop();
return;
}
if (!suggested_path.empty()) {
base::FilePath new_path(download_prefs_->DownloadPath().Append(
suggested_path).NormalizePathSeparators());
GenerateSafeFileName(&new_path, virtual_path_.Extension(),
download_->GetMimeType());
virtual_path_ = new_path;
create_target_directory_ = true;
}
if (conflict_action != DownloadPathReservationTracker::UNIQUIFY)
conflict_action_ = conflict_action;
DoLoop();
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoReserveVirtualPath() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
next_state_ = STATE_PROMPT_USER_FOR_DOWNLOAD_PATH;
if (download_->GetState() != DownloadItem::IN_PROGRESS)
return CONTINUE;
delegate_->ReserveVirtualPath(
download_, virtual_path_, create_target_directory_, conflict_action_,
base::BindOnce(&DownloadTargetDeterminer::ReserveVirtualPathDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
void DownloadTargetDeterminer::ReserveVirtualPathDone(
download::PathValidationResult result,
const base::FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DVLOG(20) << "Reserved path: " << path.AsUTF8Unsafe()
<< " Result:" << static_cast<int>(result);
DCHECK_EQ(STATE_PROMPT_USER_FOR_DOWNLOAD_PATH, next_state_);
RecordDownloadPathValidation(result, download_->IsTransient());
if (download_->IsTransient()) {
DCHECK_EQ(DownloadConfirmationReason::NONE, confirmation_reason_)
<< "Transient download should not ask the user for confirmation.";
DCHECK(result != download::PathValidationResult::CONFLICT)
<< "Transient download"
"should always overwrite or uniquify the file.";
switch (result) {
case download::PathValidationResult::PATH_NOT_WRITABLE:
case download::PathValidationResult::NAME_TOO_LONG:
case download::PathValidationResult::CONFLICT:
RecordDownloadCancelReason(
DownloadCancelReason::kFailedPathReservation);
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
return;
case download::PathValidationResult::SUCCESS:
case download::PathValidationResult::SUCCESS_RESOLVED_CONFLICT:
DCHECK(virtual_path_ == path ||
conflict_action_ == DownloadPathReservationTracker::UNIQUIFY);
break;
case download::PathValidationResult::SAME_AS_SOURCE:
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
return;
case download::PathValidationResult::COUNT:
NOTREACHED();
}
} else {
virtual_path_ = path;
switch (result) {
case download::PathValidationResult::SUCCESS:
break;
case download::PathValidationResult::SAME_AS_SOURCE:
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
return;
case download::PathValidationResult::SUCCESS_RESOLVED_CONFLICT:
break;
case download::PathValidationResult::PATH_NOT_WRITABLE:
confirmation_reason_ =
DownloadConfirmationReason::TARGET_PATH_NOT_WRITEABLE;
break;
case download::PathValidationResult::NAME_TOO_LONG:
confirmation_reason_ = DownloadConfirmationReason::NAME_TOO_LONG;
break;
case download::PathValidationResult::CONFLICT:
confirmation_reason_ = DownloadConfirmationReason::TARGET_CONFLICT;
break;
case download::PathValidationResult::COUNT:
NOTREACHED();
}
}
DoLoop();
}
#if BUILDFLAG(IS_ANDROID)
void DownloadTargetDeterminer::RequestIncognitoWarningConfirmationDone(
bool accepted) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (accepted) {
DoLoop();
} else {
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
return;
}
}
#endif
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoRequestConfirmation() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
#if BUILDFLAG(IS_ANDROID)
DCHECK(!download_->IsTransient() ||
confirmation_reason_ == DownloadConfirmationReason::NONE ||
confirmation_reason_ == DownloadConfirmationReason::PREFERENCE);
#else
DCHECK(!download_->IsTransient() ||
confirmation_reason_ == DownloadConfirmationReason::NONE);
#endif
next_state_ = STATE_DETERMINE_LOCAL_PATH;
if (download_->GetState() == DownloadItem::IN_PROGRESS) {
#if BUILDFLAG(IS_ANDROID)
if (is_checking_dialog_confirmed_path_ &&
(confirmation_reason_ == DownloadConfirmationReason::PREFERENCE ||
confirmation_reason_ == DownloadConfirmationReason::NONE)) {
is_checking_dialog_confirmed_path_ = false;
return CONTINUE;
}
#endif
if (confirmation_reason_ != DownloadConfirmationReason::NONE) {
base::FilePath sanitized_path = virtual_path_;
#if BUILDFLAG(IS_WIN)
std::wstring sanitized_name = ui::RemoveEnvVarFromFileName<wchar_t>(
virtual_path_.BaseName().value(), L"%");
while (!sanitized_name.empty() && sanitized_name.back() == L'.') {
sanitized_name.pop_back();
}
base::TrimWhitespace(sanitized_name, base::TrimPositions::TRIM_TRAILING, &sanitized_name);
if (sanitized_name.empty()) {
sanitized_name = base::UTF8ToWide(
l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
}
sanitized_path =
virtual_path_.DirName().Append(base::FilePath(sanitized_name));
const base::FilePath::StringType post_sanitize_ext =
base::FilePath(sanitized_name).Extension();
GenerateSafeFileName(&sanitized_path, post_sanitize_ext,
download_->GetMimeType());
#endif
delegate_->RequestConfirmation(
download_, sanitized_path, confirmation_reason_,
base::BindRepeating(
&DownloadTargetDeterminer::RequestConfirmationDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
} else {
#if BUILDFLAG(IS_ANDROID)
content::BrowserContext* browser_context =
content::DownloadItemUtils::GetBrowserContext(download_);
bool isOffTheRecord =
Profile::FromBrowserContext(browser_context)->IsOffTheRecord();
if (isOffTheRecord && (!download_->IsTransient() ||
!download_->AllowAutoOpenAfterCompletion())) {
delegate_->RequestIncognitoWarningConfirmation(base::BindOnce(
&DownloadTargetDeterminer::RequestIncognitoWarningConfirmationDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
#endif
}
}
return CONTINUE;
}
void DownloadTargetDeterminer::RequestConfirmationDone(
DownloadConfirmationResult result,
const ui::SelectedFileInfo& selected_file_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!download_->IsTransient());
base::FilePath virtual_path = selected_file_info.path();
DVLOG(20) << "User selected path:" << virtual_path.AsUTF8Unsafe();
#if BUILDFLAG(IS_ANDROID)
is_checking_dialog_confirmed_path_ = false;
#endif
if (result == DownloadConfirmationResult::CANCELED) {
RecordDownloadCancelReason(DownloadCancelReason::kTargetConfirmationResult);
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
return;
}
DCHECK(!virtual_path.empty());
DCHECK_EQ(STATE_DETERMINE_LOCAL_PATH, next_state_);
if (result == DownloadConfirmationResult::CONTINUE_WITHOUT_CONFIRMATION)
confirmation_reason_ = DownloadConfirmationReason::NONE;
virtual_path_ = virtual_path;
#if BUILDFLAG(IS_MAC)
file_tags_ = selected_file_info.file_tags;
#endif
#if BUILDFLAG(IS_ANDROID)
if (result == DownloadConfirmationResult::CONFIRMED_WITH_DIALOG) {
is_checking_dialog_confirmed_path_ = true;
if (confirmation_reason_ != DownloadConfirmationReason::PREFERENCE) {
confirmation_reason_ = DownloadConfirmationReason::NONE;
}
next_state_ = STATE_RESERVE_VIRTUAL_PATH;
}
#endif
download_prefs_->SetSaveFilePath(virtual_path_.DirName());
DoLoop();
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoDetermineLocalPath() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
DCHECK(local_path_.empty());
next_state_ = STATE_DETERMINE_MIME_TYPE;
delegate_->DetermineLocalPath(
download_, virtual_path_,
base::BindOnce(&DownloadTargetDeterminer::DetermineLocalPathDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
void DownloadTargetDeterminer::DetermineLocalPathDone(
const base::FilePath& local_path,
const base::FilePath& file_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe();
if (local_path.empty()) {
RecordDownloadCancelReason(DownloadCancelReason::kEmptyLocalPath);
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
return;
}
DCHECK_EQ(STATE_DETERMINE_MIME_TYPE, next_state_);
local_path_ = local_path;
#if BUILDFLAG(IS_ANDROID)
if (local_path_.IsContentUri() && !virtual_path_.IsContentUri()) {
virtual_path_ = virtual_path_.DirName().Append(file_name);
}
#endif
DoLoop();
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoDetermineMimeType() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
DCHECK(!local_path_.empty());
DCHECK(mime_type_.empty());
next_state_ = STATE_CHECK_DOWNLOAD_URL;
if (virtual_path_ == local_path_
#if BUILDFLAG(IS_ANDROID)
|| local_path_.IsContentUri()
#endif
) {
delegate_->GetFileMimeType(
local_path_,
base::BindOnce(&DownloadTargetDeterminer::DetermineMimeTypeDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
return CONTINUE;
}
void DownloadTargetDeterminer::DetermineMimeTypeDone(
const std::string& mime_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << "MIME type: " << mime_type;
DCHECK_EQ(STATE_CHECK_DOWNLOAD_URL, next_state_);
DCHECK(!local_path_.empty());
DCHECK(!is_filetype_handled_safely_);
mime_type_ = mime_type;
if (!mime_type_.empty()) {
is_filetype_handled_safely_ =
DetermineIfHandledSafelyHelper(download_, local_path_, mime_type_);
DVLOG(20) << "Is file type handled safely: " << is_filetype_handled_safely_;
}
DoLoop();
}
bool DownloadTargetDeterminer::DetermineIfHandledSafelyHelper(
download::DownloadItem* download,
const base::FilePath& local_path,
const std::string& mime_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (blink::IsSupportedMimeType(mime_type)) {
return true;
}
#if BUILDFLAG(ENABLE_PLUGINS)
DCHECK(!mime_type.empty());
auto* plugin_service = content::PluginService::GetInstance();
plugin_service->GetPlugins();
return plugin_service->HasPlugin(
content::DownloadItemUtils::GetBrowserContext(download),
net::FilePathToFileURL(local_path), mime_type);
#else
return false;
#endif
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoCheckDownloadUrl() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
#if BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled(
safe_browsing::kGooglePlayProtectReducesWarnings)) {
next_state_ = STATE_CHECK_APP_VERIFICATION;
} else {
next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE;
}
#else
next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE;
#endif
if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED)
return CONTINUE;
delegate_->CheckDownloadUrl(
download_, virtual_path_,
base::BindOnce(&DownloadTargetDeterminer::CheckDownloadUrlDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
void DownloadTargetDeterminer::CheckDownloadUrlDone(
download::DownloadDangerType danger_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << "URL Check Result:" << danger_type;
#if BUILDFLAG(IS_ANDROID)
DCHECK_EQ(base::FeatureList::IsEnabled(
safe_browsing::kGooglePlayProtectReducesWarnings)
? STATE_CHECK_APP_VERIFICATION
: STATE_CHECK_VISITED_REFERRER_BEFORE,
next_state_);
#else
DCHECK_EQ(STATE_CHECK_VISITED_REFERRER_BEFORE, next_state_);
#endif
danger_type_ = danger_type;
DoLoop();
}
#if BUILDFLAG(IS_ANDROID)
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoCheckAppVerification() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE;
safe_browsing::SafeBrowsingApiHandlerBridge::GetInstance()
.StartIsVerifyAppsEnabled(
base::BindOnce(&DownloadTargetDeterminer::CheckAppVerificationDone,
weak_ptr_factory_.GetWeakPtr()));
return QUIT_DOLOOP;
}
void DownloadTargetDeterminer::CheckAppVerificationDone(
safe_browsing::VerifyAppsEnabledResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(STATE_CHECK_VISITED_REFERRER_BEFORE, next_state_);
is_app_verification_enabled_ =
result == safe_browsing::VerifyAppsEnabledResult::SUCCESS_ENABLED;
DoLoop();
}
#endif
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoCheckVisitedReferrerBefore() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
next_state_ = STATE_DETERMINE_INTERMEDIATE_PATH;
if (danger_type_ != download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS &&
danger_type_ != download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT &&
danger_type_ != download::DOWNLOAD_DANGER_TYPE_ALLOWLISTED_BY_POLICY) {
return CONTINUE;
}
danger_level_ = GetDangerLevel(NO_VISITS_TO_REFERRER);
if (danger_level_ == DownloadFileType::NOT_DANGEROUS)
return CONTINUE;
if (danger_level_ == DownloadFileType::ALLOW_ON_USER_GESTURE) {
#if BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled(
safe_browsing::kGooglePlayProtectReducesWarnings) &&
is_app_verification_enabled_) {
return CONTINUE;
}
#endif
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(
GetProfile(), ServiceAccessType::EXPLICIT_ACCESS);
if (history_service && download_->GetReferrerUrl().is_valid()) {
history_service->GetVisibleVisitCountToHost(
download_->GetReferrerUrl(),
base::BindOnce(
&VisitCountsToVisitedBefore,
base::BindOnce(
&DownloadTargetDeterminer::CheckVisitedReferrerBeforeDone,
weak_ptr_factory_.GetWeakPtr())),
&history_tracker_);
return QUIT_DOLOOP;
}
}
if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
danger_type_ = download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
return CONTINUE;
}
void DownloadTargetDeterminer::CheckVisitedReferrerBeforeDone(
bool visited_referrer_before) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(STATE_DETERMINE_INTERMEDIATE_PATH, next_state_);
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
safe_browsing::RecordDownloadFileTypeAttributes(
safe_browsing::FileTypePolicies::GetInstance()->GetFileDangerLevel(
virtual_path_.BaseName(), download_->GetURL(),
GetProfile()->GetPrefs()),
download_->HasUserGesture(), visited_referrer_before,
GetLastDownloadBypassTimestamp());
#endif
danger_level_ = GetDangerLevel(
visited_referrer_before ? VISITED_REFERRER : NO_VISITS_TO_REFERRER);
if (danger_level_ != DownloadFileType::NOT_DANGEROUS &&
danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
danger_type_ = download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
DoLoop();
}
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoDetermineIntermediatePath() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!virtual_path_.empty());
DCHECK(!local_path_.empty());
DCHECK(intermediate_path_.empty());
DCHECK(!virtual_path_.MatchesExtension(kCrdownloadSuffix));
DCHECK(!local_path_.MatchesExtension(kCrdownloadSuffix));
next_state_ = STATE_NONE;
#if BUILDFLAG(IS_ANDROID)
if (local_path_.IsContentUri()) {
intermediate_path_ = local_path_;
return COMPLETE;
}
#endif
if (virtual_path_.BaseName() != local_path_.BaseName()) {
intermediate_path_ = local_path_;
return COMPLETE;
}
if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS &&
!download_->GetForcedFilePath().empty()) {
DCHECK_EQ(download_->GetForcedFilePath().value(), local_path_.value());
intermediate_path_ = local_path_;
return COMPLETE;
}
if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS &&
download_->IsTransient()) {
intermediate_path_ = local_path_;
return COMPLETE;
}
if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) {
intermediate_path_ = GetCrDownloadPath(local_path_);
return COMPLETE;
}
if (is_resumption_ && !download_->GetFullPath().empty() &&
local_path_.DirName() == download_->GetFullPath().DirName()) {
DCHECK_NE(download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
download_->GetDangerType());
DCHECK_EQ(kCrdownloadSuffix, download_->GetFullPath().Extension());
intermediate_path_ = download_->GetFullPath();
return COMPLETE;
}
static constexpr char kUnconfirmedFormatSuffix[] = " %d.crdownload";
constexpr int kUnconfirmedUniquifierRange = 1000000;
std::string file_name =
l10n_util::GetStringUTF8(IDS_DOWNLOAD_UNCONFIRMED_PREFIX) +
base::StringPrintf(kUnconfirmedFormatSuffix,
base::RandInt(0, kUnconfirmedUniquifierRange));
intermediate_path_ =
local_path_.DirName().Append(base::FilePath::FromUTF8Unsafe(file_name));
return COMPLETE;
}
void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf(
download::DownloadInterruptReason interrupt_reason) {
DCHECK(download_);
DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe()
<< " Local:" << local_path_.AsUTF8Unsafe()
<< " Intermediate:" << intermediate_path_.AsUTF8Unsafe()
<< " Confirmation reason:" << static_cast<int>(confirmation_reason_)
<< " Danger type:" << danger_type_
<< " Danger level:" << danger_level_
<< " Interrupt reason:" << static_cast<int>(interrupt_reason);
download::DownloadTargetInfo target_info;
target_info.target_path = local_path_;
target_info.intermediate_path = intermediate_path_;
#if BUILDFLAG(IS_ANDROID)
if (local_path_.IsContentUri() && !virtual_path_.IsContentUri()) {
target_info.display_name = virtual_path_.BaseName();
} else if (download_->GetDownloadFile() &&
download_->GetDownloadFile()->IsMemoryFile()) {
target_info.display_name = GenerateFileName();
}
#endif
target_info.mime_type = mime_type_;
#if BUILDFLAG(IS_MAC)
target_info.file_tags = file_tags_;
#endif
target_info.is_filetype_handled_safely = is_filetype_handled_safely_;
target_info.target_disposition =
(HasPromptedForPath() ||
confirmation_reason_ != DownloadConfirmationReason::NONE
? DownloadItem::TARGET_DISPOSITION_PROMPT
: DownloadItem::TARGET_DISPOSITION_OVERWRITE);
target_info.danger_type = danger_type_;
target_info.interrupt_reason = interrupt_reason;
target_info.insecure_download_status = insecure_download_status_;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(completion_callback_),
std::move(target_info), danger_level_));
delete this;
}
Profile* DownloadTargetDeterminer::GetProfile() const {
DCHECK(content::DownloadItemUtils::GetBrowserContext(download_));
return Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(download_));
}
DownloadConfirmationReason DownloadTargetDeterminer::NeedsConfirmation(
const base::FilePath& filename) const {
if (download_->IsTransient())
return DownloadConfirmationReason::NONE;
if (is_resumption_) {
download::DownloadInterruptReason reason = download_->GetLastReason();
switch (reason) {
case download::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
return DownloadConfirmationReason::TARGET_PATH_NOT_WRITEABLE;
case download::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
case download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
return DownloadConfirmationReason::TARGET_NO_SPACE;
default:
return DownloadConfirmationReason::NONE;
}
}
if (!download_->GetForcedFilePath().empty()) {
DCHECK(DownloadItem::TARGET_DISPOSITION_PROMPT !=
download_->GetTargetDisposition());
return DownloadConfirmationReason::NONE;
}
bool isDefaultPathDlpBlocked =
IsDownloadDlpBlocked(download_prefs_->DownloadPath());
if (download_prefs_->IsDownloadPathManaged() && !isDefaultPathDlpBlocked)
return DownloadConfirmationReason::NONE;
if (download_->GetTargetDisposition() ==
DownloadItem::TARGET_DISPOSITION_PROMPT)
return DownloadConfirmationReason::SAVE_AS;
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
if (download_crx_util::IsTrustedExtensionDownload(GetProfile(), *download_))
return DownloadConfirmationReason::NONE;
#endif
if (download_prefs_->IsAutoOpenEnabled(download_->GetURL(), filename))
return DownloadConfirmationReason::NONE;
if (download_prefs_->PromptForDownload()) {
return DownloadConfirmationReason::PREFERENCE;
} else {
return isDefaultPathDlpBlocked ? DownloadConfirmationReason::DLP_BLOCKED
: DownloadConfirmationReason::NONE;
}
}
bool DownloadTargetDeterminer::IsDownloadDlpBlocked(
const base::FilePath& download_path) const {
#if BUILDFLAG(IS_CHROMEOS)
auto* web_contents =
download_ ? content::DownloadItemUtils::GetWebContents(download_)
: nullptr;
if (!web_contents)
return false;
policy::DlpRulesManager* rules_manager =
policy::DlpRulesManagerFactory::GetForPrimaryProfile();
if (!rules_manager)
return false;
policy::DlpFilesControllerAsh* files_controller =
static_cast<policy::DlpFilesControllerAsh*>(
rules_manager->GetDlpFilesController());
if (!files_controller)
return false;
const GURL authority_url = download::BaseFile::GetEffectiveAuthorityURL(
download_->GetURL(), download_->GetReferrerUrl());
if (!authority_url.is_valid()) {
return true;
}
return files_controller->ShouldPromptBeforeDownload(
policy::DlpFileDestination(authority_url), download_path);
#else
return false;
#endif
}
bool DownloadTargetDeterminer::HasPromptedForPath() const {
return (is_resumption_ && download_->GetTargetDisposition() ==
DownloadItem::TARGET_DISPOSITION_PROMPT);
}
DownloadFileType::DangerLevel DownloadTargetDeterminer::GetDangerLevel(
PriorVisitsToReferrer visits) const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (download_->HasUserGesture() &&
download_crx_util::IsTrustedExtensionDownload(GetProfile(), *download_)) {
return DownloadFileType::NOT_DANGEROUS;
}
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
DownloadFileType::DangerLevel danger_level =
safe_browsing::FileTypePolicies::GetInstance()->GetFileDangerLevel(
virtual_path_.BaseName(), download_->GetURL(),
GetProfile()->GetPrefs());
policy::DownloadRestriction download_restriction =
static_cast<policy::DownloadRestriction>(
GetProfile()->GetPrefs()->GetInteger(
policy::policy_prefs::kDownloadRestrictions));
bool user_approved_path =
!download_->GetForcedFilePath().empty() &&
download_->GetDownloadSource() != download::DownloadSource::DRAG_AND_DROP;
if (HasPromptedForPath() ||
confirmation_reason_ != DownloadConfirmationReason::NONE ||
user_approved_path) {
if ((download_restriction == policy::DownloadRestriction::DANGEROUS_FILES ||
download_restriction ==
policy::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES) &&
danger_level != DownloadFileType::NOT_DANGEROUS) {
return DownloadFileType::DANGEROUS;
}
return DownloadFileType::NOT_DANGEROUS;
}
if (download_prefs_->IsAutoOpenEnabled(download_->GetURL(), virtual_path_) &&
download_->HasUserGesture())
return DownloadFileType::NOT_DANGEROUS;
if (danger_level == DownloadFileType::ALLOW_ON_USER_GESTURE &&
((download_->GetTransitionType() &
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0 ||
(download_->HasUserGesture() && visits == VISITED_REFERRER)))
return DownloadFileType::NOT_DANGEROUS;
return danger_level;
#else
return DownloadFileType::NOT_DANGEROUS;
#endif
}
std::optional<base::Time>
DownloadTargetDeterminer::GetLastDownloadBypassTimestamp() const {
safe_browsing::SafeBrowsingMetricsCollector* metrics_collector =
safe_browsing::SafeBrowsingMetricsCollectorFactory::GetForProfile(
GetProfile());
return metrics_collector ? metrics_collector->GetLatestEventTimestamp(
safe_browsing::SafeBrowsingMetricsCollector::
EventType::DANGEROUS_DOWNLOAD_BYPASS)
: std::nullopt;
}
void DownloadTargetDeterminer::OnDownloadDestroyed(
DownloadItem* download) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(download_, download);
ScheduleCallbackAndDeleteSelf(
download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
}
void DownloadTargetDeterminer::Start(
download::DownloadItem* download,
const base::FilePath& initial_virtual_path,
DownloadPathReservationTracker::FilenameConflictAction conflict_action,
DownloadPrefs* download_prefs,
DownloadTargetDeterminerDelegate* delegate,
CompletionCallback callback) {
new DownloadTargetDeterminer(download, initial_virtual_path, conflict_action,
download_prefs, delegate, std::move(callback));
}
base::FilePath DownloadTargetDeterminer::GetCrDownloadPath(
const base::FilePath& suggested_path) {
return base::FilePath(suggested_path.value() + kCrdownloadSuffix);
}