Index: chrome/browser/download/download_target_determiner.cc |
diff --git a/chrome/browser/download/download_target_determiner.cc b/chrome/browser/download/download_target_determiner.cc |
index b3841d70bb7eaae3d542a3a9c7f8a35be23c1f14..fd4bb399b8233c01790f035356920d7eafcfa7be 100644 |
--- a/chrome/browser/download/download_target_determiner.cc |
+++ b/chrome/browser/download/download_target_determiner.cc |
@@ -4,13 +4,15 @@ |
#include "chrome/browser/download/download_target_determiner.h" |
+#include <string> |
+#include <vector> |
+ |
#include "base/location.h" |
#include "base/rand_util.h" |
#include "base/single_thread_task_runner.h" |
#include "base/strings/stringprintf.h" |
#include "base/threading/thread_task_runner_handle.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_crx_util.h" |
#include "chrome/browser/download/download_prefs.h" |
@@ -41,11 +43,6 @@ |
#include "content/public/common/webplugininfo.h" |
#endif |
-#if defined(OS_ANDROID) |
-#include "chrome/browser/android/download/download_controller.h" |
-#include "chrome/browser/android/download/download_manager_service.h" |
-#endif |
- |
#if defined(OS_WIN) |
#include "chrome/browser/ui/pdf/adobe_reader_info_win.h" |
#endif |
@@ -80,32 +77,26 @@ bool g_is_adobe_reader_up_to_date_ = false; |
} // namespace |
-DownloadTargetInfo::DownloadTargetInfo() |
- : target_disposition(DownloadItem::TARGET_DISPOSITION_OVERWRITE), |
- danger_type(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), |
- danger_level(DownloadFileType::NOT_DANGEROUS), |
- is_filetype_handled_safely(false) {} |
- |
-DownloadTargetInfo::~DownloadTargetInfo() {} |
- |
DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() { |
} |
DownloadTargetDeterminer::DownloadTargetDeterminer( |
DownloadItem* download, |
const base::FilePath& initial_virtual_path, |
+ DownloadPathReservationTracker::FilenameConflictAction conflict_action, |
DownloadPrefs* download_prefs, |
DownloadTargetDeterminerDelegate* delegate, |
const CompletionCallback& callback) |
: next_state_(STATE_GENERATE_TARGET_PATH), |
- should_prompt_(false), |
+ confirmation_reason_(DownloadConfirmationReason::NONE), |
should_notify_extensions_(false), |
create_target_directory_(false), |
- conflict_action_(DownloadPathReservationTracker::OVERWRITE), |
+ conflict_action_(conflict_action), |
danger_type_(download->GetDangerType()), |
danger_level_(DownloadFileType::NOT_DANGEROUS), |
virtual_path_(initial_virtual_path), |
is_filetype_handled_safely_(false), |
+ result_(DownloadTargetResult::SUCCESS), |
download_(download), |
is_resumption_(download_->GetLastReason() != |
content::DOWNLOAD_INTERRUPT_REASON_NONE && |
@@ -146,7 +137,7 @@ void DownloadTargetDeterminer::DoLoop() { |
result = DoReserveVirtualPath(); |
break; |
case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH: |
- result = DoPromptUserForDownloadPath(); |
+ result = DoRequestConfirmation(); |
break; |
case STATE_DETERMINE_LOCAL_PATH: |
result = DoDetermineLocalPath(); |
@@ -179,16 +170,15 @@ void DownloadTargetDeterminer::DoLoop() { |
// determination and delete |this|. |
if (result == COMPLETE) |
- ScheduleCallbackAndDeleteSelf(); |
+ ScheduleCallbackAndDeleteSelf(result_); |
} |
DownloadTargetDeterminer::Result |
DownloadTargetDeterminer::DoGenerateTargetPath() { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
DCHECK(local_path_.empty()); |
- DCHECK(!should_prompt_); |
+ DCHECK_EQ(confirmation_reason_, DownloadConfirmationReason::NONE); |
DCHECK(!should_notify_extensions_); |
- DCHECK_EQ(DownloadPathReservationTracker::OVERWRITE, conflict_action_); |
bool is_forced_path = !download_->GetForcedFilePath().empty(); |
next_state_ = STATE_NOTIFY_EXTENSIONS; |
@@ -197,7 +187,8 @@ DownloadTargetDeterminer::Result |
// The download is being resumed and the user has already been prompted for |
// a path. Assume that it's okay to overwrite the file if there's a conflict |
// and reuse the selection. |
- should_prompt_ = ShouldPromptForDownload(virtual_path_); |
+ confirmation_reason_ = ShouldPromptForDownload(virtual_path_); |
+ conflict_action_ = DownloadPathReservationTracker::OVERWRITE; |
} else if (!is_forced_path) { |
// If we don't have a forced path, we should construct a path for the |
// download. Forced paths are only specified for programmatic downloads |
@@ -220,9 +211,9 @@ DownloadTargetDeterminer::Result |
suggested_filename, |
download_->GetMimeType(), |
default_filename); |
- should_prompt_ = ShouldPromptForDownload(generated_filename); |
+ confirmation_reason_ = ShouldPromptForDownload(generated_filename); |
base::FilePath target_directory; |
- if (should_prompt_) { |
+ if (confirmation_reason_ != DownloadConfirmationReason::NONE) { |
DCHECK(!download_prefs_->IsDownloadPathManaged()); |
// If the user is going to be prompted and the user has been prompted |
// before, then always prefer the last directory that the user selected. |
@@ -231,13 +222,9 @@ DownloadTargetDeterminer::Result |
target_directory = download_prefs_->DownloadPath(); |
} |
virtual_path_ = target_directory.Append(generated_filename); |
-#if defined(OS_ANDROID) |
- conflict_action_ = DownloadPathReservationTracker::PROMPT; |
-#else |
- conflict_action_ = DownloadPathReservationTracker::UNIQUIFY; |
-#endif |
should_notify_extensions_ = true; |
} else { |
+ conflict_action_ = DownloadPathReservationTracker::OVERWRITE; |
virtual_path_ = download_->GetForcedFilePath(); |
// If this is a resumed download which was previously interrupted due to an |
// issue with the forced path, the user is still not prompted. If the path |
@@ -317,39 +304,44 @@ DownloadTargetDeterminer::Result |
} |
void DownloadTargetDeterminer::ReserveVirtualPathDone( |
- const base::FilePath& path, bool verified) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ const base::FilePath& path, |
+ DownloadTargetResult result) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DVLOG(20) << "Reserved path: " << path.AsUTF8Unsafe() |
- << " Verified:" << verified; |
+ << " Result:" << static_cast<int>(result); |
DCHECK_EQ(STATE_PROMPT_USER_FOR_DOWNLOAD_PATH, next_state_); |
-#if BUILDFLAG(ANDROID_JAVA_UI) |
- if (!verified) { |
- if (path.empty()) { |
- DownloadManagerService::OnDownloadCanceled( |
- download_, DownloadController::CANCEL_REASON_NO_EXTERNAL_STORAGE); |
- CancelOnFailureAndDeleteSelf(); |
- return; |
- } |
- if (!download_->GetWebContents()) { |
- // If we cannot reserve the path and the WebContent is already gone, there |
- // is no way to prompt user for an infobar. This could happen after chrome |
- // gets killed, and user tries to resume a download while another app has |
- // created the target file (not the temporary .crdownload file). |
- DownloadManagerService::OnDownloadCanceled( |
- download_, |
- DownloadController::CANCEL_REASON_CANNOT_DETERMINE_DOWNLOAD_TARGET); |
- CancelOnFailureAndDeleteSelf(); |
- return; |
- } |
- } |
-#endif |
- should_prompt_ = (should_prompt_ || !verified); |
+ |
virtual_path_ = path; |
+ result_ = result; |
+ |
+ switch (result) { |
+ case DownloadTargetResult::SUCCESS: |
+ break; |
+ |
+ case DownloadTargetResult::PATH_NOT_WRITEABLE: |
+ confirmation_reason_ = DownloadConfirmationReason::TARGET_NOT_WRITEABLE; |
+ break; |
+ |
+ case DownloadTargetResult::NAME_TOO_LONG: |
+ confirmation_reason_ = DownloadConfirmationReason::NAME_TOO_LONG; |
+ break; |
+ |
+ case DownloadTargetResult::CONFLICT: |
+ confirmation_reason_ = DownloadConfirmationReason::TARGET_CONFLICT; |
+ break; |
+ |
+ case DownloadTargetResult::USER_CANCELED: |
+ case DownloadTargetResult::UNEXPECTED: |
+ // These are not considered recoverable errors. The download needs to be |
+ // interrupted. |
+ break; |
+ } |
+ |
DoLoop(); |
} |
DownloadTargetDeterminer::Result |
- DownloadTargetDeterminer::DoPromptUserForDownloadPath() { |
+DownloadTargetDeterminer::DoRequestConfirmation() { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
DCHECK(!virtual_path_.empty()); |
@@ -357,27 +349,36 @@ DownloadTargetDeterminer::Result |
// Avoid prompting for a download if it isn't in-progress. The user will be |
// prompted once the download is resumed and headers are available. |
- if (should_prompt_ && download_->GetState() == DownloadItem::IN_PROGRESS) { |
- delegate_->PromptUserForDownloadPath( |
- download_, |
- virtual_path_, |
- base::Bind(&DownloadTargetDeterminer::PromptUserForDownloadPathDone, |
+ if (confirmation_reason_ != DownloadConfirmationReason::NONE && |
+ download_->GetState() == DownloadItem::IN_PROGRESS) { |
+ delegate_->RequestConfirmation( |
+ download_, virtual_path_, confirmation_reason_, |
+ base::Bind(&DownloadTargetDeterminer::RequestConfirmationDone, |
weak_ptr_factory_.GetWeakPtr())); |
return QUIT_DOLOOP; |
} |
return CONTINUE; |
} |
-void DownloadTargetDeterminer::PromptUserForDownloadPathDone( |
+void DownloadTargetDeterminer::RequestConfirmationDone( |
+ DownloadConfirmationResult result, |
const base::FilePath& virtual_path) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
DVLOG(20) << "User selected path:" << virtual_path.AsUTF8Unsafe(); |
- if (virtual_path.empty()) { |
- CancelOnFailureAndDeleteSelf(); |
+ if (result == DownloadConfirmationResult::CANCELED) { |
+ ScheduleCallbackAndDeleteSelf(DownloadTargetResult::USER_CANCELED); |
return; |
} |
+ DCHECK(!virtual_path.empty()); |
DCHECK_EQ(STATE_DETERMINE_LOCAL_PATH, next_state_); |
+ // If the user wasn't prompted, then we need to clear the |
+ // confirmation_reason_. This way it's clear that user has not given consent |
+ // to download this resource. |
+ if (result == DownloadConfirmationResult::CONTINUE_WITHOUT_CONFIRMATION) |
+ confirmation_reason_ = DownloadConfirmationReason::NONE; |
+ |
+ result_ = DownloadTargetResult::SUCCESS; |
virtual_path_ = virtual_path; |
download_prefs_->SetSaveFilePath(virtual_path_.DirName()); |
DoLoop(); |
@@ -404,8 +405,11 @@ void DownloadTargetDeterminer::DetermineLocalPathDone( |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe(); |
if (local_path.empty()) { |
- // Path subsitution failed. |
- CancelOnFailureAndDeleteSelf(); |
+ // Path subsitution failed. Usually caused by something going wrong with the |
+ // Google Drive logic (e.g. filesystem error while trying to create the |
+ // cache file). We are going to return a generic error here since a more |
+ // specific one is unlikely to be helpful to the user. |
+ ScheduleCallbackAndDeleteSelf(DownloadTargetResult::UNEXPECTED); |
return; |
} |
DCHECK_EQ(STATE_DETERMINE_MIME_TYPE, next_state_); |
@@ -750,19 +754,23 @@ DownloadTargetDeterminer::Result |
return COMPLETE; |
} |
-void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { |
+void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf( |
+ DownloadTargetResult result) { |
DCHECK(download_); |
DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe() |
<< " Local:" << local_path_.AsUTF8Unsafe() |
<< " Intermediate:" << intermediate_path_.AsUTF8Unsafe() |
- << " Should prompt:" << should_prompt_ |
+ << " Confirmation reason:" << static_cast<int>(confirmation_reason_) |
<< " Danger type:" << danger_type_ |
- << " Danger level:" << danger_level_; |
+ << " Danger level:" << danger_level_ |
+ << " Result:" << static_cast<int>(result); |
std::unique_ptr<DownloadTargetInfo> target_info(new DownloadTargetInfo); |
target_info->target_path = local_path_; |
+ target_info->result = result; |
target_info->target_disposition = |
- (HasPromptedForPath() || should_prompt_ |
+ (HasPromptedForPath() || |
+ confirmation_reason_ != DownloadConfirmationReason::NONE |
? DownloadItem::TARGET_DISPOSITION_PROMPT |
: DownloadItem::TARGET_DISPOSITION_OVERWRITE); |
target_info->danger_type = danger_type_; |
@@ -777,35 +785,30 @@ void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { |
delete this; |
} |
-void DownloadTargetDeterminer::CancelOnFailureAndDeleteSelf() { |
- // Path substitution failed. |
- virtual_path_.clear(); |
- local_path_.clear(); |
- intermediate_path_.clear(); |
- ScheduleCallbackAndDeleteSelf(); |
-} |
- |
Profile* DownloadTargetDeterminer::GetProfile() const { |
DCHECK(download_->GetBrowserContext()); |
return Profile::FromBrowserContext(download_->GetBrowserContext()); |
} |
-bool DownloadTargetDeterminer::ShouldPromptForDownload( |
+DownloadConfirmationReason DownloadTargetDeterminer::ShouldPromptForDownload( |
const base::FilePath& filename) const { |
-#if BUILDFLAG(ANDROID_JAVA_UI) |
- // Don't prompt user about saving path on Android. |
- // TODO(qinmin): show an error toast to warn user in certain cases. |
- return false; |
-#endif |
if (is_resumption_) { |
// For resumed downloads, if the target disposition or prefs require |
// prompting, the user has already been prompted. Try to respect the user's |
// selection, unless we've discovered that the target path cannot be used |
// for some reason. |
content::DownloadInterruptReason reason = download_->GetLastReason(); |
- return (reason == content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED || |
- reason == content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE || |
- reason == content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE); |
+ switch (reason) { |
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED: |
+ return DownloadConfirmationReason::TARGET_NOT_WRITEABLE; |
+ |
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE: |
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: |
+ return DownloadConfirmationReason::TARGET_NO_SPACE; |
+ |
+ default: |
+ return DownloadConfirmationReason::NONE; |
+ } |
} |
// If the download path is forced, don't prompt. |
@@ -813,38 +816,38 @@ bool DownloadTargetDeterminer::ShouldPromptForDownload( |
// 'Save As' downloads shouldn't have a forced path. |
DCHECK(DownloadItem::TARGET_DISPOSITION_PROMPT != |
download_->GetTargetDisposition()); |
- return false; |
+ return DownloadConfirmationReason::NONE; |
} |
// Don't ask where to save if the download path is managed. Even if the user |
// wanted to be prompted for "all" downloads, or if this was a 'Save As' |
// download. |
if (download_prefs_->IsDownloadPathManaged()) |
- return false; |
+ return DownloadConfirmationReason::NONE; |
// Prompt if this is a 'Save As' download. |
if (download_->GetTargetDisposition() == |
DownloadItem::TARGET_DISPOSITION_PROMPT) |
- return true; |
- |
- // Check if the user has the "Always prompt for download location" preference |
- // set. If so we prompt for most downloads except for the following scenarios: |
- // 1) Extension installation. Note that we only care here about the case where |
- // an extension is installed, not when one is downloaded with "save as...". |
- // 2) Filetypes marked "always open." If the user just wants this file opened, |
- // don't bother asking where to keep it. |
- if (download_prefs_->PromptForDownload() && |
- !download_crx_util::IsExtensionDownload(*download_) && |
- !filename.MatchesExtension(extensions::kExtensionFileExtension) && |
- !download_prefs_->IsAutoOpenEnabledBasedOnExtension(filename)) |
- return true; |
- |
- // Otherwise, don't prompt. Note that the user might still be prompted if |
- // there are unresolved conflicts during path reservation (e.g. due to the |
- // target path being unwriteable or because there are too many conflicting |
- // files), or if an extension signals that the user be prompted on a filename |
- // conflict. |
- return false; |
+ return DownloadConfirmationReason::SAVE_AS; |
+ |
+#if defined(ENABLE_EXTENSIONS) |
+ // Don't prompt for extension downloads. |
+ if (download_crx_util::IsExtensionDownload(*download_) || |
+ filename.MatchesExtension(extensions::kExtensionFileExtension)) |
+ return DownloadConfirmationReason::NONE; |
+#endif |
+ |
+ // Don't prompt for file types that are marked for opening automatically. |
+ if (download_prefs_->IsAutoOpenEnabledBasedOnExtension(filename)) |
+ return DownloadConfirmationReason::NONE; |
+ |
+ // For everything else, prompting is controlled by the PromptForDownload pref. |
+ // The user may still be prompted even if this pref is disabled due to, for |
+ // example, there being an unresolvable filename conflict or the target path |
+ // is not writeable. |
+ return download_prefs_->PromptForDownload() |
+ ? DownloadConfirmationReason::PREFERENCE |
+ : DownloadConfirmationReason::NONE; |
} |
bool DownloadTargetDeterminer::HasPromptedForPath() const { |
@@ -859,7 +862,8 @@ DownloadFileType::DangerLevel DownloadTargetDeterminer::GetDangerLevel( |
// If the user has has been prompted or will be, assume that the user has |
// approved the download. A programmatic download is considered safe unless it |
// contains malware. |
- if (HasPromptedForPath() || should_prompt_ || |
+ if (HasPromptedForPath() || |
+ confirmation_reason_ != DownloadConfirmationReason::NONE || |
!download_->GetForcedFilePath().empty()) |
return DownloadFileType::NOT_DANGEROUS; |
@@ -877,8 +881,8 @@ DownloadFileType::DangerLevel DownloadTargetDeterminer::GetDangerLevel( |
#if defined(ENABLE_EXTENSIONS) |
// Extensions that are not from the gallery are considered dangerous. |
- // When off-store install is disabled we skip this, since in this case, we |
- // will not offer to install the extension. |
+ // Exception: If off-store install is disabled, then extension downloads are |
+ // not considered dangerous since we will not offer to install these. |
if (extensions::FeatureSwitch::easy_off_store_install()->IsEnabled() && |
is_extension_download && |
!extensions::WebstoreInstaller::GetAssociatedApproval(*download_)) { |
@@ -920,20 +924,22 @@ void DownloadTargetDeterminer::OnDownloadDestroyed( |
DownloadItem* download) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
DCHECK_EQ(download_, download); |
- CancelOnFailureAndDeleteSelf(); |
+ ScheduleCallbackAndDeleteSelf(DownloadTargetResult::USER_CANCELED); |
} |
// static |
-void DownloadTargetDeterminer::Start(content::DownloadItem* download, |
- const base::FilePath& initial_virtual_path, |
- DownloadPrefs* download_prefs, |
- DownloadTargetDeterminerDelegate* delegate, |
- const CompletionCallback& callback) { |
+void DownloadTargetDeterminer::Start( |
+ content::DownloadItem* download, |
+ const base::FilePath& initial_virtual_path, |
+ DownloadPathReservationTracker::FilenameConflictAction conflict_action, |
+ DownloadPrefs* download_prefs, |
+ DownloadTargetDeterminerDelegate* delegate, |
+ const CompletionCallback& callback) { |
// DownloadTargetDeterminer owns itself and will self destruct when the job is |
// complete or the download item is destroyed. The callback is always invoked |
// asynchronously. |
- new DownloadTargetDeterminer(download, initial_virtual_path, download_prefs, |
- delegate, callback); |
+ new DownloadTargetDeterminer(download, initial_virtual_path, conflict_action, |
+ download_prefs, delegate, callback); |
} |
// static |