Chromium Code Reviews| 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 |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cee48e88f584b5710697535cf81d5a629bcbe5a9 |
| --- /dev/null |
| +++ b/chrome/browser/download/download_target_determiner.cc |
| @@ -0,0 +1,630 @@ |
| +// Copyright 2013 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_target_determiner.h" |
| + |
| +#include "base/prefs/pref_service.h" |
| +#include "base/rand_util.h" |
| +#include "base/stringprintf.h" |
| +#include "base/time.h" |
| +#include "chrome/browser/download/chrome_download_manager_delegate.h" |
| +#include "chrome/browser/download/download_crx_util.h" |
| +#include "chrome/browser/download/download_extensions.h" |
| +#include "chrome/browser/download/download_prefs.h" |
| +#include "chrome/browser/download/download_util.h" |
| +#include "chrome/browser/extensions/api/downloads/downloads_api.h" |
| +#include "chrome/browser/extensions/webstore_installer.h" |
| +#include "chrome/browser/history/history_service.h" |
| +#include "chrome/browser/history/history_service_factory.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/browser/safe_browsing/download_protection_service.h" |
| +#include "chrome/common/extensions/feature_switch.h" |
| +#include "chrome/common/pref_names.h" |
| +#include "content/public/browser/browser_context.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "grit/generated_resources.h" |
| +#include "net/base/net_util.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| + |
| +#if defined(OS_CHROMEOS) |
| +#include "chrome/browser/chromeos/drive/drive_download_handler.h" |
| +#include "chrome/browser/chromeos/drive/drive_file_system_util.h" |
| +#endif |
| + |
| +using content::BrowserThread; |
| +using content::DownloadItem; |
| +using safe_browsing::DownloadProtectionService; |
| + |
| +namespace { |
| + |
| +// Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a |
| +// single bool. A host is considered visited before if prior visible vists were |
| +// found in history and the first such visit was earlier than the most recent |
| +// midnight. |
| +void VisitCountsToVisitedBefore( |
| + const base::Callback<void(bool)>& callback, |
| + HistoryService::Handle unused_handle, |
| + bool found_visits, |
| + int count, |
| + base::Time first_visit) { |
| + callback.Run( |
| + found_visits && count > 0 && |
| + (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); |
| +} |
| + |
| +} // namespace |
| + |
| +DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() { |
| +} |
| + |
| +DownloadTargetDeterminer::DownloadTargetDeterminer( |
| + DownloadItem* download, |
| + DownloadPrefs* download_prefs, |
| + const base::FilePath& last_selected_directory, |
| + DownloadTargetDeterminerDelegate* delegate, |
| + const CompletionCallback& callback) |
| + : next_state_(STATE_GENERATE_TARGET_PATH), |
| + should_prompt_(false), |
| + should_overwrite_(!download->GetForcedFilePath().empty()), |
| + danger_type_(download->GetDangerType()), |
| + download_(download), |
| + download_prefs_(download_prefs), |
| + delegate_(delegate), |
| + last_selected_directory_(last_selected_directory), |
| + completion_callback_(callback), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(download_); |
| + DCHECK(delegate); |
| + download_->AddObserver(this); |
| + |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::~DownloadTargetDeterminer() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(download_); |
| + DCHECK(completion_callback_.is_null()); |
| + download_->RemoveObserver(this); |
| +} |
| + |
| +void DownloadTargetDeterminer::DoLoop() { |
| + Result result = CONTINUE; |
| + |
| + do { |
| + switch (next_state_) { |
| + case STATE_GENERATE_TARGET_PATH: |
| + result = DoGenerateTargetPath(); |
| + break; |
| + case STATE_NOTIFY_EXTENSIONS: |
| + result = DoNotifyExtensions(); |
| + break; |
| + case STATE_RESERVE_VIRTUAL_PATH: |
| + result = DoReserveVirtualPath(); |
| + break; |
| + case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH: |
| + result = DoPromptUserForDownloadPath(); |
| + break; |
| + case STATE_DETERMINE_LOCAL_PATH: |
| + result = DoDetermineLocalPath(); |
| + break; |
| + case STATE_CHECK_DOWNLOAD_URL: |
| + result = DoCheckDownloadUrl(); |
| + break; |
| + case STATE_DETERMINE_DANGER_TYPE: |
| + result = DoDetermineDangerType(); |
| + break; |
| + case STATE_DETERMINE_INTERMEDIATE_PATH: |
| + result = DoDetermineIntermediatePath(); |
| + break; |
| + case STATE_CHECK_VISITED_REFERRER_BEFORE: |
| + result = DoCheckVisitedReferrerBefore(); |
| + break; |
| + case STATE_NONE: |
| + NOTREACHED(); |
| + break; |
| + } |
| + } while (result == CONTINUE && next_state_ != STATE_NONE); |
| + |
| + // Note that if a callback completes synchronously, the handler can still |
| + // return PENDING. In this case, an inner DoLoop() may complete the target |
| + // determination and delete |this|. |
| + if (result == COMPLETE) |
| + ScheduleCallbackAndDeleteSelf(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoGenerateTargetPath() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(virtual_path_.empty()); |
| + DCHECK(local_path_.empty()); |
| + bool is_forced_path = !download_->GetForcedFilePath().empty(); |
| + base::FilePath suggested_path; |
| + |
| + next_state_ = STATE_NOTIFY_EXTENSIONS; |
| + |
| + // If we don't have a forced path, we should construct a path for the |
| + // download. Forced paths are only specified for programmatic downloads |
| + // (WebStore, Drag&Drop). |
| + if (!is_forced_path) { |
| + std::string default_filename( |
| + l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME)); |
| + base::FilePath generated_filename = net::GenerateFileName( |
| + download_->GetURL(), |
| + download_->GetContentDisposition(), |
| + GetProfile()->GetPrefs()->GetString(prefs::kDefaultCharset), |
| + download_->GetSuggestedFilename(), |
| + download_->GetMimeType(), |
| + default_filename); |
| + should_prompt_ = ShouldPromptForDownload(generated_filename); |
| + base::FilePath target_directory; |
| + if (should_prompt_ && !last_selected_directory_.empty()) { |
| + 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. |
| + target_directory = last_selected_directory_; |
| + } else { |
| + target_directory = download_prefs_->DownloadPath(); |
| + } |
| + suggested_path = target_directory.Append(generated_filename); |
| + } else { |
| + DCHECK(!should_prompt_); |
| + suggested_path = download_->GetForcedFilePath(); |
| + } |
| + DCHECK(suggested_path.IsAbsolute()); |
| + |
| + // Treat the suggested_path as a virtual path. We will eventually determine |
| + // whether this is a local path and if not, figure out a local path. |
| + virtual_path_ = suggested_path; |
| + DVLOG(20) << "Generated virtual path: " << virtual_path_.AsUTF8Unsafe(); |
| + |
| + // If the download is DOA, don't bother going any further. This would be the |
| + // case for a download that failed to initialize (e.g. the initial temporary |
| + // file couldn't be created because both the downloads directory and the |
| + // temporary directory are unwriteable). |
| + if (!download_->IsInProgress()) |
| + return COMPLETE; |
|
Randy Smith (Not in Mondays)
2013/04/09 19:32:17
What's the logic for having this only here? This
asanka
2013/04/16 20:34:01
This early return is there so that a download that
Randy Smith (Not in Mondays)
2013/04/26 19:05:46
Makes sense. I don't find the comment clear for t
|
| + return CONTINUE; |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoNotifyExtensions() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!virtual_path_.empty()); |
| + ExtensionDownloadsEventRouter* router = delegate_->GetExtensionEventRouter(); |
| + |
| + next_state_ = STATE_RESERVE_VIRTUAL_PATH; |
| + |
| + // If the target path is forced or if we don't have an extensions event |
| + // router, then proceed with the original path. |
| + if (!download_->GetForcedFilePath().empty() || !router) |
| + return CONTINUE; |
| + |
| + base::Closure original_path_callback = |
| + base::Bind(&DownloadTargetDeterminer::NotifyExtensionsDone, |
| + weak_ptr_factory_.GetWeakPtr(), base::FilePath(), false); |
| + ExtensionDownloadsEventRouter::FilenameChangedCallback override_callback = |
| + base::Bind(&DownloadTargetDeterminer::NotifyExtensionsDone, |
| + weak_ptr_factory_.GetWeakPtr()); |
| + router->OnDeterminingFilename(download_, virtual_path_.BaseName(), |
| + original_path_callback, |
| + override_callback); |
| + return PENDING; |
| +} |
| + |
| +void DownloadTargetDeterminer::NotifyExtensionsDone( |
| + const base::FilePath& suggested_path, |
| + bool should_overwrite) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DVLOG(20) << "Extension suggested path: " << suggested_path.AsUTF8Unsafe(); |
| + |
| + if (!suggested_path.empty()) { |
| + // TODO(asanka, benjhayden): The suggested path may contain path fragments. |
| + // We need to validate each component individually. http://crbug.com/181332. |
| + |
| + // If an extension overrides the filename, then the target directory will be |
| + // forced to download_prefs_->DownloadPath() since extensions cannot place |
| + // downloaded files anywhere except there. This prevents subdirectories from |
| + // accumulating: if an extension is allowed to say that a file should go in |
| + // last_download_path/music/foo.mp3, then last_download_path will accumulate |
| + // the subdirectory /music/ so that the next download may end up in |
| + // Downloads/music/music/music/bar.mp3. |
| + base::FilePath new_path(download_prefs_->DownloadPath().Append( |
| + suggested_path).NormalizePathSeparators()); |
| + // Do not pass a mime type to GenerateSafeFileName so that it does not force |
| + // the filename to have an extension if the (Chrome) extension does not |
| + // suggest it. |
| + net::GenerateSafeFileName(std::string(), false, &new_path); |
| + virtual_path_ = new_path; |
| + |
| + // If |is_forced_path| were true, then extensions would not have been |
| + // consulted, so use |overwrite| instead of |is_forced_path|. This does NOT |
| + // set DownloadItem::GetForcedFilePath()! |
| + should_overwrite_ = should_overwrite; |
| + } |
| + |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoReserveVirtualPath() { |
|
Randy Smith (Not in Mondays)
2013/04/09 19:32:17
What are your conceptual thoughts about reserving
asanka
2013/04/16 20:34:01
Reservation is supposed the check ...:
- Whether t
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!virtual_path_.empty()); |
| + |
| + next_state_ = STATE_PROMPT_USER_FOR_DOWNLOAD_PATH; |
| + |
| + delegate_->ReserveVirtualPath( |
| + download_, virtual_path_, !should_overwrite_, |
| + base::Bind(&DownloadTargetDeterminer::ReserveVirtualPathDone, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + return PENDING; |
| +} |
| + |
| +void DownloadTargetDeterminer::ReserveVirtualPathDone( |
| + const base::FilePath& path, bool verified) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DVLOG(20) << "Reserved path: " << path.AsUTF8Unsafe() |
| + << " Verified:" << verified; |
| + should_prompt_ = (should_prompt_ || !verified); |
| + if (verified) |
| + virtual_path_ = path; |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoPromptUserForDownloadPath() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!virtual_path_.empty()); |
| + |
| + // We also prompt if the download path couldn't be verified. |
|
Randy Smith (Not in Mondays)
2013/04/09 19:32:17
I was a bit confused by this (I thought it might b
asanka
2013/04/16 20:34:01
Removed the comment. It was stale.
|
| + if (should_prompt_) { |
| + // If we are prompting, then delegate_->PromptUserForDownloadPathDone() |
| + // returns both a local and virtual path, so we don't need to determine a |
| + // local path in that case. |
| + next_state_ = STATE_CHECK_DOWNLOAD_URL; |
| + delegate_->PromptUserForDownloadPath( |
| + download_, |
| + virtual_path_, |
| + base::Bind(&DownloadTargetDeterminer::PromptUserForDownloadPathDone, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + return PENDING; |
| + } |
| + |
| + next_state_ = STATE_DETERMINE_LOCAL_PATH; |
| + return CONTINUE; |
| +} |
| + |
| +void DownloadTargetDeterminer::PromptUserForDownloadPathDone( |
| + const base::FilePath& virtual_path, |
| + const base::FilePath& local_path) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DVLOG(20) << "User selected paths (virtual):" << virtual_path.AsUTF8Unsafe() |
| + << " (local):" << local_path.AsUTF8Unsafe(); |
| + if (virtual_path.empty()) { |
| + CancelOnFailureAndDeleteSelf(); |
| + return; |
| + } |
| + virtual_path_ = virtual_path; |
| + local_path_ = local_path; |
| + |
| + DCHECK_EQ(STATE_CHECK_DOWNLOAD_URL, next_state_); |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoDetermineLocalPath() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!virtual_path_.empty()); |
| + DCHECK(local_path_.empty()); |
| + |
| + next_state_ = STATE_CHECK_DOWNLOAD_URL; |
| + |
| + delegate_->DetermineLocalPath( |
| + download_, |
| + virtual_path_, |
| + base::Bind(&DownloadTargetDeterminer::DetermineLocalPathDone, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + return PENDING; |
| +} |
| + |
| +void DownloadTargetDeterminer::DetermineLocalPathDone( |
| + const base::FilePath& local_path) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe(); |
| + local_path_ = local_path; |
| + if (local_path_.empty()) { |
| + // Path subsitution failed. |
| + CancelOnFailureAndDeleteSelf(); |
| + return; |
| + } |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoCheckDownloadUrl() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + next_state_ = STATE_DETERMINE_DANGER_TYPE; |
| + |
| +#if defined(FULL_SAFE_BROWSING) |
|
Randy Smith (Not in Mondays)
2013/04/09 19:32:17
Given that we have to have the ifdef anyway, I won
asanka
2013/04/16 20:34:01
Discussed elsewhere.
|
| + safe_browsing::DownloadProtectionService* service = |
| + delegate_->GetDownloadProtectionService(); |
| + if (service) { |
| + VLOG(2) << __FUNCTION__ << "() Start SB URL check for download = " |
| + << download_->DebugString(false); |
| + service->CheckDownloadUrl( |
| + *download_, |
| + base::Bind( |
| + &DownloadTargetDeterminer::CheckDownloadUrlDone, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + return PENDING; |
| + } |
| +#endif |
| + return CONTINUE; |
| +} |
| + |
| +void DownloadTargetDeterminer::CheckDownloadUrlDone( |
| + DownloadProtectionService::DownloadCheckResult result) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DVLOG(20) << "URL Check Result:" << result; |
| + if (result != DownloadProtectionService::SAFE) |
| + danger_type_ = content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL; |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoDetermineDangerType() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!virtual_path_.empty()); |
| + |
| + next_state_ = STATE_DETERMINE_INTERMEDIATE_PATH; |
| + |
| +#if defined(FULL_SAFE_BROWSING) |
| + // If the download hasn't already been marked dangerous (could be |
| + // DANGEROUS_URL), check if it is a dangerous file. |
| + if (danger_type_ == content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) { |
| + // If this type of files is handled by the enhanced SafeBrowsing download |
| + // protection, mark it as potentially dangerous content until we are done |
| + // with scanning it. |
| + safe_browsing::DownloadProtectionService* service = |
| + delegate_->GetDownloadProtectionService(); |
| + if (service && service->IsSupportedDownload(*download_, virtual_path_)) { |
| + // TODO(noelutz): if the user changes the extension name in the UI to |
| + // something like .exe SafeBrowsing will currently *not* check if the |
| + // download is malicious. |
| + danger_type_ = content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT; |
| + } |
| + } else { |
| + DCHECK_EQ(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, danger_type_); |
| + } |
| +#endif |
| + |
| + if (danger_type_ == content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS && |
| + !should_prompt_ && |
| + download_->GetForcedFilePath().empty()) { |
| + next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE; |
| + } |
| + return CONTINUE; |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoCheckVisitedReferrerBefore() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + next_state_ = STATE_DETERMINE_INTERMEDIATE_PATH; |
| + |
| + // HistoryServiceFactory redirects incognito profiles to on-record profiles. |
| + HistoryService* history_service = HistoryServiceFactory::GetForProfile( |
| + GetProfile(), Profile::EXPLICIT_ACCESS); |
| + |
| + if (history_service && download_->GetReferrerUrl().is_valid()) { |
| + history_service->GetVisibleVisitCountToHost( |
| + download_->GetReferrerUrl(), &history_consumer_, |
| + base::Bind(&VisitCountsToVisitedBefore, base::Bind( |
| + &DownloadTargetDeterminer::CheckVisitedReferrerBeforeDone, |
| + weak_ptr_factory_.GetWeakPtr()))); |
| + return PENDING; |
| + } |
| + |
| + // If the original profile doesn't have a HistoryService or the referrer url |
| + // is invalid, then give up and assume the referrer has not been visited |
| + // before. There's no history for on-record profiles in unit_tests, for |
| + // example. |
| + if (IsDangerousFile(false)) |
|
Randy Smith (Not in Mondays)
2013/04/09 19:32:17
I'm not clear it's worth it, but you could refacto
asanka
2013/04/16 20:34:01
I decided to only ping the history thread if (!IsD
|
| + danger_type_ = content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE; |
| + return CONTINUE; |
| +} |
| + |
| +void DownloadTargetDeterminer::CheckVisitedReferrerBeforeDone( |
| + bool visited_referrer_before) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + if (IsDangerousFile(visited_referrer_before)) |
| + danger_type_ = content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE; |
| + DoLoop(); |
| +} |
| + |
| +DownloadTargetDeterminer::Result |
| + DownloadTargetDeterminer::DoDetermineIntermediatePath() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!virtual_path_.empty()); |
| + DCHECK(!local_path_.empty()); |
| + DCHECK(intermediate_path_.empty()); |
| + |
| + next_state_ = STATE_NONE; |
| + |
| + if (virtual_path_.BaseName() != local_path_.BaseName() || |
| + (danger_type_ == content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS && |
| + !download_->GetForcedFilePath().empty())) { |
| + // If the actual target of the download is a virtual path, then the local |
| + // path is considered to point to a temporary path. A separate intermediate |
| + // path is unnecessary since the local path already serves that purpose. |
| + // |
| + // Also, if the download has a forced path and is safe, then just use the |
| + // target path. |
| + intermediate_path_ = local_path_; |
| + } else if (danger_type_ == content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) { |
| + // If the download is not dangerous, just append .crdownload to the target |
| + // path. |
| + intermediate_path_ = download_util::GetCrDownloadPath(local_path_); |
| + } else { |
| + // If the download is potentially dangerous we create a filename of the form |
| + // 'Unconfirmed <random>.crdownload'. |
| + base::FilePath::StringType file_name; |
| + base::FilePath dir = local_path_.DirName(); |
| +#if defined(OS_WIN) |
| + string16 unconfirmed_prefix = |
| + l10n_util::GetStringUTF16(IDS_DOWNLOAD_UNCONFIRMED_PREFIX); |
| +#else |
| + std::string unconfirmed_prefix = |
| + l10n_util::GetStringUTF8(IDS_DOWNLOAD_UNCONFIRMED_PREFIX); |
| +#endif |
| + base::SStringPrintf( |
| + &file_name, |
| + unconfirmed_prefix.append( |
| + FILE_PATH_LITERAL(" %d.crdownload")).c_str(), |
| + base::RandInt(0, 1000000)); |
| + intermediate_path_ = dir.Append(file_name); |
| + } |
| + |
| + return COMPLETE; |
| +} |
| + |
| +void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { |
| + DCHECK(download_); |
| + DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe() |
| + << " Local:" << local_path_.AsUTF8Unsafe() |
| + << " Intermediate:" << intermediate_path_.AsUTF8Unsafe() |
| + << " Should prompt:" << should_prompt_ |
| + << " Danger type:" << danger_type_; |
| + MessageLoop::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(completion_callback_, |
| + virtual_path_, |
| + local_path_, |
| + intermediate_path_, |
| + (should_prompt_ ? DownloadItem::TARGET_DISPOSITION_PROMPT : |
| + DownloadItem::TARGET_DISPOSITION_OVERWRITE), |
| + danger_type_)); |
| + completion_callback_.Reset(); |
| + delete this; |
| +} |
| + |
| +void DownloadTargetDeterminer::CancelOnFailureAndDeleteSelf() { |
| + // Path substitution failed. |
| + virtual_path_.clear(); |
| + local_path_.clear(); |
| + intermediate_path_.clear(); |
| + ScheduleCallbackAndDeleteSelf(); |
| +} |
| + |
| +Profile* DownloadTargetDeterminer::GetProfile() { |
| + DCHECK(download_->GetBrowserContext()); |
| + return Profile::FromBrowserContext(download_->GetBrowserContext()); |
| +} |
| + |
| +bool DownloadTargetDeterminer::ShouldPromptForDownload( |
| + const base::FilePath& filename) { |
| + // If the download path is forced, don't prompt. |
| + if (!download_->GetForcedFilePath().empty()) { |
| + // 'Save As' downloads shouldn't have a forced path. |
| + DCHECK_NE(DownloadItem::TARGET_DISPOSITION_PROMPT, |
| + download_->GetTargetDisposition()); |
| + return false; |
| + } |
| + |
| + // 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; |
| + |
| + // 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_) && |
| + !extensions::Extension::IsExtension(filename) && |
| + !download_prefs_->IsAutoOpenEnabledBasedOnExtension(filename)) |
| + return true; |
| + |
| + // Otherwise, don't prompt. |
| + return false; |
| +} |
| + |
| +bool DownloadTargetDeterminer::IsDangerousFile(bool visited_referrer_before) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + const bool is_extension_download = |
| + download_crx_util::IsExtensionDownload(*download_); |
| + |
| + // User-initiated extension downloads from pref-whitelisted sources are not |
| + // considered dangerous. |
| + if (download_->HasUserGesture() && |
| + is_extension_download && |
| + download_crx_util::OffStoreInstallAllowedByPrefs( |
| + GetProfile(), *download_)) { |
| + return false; |
| + } |
| + |
| + // 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. |
| + if (extensions::FeatureSwitch::easy_off_store_install()->IsEnabled() && |
| + is_extension_download && |
| + !extensions::WebstoreInstaller::GetAssociatedApproval(*download_)) { |
| + return true; |
| + } |
| + |
| + // Anything the user has marked auto-open is OK if it's user-initiated. |
| + if (download_prefs_->IsAutoOpenEnabledBasedOnExtension(virtual_path_) && |
| + download_->HasUserGesture()) |
| + return false; |
| + |
| + switch (download_util::GetFileDangerLevel(virtual_path_.BaseName())) { |
| + case download_util::NotDangerous: |
| + return false; |
| + |
| + case download_util::AllowOnUserGesture: |
| + // "Allow on user gesture" is OK when we have a user gesture and the |
| + // hosting page has been visited before today. |
| + if (download_->GetTransitionType() & |
| + content::PAGE_TRANSITION_FROM_ADDRESS_BAR) { |
| + return false; |
| + } |
| + return !download_->HasUserGesture() || !visited_referrer_before; |
| + |
| + case download_util::Dangerous: |
| + return true; |
| + } |
| + NOTREACHED(); |
| + return false; |
| +} |
| + |
| +void DownloadTargetDeterminer::OnDownloadDestroyed( |
| + DownloadItem* download) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK_EQ(download_, download); |
| + CancelOnFailureAndDeleteSelf(); |
| +} |
| + |
| +// static |
| +void DownloadTargetDeterminer::Start( |
| + content::DownloadItem* download, |
| + DownloadPrefs* download_prefs, |
| + const base::FilePath& last_selected_directory, |
| + 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, download_prefs, |
| + last_selected_directory, delegate, callback); |
| +} |