// 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. // This file contains download browser tests that are known to be runnable // in a pure content context. Over time tests should be migrated here. #include "base/command_line.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "content/browser/byte_stream.h" #include "content/browser/download/download_file_factory.h" #include "content/browser/download/download_file_impl.h" #include "content/browser/download/download_item_impl.h" #include "content/browser/download/download_manager_impl.h" #include "content/browser/download/download_resource_handler.h" #include "content/browser/plugin_service_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/power_save_blocker.h" #include "content/public/common/content_switches.h" #include "content/public/common/webplugininfo.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/download_test_observer.h" #include "content/public/test/test_file_error_injector.h" #include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "content/shell/browser/shell_browser_context.h" #include "content/shell/browser/shell_download_manager_delegate.h" #include "content/shell/browser/shell_network_delegate.h" #include "content/test/net/url_request_mock_http_job.h" #include "content/test/net/url_request_slow_download_job.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using ::net::test_server::EmbeddedTestServer; using ::testing::AllOf; using ::testing::Field; using ::testing::InSequence; using ::testing::Property; using ::testing::Return; using ::testing::StrictMock; using ::testing::_; namespace content { namespace { class MockDownloadItemObserver : public DownloadItem::Observer { public: MockDownloadItemObserver() {} virtual ~MockDownloadItemObserver() {} MOCK_METHOD1(OnDownloadUpdated, void(DownloadItem*)); MOCK_METHOD1(OnDownloadOpened, void(DownloadItem*)); MOCK_METHOD1(OnDownloadRemoved, void(DownloadItem*)); MOCK_METHOD1(OnDownloadDestroyed, void(DownloadItem*)); }; class MockDownloadManagerObserver : public DownloadManager::Observer { public: MockDownloadManagerObserver(DownloadManager* manager) { manager_ = manager; manager->AddObserver(this); } virtual ~MockDownloadManagerObserver() { if (manager_) manager_->RemoveObserver(this); } MOCK_METHOD2(OnDownloadCreated, void(DownloadManager*, DownloadItem*)); MOCK_METHOD1(ModelChanged, void(DownloadManager*)); void ManagerGoingDown(DownloadManager* manager) { DCHECK_EQ(manager_, manager); MockManagerGoingDown(manager); manager_->RemoveObserver(this); manager_ = NULL; } MOCK_METHOD1(MockManagerGoingDown, void(DownloadManager*)); private: DownloadManager* manager_; }; class DownloadFileWithDelayFactory; static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) { // We're in a content_browsertest; we know that the DownloadManager // is a DownloadManagerImpl. return static_cast<DownloadManagerImpl*>( BrowserContext::GetDownloadManager( shell->web_contents()->GetBrowserContext())); } class DownloadFileWithDelay : public DownloadFileImpl { public: DownloadFileWithDelay( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, scoped_ptr<PowerSaveBlocker> power_save_blocker, base::WeakPtr<DownloadDestinationObserver> observer, base::WeakPtr<DownloadFileWithDelayFactory> owner); virtual ~DownloadFileWithDelay(); // Wraps DownloadFileImpl::Rename* and intercepts the return callback, // storing it in the factory that produced this object for later // retrieval. virtual void RenameAndUniquify( const base::FilePath& full_path, const RenameCompletionCallback& callback) OVERRIDE; virtual void RenameAndAnnotate( const base::FilePath& full_path, const RenameCompletionCallback& callback) OVERRIDE; private: static void RenameCallbackWrapper( const base::WeakPtr<DownloadFileWithDelayFactory>& factory, const RenameCompletionCallback& original_callback, DownloadInterruptReason reason, const base::FilePath& path); // This variable may only be read on the FILE thread, and may only be // indirected through (e.g. methods on DownloadFileWithDelayFactory called) // on the UI thread. This is because after construction, // DownloadFileWithDelay lives on the file thread, but // DownloadFileWithDelayFactory is purely a UI thread object. base::WeakPtr<DownloadFileWithDelayFactory> owner_; DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelay); }; // All routines on this class must be called on the UI thread. class DownloadFileWithDelayFactory : public DownloadFileFactory { public: DownloadFileWithDelayFactory(); virtual ~DownloadFileWithDelayFactory(); // DownloadFileFactory interface. virtual DownloadFile* CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE; void AddRenameCallback(base::Closure callback); void GetAllRenameCallbacks(std::vector<base::Closure>* results); // Do not return until GetAllRenameCallbacks() will return a non-empty list. void WaitForSomeCallback(); private: base::WeakPtrFactory<DownloadFileWithDelayFactory> weak_ptr_factory_; std::vector<base::Closure> rename_callbacks_; bool waiting_; DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelayFactory); }; DownloadFileWithDelay::DownloadFileWithDelay( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, scoped_ptr<PowerSaveBlocker> power_save_blocker, base::WeakPtr<DownloadDestinationObserver> observer, base::WeakPtr<DownloadFileWithDelayFactory> owner) : DownloadFileImpl( save_info.Pass(), default_download_directory, url, referrer_url, calculate_hash, stream.Pass(), bound_net_log, observer), owner_(owner) {} DownloadFileWithDelay::~DownloadFileWithDelay() {} void DownloadFileWithDelay::RenameAndUniquify( const base::FilePath& full_path, const RenameCompletionCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFileImpl::RenameAndUniquify( full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper, owner_, callback)); } void DownloadFileWithDelay::RenameAndAnnotate( const base::FilePath& full_path, const RenameCompletionCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFileImpl::RenameAndAnnotate( full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper, owner_, callback)); } // static void DownloadFileWithDelay::RenameCallbackWrapper( const base::WeakPtr<DownloadFileWithDelayFactory>& factory, const RenameCompletionCallback& original_callback, DownloadInterruptReason reason, const base::FilePath& path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!factory) return; factory->AddRenameCallback(base::Bind(original_callback, reason, path)); } DownloadFileWithDelayFactory::DownloadFileWithDelayFactory() : weak_ptr_factory_(this), waiting_(false) {} DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {} DownloadFile* DownloadFileWithDelayFactory::CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) { scoped_ptr<PowerSaveBlocker> psb( PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, "Download in progress")); return new DownloadFileWithDelay( save_info.Pass(), default_download_directory, url, referrer_url, calculate_hash, stream.Pass(), bound_net_log, psb.Pass(), observer, weak_ptr_factory_.GetWeakPtr()); } void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); rename_callbacks_.push_back(callback); if (waiting_) base::MessageLoopForUI::current()->Quit(); } void DownloadFileWithDelayFactory::GetAllRenameCallbacks( std::vector<base::Closure>* results) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); results->swap(rename_callbacks_); } void DownloadFileWithDelayFactory::WaitForSomeCallback() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (rename_callbacks_.empty()) { waiting_ = true; RunMessageLoop(); waiting_ = false; } } class CountingDownloadFile : public DownloadFileImpl { public: CountingDownloadFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, scoped_ptr<PowerSaveBlocker> power_save_blocker, base::WeakPtr<DownloadDestinationObserver> observer) : DownloadFileImpl(save_info.Pass(), default_downloads_directory, url, referrer_url, calculate_hash, stream.Pass(), bound_net_log, observer) {} virtual ~CountingDownloadFile() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); active_files_--; } virtual void Initialize(const InitializeCallback& callback) OVERRIDE { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); active_files_++; return DownloadFileImpl::Initialize(callback); } static void GetNumberActiveFiles(int* result) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); *result = active_files_; } // Can be called on any thread, and will block (running message loop) // until data is returned. static int GetNumberActiveFilesFromFileThread() { int result = -1; BrowserThread::PostTaskAndReply( BrowserThread::FILE, FROM_HERE, base::Bind(&CountingDownloadFile::GetNumberActiveFiles, &result), base::MessageLoop::current()->QuitClosure()); base::MessageLoop::current()->Run(); DCHECK_NE(-1, result); return result; } private: static int active_files_; }; int CountingDownloadFile::active_files_ = 0; class CountingDownloadFileFactory : public DownloadFileFactory { public: CountingDownloadFileFactory() {} virtual ~CountingDownloadFileFactory() {} // DownloadFileFactory interface. virtual DownloadFile* CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE { scoped_ptr<PowerSaveBlocker> psb( PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, "Download in progress")); return new CountingDownloadFile( save_info.Pass(), default_downloads_directory, url, referrer_url, calculate_hash, stream.Pass(), bound_net_log, psb.Pass(), observer); } }; class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate { public: TestShellDownloadManagerDelegate() : delay_download_open_(false) {} virtual ~TestShellDownloadManagerDelegate() {} virtual bool ShouldOpenDownload( DownloadItem* item, const DownloadOpenDelayedCallback& callback) OVERRIDE { if (delay_download_open_) { delayed_callbacks_.push_back(callback); return false; } return true; } void SetDelayedOpen(bool delay) { delay_download_open_ = delay; } void GetDelayedCallbacks( std::vector<DownloadOpenDelayedCallback>* callbacks) { callbacks->swap(delayed_callbacks_); } private: bool delay_download_open_; std::vector<DownloadOpenDelayedCallback> delayed_callbacks_; }; // Record all state transitions and byte counts on the observed download. class RecordingDownloadObserver : DownloadItem::Observer { public: struct RecordStruct { DownloadItem::DownloadState state; int bytes_received; }; typedef std::vector<RecordStruct> RecordVector; RecordingDownloadObserver(DownloadItem* download) : download_(download) { last_state_.state = download->GetState(); last_state_.bytes_received = download->GetReceivedBytes(); download_->AddObserver(this); } virtual ~RecordingDownloadObserver() { RemoveObserver(); } void CompareToExpectedRecord(const RecordStruct expected[], size_t size) { EXPECT_EQ(size, record_.size()); int min = size > record_.size() ? record_.size() : size; for (int i = 0; i < min; ++i) { EXPECT_EQ(expected[i].state, record_[i].state) << "Iteration " << i; EXPECT_EQ(expected[i].bytes_received, record_[i].bytes_received) << "Iteration " << i; } } private: virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE { DCHECK_EQ(download_, download); DownloadItem::DownloadState state = download->GetState(); int bytes = download->GetReceivedBytes(); if (last_state_.state != state || last_state_.bytes_received > bytes) { last_state_.state = state; last_state_.bytes_received = bytes; record_.push_back(last_state_); } } virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE { DCHECK_EQ(download_, download); RemoveObserver(); } void RemoveObserver() { if (download_) { download_->RemoveObserver(this); download_ = NULL; } } DownloadItem* download_; RecordStruct last_state_; RecordVector record_; }; // Get the next created download. class DownloadCreateObserver : DownloadManager::Observer { public: DownloadCreateObserver(DownloadManager* manager) : manager_(manager), item_(NULL), waiting_(false) { manager_->AddObserver(this); } virtual ~DownloadCreateObserver() { if (manager_) manager_->RemoveObserver(this); manager_ = NULL; } virtual void ManagerGoingDown(DownloadManager* manager) OVERRIDE { DCHECK_EQ(manager_, manager); manager_->RemoveObserver(this); manager_ = NULL; } virtual void OnDownloadCreated(DownloadManager* manager, DownloadItem* download) OVERRIDE { if (!item_) item_ = download; if (waiting_) base::MessageLoopForUI::current()->Quit(); } DownloadItem* WaitForFinished() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!item_) { waiting_ = true; RunMessageLoop(); waiting_ = false; } return item_; } private: DownloadManager* manager_; DownloadItem* item_; bool waiting_; }; // Filter for waiting for a certain number of bytes. bool DataReceivedFilter(int number_of_bytes, DownloadItem* download) { return download->GetReceivedBytes() >= number_of_bytes; } // Filter for download completion. bool DownloadCompleteFilter(DownloadItem* download) { return download->GetState() == DownloadItem::COMPLETE; } // Filter for saving the size of the download when the first IN_PROGRESS // is hit. bool InitialSizeFilter(int* download_size, DownloadItem* download) { if (download->GetState() != DownloadItem::IN_PROGRESS) return false; *download_size = download->GetReceivedBytes(); return true; } // Request handler to be used with CreateRedirectHandler(). scoped_ptr<net::test_server::HttpResponse> HandleRequestAndSendRedirectResponse( const std::string& relative_url, const GURL& target_url, const net::test_server::HttpRequest& request) { scoped_ptr<net::test_server::BasicHttpResponse> response; if (request.relative_url == relative_url) { response.reset(new net::test_server::BasicHttpResponse); response->set_code(net::HTTP_FOUND); response->AddCustomHeader("Location", target_url.spec()); } return response.PassAs<net::test_server::HttpResponse>(); } // Creates a request handler for EmbeddedTestServer that responds with a HTTP // 302 redirect if the request URL matches |relative_url|. EmbeddedTestServer::HandleRequestCallback CreateRedirectHandler( const std::string& relative_url, const GURL& target_url) { return base::Bind( &HandleRequestAndSendRedirectResponse, relative_url, target_url); } // Request handler to be used with CreateBasicResponseHandler(). scoped_ptr<net::test_server::HttpResponse> HandleRequestAndSendBasicResponse( const std::string& relative_url, const std::string& content_type, const std::string& body, const net::test_server::HttpRequest& request) { scoped_ptr<net::test_server::BasicHttpResponse> response; if (request.relative_url == relative_url) { response.reset(new net::test_server::BasicHttpResponse); response->set_content_type(content_type); response->set_content(body); } return response.PassAs<net::test_server::HttpResponse>(); } // Creates a request handler for an EmbeddedTestServer that response with an // HTTP 200 status code, a Content-Type header and a body. EmbeddedTestServer::HandleRequestCallback CreateBasicResponseHandler( const std::string& relative_url, const std::string& content_type, const std::string& body) { return base::Bind( &HandleRequestAndSendBasicResponse, relative_url, content_type, body); } } // namespace class DownloadContentTest : public ContentBrowserTest { protected: // An initial send from a website of at least this size will not be // help up by buffering in the underlying downloads ByteStream data // transfer. This is important because on resumption tests we wait // until we've gotten the data we expect before allowing the test server // to send its reset, to get around hard close semantics on the Windows // socket layer implementation. int GetSafeBufferChunk() const { return (DownloadResourceHandler::kDownloadByteStreamSize / ByteStreamWriter::kFractionBufferBeforeSending) + 1; } virtual void SetUpOnMainThread() OVERRIDE { ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir()); test_delegate_.reset(new TestShellDownloadManagerDelegate()); test_delegate_->SetDownloadBehaviorForTesting(downloads_directory_.path()); DownloadManager* manager = DownloadManagerForShell(shell()); manager->GetDelegate()->Shutdown(); manager->SetDelegate(test_delegate_.get()); test_delegate_->SetDownloadManager(manager); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestSlowDownloadJob::AddUrlHandler)); base::FilePath mock_base(GetTestFilePath("download", "")); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestMockHTTPJob::AddUrlHandler, mock_base)); } TestShellDownloadManagerDelegate* GetDownloadManagerDelegate() { return test_delegate_.get(); } // Create a DownloadTestObserverTerminal that will wait for the // specified number of downloads to finish. DownloadTestObserver* CreateWaiter( Shell* shell, int num_downloads) { DownloadManager* download_manager = DownloadManagerForShell(shell); return new DownloadTestObserverTerminal(download_manager, num_downloads, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); } // Create a DownloadTestObserverInProgress that will wait for the // specified number of downloads to start. DownloadCreateObserver* CreateInProgressWaiter( Shell* shell, int num_downloads) { DownloadManager* download_manager = DownloadManagerForShell(shell); return new DownloadCreateObserver(download_manager); } DownloadTestObserver* CreateInterruptedWaiter( Shell* shell, int num_downloads) { DownloadManager* download_manager = DownloadManagerForShell(shell); return new DownloadTestObserverInterrupted(download_manager, num_downloads, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); } // Note: Cannot be used with other alternative DownloadFileFactorys void SetupEnsureNoPendingDownloads() { DownloadManagerForShell(shell())->SetDownloadFileFactoryForTesting( scoped_ptr<DownloadFileFactory>( new CountingDownloadFileFactory()).Pass()); } bool EnsureNoPendingDownloads() { bool result = true; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&EnsureNoPendingDownloadJobsOnIO, &result)); base::MessageLoop::current()->Run(); return result && (CountingDownloadFile::GetNumberActiveFilesFromFileThread() == 0); } void NavigateToURLAndWaitForDownload( Shell* shell, const GURL& url, DownloadItem::DownloadState expected_terminal_state) { scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell, 1)); NavigateToURL(shell, url); observer->WaitForFinished(); EXPECT_EQ(1u, observer->NumDownloadsSeenInState(expected_terminal_state)); } // Checks that |path| is has |file_size| bytes, and matches the |value| // string. bool VerifyFile(const base::FilePath& path, const std::string& value, const int64 file_size) { std::string file_contents; bool read = base::ReadFileToString(path, &file_contents); EXPECT_TRUE(read) << "Failed reading file: " << path.value() << std::endl; if (!read) return false; // Couldn't read the file. // Note: we don't handle really large files (more than size_t can hold) // so we will fail in that case. size_t expected_size = static_cast<size_t>(file_size); // Check the size. EXPECT_EQ(expected_size, file_contents.size()); if (expected_size != file_contents.size()) return false; // Check the contents. EXPECT_EQ(value, file_contents); if (memcmp(file_contents.c_str(), value.c_str(), expected_size) != 0) return false; return true; } // Start a download and return the item. DownloadItem* StartDownloadAndReturnItem(GURL url) { scoped_ptr<DownloadCreateObserver> observer( CreateInProgressWaiter(shell(), 1)); NavigateToURL(shell(), url); observer->WaitForFinished(); std::vector<DownloadItem*> downloads; DownloadManagerForShell(shell())->GetAllDownloads(&downloads); EXPECT_EQ(1u, downloads.size()); if (1u != downloads.size()) return NULL; return downloads[0]; } // Wait for data void WaitForData(DownloadItem* download, int size) { DownloadUpdatedObserver data_observer( download, base::Bind(&DataReceivedFilter, size)); data_observer.WaitForEvent(); ASSERT_EQ(size, download->GetReceivedBytes()); ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState()); } // Tell the test server to release a pending RST and confirm // that the interrupt is received properly (for download resumption // testing). void ReleaseRSTAndConfirmInterruptForResume(DownloadItem* download) { scoped_ptr<DownloadTestObserver> rst_observer( CreateInterruptedWaiter(shell(), 1)); NavigateToURL(shell(), test_server()->GetURL("download-finish")); rst_observer->WaitForFinished(); EXPECT_EQ(DownloadItem::INTERRUPTED, download->GetState()); } // Confirm file status expected for the given location in a stream // provided by the resume test server. void ConfirmFileStatusForResume( DownloadItem* download, bool file_exists, int received_bytes, int total_bytes, const base::FilePath& expected_filename) { // expected_filename is only known if the file exists. ASSERT_EQ(file_exists, !expected_filename.empty()); EXPECT_EQ(received_bytes, download->GetReceivedBytes()); EXPECT_EQ(total_bytes, download->GetTotalBytes()); EXPECT_EQ(expected_filename.value(), download->GetFullPath().BaseName().value()); EXPECT_EQ(file_exists, (!download->GetFullPath().empty() && base::PathExists(download->GetFullPath()))); if (file_exists) { std::string file_contents; EXPECT_TRUE(base::ReadFileToString( download->GetFullPath(), &file_contents)); ASSERT_EQ(static_cast<size_t>(received_bytes), file_contents.size()); for (int i = 0; i < received_bytes; ++i) { EXPECT_EQ(static_cast<char>((i * 2 + 15) % 256), file_contents[i]) << "File contents diverged at position " << i << " for " << expected_filename.value(); if (static_cast<char>((i * 2 + 15) % 256) != file_contents[i]) return; } } } private: static void EnsureNoPendingDownloadJobsOnIO(bool* result) { if (URLRequestSlowDownloadJob::NumberOutstandingRequests()) *result = false; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure()); } // Location of the downloads directory for these tests base::ScopedTempDir downloads_directory_; scoped_ptr<TestShellDownloadManagerDelegate> test_delegate_; }; IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) { SetupEnsureNoPendingDownloads(); // Create a download, wait until it's started, and confirm // we're in the expected state. scoped_ptr<DownloadCreateObserver> observer( CreateInProgressWaiter(shell(), 1)); NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl)); observer->WaitForFinished(); std::vector<DownloadItem*> downloads; DownloadManagerForShell(shell())->GetAllDownloads(&downloads); ASSERT_EQ(1u, downloads.size()); ASSERT_EQ(DownloadItem::IN_PROGRESS, downloads[0]->GetState()); // Cancel the download and wait for download system quiesce. downloads[0]->Cancel(true); scoped_refptr<DownloadTestFlushObserver> flush_observer( new DownloadTestFlushObserver(DownloadManagerForShell(shell()))); flush_observer->WaitForFlush(); // Get the important info from other threads and check it. EXPECT_TRUE(EnsureNoPendingDownloads()); } // Check that downloading multiple (in this case, 2) files does not result in // corrupted files. IN_PROC_BROWSER_TEST_F(DownloadContentTest, MultiDownload) { SetupEnsureNoPendingDownloads(); // Create a download, wait until it's started, and confirm // we're in the expected state. scoped_ptr<DownloadCreateObserver> observer1( CreateInProgressWaiter(shell(), 1)); NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl)); observer1->WaitForFinished(); std::vector<DownloadItem*> downloads; DownloadManagerForShell(shell())->GetAllDownloads(&downloads); ASSERT_EQ(1u, downloads.size()); ASSERT_EQ(DownloadItem::IN_PROGRESS, downloads[0]->GetState()); DownloadItem* download1 = downloads[0]; // The only download. // Start the second download and wait until it's done. base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); GURL url(URLRequestMockHTTPJob::GetMockUrl(file)); // Download the file and wait. NavigateToURLAndWaitForDownload(shell(), url, DownloadItem::COMPLETE); // Should now have 2 items on the manager. downloads.clear(); DownloadManagerForShell(shell())->GetAllDownloads(&downloads); ASSERT_EQ(2u, downloads.size()); // We don't know the order of the downloads. DownloadItem* download2 = downloads[(download1 == downloads[0]) ? 1 : 0]; ASSERT_EQ(DownloadItem::IN_PROGRESS, download1->GetState()); ASSERT_EQ(DownloadItem::COMPLETE, download2->GetState()); // Allow the first request to finish. scoped_ptr<DownloadTestObserver> observer2(CreateWaiter(shell(), 1)); NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kFinishDownloadUrl)); observer2->WaitForFinished(); // Wait for the third request. EXPECT_EQ(1u, observer2->NumDownloadsSeenInState(DownloadItem::COMPLETE)); // Get the important info from other threads and check it. EXPECT_TRUE(EnsureNoPendingDownloads()); // The |DownloadItem|s should now be done and have the final file names. // Verify that the files have the expected data and size. // |file1| should be full of '*'s, and |file2| should be the same as the // source file. base::FilePath file1(download1->GetTargetFilePath()); size_t file_size1 = URLRequestSlowDownloadJob::kFirstDownloadSize + URLRequestSlowDownloadJob::kSecondDownloadSize; std::string expected_contents(file_size1, '*'); ASSERT_TRUE(VerifyFile(file1, expected_contents, file_size1)); base::FilePath file2(download2->GetTargetFilePath()); ASSERT_TRUE(base::ContentsEqual( file2, GetTestFilePath("download", "download-test.lib"))); } #if defined(ENABLE_PLUGINS) // Content served with a MIME type of application/octet-stream should be // downloaded even when a plugin can be found that handles the file type. IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadOctetStream) { const base::FilePath::CharType kTestFilePath[] = FILE_PATH_LITERAL("octet-stream.abc"); const char kTestPluginName[] = "TestPlugin"; const char kTestMimeType[] = "application/x-test-mime-type"; const char kTestFileType[] = "abc"; WebPluginInfo plugin_info; plugin_info.name = base::ASCIIToUTF16(kTestPluginName); plugin_info.mime_types.push_back( WebPluginMimeType(kTestMimeType, kTestFileType, "")); PluginServiceImpl::GetInstance()->RegisterInternalPlugin(plugin_info, false); // The following is served with a Content-Type of application/octet-stream. GURL url(URLRequestMockHTTPJob::GetMockUrl(base::FilePath(kTestFilePath))); NavigateToURLAndWaitForDownload(shell(), url, DownloadItem::COMPLETE); } #endif // Try to cancel just before we release the download file, by delaying final // rename callback. IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtFinalRename) { // Setup new factory. DownloadFileWithDelayFactory* file_factory = new DownloadFileWithDelayFactory(); DownloadManagerImpl* download_manager(DownloadManagerForShell(shell())); download_manager->SetDownloadFileFactoryForTesting( scoped_ptr<DownloadFileFactory>(file_factory).Pass()); // Create a download base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file)); // Wait until the first (intermediate file) rename and execute the callback. file_factory->WaitForSomeCallback(); std::vector<base::Closure> callbacks; file_factory->GetAllRenameCallbacks(&callbacks); ASSERT_EQ(1u, callbacks.size()); callbacks[0].Run(); callbacks.clear(); // Wait until the second (final) rename callback is posted. file_factory->WaitForSomeCallback(); file_factory->GetAllRenameCallbacks(&callbacks); ASSERT_EQ(1u, callbacks.size()); // Cancel it. std::vector<DownloadItem*> items; download_manager->GetAllDownloads(&items); ASSERT_EQ(1u, items.size()); items[0]->Cancel(true); RunAllPendingInMessageLoop(); // Check state. EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState()); // Run final rename callback. callbacks[0].Run(); callbacks.clear(); // Check state. EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState()); } // Try to cancel just after we release the download file, by delaying // in ShouldOpenDownload. IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtRelease) { DownloadManagerImpl* download_manager(DownloadManagerForShell(shell())); // Mark delegate for delayed open. GetDownloadManagerDelegate()->SetDelayedOpen(true); // Setup new factory. DownloadFileWithDelayFactory* file_factory = new DownloadFileWithDelayFactory(); download_manager->SetDownloadFileFactoryForTesting( scoped_ptr<DownloadFileFactory>(file_factory).Pass()); // Create a download base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file)); // Wait until the first (intermediate file) rename and execute the callback. file_factory->WaitForSomeCallback(); std::vector<base::Closure> callbacks; file_factory->GetAllRenameCallbacks(&callbacks); ASSERT_EQ(1u, callbacks.size()); callbacks[0].Run(); callbacks.clear(); // Wait until the second (final) rename callback is posted. file_factory->WaitForSomeCallback(); file_factory->GetAllRenameCallbacks(&callbacks); ASSERT_EQ(1u, callbacks.size()); // Call it. callbacks[0].Run(); callbacks.clear(); // Confirm download still IN_PROGRESS (internal state COMPLETING). std::vector<DownloadItem*> items; download_manager->GetAllDownloads(&items); EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); // Cancel the download; confirm cancel fails. ASSERT_EQ(1u, items.size()); items[0]->Cancel(true); EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); // Need to complete open test. std::vector<DownloadOpenDelayedCallback> delayed_callbacks; GetDownloadManagerDelegate()->GetDelayedCallbacks( &delayed_callbacks); ASSERT_EQ(1u, delayed_callbacks.size()); delayed_callbacks[0].Run(true); // *Now* the download should be complete. EXPECT_EQ(DownloadItem::COMPLETE, items[0]->GetState()); } // Try to shutdown with a download in progress to make sure shutdown path // works properly. IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownInProgress) { // Create a download that won't complete. scoped_ptr<DownloadCreateObserver> observer( CreateInProgressWaiter(shell(), 1)); NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl)); observer->WaitForFinished(); // Get the item. std::vector<DownloadItem*> items; DownloadManagerForShell(shell())->GetAllDownloads(&items); ASSERT_EQ(1u, items.size()); EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); // Shutdown the download manager and make sure we get the right // notifications in the right order. StrictMock<MockDownloadItemObserver> item_observer; items[0]->AddObserver(&item_observer); MockDownloadManagerObserver manager_observer( DownloadManagerForShell(shell())); // Don't care about ModelChanged() events. EXPECT_CALL(manager_observer, ModelChanged(_)) .WillRepeatedly(Return()); { InSequence notifications; EXPECT_CALL(manager_observer, MockManagerGoingDown( DownloadManagerForShell(shell()))) .WillOnce(Return()); EXPECT_CALL(item_observer, OnDownloadUpdated( AllOf(items[0], Property(&DownloadItem::GetState, DownloadItem::CANCELLED)))) .WillOnce(Return()); EXPECT_CALL(item_observer, OnDownloadDestroyed(items[0])) .WillOnce(Return()); } // See http://crbug.com/324525. If we have a refcount release/post task // race, the second post will stall the IO thread long enough so that we'll // lose the race and crash. The first stall is just to give the UI thread // a chance to get the second stall onto the IO thread queue after the cancel // message created by Shutdown and before the notification callback // created by the IO thread in canceling the request. BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&base::PlatformThread::Sleep, base::TimeDelta::FromMilliseconds(25))); DownloadManagerForShell(shell())->Shutdown(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&base::PlatformThread::Sleep, base::TimeDelta::FromMilliseconds(25))); items.clear(); } // Try to shutdown just after we release the download file, by delaying // release. IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownAtRelease) { DownloadManagerImpl* download_manager(DownloadManagerForShell(shell())); // Mark delegate for delayed open. GetDownloadManagerDelegate()->SetDelayedOpen(true); // Setup new factory. DownloadFileWithDelayFactory* file_factory = new DownloadFileWithDelayFactory(); download_manager->SetDownloadFileFactoryForTesting( scoped_ptr<DownloadFileFactory>(file_factory).Pass()); // Create a download base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file)); // Wait until the first (intermediate file) rename and execute the callback. file_factory->WaitForSomeCallback(); std::vector<base::Closure> callbacks; file_factory->GetAllRenameCallbacks(&callbacks); ASSERT_EQ(1u, callbacks.size()); callbacks[0].Run(); callbacks.clear(); // Wait until the second (final) rename callback is posted. file_factory->WaitForSomeCallback(); file_factory->GetAllRenameCallbacks(&callbacks); ASSERT_EQ(1u, callbacks.size()); // Call it. callbacks[0].Run(); callbacks.clear(); // Confirm download isn't complete yet. std::vector<DownloadItem*> items; DownloadManagerForShell(shell())->GetAllDownloads(&items); EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); // Cancel the download; confirm cancel fails anyway. ASSERT_EQ(1u, items.size()); items[0]->Cancel(true); EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); RunAllPendingInMessageLoop(); EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); MockDownloadItemObserver observer; items[0]->AddObserver(&observer); EXPECT_CALL(observer, OnDownloadDestroyed(items[0])); // Shutdown the download manager. Mostly this is confirming a lack of // crashes. DownloadManagerForShell(shell())->Shutdown(); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownload) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url = test_server()->GetURL( base::StringPrintf("rangereset?size=%d&rst_boundary=%d", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); ::testing::Mock::VerifyAndClearExpectations(&dm_observer); // Confirm resumption while in progress doesn't do anything. download->Resume(); ASSERT_EQ(GetSafeBufferChunk(), download->GetReceivedBytes()); ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState()); // Tell the server to send the RST and confirm the interrupt happens. ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); // Resume, confirming received bytes on resumption is correct. // Make sure no creation calls are included. EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(0); int initial_size = 0; DownloadUpdatedObserver initial_size_observer( download, base::Bind(&InitialSizeFilter, &initial_size)); download->Resume(); initial_size_observer.WaitForEvent(); EXPECT_EQ(GetSafeBufferChunk(), initial_size); ::testing::Mock::VerifyAndClearExpectations(&dm_observer); // and wait for expected data. WaitForData(download, GetSafeBufferChunk() * 2); // Tell the server to send the RST and confirm the interrupt happens. ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk() * 2, GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); // Resume and wait for completion. DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset"))); // Confirm resumption while complete doesn't do anything. download->Resume(); ASSERT_EQ(GetSafeBufferChunk() * 3, download->GetReceivedBytes()); ASSERT_EQ(DownloadItem::COMPLETE, download->GetState()); RunAllPendingInMessageLoop(); ASSERT_EQ(GetSafeBufferChunk() * 3, download->GetReceivedBytes()); ASSERT_EQ(DownloadItem::COMPLETE, download->GetState()); } // Confirm restart fallback happens if a range request is bounced. IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownloadNoRange) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); // Auto-restart if server doesn't handle ranges. GURL url = test_server()->GetURL( base::StringPrintf( // First download hits an RST, rest don't, no ranges. "rangereset?size=%d&rst_boundary=%d&" "token=NoRange&rst_limit=1&bounce_range", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); // Start the download and wait for first data chunk. DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); RecordingDownloadObserver recorder(download); ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset"))); static const RecordingDownloadObserver::RecordStruct expected_record[] = { // Result of RST {DownloadItem::INTERRUPTED, GetSafeBufferChunk()}, // Starting continuation {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()}, // Notification of receiving whole file. {DownloadItem::IN_PROGRESS, 0}, // Completion. {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3}, }; recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record)); } // Confirm restart fallback happens if a precondition is failed. IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownloadBadPrecondition) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url = test_server()->GetURL(base::StringPrintf( // First download hits an RST, rest don't, precondition fail. "rangereset?size=%d&rst_boundary=%d&" "token=BadPrecondition&rst_limit=1&fail_precondition=2", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); // Start the download and wait for first data chunk. DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); RecordingDownloadObserver recorder(download); ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); EXPECT_EQ("BadPrecondition2", download->GetETag()); DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset"))); EXPECT_EQ("BadPrecondition0", download->GetETag()); static const RecordingDownloadObserver::RecordStruct expected_record[] = { // Result of RST {DownloadItem::INTERRUPTED, GetSafeBufferChunk()}, // Starting continuation {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()}, // Server precondition fail. {DownloadItem::INTERRUPTED, 0}, // Notification of successful restart. {DownloadItem::IN_PROGRESS, 0}, // Completion. {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3}, }; recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record)); } // Confirm we don't try to resume if we don't have a verifier. IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownloadNoVerifiers) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url = test_server()->GetURL( base::StringPrintf( // First download hits an RST, rest don't, no verifiers. "rangereset?size=%d&rst_boundary=%d&" "token=NoRange&rst_limit=1&no_verifiers", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); // Start the download and wait for first data chunk. DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); RecordingDownloadObserver recorder(download); ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, false, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath()); DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset"))); static const RecordingDownloadObserver::RecordStruct expected_record[] = { // Result of RST {DownloadItem::INTERRUPTED, GetSafeBufferChunk()}, // Restart for lack of verifiers {DownloadItem::IN_PROGRESS, 0}, // Completion. {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3}, }; recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record)); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithDeletedFile) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url = test_server()->GetURL( base::StringPrintf( // First download hits an RST, rest don't "rangereset?size=%d&rst_boundary=%d&" "token=NoRange&rst_limit=1", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); // Start the download and wait for first data chunk. DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); RecordingDownloadObserver recorder(download); ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); // Delete the intermediate file. base::DeleteFile(download->GetFullPath(), false); DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset"))); static const RecordingDownloadObserver::RecordStruct expected_record[] = { // Result of RST {DownloadItem::INTERRUPTED, GetSafeBufferChunk()}, // Starting continuation {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()}, // Error because file isn't there. {DownloadItem::INTERRUPTED, 0}, // Restart. {DownloadItem::IN_PROGRESS, 0}, // Completion. {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3}, }; recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record)); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileInitError) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); GURL url(URLRequestMockHTTPJob::GetMockUrl(file)); // Setup the error injector. scoped_refptr<TestFileErrorInjector> injector( TestFileErrorInjector::Create(DownloadManagerForShell(shell()))); TestFileErrorInjector::FileErrorInfo err = { url.spec(), TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 0, DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE }; injector->AddError(err); injector->InjectErrors(); // Start and watch for interrupt. scoped_ptr<DownloadTestObserver> int_observer( CreateInterruptedWaiter(shell(), 1)); DownloadItem* download(StartDownloadAndReturnItem(url)); int_observer->WaitForFinished(); ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE, download->GetLastReason()); EXPECT_EQ(0, download->GetReceivedBytes()); EXPECT_TRUE(download->GetFullPath().empty()); EXPECT_TRUE(download->GetTargetFilePath().empty()); // We need to make sure that any cross-thread downloads communication has // quiesced before clearing and injecting the new errors, as the // InjectErrors() routine alters the currently in use download file // factory, which is a file thread object. RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); // Clear the old errors list. injector->ClearErrors(); injector->InjectErrors(); // Resume and watch completion. DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileIntermediateRenameError) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); GURL url(URLRequestMockHTTPJob::GetMockUrl(file)); // Setup the error injector. scoped_refptr<TestFileErrorInjector> injector( TestFileErrorInjector::Create(DownloadManagerForShell(shell()))); TestFileErrorInjector::FileErrorInfo err = { url.spec(), TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY, 0, DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE }; injector->AddError(err); injector->InjectErrors(); // Start and watch for interrupt. scoped_ptr<DownloadTestObserver> int_observer( CreateInterruptedWaiter(shell(), 1)); DownloadItem* download(StartDownloadAndReturnItem(url)); int_observer->WaitForFinished(); ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE, download->GetLastReason()); EXPECT_TRUE(download->GetFullPath().empty()); // Target path will have been set after file name determination. GetFullPath() // being empty is sufficient to signal that filename determination needs to be // redone. EXPECT_FALSE(download->GetTargetFilePath().empty()); // We need to make sure that any cross-thread downloads communication has // quiesced before clearing and injecting the new errors, as the // InjectErrors() routine alters the currently in use download file // factory, which is a file thread object. RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); // Clear the old errors list. injector->ClearErrors(); injector->InjectErrors(); // Resume and watch completion. DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileFinalRenameError) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); base::FilePath file(FILE_PATH_LITERAL("download-test.lib")); GURL url(URLRequestMockHTTPJob::GetMockUrl(file)); // Setup the error injector. scoped_refptr<TestFileErrorInjector> injector( TestFileErrorInjector::Create(DownloadManagerForShell(shell()))); DownloadManagerForShell(shell())->RemoveAllDownloads(); TestFileErrorInjector::FileErrorInfo err = { url.spec(), TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE, 0, DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE }; injector->AddError(err); injector->InjectErrors(); // Start and watch for interrupt. scoped_ptr<DownloadTestObserver> int_observer( CreateInterruptedWaiter(shell(), 1)); DownloadItem* download(StartDownloadAndReturnItem(url)); int_observer->WaitForFinished(); ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE, download->GetLastReason()); EXPECT_TRUE(download->GetFullPath().empty()); // Target path should still be intact. EXPECT_FALSE(download->GetTargetFilePath().empty()); // We need to make sure that any cross-thread downloads communication has // quiesced before clearing and injecting the new errors, as the // InjectErrors() routine alters the currently in use download file // factory, which is a file thread object. RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); // Clear the old errors list. injector->ClearErrors(); injector->InjectErrors(); // Resume and watch completion. DownloadUpdatedObserver completion_observer( download, base::Bind(DownloadCompleteFilter)); download->Resume(); completion_observer.WaitForEvent(); EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); } // An interrupted download should remove the intermediate file when it is // cancelled. IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelInterruptedDownload) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url1 = test_server()->GetURL( base::StringPrintf("rangereset?size=%d&rst_boundary=%d", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); DownloadItem* download(StartDownloadAndReturnItem(url1)); WaitForData(download, GetSafeBufferChunk()); ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); base::FilePath intermediate_path(download->GetFullPath()); ASSERT_FALSE(intermediate_path.empty()); EXPECT_TRUE(base::PathExists(intermediate_path)); download->Cancel(true /* user_cancel */); RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); // The intermediate file should now be gone. EXPECT_FALSE(base::PathExists(intermediate_path)); EXPECT_TRUE(download->GetFullPath().empty()); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveDownload) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); // An interrupted download should remove the intermediate file when it is // removed. { GURL url1 = test_server()->GetURL( base::StringPrintf("rangereset?size=%d&rst_boundary=%d", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); DownloadItem* download(StartDownloadAndReturnItem(url1)); WaitForData(download, GetSafeBufferChunk()); ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); base::FilePath intermediate_path(download->GetFullPath()); ASSERT_FALSE(intermediate_path.empty()); EXPECT_TRUE(base::PathExists(intermediate_path)); download->Remove(); RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); // The intermediate file should now be gone. EXPECT_FALSE(base::PathExists(intermediate_path)); } // A completed download shouldn't delete the downloaded file when it is // removed. { // Start the second download and wait until it's done. base::FilePath file2(FILE_PATH_LITERAL("download-test.lib")); GURL url2(URLRequestMockHTTPJob::GetMockUrl(file2)); scoped_ptr<DownloadTestObserver> completion_observer( CreateWaiter(shell(), 1)); DownloadItem* download(StartDownloadAndReturnItem(url2)); completion_observer->WaitForFinished(); // The target path should exist. base::FilePath target_path(download->GetTargetFilePath()); EXPECT_TRUE(base::PathExists(target_path)); download->Remove(); RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); // The file should still exist. EXPECT_TRUE(base::PathExists(target_path)); } } IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveResumingDownload) { SetupEnsureNoPendingDownloads(); CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url = test_server()->GetURL( base::StringPrintf("rangereset?size=%d&rst_boundary=%d", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); ::testing::Mock::VerifyAndClearExpectations(&dm_observer); // Tell the server to send the RST and confirm the interrupt happens. ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); base::FilePath intermediate_path(download->GetFullPath()); ASSERT_FALSE(intermediate_path.empty()); EXPECT_TRUE(base::PathExists(intermediate_path)); // Resume and remove download. We expect only a single OnDownloadCreated() // call, and that's for the second download created below. EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); download->Resume(); download->Remove(); // The intermediate file should now be gone. RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); EXPECT_FALSE(base::PathExists(intermediate_path)); // Start the second download and wait until it's done. The test server is // single threaded. The response to this download request should follow the // response to the previous resumption request. GURL url2(test_server()->GetURL("rangereset?size=100&rst_limit=0&token=x")); NavigateToURLAndWaitForDownload(shell(), url2, DownloadItem::COMPLETE); EXPECT_TRUE(EnsureNoPendingDownloads()); } IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelResumingDownload) { SetupEnsureNoPendingDownloads(); CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableDownloadResumption); ASSERT_TRUE(test_server()->Start()); GURL url = test_server()->GetURL( base::StringPrintf("rangereset?size=%d&rst_boundary=%d", GetSafeBufferChunk() * 3, GetSafeBufferChunk())); MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); DownloadItem* download(StartDownloadAndReturnItem(url)); WaitForData(download, GetSafeBufferChunk()); ::testing::Mock::VerifyAndClearExpectations(&dm_observer); // Tell the server to send the RST and confirm the interrupt happens. ReleaseRSTAndConfirmInterruptForResume(download); ConfirmFileStatusForResume( download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3, base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload"))); base::FilePath intermediate_path(download->GetFullPath()); ASSERT_FALSE(intermediate_path.empty()); EXPECT_TRUE(base::PathExists(intermediate_path)); // Resume and cancel download. We expect only a single OnDownloadCreated() // call, and that's for the second download created below. EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); download->Resume(); download->Cancel(true); // The intermediate file should now be gone. RunAllPendingInMessageLoop(BrowserThread::FILE); RunAllPendingInMessageLoop(); EXPECT_FALSE(base::PathExists(intermediate_path)); EXPECT_TRUE(download->GetFullPath().empty()); // Start the second download and wait until it's done. The test server is // single threaded. The response to this download request should follow the // response to the previous resumption request. GURL url2(test_server()->GetURL("rangereset?size=100&rst_limit=0&token=x")); NavigateToURLAndWaitForDownload(shell(), url2, DownloadItem::COMPLETE); EXPECT_TRUE(EnsureNoPendingDownloads()); } // Check that the cookie policy is correctly updated when downloading a file // that redirects cross origin. IN_PROC_BROWSER_TEST_F(DownloadContentTest, CookiePolicy) { ASSERT_TRUE(test_server()->Start()); net::HostPortPair host_port = test_server()->host_port_pair(); DCHECK_EQ(host_port.host(), std::string("127.0.0.1")); // Block third-party cookies. ShellNetworkDelegate::SetAcceptAllCookies(false); // |url| redirects to a different origin |download| which tries to set a // cookie. std::string download(base::StringPrintf( "http://localhost:%d/set-cookie?A=B", host_port.port())); GURL url(test_server()->GetURL("server-redirect?" + download)); // Download the file. SetupEnsureNoPendingDownloads(); scoped_ptr<DownloadUrlParameters> dl_params( DownloadUrlParameters::FromWebContents(shell()->web_contents(), url)); scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell(), 1)); DownloadManagerForShell(shell())->DownloadUrl(dl_params.Pass()); observer->WaitForFinished(); // Get the important info from other threads and check it. EXPECT_TRUE(EnsureNoPendingDownloads()); std::vector<DownloadItem*> downloads; DownloadManagerForShell(shell())->GetAllDownloads(&downloads); ASSERT_EQ(1u, downloads.size()); ASSERT_EQ(DownloadItem::COMPLETE, downloads[0]->GetState()); // Check that the cookies were correctly set. EXPECT_EQ("A=B", content::GetCookies(shell()->web_contents()->GetBrowserContext(), GURL(download))); } // A filename suggestion specified via a @download attribute should not be // effective if the final download URL is in another origin from the original // download URL. IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeCrossOriginRedirect) { EmbeddedTestServer origin_one; EmbeddedTestServer origin_two; ASSERT_TRUE(origin_one.InitializeAndWaitUntilReady()); ASSERT_TRUE(origin_two.InitializeAndWaitUntilReady()); // The download-attribute.html page contains an anchor element whose href is // set to the value of the query parameter (specified as |target| in the URL // below). The suggested filename for the anchor is 'suggested-filename'. When // the page is loaded, a script simulates a click on the anchor, triggering a // download of the target URL. // // We construct two test servers; origin_one and origin_two. Once started, the // server URLs will differ by the port number. Therefore they will be in // different origins. GURL download_url = origin_one.GetURL("/ping"); GURL referrer_url = origin_one.GetURL( std::string("/download-attribute.html?target=") + download_url.spec()); // <origin_one>/download-attribute.html initiates a download of // <origin_one>/ping, which redirects to <origin_two>/download. origin_one.ServeFilesFromDirectory(GetTestFilePath("download", "")); origin_one.RegisterRequestHandler( CreateRedirectHandler("/ping", origin_two.GetURL("/download"))); origin_two.RegisterRequestHandler(CreateBasicResponseHandler( "/download", "application/octet-stream", "Hello")); NavigateToURLAndWaitForDownload( shell(), referrer_url, DownloadItem::COMPLETE); std::vector<DownloadItem*> downloads; DownloadManagerForShell(shell())->GetAllDownloads(&downloads); ASSERT_EQ(1u, downloads.size()); EXPECT_EQ(FILE_PATH_LITERAL("download"), downloads[0]->GetTargetFilePath().BaseName().value()); ASSERT_TRUE(origin_one.ShutdownAndWaitUntilComplete()); ASSERT_TRUE(origin_two.ShutdownAndWaitUntilComplete()); } // A filename suggestion specified via a @download attribute should be effective // if the final download URL is in the same origin as the initial download URL. // Test that this holds even if there are cross origin redirects in the middle // of the redirect chain. IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeSameOriginRedirect) { EmbeddedTestServer origin_one; EmbeddedTestServer origin_two; ASSERT_TRUE(origin_one.InitializeAndWaitUntilReady()); ASSERT_TRUE(origin_two.InitializeAndWaitUntilReady()); // The download-attribute.html page contains an anchor element whose href is // set to the value of the query parameter (specified as |target| in the URL // below). The suggested filename for the anchor is 'suggested-filename'. When // the page is loaded, a script simulates a click on the anchor, triggering a // download of the target URL. // // We construct two test servers; origin_one and origin_two. Once started, the // server URLs will differ by the port number. Therefore they will be in // different origins. GURL download_url = origin_one.GetURL("/ping"); GURL referrer_url = origin_one.GetURL( std::string("/download-attribute.html?target=") + download_url.spec()); origin_one.ServeFilesFromDirectory(GetTestFilePath("download", "")); // <origin_one>/download-attribute.html initiates a download of // <origin_one>/ping, which redirects to <origin_two>/pong, and then finally // to <origin_one>/download. origin_one.RegisterRequestHandler( CreateRedirectHandler("/ping", origin_two.GetURL("/pong"))); origin_two.RegisterRequestHandler( CreateRedirectHandler("/pong", origin_one.GetURL("/download"))); origin_one.RegisterRequestHandler(CreateBasicResponseHandler( "/download", "application/octet-stream", "Hello")); NavigateToURLAndWaitForDownload( shell(), referrer_url, DownloadItem::COMPLETE); std::vector<DownloadItem*> downloads; DownloadManagerForShell(shell())->GetAllDownloads(&downloads); ASSERT_EQ(1u, downloads.size()); EXPECT_EQ(FILE_PATH_LITERAL("suggested-filename"), downloads[0]->GetTargetFilePath().BaseName().value()); ASSERT_TRUE(origin_one.ShutdownAndWaitUntilComplete()); ASSERT_TRUE(origin_two.ShutdownAndWaitUntilComplete()); } // The file empty.bin is served with a MIME type of application/octet-stream. // The content body is empty. Make sure this case is handled properly and we // don't regress on http://crbug.com/320394. IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadGZipWithNoContent) { EmbeddedTestServer test_server; ASSERT_TRUE(test_server.InitializeAndWaitUntilReady()); GURL url = test_server.GetURL("/empty.bin"); test_server.ServeFilesFromDirectory(GetTestFilePath("download", "")); NavigateToURLAndWaitForDownload(shell(), url, DownloadItem::COMPLETE); // That's it. This should work without crashing. } } // namespace content