diff options
author | asanka@chromium.org <asanka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-02 14:57:06 +0000 |
---|---|---|
committer | asanka@chromium.org <asanka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-02 14:57:06 +0000 |
commit | 500057e7dc62bc041d248c03e10e73165d044e3e (patch) | |
tree | 48a3d7d69852fb875ef1ba1b91772dff10cb2616 /chrome/browser/download/download_target_determiner_unittest.cc | |
parent | 1b6a9c5ea17267260ca4336db9037f762eb8dbf5 (diff) | |
download | chromium_src-500057e7dc62bc041d248c03e10e73165d044e3e.zip chromium_src-500057e7dc62bc041d248c03e10e73165d044e3e.tar.gz chromium_src-500057e7dc62bc041d248c03e10e73165d044e3e.tar.bz2 |
Move download filename determination into a separate class.
[Relanding. It was reverted due to a memory leak.]
* Extracts download filename determination into
DownloadTargetDeterminer. The new class maintains state and observes
the download. Doing so eliminates the need to pass state around as
bound arguments.
* Guarantees that the completion callback to
DownloadManagerDelegate::DetermineDownloadTarget is always invoked,
even if the download is interrupted on initialization. This is
required for reliably resuming downloads.
* The DownloadFilePicker always returns the virtual path for downloads
to Drive. ChromeDownloadManagerDelegate can use it to keep track of
the correct last selected directory for 'Save As' downloads.
* Since no path subsubstituion is necessary during prompting,
DownloadFilePickerChromeOS is no longer necessary.
* Re-orders the sequence of events so that the user is prompted as early
as possible. Expensive history database lookups won't introduce
unnecssary jank.
* History database lookups are only performed if the results of the
lookup are necessary.
* Downloads to Drive don't need the temporary local path to be
determined twice when the default downloads directory is Drive.
BUG=151618
BUG=104335
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=197518
Reverted: https://src.chromium.org/viewvc/chrome?view=rev&revision=197578
Review URL: https://chromiumcodereview.appspot.com/14773004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197920 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/download/download_target_determiner_unittest.cc')
-rw-r--r-- | chrome/browser/download/download_target_determiner_unittest.cc | 1479 |
1 files changed, 1479 insertions, 0 deletions
diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc new file mode 100644 index 0000000..ecffb69 --- /dev/null +++ b/chrome/browser/download/download_target_determiner_unittest.cc @@ -0,0 +1,1479 @@ +// 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 "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop.h" +#include "base/observer_list.h" +#include "base/prefs/pref_service.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/value_conversions.h" +#include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/browser/download/download_extensions.h" +#include "chrome/browser/download/download_prefs.h" +#include "chrome/browser/download/download_target_determiner.h" +#include "chrome/browser/download/download_util.h" +#include "chrome/browser/history/history_service.h" +#include "chrome/browser/history/history_service_factory.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_pref_service_syncable.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/test/mock_download_item.h" +#include "content/public/test/test_browser_thread.h" +#include "content/public/test/test_renderer_host.h" +#include "content/public/test/web_contents_tester.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AnyNumber; +using ::testing::Invoke; +using ::testing::Ref; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::ReturnRefOfCopy; +using ::testing::Truly; +using ::testing::WithArg; +using ::testing::_; +using content::DownloadItem; + +namespace { + +// No-op delegate. +class NullWebContentsDelegate : public content::WebContentsDelegate { + public: + NullWebContentsDelegate() {} + virtual ~NullWebContentsDelegate() {} +}; + +// Google Mock action that posts a task to the current message loop that invokes +// the first argument of the mocked method as a callback. Said argument must be +// a base::Callback<void(ParamType)>. |result| must be of |ParamType| and is +// bound as that parameter. +// Example: +// class FooClass { +// public: +// virtual void Foo(base::Callback<void(bool)> callback); +// }; +// ... +// EXPECT_CALL(mock_fooclass_instance, Foo(callback)) +// .WillOnce(ScheduleCallback(false)); +ACTION_P(ScheduleCallback, result0) { + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(arg0, result0)); +} + +// Similar to ScheduleCallback, but binds 2 arguments. +ACTION_P2(ScheduleCallback2, result0, result1) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(arg0, result0, result1)); +} + +// Used with DownloadTestCase. Indicates the type of test case. The expectations +// for the test is set based on the type. +enum TestCaseType { + SAVE_AS, + AUTOMATIC, + FORCED // Requires that forced_file_path be non-empty. +}; + +// Used with DownloadTestCase. Type of intermediate filename to expect. +enum TestCaseExpectIntermediate { + EXPECT_CRDOWNLOAD, // Expect path/to/target.crdownload. + EXPECT_UNCONFIRMED, // Expect path/to/Unconfirmed xxx.crdownload. + EXPECT_LOCAL_PATH, // Expect target path. +}; + +// Typical download test case. Used with +// DownloadTargetDeterminerTest::RunTestCase(). +struct DownloadTestCase { + // Type of test. + TestCaseType test_type; + + // Expected danger type. Verified at the end of target determination. + content::DownloadDangerType expected_danger_type; + + // Value of DownloadItem::GetURL() + const char* url; + + // Value of DownloadItem::GetMimeType() + const char* mime_type; + + // Should be non-empty if |test_type| == FORCED. Value of GetForcedFilePath(). + const base::FilePath::CharType* forced_file_path; + + // Expected virtual path. Specified relative to the virtual download path. If + // empty, assumed to be the same as |expected_local_path|. + const base::FilePath::CharType* expected_virtual_path; + + // Expected local path. Specified relative to the test download path. + const base::FilePath::CharType* expected_local_path; + + // Expected target disposition. If this is TARGET_DISPOSITION_PROMPT, then the + // test run will expect ChromeDownloadManagerDelegate to prompt the user for a + // download location. + DownloadItem::TargetDisposition expected_disposition; + + // Type of intermediate path to expect. + TestCaseExpectIntermediate expected_intermediate; +}; + +class MockDownloadTargetDeterminerDelegate : + public DownloadTargetDeterminerDelegate { + public: + MOCK_METHOD3(CheckDownloadUrl, + void(content::DownloadItem*, const base::FilePath&, + const CheckDownloadUrlCallback&)); + MOCK_METHOD3(NotifyExtensions, + void(content::DownloadItem*, const base::FilePath&, + const NotifyExtensionsCallback&)); + MOCK_METHOD3(PromptUserForDownloadPath, + void(content::DownloadItem*, const base::FilePath&, + const FileSelectedCallback&)); + MOCK_METHOD3(DetermineLocalPath, + void(DownloadItem*, const base::FilePath&, + const LocalPathCallback&)); + MOCK_METHOD4(ReserveVirtualPath, + void(DownloadItem*, const base::FilePath&, + DownloadPathReservationTracker::FilenameConflictAction, + const ReservedPathCallback&)); + + void SetupDefaults() { + ON_CALL(*this, CheckDownloadUrl(_, _, _)) + .WillByDefault(WithArg<2>( + ScheduleCallback(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS))); + ON_CALL(*this, NotifyExtensions(_, _, _)) + .WillByDefault(WithArg<2>( + ScheduleCallback2(base::FilePath(), + DownloadPathReservationTracker::UNIQUIFY))); + ON_CALL(*this, ReserveVirtualPath(_, _, _, _)) + .WillByDefault(Invoke( + &MockDownloadTargetDeterminerDelegate::NullReserveVirtualPath)); + ON_CALL(*this, PromptUserForDownloadPath(_, _, _)) + .WillByDefault(Invoke( + &MockDownloadTargetDeterminerDelegate::NullPromptUser)); + ON_CALL(*this, DetermineLocalPath(_, _, _)) + .WillByDefault(Invoke( + &MockDownloadTargetDeterminerDelegate::NullDetermineLocalPath)); + } + private: + static void NullReserveVirtualPath( + DownloadItem* download, + const base::FilePath& virtual_path, + DownloadPathReservationTracker::FilenameConflictAction conflict_action, + const DownloadTargetDeterminerDelegate::ReservedPathCallback& callback); + static void NullPromptUser( + DownloadItem* download, const base::FilePath& suggested_path, + const FileSelectedCallback& callback); + static void NullDetermineLocalPath( + DownloadItem* download, const base::FilePath& virtual_path, + const LocalPathCallback& callback); +}; + +class DownloadTargetDeterminerTest : public ChromeRenderViewHostTestHarness { + public: + DownloadTargetDeterminerTest(); + + // ::testing::Test + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + // Creates MockDownloadItem and sets up default expectations. + content::MockDownloadItem* CreateActiveDownloadItem( + int32 id, + const DownloadTestCase& test_case); + + // Sets the AutoOpenBasedOnExtension user preference for |path|. + void EnableAutoOpenBasedOnExtension(const base::FilePath& path); + + // Set the kDownloadDefaultDirectory user preference to |path|. + void SetDefaultDownloadPath(const base::FilePath& path); + + // Set the kDownloadDefaultDirectory managed preference to |path|. + void SetManagedDownloadPath(const base::FilePath& path); + + // Set the kPromptForDownload user preference to |prompt|. + void SetPromptForDownload(bool prompt); + + // Given the relative path |path|, returns the full path under the temporary + // downloads directory. + base::FilePath GetPathInDownloadDir(const base::FilePath::StringType& path); + + // Run |test_case| using |item|. + void RunTestCase(const DownloadTestCase& test_case, + content::MockDownloadItem* item); + + // Run through |test_case_count| tests in |test_cases|. A new MockDownloadItem + // will be created for each test case and destroyed when the test case is + // complete. + void RunTestCasesWithActiveItem(const DownloadTestCase test_cases[], + size_t test_case_count); + + const base::FilePath& test_download_dir() const { + return test_download_dir_.path(); + } + + const base::FilePath& test_virtual_dir() const { + return test_virtual_dir_; + } + + MockDownloadTargetDeterminerDelegate* delegate() { + return &delegate_; + } + + DownloadPrefs* download_prefs() { + return download_prefs_.get(); + } + + void set_last_selected_directory(const base::FilePath& path) { + last_selected_directory_ = path; + } + + private: + // Verifies that |target_path|, |disposition|, |expected_danger_type| and + // |intermediate_path| matches the expectations of |test_case|. Posts + // |closure| to the current message loop when done. + void DownloadTargetVerifier(const base::Closure& closure, + const DownloadTestCase& test_case, + const base::FilePath& local_path, + DownloadItem::TargetDisposition disposition, + content::DownloadDangerType danger_type, + const base::FilePath& intermediate_path); + + scoped_ptr<DownloadPrefs> download_prefs_; + ::testing::NiceMock<MockDownloadTargetDeterminerDelegate> delegate_; + NullWebContentsDelegate web_contents_delegate_; + base::ScopedTempDir test_download_dir_; + base::FilePath test_virtual_dir_; + base::FilePath last_selected_directory_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; +}; + +DownloadTargetDeterminerTest::DownloadTargetDeterminerTest() + : ChromeRenderViewHostTestHarness(), + ui_thread_(content::BrowserThread::UI, &message_loop_), + file_thread_(content::BrowserThread::FILE, &message_loop_) { +} + +void DownloadTargetDeterminerTest::SetUp() { + ChromeRenderViewHostTestHarness::SetUp(); + CHECK(profile()); + download_prefs_.reset(new DownloadPrefs(profile())); + web_contents()->SetDelegate(&web_contents_delegate_); + ASSERT_TRUE(test_download_dir_.CreateUniqueTempDir()); + test_virtual_dir_ = test_download_dir().Append(FILE_PATH_LITERAL("virtual")); + SetDefaultDownloadPath(test_download_dir()); + delegate_.SetupDefaults(); +} + +void DownloadTargetDeterminerTest::TearDown() { + download_prefs_.reset(); + message_loop_.RunUntilIdle(); + ChromeRenderViewHostTestHarness::TearDown(); +} + +content::MockDownloadItem* +DownloadTargetDeterminerTest::CreateActiveDownloadItem( + int32 id, + const DownloadTestCase& test_case) { + content::MockDownloadItem* item = + new ::testing::NiceMock<content::MockDownloadItem>(); + GURL download_url(test_case.url); + std::vector<GURL> url_chain; + url_chain.push_back(download_url); + base::FilePath forced_file_path = + GetPathInDownloadDir(test_case.forced_file_path); + DownloadItem::TargetDisposition initial_disposition = + (test_case.test_type == SAVE_AS) ? + DownloadItem::TARGET_DISPOSITION_PROMPT : + DownloadItem::TARGET_DISPOSITION_OVERWRITE; + EXPECT_EQ(test_case.test_type == FORCED, + !forced_file_path.empty()); + + ON_CALL(*item, GetBrowserContext()) + .WillByDefault(Return(profile())); + ON_CALL(*item, GetDangerType()) + .WillByDefault(Return(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)); + ON_CALL(*item, GetForcedFilePath()) + .WillByDefault(ReturnRefOfCopy(forced_file_path)); + ON_CALL(*item, GetFullPath()) + .WillByDefault(ReturnRefOfCopy(base::FilePath())); + ON_CALL(*item, GetHash()) + .WillByDefault(ReturnRefOfCopy(std::string())); + ON_CALL(*item, GetId()) + .WillByDefault(Return(id)); + ON_CALL(*item, GetMimeType()) + .WillByDefault(Return(test_case.mime_type)); + ON_CALL(*item, GetReferrerUrl()) + .WillByDefault(ReturnRefOfCopy(download_url)); + ON_CALL(*item, GetState()) + .WillByDefault(Return(DownloadItem::IN_PROGRESS)); + ON_CALL(*item, GetTargetDisposition()) + .WillByDefault(Return(initial_disposition)); + ON_CALL(*item, GetTargetFilePath()) + .WillByDefault(ReturnRefOfCopy(base::FilePath())); + ON_CALL(*item, GetTransitionType()) + .WillByDefault(Return(content::PAGE_TRANSITION_LINK)); + ON_CALL(*item, GetURL()) + .WillByDefault(ReturnRefOfCopy(download_url)); + ON_CALL(*item, GetUrlChain()) + .WillByDefault(ReturnRefOfCopy(url_chain)); + ON_CALL(*item, GetWebContents()) + .WillByDefault(Return(web_contents())); + ON_CALL(*item, HasUserGesture()) + .WillByDefault(Return(true)); + ON_CALL(*item, IsDangerous()) + .WillByDefault(Return(false)); + ON_CALL(*item, IsInProgress()) + .WillByDefault(Return(true)); + ON_CALL(*item, IsTemporary()) + .WillByDefault(Return(false)); + return item; +} + +void DownloadTargetDeterminerTest::EnableAutoOpenBasedOnExtension( + const base::FilePath& path) { + EXPECT_TRUE(download_prefs_->EnableAutoOpenBasedOnExtension(path)); +} + +void DownloadTargetDeterminerTest::SetDefaultDownloadPath( + const base::FilePath& path) { + profile()->GetTestingPrefService()-> + SetFilePath(prefs::kDownloadDefaultDirectory, path); +} + +void DownloadTargetDeterminerTest::SetManagedDownloadPath( + const base::FilePath& path) { + profile()->GetTestingPrefService()-> + SetManagedPref(prefs::kDownloadDefaultDirectory, + base::CreateFilePathValue(path)); +} + +void DownloadTargetDeterminerTest::SetPromptForDownload(bool prompt) { + profile()->GetTestingPrefService()-> + SetBoolean(prefs::kPromptForDownload, prompt); +} + +base::FilePath DownloadTargetDeterminerTest::GetPathInDownloadDir( + const base::FilePath::StringType& relative_path) { + if (relative_path.empty()) + return base::FilePath(); + base::FilePath full_path(test_download_dir().Append(relative_path)); + return full_path.NormalizePathSeparators(); +} + +void DownloadTargetDeterminerTest::RunTestCase( + const DownloadTestCase& test_case, + content::MockDownloadItem* item) { + // Kick off the test. + base::WeakPtrFactory<DownloadTargetDeterminerTest> factory(this); + base::RunLoop run_loop; + DownloadTargetDeterminer::Start( + item, download_prefs_.get(), last_selected_directory_, delegate(), + base::Bind(&DownloadTargetDeterminerTest::DownloadTargetVerifier, + factory.GetWeakPtr(), run_loop.QuitClosure(), test_case)); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(delegate()); +} + +void DownloadTargetDeterminerTest::RunTestCasesWithActiveItem( + const DownloadTestCase test_cases[], + size_t test_case_count) { + for (size_t i = 0; i < test_case_count; ++i) { + scoped_ptr<content::MockDownloadItem> item( + CreateActiveDownloadItem(i, test_cases[i])); + SCOPED_TRACE(testing::Message() << "Running test case " << i); + RunTestCase(test_cases[i], item.get()); + } +} + +void DownloadTargetDeterminerTest::DownloadTargetVerifier( + const base::Closure& closure, + const DownloadTestCase& test_case, + const base::FilePath& local_path, + DownloadItem::TargetDisposition disposition, + content::DownloadDangerType danger_type, + const base::FilePath& intermediate_path) { + base::FilePath expected_local_path( + GetPathInDownloadDir(test_case.expected_local_path)); + EXPECT_EQ(expected_local_path.value(), local_path.value()); + EXPECT_EQ(test_case.expected_disposition, disposition); + EXPECT_EQ(test_case.expected_danger_type, danger_type); + + switch (test_case.expected_intermediate) { + case EXPECT_CRDOWNLOAD: + EXPECT_EQ(download_util::GetCrDownloadPath(local_path).value(), + intermediate_path.value()); + break; + + case EXPECT_UNCONFIRMED: + // The paths (in English) look like: /path/Unconfirmed xxx.crdownload. + // Of this, we only check that the path is: + // 1. Not "/path/target.crdownload", + // 2. Points to the same directory as the target. + // 3. Has extension ".crdownload". + // 4. Basename starts with "Unconfirmed ". + EXPECT_NE(download_util::GetCrDownloadPath(expected_local_path).value(), + intermediate_path.value()); + EXPECT_EQ(expected_local_path.DirName().value(), + intermediate_path.DirName().value()); + EXPECT_TRUE(intermediate_path.MatchesExtension( + FILE_PATH_LITERAL(".crdownload"))); + EXPECT_EQ(0u, intermediate_path.BaseName().value().find( + FILE_PATH_LITERAL("Unconfirmed "))); + break; + + case EXPECT_LOCAL_PATH: + EXPECT_EQ(expected_local_path.value(), intermediate_path.value()); + break; + } + base::MessageLoop::current()->PostTask(FROM_HERE, closure); +} + +// static +void MockDownloadTargetDeterminerDelegate::NullReserveVirtualPath( + DownloadItem* download, + const base::FilePath& virtual_path, + DownloadPathReservationTracker::FilenameConflictAction conflict_action, + const DownloadTargetDeterminerDelegate::ReservedPathCallback& callback) { + callback.Run(virtual_path, true); +} + +// static +void MockDownloadTargetDeterminerDelegate::NullPromptUser( + DownloadItem* download, const base::FilePath& suggested_path, + const FileSelectedCallback& callback) { + callback.Run(suggested_path); +} + +// static +void MockDownloadTargetDeterminerDelegate::NullDetermineLocalPath( + DownloadItem* download, const base::FilePath& virtual_path, + const LocalPathCallback& callback) { + callback.Run(virtual_path); +} + +// NotifyExtensions implementation that overrides the path so that the target +// file is in a subdirectory called 'overridden'. If the extension is '.remove', +// the extension is removed. +void NotifyExtensionsOverridePath( + content::DownloadItem* download, + const base::FilePath& path, + const DownloadTargetDeterminerDelegate::NotifyExtensionsCallback& + callback) { + base::FilePath new_path = + base::FilePath() + .AppendASCII("overridden") + .Append(path.BaseName()); + if (new_path.MatchesExtension(FILE_PATH_LITERAL(".remove"))) + new_path = new_path.RemoveExtension(); + callback.Run(new_path, DownloadPathReservationTracker::UNIQUIFY); +} + +void CheckDownloadUrlCheckExes( + content::DownloadItem* download, + const base::FilePath& path, + const DownloadTargetDeterminerDelegate::CheckDownloadUrlCallback& + callback) { + if (path.MatchesExtension(FILE_PATH_LITERAL(".exe"))) + callback.Run(content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT); + else + callback.Run(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); +} + +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_Basic) { + const DownloadTestCase kBasicTestCases[] = { + { + // 0: Automatic Safe + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + + { + // 1: Save_As Safe + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }, + + { + // 2: Automatic Dangerous + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, + "http://example.com/foo.html", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 3: Forced Safe + FORCED, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "", + FILE_PATH_LITERAL("forced-foo.txt"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("forced-foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }, + }; + + // The test assumes that .html files have a danger level of + // AllowOnUserGesture. + ASSERT_EQ(download_util::AllowOnUserGesture, + download_util::GetFileDangerLevel( + base::FilePath(FILE_PATH_LITERAL("foo.html")))); + RunTestCasesWithActiveItem(kBasicTestCases, arraysize(kBasicTestCases)); +} + +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_CancelSaveAs) { + const DownloadTestCase kCancelSaveAsTestCases[] = { + { + // 0: Save_As Safe, Cancelled. + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL(""), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_LOCAL_PATH + } + }; + ON_CALL(*delegate(), PromptUserForDownloadPath(_, _, _)) + .WillByDefault(WithArg<2>(ScheduleCallback(base::FilePath()))); + RunTestCasesWithActiveItem(kCancelSaveAsTestCases, + arraysize(kCancelSaveAsTestCases)); +} + +// The SafeBrowsing check is performed early. Make sure that a download item +// that has been marked as DANGEROUS_URL behaves correctly. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_DangerousUrl) { + const DownloadTestCase kSafeBrowsingTestCases[] = { + { + // 0: Automatic Dangerous URL + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, + "http://phishing.example.com/foo.txt", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 1: Save As Dangerous URL + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, + "http://phishing.example.com/foo.txt", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_UNCONFIRMED + }, + + { + // 2: Forced Dangerous URL + FORCED, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, + "http://phishing.example.com/foo.txt", "", + FILE_PATH_LITERAL("forced-foo.txt"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("forced-foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 3: Automatic Dangerous URL + Dangerous file. Dangerous URL takes + // precendence. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, + "http://phishing.example.com/foo.html", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 4: Save As Dangerous URL + Dangerous file + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, + "http://phishing.example.com/foo.html", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_UNCONFIRMED + }, + + { + // 5: Forced Dangerous URL + Dangerous file + FORCED, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, + "http://phishing.example.com/foo.html", "", + FILE_PATH_LITERAL("forced-foo.html"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("forced-foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + }; + + ON_CALL(*delegate(), CheckDownloadUrl(_, _, _)) + .WillByDefault(WithArg<2>(ScheduleCallback( + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL))); + RunTestCasesWithActiveItem(kSafeBrowsingTestCases, + arraysize(kSafeBrowsingTestCases)); +} + +// The SafeBrowsing check is performed early. Make sure that a download item +// that has been marked as MAYBE_DANGEROUS_CONTENT behaves correctly. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_MaybeDangerousContent) { + const DownloadTestCase kSafeBrowsingTestCases[] = { + { + // 0: Automatic Maybe dangerous content + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, + "http://phishing.example.com/foo.exe", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.exe"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 1: Save As Maybe dangerous content + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, + "http://phishing.example.com/foo.exe", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.exe"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_UNCONFIRMED + }, + + { + // 2: Forced Maybe dangerous content + FORCED, + content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, + "http://phishing.example.com/foo.exe", "", + FILE_PATH_LITERAL("forced-foo.exe"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("forced-foo.exe"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + }; + + ON_CALL(*delegate(), CheckDownloadUrl(_, _, _)) + .WillByDefault(WithArg<2>(ScheduleCallback( + content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT))); + RunTestCasesWithActiveItem(kSafeBrowsingTestCases, + arraysize(kSafeBrowsingTestCases)); +} + +// Test whether the last saved directory is used for 'Save As' downloads. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_LastSavePath) { + const DownloadTestCase kLastSavePathTestCasesPre[] = { + { + // 0: If the last save path is empty, then the default download directory + // should be used. + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + } + }; + + // These test cases are run with a last save path set to a non-emtpy local + // download directory. + const DownloadTestCase kLastSavePathTestCasesPost[] = { + { + // 0: This test case is run with the last download directory set to + // '<test_download_dir()>/foo'. + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo/foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }, + + { + // 1: Start an automatic download. This should be saved to the user's + // default download directory and not the last used Save As directory. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + }; + + // This test case is run with the last save path set to a non-empty virtual + // directory. + const DownloadTestCase kLastSavePathTestCasesVirtual[] = { + { + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL("virtual/foo/foo.txt"), + FILE_PATH_LITERAL("bar.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_LOCAL_PATH + }, + }; + + { + SCOPED_TRACE(testing::Message() + << "Running with empty last_selected_directory"); + base::FilePath prompt_path = + GetPathInDownloadDir(FILE_PATH_LITERAL("foo.txt")); + EXPECT_CALL(*delegate(), PromptUserForDownloadPath(_, prompt_path, _)); + RunTestCasesWithActiveItem(kLastSavePathTestCasesPre, + arraysize(kLastSavePathTestCasesPre)); + } + + // Try with a non-empty last save path. + { + SCOPED_TRACE(testing::Message() + << "Running with local last_selected_directory"); + set_last_selected_directory(test_download_dir().AppendASCII("foo")); + base::FilePath prompt_path = + GetPathInDownloadDir(FILE_PATH_LITERAL("foo/foo.txt")); + EXPECT_CALL(*delegate(), PromptUserForDownloadPath(_, prompt_path, _)); + RunTestCasesWithActiveItem(kLastSavePathTestCasesPost, + arraysize(kLastSavePathTestCasesPost)); + } + + // And again, but this time use a virtual directory. + { + SCOPED_TRACE(testing::Message() + << "Running with virtual last_selected_directory"); + base::FilePath last_selected_dir = test_virtual_dir().AppendASCII("foo"); + base::FilePath virtual_path = last_selected_dir.AppendASCII("foo.txt"); + set_last_selected_directory(last_selected_dir); + EXPECT_CALL(*delegate(), PromptUserForDownloadPath( + _, last_selected_dir.AppendASCII("foo.txt"), _)); + EXPECT_CALL(*delegate(), DetermineLocalPath(_, virtual_path, _)) + .WillOnce(WithArg<2>(ScheduleCallback( + GetPathInDownloadDir(FILE_PATH_LITERAL("bar.txt"))))); + RunTestCasesWithActiveItem(kLastSavePathTestCasesVirtual, + arraysize(kLastSavePathTestCasesVirtual)); + } +} + +// These tests are run with the default downloads folder set to a virtual +// directory. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_DefaultVirtual) { + // The default download directory is the virutal path. + SetDefaultDownloadPath(test_virtual_dir()); + + { + SCOPED_TRACE(testing::Message() << "Automatic Safe Download"); + const DownloadTestCase kAutomaticDownloadToVirtualDir = { + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + // Downloaded to default virtual directory. + FILE_PATH_LITERAL("virtual/foo.txt"), + FILE_PATH_LITERAL("foo-local.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }; + EXPECT_CALL(*delegate(), DetermineLocalPath(_, _, _)) + .WillOnce(WithArg<2>(ScheduleCallback( + GetPathInDownloadDir(FILE_PATH_LITERAL("foo-local.txt"))))); + RunTestCasesWithActiveItem(&kAutomaticDownloadToVirtualDir, 1); + } + + { + SCOPED_TRACE(testing::Message() << "Save As to virtual directory"); + const DownloadTestCase kSaveAsToVirtualDir = { + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/bar.txt", "text/plain", + FILE_PATH_LITERAL(""), + + // The response to the download prompt is to choose the 'prompted.txt' + // virtual path. + FILE_PATH_LITERAL("virtual/prompted.txt"), + FILE_PATH_LITERAL("foo-local.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_LOCAL_PATH + }; + EXPECT_CALL(*delegate(), DetermineLocalPath(_, _, _)) + .WillOnce(WithArg<2>(ScheduleCallback( + GetPathInDownloadDir(FILE_PATH_LITERAL("foo-local.txt"))))); + EXPECT_CALL(*delegate(), PromptUserForDownloadPath( + _, test_virtual_dir().AppendASCII("bar.txt"), _)) + .WillOnce(WithArg<2>(ScheduleCallback( + test_virtual_dir().AppendASCII("prompted.txt")))); + RunTestCasesWithActiveItem(&kSaveAsToVirtualDir, 1); + } + + { + SCOPED_TRACE(testing::Message() << "Save As to local directory"); + const DownloadTestCase kSaveAsToLocalDir = { + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/bar.txt", "text/plain", + FILE_PATH_LITERAL(""), + + // Response to the 'Save As' is to choose the local path for 'foo-x.txt'. + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo-x.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }; + EXPECT_CALL(*delegate(), PromptUserForDownloadPath( + _, test_virtual_dir().AppendASCII("bar.txt"), _)) + .WillOnce(WithArg<2>(ScheduleCallback( + GetPathInDownloadDir(FILE_PATH_LITERAL("foo-x.txt"))))); + RunTestCasesWithActiveItem(&kSaveAsToLocalDir, 1); + } + + { + SCOPED_TRACE(testing::Message() << "Forced safe download"); + const DownloadTestCase kForcedSafe = { + FORCED, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "", + FILE_PATH_LITERAL("forced-foo.txt"), + + // Forced paths should be left as-is. + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("forced-foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }; + RunTestCasesWithActiveItem(&kForcedSafe, 1); + } +} + +// Test that an inactive download will still get a virtual or local download +// path. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_InactiveDownload) { + const DownloadTestCase kInactiveTestCases[] = { + { + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL("foo.txt"), + FILE_PATH_LITERAL(""), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }, + + { + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL("foo.txt"), + FILE_PATH_LITERAL(""), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_LOCAL_PATH + } + }; + + for (size_t i = 0; i < arraysize(kInactiveTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "Running test case " << i); + const DownloadTestCase& test_case = kInactiveTestCases[i]; + scoped_ptr<content::MockDownloadItem> item( + CreateActiveDownloadItem(i, test_case)); + EXPECT_CALL(*item.get(), IsInProgress()) + .WillRepeatedly(Return(false)); + // Even though one is a SAVE_AS download, no prompt will be displayed to + // the user because the download is inactive. + EXPECT_CALL(*delegate(), PromptUserForDownloadPath(_, _, _)) + .Times(0); + RunTestCase(kInactiveTestCases[i], item.get()); + } +} + +// If the reserved path could not be verified, then the user should see a +// prompt. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_ReservationFailed) { + const DownloadTestCase kReservationFailedCases[] = { + { + // 0: Automatic download. Since the reservation fails, the disposition of + // the target is to prompt, but the returned path is used. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("bar.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }, + }; + + // Setup ReserveVirtualPath() to fail. + ON_CALL(*delegate(), ReserveVirtualPath(_, _, _, _)) + .WillByDefault(WithArg<3>(ScheduleCallback2( + GetPathInDownloadDir(FILE_PATH_LITERAL("bar.txt")), false))); + RunTestCasesWithActiveItem(kReservationFailedCases, + arraysize(kReservationFailedCases)); +} + +// If the local path could not be determined, the download should be cancelled. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_LocalPathFailed) { + const DownloadTestCase kLocalPathFailedCases[] = { + { + // 0: Automatic download. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL(""), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }, + }; + + base::FilePath expected_virtual_path( + GetPathInDownloadDir(FILE_PATH_LITERAL("virtual/foo.txt"))); + // The default download directory is the virtual path. + SetDefaultDownloadPath(test_virtual_dir()); + // Simulate failed call to DetermineLocalPath. + EXPECT_CALL(*delegate(), DetermineLocalPath( + _, GetPathInDownloadDir(FILE_PATH_LITERAL("virtual/foo.txt")), _)) + .WillOnce(WithArg<2>(ScheduleCallback(base::FilePath()))); + RunTestCasesWithActiveItem(kLocalPathFailedCases, + arraysize(kLocalPathFailedCases)); +} + +// Downloads that have a danger level of AllowOnUserGesture should be marked as +// safe depending on whether there was a user gesture associated with the +// download and whether the referrer was visited prior to today. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_VisitedReferrer) { + const DownloadTestCase kVisitedReferrerCases[] = { + // http://visited.example.com/ is added to the history as a visit that + // happened prior to today. + { + // 0: Safe download due to visiting referrer before. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://visited.example.com/foo.html", "text/html", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + + { + // 1: Dangerous due to not having visited referrer before. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, + "http://not-visited.example.com/foo.html", "text/html", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 2: Safe because the user is being prompted. + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://not-visited.example.com/foo.html", "text/html", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }, + + { + // 3: Safe because of forced path. + FORCED, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://not-visited.example.com/foo.html", "text/html", + FILE_PATH_LITERAL("foo.html"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }, + }; + + // This test assumes that the danger level of .html files is + // AllowOnUserGesture. + ASSERT_EQ(download_util::AllowOnUserGesture, + download_util::GetFileDangerLevel( + base::FilePath(FILE_PATH_LITERAL("foo.html")))); + + // First the history service must exist. + profile()->CreateHistoryService(false, false); + + GURL url("http://visited.example.com/visited-link.html"); + // The time of visit is picked to be several seconds prior to the most recent + // midnight. + base::Time time_of_visit( + base::Time::Now().LocalMidnight() - base::TimeDelta::FromSeconds(10)); + HistoryService* history_service = + HistoryServiceFactory::GetForProfile(profile(), Profile::EXPLICIT_ACCESS); + ASSERT_TRUE(history_service); + history_service->AddPage(url, time_of_visit, history::SOURCE_BROWSED); + + RunTestCasesWithActiveItem(kVisitedReferrerCases, + arraysize(kVisitedReferrerCases)); +} + +// These test cases are run with "Prompt for download" user preference set to +// true. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_PromptAlways) { + const DownloadTestCase kPromptingTestCases[] = { + { + // 0: Safe Automatic - Should prompt because of "Prompt for download" + // preference setting. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }, + + { + // 1: Safe Forced - Shouldn't prompt. + FORCED, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL("foo.txt"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }, + + { + // 2: Automatic - The filename extension is marked as one that we will + // open automatically. Shouldn't prompt. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.dummy", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.dummy"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + }; + + SetPromptForDownload(true); + EnableAutoOpenBasedOnExtension( + base::FilePath(FILE_PATH_LITERAL("dummy.dummy"))); + RunTestCasesWithActiveItem(kPromptingTestCases, + arraysize(kPromptingTestCases)); +} + +#if !defined(OS_ANDROID) +// These test cases are run with "Prompt for download" user preference set to +// true. Automatic extension downloads shouldn't cause prompting. +// Android doesn't support extensions. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_PromptAlways_Extension) { + const DownloadTestCase kPromptingTestCases[] = { + { + // 0: Automatic Browser Extension download. - Shouldn't prompt for browser + // extension downloads even if "Prompt for download" preference is set. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, + "http://example.com/foo.crx", + extensions::Extension::kMimeType, + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.crx"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + +#if defined(OS_WIN) + { + // 1: Automatic User Script - Shouldn't prompt for user script downloads + // even if "Prompt for download" preference is set. ".js" files are + // considered dangerous on Windows. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, + "http://example.com/foo.user.js", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.user.js"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, +#else + { + // 1: Automatic User Script - Shouldn't prompt for user script downloads + // even if "Prompt for download" preference is set. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.user.js", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.user.js"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, +#endif + }; + + SetPromptForDownload(true); + RunTestCasesWithActiveItem(kPromptingTestCases, + arraysize(kPromptingTestCases)); +} +#endif + +// If the download path is managed, then we don't show any prompts. +// Note that if the download path is managed, then PromptForDownload() is false. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_ManagedPath) { + const DownloadTestCase kManagedPathTestCases[] = { + { + // 0: Automatic Safe + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + + { + // 1: Save_As Safe + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + }; + + SetManagedDownloadPath(test_download_dir()); + ASSERT_TRUE(download_prefs()->IsDownloadPathManaged()); + RunTestCasesWithActiveItem(kManagedPathTestCases, + arraysize(kManagedPathTestCases)); +} + +// Test basic functionality supporting extensions that want to override download +// filenames. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_NotifyExtensionsSafe) { + const DownloadTestCase kNotifyExtensionsTestCases[] = { + { + // 0: Automatic Safe + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }, + + { + // 1: Save_As Safe + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }, + + { + // 2: Automatic Dangerous + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, + "http://example.com/foo.html", "", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 3: Forced Safe + FORCED, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "", + FILE_PATH_LITERAL("forced-foo.txt"), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("forced-foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_LOCAL_PATH + }, + }; + + ON_CALL(*delegate(), NotifyExtensions(_, _, _)) + .WillByDefault(Invoke(&NotifyExtensionsOverridePath)); + RunTestCasesWithActiveItem(kNotifyExtensionsTestCases, + arraysize(kNotifyExtensionsTestCases)); +} + +// Test that filenames provided by extensions are passed into SafeBrowsing +// checks and dangerous download checks. +TEST_F(DownloadTargetDeterminerTest, TargetDeterminer_NotifyExtensionsUnsafe) { + const DownloadTestCase kNotifyExtensionsTestCases[] = { + { + // 0: Automatic Safe : Later overridden by a dangerous filetype. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, + "http://example.com/foo.html.remove", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.html"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + + { + // 1: Automatic Safe : Later overridden by a potentially dangerous + // filetype. + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, + "http://example.com/foo.exe.remove", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.exe"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_UNCONFIRMED + }, + }; + + ON_CALL(*delegate(), NotifyExtensions(_, _, _)) + .WillByDefault(Invoke(&NotifyExtensionsOverridePath)); + ON_CALL(*delegate(), CheckDownloadUrl(_, _, _)) + .WillByDefault(Invoke(&CheckDownloadUrlCheckExes)); + RunTestCasesWithActiveItem(kNotifyExtensionsTestCases, + arraysize(kNotifyExtensionsTestCases)); +} + +// Test that conflict actions set by extensions are passed correctly into +// ReserveVirtualPath. +TEST_F(DownloadTargetDeterminerTest, + TargetDeterminer_NotifyExtensionsConflict) { + const DownloadTestCase kNotifyExtensionsTestCase = { + AUTOMATIC, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECT_CRDOWNLOAD + }; + + const DownloadTestCase& test_case = kNotifyExtensionsTestCase; + scoped_ptr<content::MockDownloadItem> item( + CreateActiveDownloadItem(0, test_case)); + base::FilePath overridden_path(FILE_PATH_LITERAL("overridden/foo.txt")); + base::FilePath full_overridden_path = + GetPathInDownloadDir(overridden_path.value()); + + // First case: An extension sets the conflict_action to OVERWRITE. + EXPECT_CALL(*delegate(), NotifyExtensions(_, _, _)) + .WillOnce(WithArg<2>( + ScheduleCallback2(overridden_path, + DownloadPathReservationTracker::OVERWRITE))); + EXPECT_CALL(*delegate(), ReserveVirtualPath( + _, full_overridden_path, DownloadPathReservationTracker::OVERWRITE, _)) + .WillOnce(WithArg<3>( + ScheduleCallback2(full_overridden_path, true))); + + RunTestCase(test_case, item.get()); + + // Second case: An extension sets the conflict_action to PROMPT. + EXPECT_CALL(*delegate(), NotifyExtensions(_, _, _)) + .WillOnce(WithArg<2>( + ScheduleCallback2(overridden_path, + DownloadPathReservationTracker::PROMPT))); + EXPECT_CALL(*delegate(), ReserveVirtualPath( + _, full_overridden_path, DownloadPathReservationTracker::PROMPT, _)) + .WillOnce(WithArg<3>( + ScheduleCallback2(full_overridden_path, true))); + RunTestCase(test_case, item.get()); +} + +// Test that relative paths returned by extensions are always relative to the +// default downloads path. +TEST_F(DownloadTargetDeterminerTest, + TargetDeterminer_NotifyExtensionsDefaultPath) { + const DownloadTestCase kNotifyExtensionsTestCase = { + SAVE_AS, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + "http://example.com/foo.txt", "text/plain", + FILE_PATH_LITERAL(""), + + FILE_PATH_LITERAL(""), + FILE_PATH_LITERAL("overridden/foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECT_CRDOWNLOAD + }; + + const DownloadTestCase& test_case = kNotifyExtensionsTestCase; + scoped_ptr<content::MockDownloadItem> item( + CreateActiveDownloadItem(0, test_case)); + base::FilePath overridden_path(FILE_PATH_LITERAL("overridden/foo.txt")); + base::FilePath full_overridden_path = + GetPathInDownloadDir(overridden_path.value()); + + // The last selected directory is this one. Since the test case is a SAVE_AS + // download, it should use this directory for the generated path. + set_last_selected_directory(GetPathInDownloadDir( + FILE_PATH_LITERAL("last_selected"))); + + EXPECT_CALL(*delegate(), NotifyExtensions(_, _, _)) + .WillOnce(WithArg<2>( + ScheduleCallback2(overridden_path, + DownloadPathReservationTracker::UNIQUIFY))); + EXPECT_CALL(*delegate(), + PromptUserForDownloadPath(_, full_overridden_path, _)) + .WillOnce(WithArg<2>( + ScheduleCallback(full_overridden_path))); + RunTestCase(test_case, item.get()); +} +} // namespace |