| // 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 <stddef.h> |
| #include <stdint.h> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_helpers.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/format_macros.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.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 "build/build_config.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/loader/resource_dispatcher_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/power_save_blocker.h" |
| #include "content/public/browser/resource_dispatcher_host_delegate.h" |
| #include "content/public/browser/resource_throttle.h" |
| #include "content/public/common/content_features.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_download_request_handler.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 "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/url_request/url_request_mock_http_job.h" |
| #include "net/test/url_request/url_request_slow_download_job.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| #if defined(ENABLE_PLUGINS) |
| #include "content/browser/plugin_service_impl.h" |
| #endif |
| |
| 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(const DownloadSaveInfo& save_info, |
| const base::FilePath& default_download_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| base::File file, |
| scoped_ptr<ByteStreamReader> stream, |
| const net::BoundNetLog& bound_net_log, |
| scoped_ptr<PowerSaveBlocker> power_save_blocker, |
| base::WeakPtr<DownloadDestinationObserver> observer, |
| base::WeakPtr<DownloadFileWithDelayFactory> owner); |
| |
| ~DownloadFileWithDelay() override; |
| |
| // Wraps DownloadFileImpl::Rename* and intercepts the return callback, |
| // storing it in the factory that produced this object for later |
| // retrieval. |
| void RenameAndUniquify(const base::FilePath& full_path, |
| const RenameCompletionCallback& callback) override; |
| 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(); |
| ~DownloadFileWithDelayFactory() override; |
| |
| // DownloadFileFactory interface. |
| DownloadFile* CreateFile( |
| const DownloadSaveInfo& save_info, |
| const base::FilePath& default_download_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| base::File file, |
| 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: |
| std::vector<base::Closure> rename_callbacks_; |
| bool waiting_; |
| base::WeakPtrFactory<DownloadFileWithDelayFactory> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelayFactory); |
| }; |
| |
| DownloadFileWithDelay::DownloadFileWithDelay( |
| const DownloadSaveInfo& save_info, |
| const base::FilePath& default_download_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| base::File file, |
| 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, |
| default_download_directory, |
| url, |
| referrer_url, |
| calculate_hash, |
| std::move(file), |
| std::move(stream), |
| bound_net_log, |
| observer), |
| owner_(owner) {} |
| |
| DownloadFileWithDelay::~DownloadFileWithDelay() {} |
| |
| void DownloadFileWithDelay::RenameAndUniquify( |
| const base::FilePath& full_path, |
| const RenameCompletionCallback& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| DownloadFileImpl::RenameAndUniquify( |
| full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper, |
| owner_, callback)); |
| } |
| |
| void DownloadFileWithDelay::RenameAndAnnotate( |
| const base::FilePath& full_path, const RenameCompletionCallback& callback) { |
| DCHECK_CURRENTLY_ON(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_CURRENTLY_ON(BrowserThread::UI); |
| if (!factory) |
| return; |
| factory->AddRenameCallback(base::Bind(original_callback, reason, path)); |
| } |
| |
| DownloadFileWithDelayFactory::DownloadFileWithDelayFactory() |
| : waiting_(false), |
| weak_ptr_factory_(this) {} |
| |
| DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {} |
| |
| DownloadFile* DownloadFileWithDelayFactory::CreateFile( |
| const DownloadSaveInfo& save_info, |
| const base::FilePath& default_download_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| base::File file, |
| scoped_ptr<ByteStreamReader> stream, |
| const net::BoundNetLog& bound_net_log, |
| base::WeakPtr<DownloadDestinationObserver> observer) { |
| scoped_ptr<PowerSaveBlocker> psb(PowerSaveBlocker::Create( |
| PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, |
| PowerSaveBlocker::kReasonOther, "Download in progress")); |
| return new DownloadFileWithDelay( |
| save_info, default_download_directory, url, referrer_url, calculate_hash, |
| std::move(file), std::move(stream), bound_net_log, std::move(psb), |
| observer, weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| rename_callbacks_.push_back(callback); |
| if (waiting_) |
| base::MessageLoopForUI::current()->QuitWhenIdle(); |
| } |
| |
| void DownloadFileWithDelayFactory::GetAllRenameCallbacks( |
| std::vector<base::Closure>* results) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| results->swap(rename_callbacks_); |
| } |
| |
| void DownloadFileWithDelayFactory::WaitForSomeCallback() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (rename_callbacks_.empty()) { |
| waiting_ = true; |
| RunMessageLoop(); |
| waiting_ = false; |
| } |
| } |
| |
| class CountingDownloadFile : public DownloadFileImpl { |
| public: |
| CountingDownloadFile(const DownloadSaveInfo& save_info, |
| const base::FilePath& default_downloads_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| base::File file, |
| scoped_ptr<ByteStreamReader> stream, |
| const net::BoundNetLog& bound_net_log, |
| scoped_ptr<PowerSaveBlocker> power_save_blocker, |
| base::WeakPtr<DownloadDestinationObserver> observer) |
| : DownloadFileImpl(save_info, |
| default_downloads_directory, |
| url, |
| referrer_url, |
| calculate_hash, |
| std::move(file), |
| std::move(stream), |
| bound_net_log, |
| observer) {} |
| |
| ~CountingDownloadFile() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| active_files_--; |
| } |
| |
| void Initialize(const InitializeCallback& callback) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| active_files_++; |
| return DownloadFileImpl::Initialize(callback); |
| } |
| |
| static void GetNumberActiveFiles(int* result) { |
| DCHECK_CURRENTLY_ON(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()->QuitWhenIdleClosure()); |
| 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() {} |
| ~CountingDownloadFileFactory() override {} |
| |
| // DownloadFileFactory interface. |
| DownloadFile* CreateFile( |
| const DownloadSaveInfo& save_info, |
| const base::FilePath& default_downloads_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| base::File file, |
| scoped_ptr<ByteStreamReader> stream, |
| const net::BoundNetLog& bound_net_log, |
| base::WeakPtr<DownloadDestinationObserver> observer) override { |
| scoped_ptr<PowerSaveBlocker> psb(PowerSaveBlocker::Create( |
| PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, |
| PowerSaveBlocker::kReasonOther, "Download in progress")); |
| return new CountingDownloadFile(save_info, default_downloads_directory, url, |
| referrer_url, calculate_hash, |
| std::move(file), std::move(stream), |
| bound_net_log, std::move(psb), observer); |
| } |
| }; |
| |
| class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate { |
| public: |
| TestShellDownloadManagerDelegate() |
| : delay_download_open_(false) {} |
| ~TestShellDownloadManagerDelegate() override {} |
| |
| bool ShouldOpenDownload( |
| DownloadItem* item, |
| const DownloadOpenDelayedCallback& callback) override { |
| if (delay_download_open_) { |
| delayed_callbacks_.push_back(callback); |
| return false; |
| } |
| return true; |
| } |
| |
| bool GenerateFileHash() override { 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_; |
| }; |
| |
| // Get the next created download. |
| class DownloadCreateObserver : DownloadManager::Observer { |
| public: |
| DownloadCreateObserver(DownloadManager* manager) |
| : manager_(manager), item_(NULL) { |
| manager_->AddObserver(this); |
| } |
| |
| ~DownloadCreateObserver() override { |
| if (manager_) |
| manager_->RemoveObserver(this); |
| manager_ = NULL; |
| } |
| |
| void ManagerGoingDown(DownloadManager* manager) override { |
| DCHECK_EQ(manager_, manager); |
| manager_->RemoveObserver(this); |
| manager_ = NULL; |
| } |
| |
| void OnDownloadCreated(DownloadManager* manager, |
| DownloadItem* download) override { |
| if (!item_) |
| item_ = download; |
| |
| if (!completion_closure_.is_null()) |
| base::ResetAndReturn(&completion_closure_).Run(); |
| } |
| |
| DownloadItem* WaitForFinished() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!item_) { |
| base::RunLoop run_loop; |
| completion_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| return item_; |
| } |
| |
| private: |
| DownloadManager* manager_; |
| DownloadItem* item_; |
| base::Closure completion_closure_; |
| }; |
| |
| bool IsDownloadInState(DownloadItem::DownloadState state, DownloadItem* item) { |
| return item->GetState() == state; |
| } |
| |
| // 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 std::move(response); |
| } |
| |
| // Creates a request handler for EmbeddedTestServer that responds with a HTTP |
| // 302 redirect if the request URL matches |relative_url|. |
| net::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 base::StringPairs& headers, |
| 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); |
| for (const auto& pair : headers) |
| response->AddCustomHeader(pair.first, pair.second); |
| response->set_content_type(content_type); |
| response->set_content(body); |
| } |
| return std::move(response); |
| } |
| |
| // Creates a request handler for an EmbeddedTestServer that response with an |
| // HTTP 200 status code, a Content-Type header and a body. |
| net::EmbeddedTestServer::HandleRequestCallback CreateBasicResponseHandler( |
| const std::string& relative_url, |
| const base::StringPairs& headers, |
| const std::string& content_type, |
| const std::string& body) { |
| return base::Bind(&HandleRequestAndSendBasicResponse, relative_url, headers, |
| content_type, body); |
| } |
| |
| // Helper class to "flatten" handling of |
| // TestDownloadRequestHandler::OnStartHandler. |
| class TestRequestStartHandler { |
| public: |
| // Construct an OnStartHandler that can be set as the on_start_handler for |
| // TestDownloadRequestHandler::Parameters. |
| TestDownloadRequestHandler::OnStartHandler GetOnStartHandler() { |
| EXPECT_FALSE(used_) << "GetOnStartHandler() should only be called once for " |
| "an instance of TestRequestStartHandler."; |
| used_ = true; |
| return base::Bind(&TestRequestStartHandler::OnStartHandler, |
| base::Unretained(this)); |
| } |
| |
| // Wait until the OnStartHandlers returned in a prior call to |
| // GetOnStartHandler() is invoked. |
| void WaitForCallback() { |
| if (response_callback_.is_null()) |
| run_loop_.Run(); |
| } |
| |
| // Respond to the OnStartHandler() invocation using |headers| and |error|. |
| void RespondWith(const std::string& headers, net::Error error) { |
| ASSERT_FALSE(response_callback_.is_null()); |
| response_callback_.Run(headers, error); |
| } |
| |
| // Return the headers returned from the invocation of OnStartHandler. |
| const net::HttpRequestHeaders& headers() const { |
| EXPECT_FALSE(response_callback_.is_null()); |
| return request_headers_; |
| } |
| |
| private: |
| void OnStartHandler(const net::HttpRequestHeaders& headers, |
| const TestDownloadRequestHandler::OnStartResponseCallback& |
| response_callback) { |
| request_headers_ = headers; |
| response_callback_ = response_callback; |
| if (run_loop_.running()) |
| run_loop_.Quit(); |
| } |
| |
| bool used_ = false; |
| base::RunLoop run_loop_; |
| net::HttpRequestHeaders request_headers_; |
| TestDownloadRequestHandler::OnStartResponseCallback response_callback_; |
| }; |
| |
| class DownloadContentTest : public ContentBrowserTest { |
| protected: |
| 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(&net::URLRequestSlowDownloadJob::AddUrlHandler)); |
| base::FilePath mock_base(GetTestFilePath("download", "")); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind( |
| &net::URLRequestMockHTTPJob::AddUrlHandlers, mock_base, |
| make_scoped_refptr(content::BrowserThread::GetBlockingPool()))); |
| } |
| |
| 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); |
| } |
| |
| void WaitForInterrupt(DownloadItem* download) { |
| DownloadUpdatedObserver( |
| download, base::Bind(&IsDownloadInState, DownloadItem::INTERRUPTED)) |
| .WaitForEvent(); |
| } |
| |
| void WaitForInProgress(DownloadItem* download) { |
| DownloadUpdatedObserver( |
| download, base::Bind(&IsDownloadInState, DownloadItem::IN_PROGRESS)) |
| .WaitForEvent(); |
| } |
| |
| void WaitForCompletion(DownloadItem* download) { |
| DownloadUpdatedObserver( |
| download, base::Bind(&IsDownloadInState, DownloadItem::COMPLETE)) |
| .WaitForEvent(); |
| } |
| |
| void WaitForCancel(DownloadItem* download) { |
| DownloadUpdatedObserver( |
| download, base::Bind(&IsDownloadInState, DownloadItem::CANCELLED)) |
| .WaitForEvent(); |
| } |
| |
| // Note: Cannot be used with other alternative DownloadFileFactorys |
| void SetupEnsureNoPendingDownloads() { |
| DownloadManagerForShell(shell())->SetDownloadFileFactoryForTesting( |
| scoped_ptr<DownloadFileFactory>(new CountingDownloadFileFactory())); |
| } |
| |
| 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_t 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(Shell* shell, GURL url) { |
| scoped_ptr<DownloadCreateObserver> observer( |
| new DownloadCreateObserver(DownloadManagerForShell(shell))); |
| shell->LoadURL(url); |
| return observer->WaitForFinished(); |
| } |
| |
| static void ReadAndVerifyFileContents(int seed, |
| int64_t expected_size, |
| const base::FilePath& path) { |
| base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(file.IsValid()); |
| int64_t file_length = file.GetLength(); |
| ASSERT_EQ(expected_size, file_length); |
| |
| const int64_t kBufferSize = 64 * 1024; |
| std::vector<char> pattern; |
| std::vector<char> data; |
| pattern.resize(kBufferSize); |
| data.resize(kBufferSize); |
| for (int64_t offset = 0; offset < file_length;) { |
| int bytes_read = file.Read(offset, &data.front(), kBufferSize); |
| ASSERT_LT(0, bytes_read); |
| ASSERT_GE(kBufferSize, bytes_read); |
| |
| TestDownloadRequestHandler::GetPatternBytes(seed, offset, bytes_read, |
| &pattern.front()); |
| ASSERT_EQ(0, memcmp(&pattern.front(), &data.front(), bytes_read)) |
| << "Comparing block at offset " << offset << " and length " |
| << bytes_read; |
| offset += bytes_read; |
| } |
| } |
| |
| private: |
| static void EnsureNoPendingDownloadJobsOnIO(bool* result) { |
| if (net::URLRequestSlowDownloadJob::NumberOutstandingRequests()) |
| *result = false; |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::MessageLoop::QuitWhenIdleClosure()); |
| } |
| |
| // Location of the downloads directory for these tests |
| base::ScopedTempDir downloads_directory_; |
| scoped_ptr<TestShellDownloadManagerDelegate> test_delegate_; |
| }; |
| |
| // Parameters for DownloadResumptionContentTest. |
| enum class DownloadResumptionTestType { |
| RESUME_WITH_RENDERER, // Resume() is called while the originating WebContents |
| // is still alive. |
| RESUME_WITHOUT_RENDERER // Resume() is called after the originating |
| // WebContents has been deleted. |
| }; |
| |
| // Parameterized test for download resumption. Tests using this fixure will be |
| // run once with RESUME_WITH_RENDERER and once with RESUME_WITHOUT_RENDERER. |
| // Use initiator_shell_for_resumption() to retrieve the Shell object that should |
| // be used as the originator for the initial download. Prior to calling |
| // Resume(), call PrepareToResume() which will cause the originating Shell to be |
| // deleted if the test parameter is RESUME_WITHOUT_RENDERER. |
| class DownloadResumptionContentTest |
| : public DownloadContentTest, |
| public ::testing::WithParamInterface<DownloadResumptionTestType> { |
| public: |
| void SetUpOnMainThread() override { |
| base::FeatureList::ClearInstanceForTesting(); |
| scoped_ptr<base::FeatureList> feature_list(new base::FeatureList); |
| feature_list->InitializeFromCommandLine( |
| features::kDownloadResumption.name, std::string()); |
| base::FeatureList::SetInstance(std::move(feature_list)); |
| |
| DownloadContentTest::SetUpOnMainThread(); |
| |
| if (GetParam() == DownloadResumptionTestType::RESUME_WITHOUT_RENDERER) |
| initiator_shell_for_resumption_ = CreateBrowser(); |
| else |
| initiator_shell_for_resumption_ = shell(); |
| |
| ASSERT_EQ(DownloadManagerForShell(shell()), |
| DownloadManagerForShell(initiator_shell_for_resumption())); |
| } |
| |
| // Shell to use for initiating a download. Only valid *before* |
| // PrepareToResume() is called. |
| Shell* initiator_shell_for_resumption() const { |
| DCHECK(initiator_shell_for_resumption_); |
| return initiator_shell_for_resumption_; |
| } |
| |
| // Should be called once before calling DownloadItem::Resume() on an |
| // interrupted download. This may cause initiator_shell_for_resumption() to |
| // become invalidated. |
| void PrepareToResume() { |
| if (GetParam() == DownloadResumptionTestType::RESUME_WITH_RENDERER) |
| return; |
| DCHECK_NE(initiator_shell_for_resumption(), shell()); |
| DCHECK(initiator_shell_for_resumption()); |
| initiator_shell_for_resumption_->Close(); |
| initiator_shell_for_resumption_ = nullptr; |
| } |
| |
| private: |
| Shell* initiator_shell_for_resumption_ = nullptr; |
| }; |
| |
| INSTANTIATE_TEST_CASE_P( |
| _, |
| DownloadResumptionContentTest, |
| ::testing::Values(DownloadResumptionTestType::RESUME_WITH_RENDERER, |
| DownloadResumptionTestType::RESUME_WITHOUT_RENDERER)); |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) { |
| SetupEnsureNoPendingDownloads(); |
| |
| // Create a download, wait until it's started, and confirm |
| // we're in the expected state. |
| DownloadItem* download = StartDownloadAndReturnItem( |
| shell(), GURL(net::URLRequestSlowDownloadJob::kUnknownSizeUrl)); |
| ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState()); |
| |
| // Cancel the download and wait for download system quiesce. |
| download->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. |
| DownloadItem* download1 = StartDownloadAndReturnItem( |
| shell(), GURL(net::URLRequestSlowDownloadJob::kUnknownSizeUrl)); |
| ASSERT_EQ(DownloadItem::IN_PROGRESS, download1->GetState()); |
| |
| // Start the second download and wait until it's done. |
| GURL url(net::URLRequestMockHTTPJob::GetMockUrl("download-test.lib")); |
| DownloadItem* download2 = StartDownloadAndReturnItem(shell(), url); |
| WaitForCompletion(download2); |
| |
| 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(net::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 = net::URLRequestSlowDownloadJob::kFirstDownloadSize + |
| net::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 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, "")); |
| plugin_info.type = WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS; |
| PluginServiceImpl::GetInstance()->RegisterInternalPlugin(plugin_info, false); |
| |
| // The following is served with a Content-Type of application/octet-stream. |
| GURL url( |
| net::URLRequestMockHTTPJob::GetMockUrl("octet-stream.abc")); |
| 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)); |
| |
| // Create a download |
| NavigateToURL(shell(), |
| net::URLRequestMockHTTPJob::GetMockUrl("download-test.lib")); |
| |
| // 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)); |
| |
| // Create a download |
| NavigateToURL(shell(), |
| net::URLRequestMockHTTPJob::GetMockUrl("download-test.lib")); |
| |
| // 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()); |
| } |
| |
| #if defined(OS_ANDROID) |
| // Flaky on android: https://crbug.com/324525 |
| #define MAYBE_ShutdownInProgress DISABLED_ShutdownInProgress |
| #else |
| #define MAYBE_ShutdownInProgress ShutdownInProgress |
| #endif |
| |
| // Try to shutdown with a download in progress to make sure shutdown path |
| // works properly. |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, MAYBE_ShutdownInProgress) { |
| // Create a download that won't complete. |
| DownloadItem* download = StartDownloadAndReturnItem( |
| shell(), GURL(net::URLRequestSlowDownloadJob::kUnknownSizeUrl)); |
| |
| EXPECT_EQ(DownloadItem::IN_PROGRESS, download->GetState()); |
| |
| // Shutdown the download manager and make sure we get the right |
| // notifications in the right order. |
| StrictMock<MockDownloadItemObserver> item_observer; |
| download->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(download, Property(&DownloadItem::GetState, |
| DownloadItem::CANCELLED)))) |
| .WillOnce(Return()); |
| EXPECT_CALL(item_observer, OnDownloadDestroyed(download)) |
| .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))); |
| } |
| |
| // 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)); |
| |
| // Create a download |
| NavigateToURL(shell(), |
| net::URLRequestMockHTTPJob::GetMockUrl("download-test.lib")); |
| |
| // 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(); |
| } |
| |
| // Test resumption with a response that contains strong validators. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, StrongValidators) { |
| TestDownloadRequestHandler request_handler; |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| const TestDownloadRequestHandler::InjectedError interruption = |
| parameters.injected_errors.front(); |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| ASSERT_EQ(interruption.offset, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| ASSERT_EQ(parameters.size, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( |
| parameters.pattern_generator_seed, parameters.size, |
| download->GetTargetFilePath())); |
| |
| // Characterization risk: The next portion of the test examines the requests |
| // that were sent out while downloading our resource. These requests |
| // correspond to the requests that were generated by the browser and the |
| // downloads system and may change as implementation details change. |
| TestDownloadRequestHandler::CompletedRequests requests; |
| request_handler.GetCompletedRequestInfo(&requests); |
| |
| ASSERT_EQ(2u, requests.size()); |
| |
| // The first request only transferrs bytes up until the interruption point. |
| EXPECT_EQ(interruption.offset, requests[0].transferred_byte_count); |
| |
| // The next request should only have transferred the remainder of the |
| // resource. |
| EXPECT_EQ(parameters.size - interruption.offset, |
| requests[1].transferred_byte_count); |
| |
| std::string value; |
| ASSERT_TRUE(requests[1].request_headers.GetHeader( |
| net::HttpRequestHeaders::kIfRange, &value)); |
| EXPECT_EQ(parameters.etag, value); |
| |
| ASSERT_TRUE(requests[1].request_headers.GetHeader( |
| net::HttpRequestHeaders::kRange, &value)); |
| EXPECT_EQ(base::StringPrintf("bytes=%" PRId64 "-", interruption.offset), |
| value); |
| } |
| |
| // Resumption should only attempt to contact the final URL if the download has a |
| // URL chain. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RedirectBeforeResume) { |
| TestDownloadRequestHandler request_handler_1( |
| GURL("http://example.com/first-url")); |
| request_handler_1.StartServingStaticResponse( |
| "HTTP/1.1 302 Redirect\r\n" |
| "Location: http://example.com/second-url\r\n" |
| "\r\n"); |
| |
| TestDownloadRequestHandler request_handler_2( |
| GURL("http://example.com/second-url")); |
| request_handler_2.StartServingStaticResponse( |
| "HTTP/1.1 302 Redirect\r\n" |
| "Location: http://example.com/third-url\r\n" |
| "\r\n"); |
| |
| TestDownloadRequestHandler request_handler_3( |
| GURL("http://example.com/third-url")); |
| request_handler_3.StartServingStaticResponse( |
| "HTTP/1.1 302 Redirect\r\n" |
| "Location: http://example.com/download\r\n" |
| "\r\n"); |
| |
| TestDownloadRequestHandler resumable_request_handler( |
| GURL("http://example.com/download")); |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| resumable_request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler_1.url()); |
| WaitForInterrupt(download); |
| |
| EXPECT_EQ(4u, download->GetUrlChain().size()); |
| EXPECT_EQ(request_handler_1.url(), download->GetOriginalUrl()); |
| EXPECT_EQ(resumable_request_handler.url(), download->GetURL()); |
| |
| // Now that the download is interrupted, make all intermediate servers return |
| // a 404. The only way a resumption request would succeed if the resumption |
| // request is sent to the final server in the chain. |
| const char k404Response[] = "HTTP/1.1 404 Not found\r\n\r\n"; |
| request_handler_1.StartServingStaticResponse(k404Response); |
| request_handler_2.StartServingStaticResponse(k404Response); |
| request_handler_3.StartServingStaticResponse(k404Response); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( |
| parameters.pattern_generator_seed, parameters.size, |
| download->GetTargetFilePath())); |
| } |
| |
| // If the server response for the resumption request specifies a bad range (i.e. |
| // not the range that was requested or an invalid or missing Content-Range |
| // header), then the download should be marked as interrupted again without |
| // discarding the partial state. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, BadRangeHeader) { |
| TestDownloadRequestHandler request_handler; |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| // Upon resumption, the server starts responding with a bad range header. |
| request_handler.StartServingStaticResponse( |
| "HTTP/1.1 206 Partial Content\r\n" |
| "Content-Range: bytes 1000000-2000000/3000000\r\n" |
| "\r\n"); |
| PrepareToResume(); |
| download->Resume(); |
| WaitForInterrupt(download); |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, |
| download->GetLastReason()); |
| |
| // Or this time, the server sends a response with an invalid Content-Range |
| // header. |
| request_handler.StartServingStaticResponse( |
| "HTTP/1.1 206 Partial Content\r\n" |
| "Content-Range: ooga-booga-booga-booga\r\n" |
| "\r\n"); |
| download->Resume(); |
| WaitForInterrupt(download); |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, |
| download->GetLastReason()); |
| |
| // Or no Content-Range header at all. |
| request_handler.StartServingStaticResponse( |
| "HTTP/1.1 206 Partial Content\r\n" |
| "Some-Headers: ooga-booga-booga-booga\r\n" |
| "\r\n"); |
| download->Resume(); |
| WaitForInterrupt(download); |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, |
| download->GetLastReason()); |
| |
| // Back to the original request handler. Resumption should now succeed, and |
| // use the partial data it had prior to the first interruption. |
| request_handler.StartServing(parameters); |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| ASSERT_EQ(parameters.size, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( |
| parameters.pattern_generator_seed, parameters.size, |
| download->GetTargetFilePath())); |
| |
| // Characterization risk: The next portion of the test examines the requests |
| // that were sent out while downloading our resource. These requests |
| // correspond to the requests that were generated by the browser and the |
| // downloads system and may change as implementation details change. |
| TestDownloadRequestHandler::CompletedRequests requests; |
| request_handler.GetCompletedRequestInfo(&requests); |
| |
| ASSERT_EQ(5u, requests.size()); |
| |
| // None of the request should have transferred the entire resource. |
| EXPECT_GT(parameters.size, requests[0].transferred_byte_count); |
| EXPECT_EQ(0, requests[1].transferred_byte_count); |
| EXPECT_EQ(0, requests[2].transferred_byte_count); |
| EXPECT_EQ(0, requests[3].transferred_byte_count); |
| EXPECT_GT(parameters.size, requests[4].transferred_byte_count); |
| } |
| |
| // A partial resumption results in an HTTP 200 response. I.e. the server ignored |
| // the range request and sent the entire resource instead. For If-Range requests |
| // (as opposed to If-Match), the behavior for a precondition failure is also to |
| // respond with a 200. So this test case covers both validation failure and |
| // ignoring the range request. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, |
| RestartIfNotPartialResponse) { |
| const int kOriginalPatternGeneratorSeed = 1; |
| const int kNewPatternGeneratorSeed = 2; |
| |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| parameters.pattern_generator_seed = kOriginalPatternGeneratorSeed; |
| const TestDownloadRequestHandler::InjectedError interruption = |
| parameters.injected_errors.front(); |
| |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| ASSERT_EQ(interruption.offset, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| |
| parameters = TestDownloadRequestHandler::Parameters(); |
| parameters.support_byte_ranges = false; |
| parameters.pattern_generator_seed = kNewPatternGeneratorSeed; |
| request_handler.StartServing(parameters); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| ASSERT_EQ(parameters.size, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| ASSERT_NO_FATAL_FAILURE( |
| ReadAndVerifyFileContents(kNewPatternGeneratorSeed, parameters.size, |
| download->GetTargetFilePath())); |
| |
| // When the downloads system sees the full response, it should accept the |
| // response without restarting. On the network, we should deterministically |
| // see two requests: |
| // * The original request which transfers upto our interruption point. |
| // * The resumption attempt, which receives the entire entity. |
| TestDownloadRequestHandler::CompletedRequests requests; |
| request_handler.GetCompletedRequestInfo(&requests); |
| |
| ASSERT_EQ(2u, requests.size()); |
| |
| // The first request only transfers data up to the interruption point. |
| EXPECT_EQ(interruption.offset, requests[0].transferred_byte_count); |
| |
| // The second request transfers the entire response. |
| EXPECT_EQ(parameters.size, requests[1].transferred_byte_count); |
| |
| std::string value; |
| ASSERT_TRUE(requests[1].request_headers.GetHeader( |
| net::HttpRequestHeaders::kIfRange, &value)); |
| EXPECT_EQ(parameters.etag, value); |
| |
| ASSERT_TRUE(requests[1].request_headers.GetHeader( |
| net::HttpRequestHeaders::kRange, &value)); |
| EXPECT_EQ(base::StringPrintf("bytes=%" PRId64 "-", interruption.offset), |
| value); |
| } |
| |
| // Confirm we restart if we don't have a verifier. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoETag) { |
| const int kOriginalPatternGeneratorSeed = 1; |
| const int kNewPatternGeneratorSeed = 2; |
| |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| ASSERT_EQ(1u, parameters.injected_errors.size()); |
| parameters.etag.clear(); |
| parameters.pattern_generator_seed = kOriginalPatternGeneratorSeed; |
| |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| parameters.pattern_generator_seed = kNewPatternGeneratorSeed; |
| parameters.ClearInjectedErrors(); |
| request_handler.StartServing(parameters); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| ASSERT_EQ(parameters.size, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| ASSERT_NO_FATAL_FAILURE( |
| ReadAndVerifyFileContents(kNewPatternGeneratorSeed, parameters.size, |
| download->GetTargetFilePath())); |
| |
| TestDownloadRequestHandler::CompletedRequests requests; |
| request_handler.GetCompletedRequestInfo(&requests); |
| |
| // Neither If-Range nor Range headers should be present in the second request. |
| ASSERT_EQ(2u, requests.size()); |
| std::string value; |
| EXPECT_FALSE(requests[1].request_headers.GetHeader( |
| net::HttpRequestHeaders::kIfRange, &value)); |
| EXPECT_FALSE(requests[1].request_headers.GetHeader( |
| net::HttpRequestHeaders::kRange, &value)); |
| } |
| |
| // Partial file goes missing before the download is resumed. The download should |
| // restart. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoPartialFile) { |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| // Delete the intermediate file. |
| ASSERT_TRUE(base::PathExists(download->GetFullPath())); |
| ASSERT_TRUE(base::DeleteFile(download->GetFullPath(), false)); |
| |
| parameters.ClearInjectedErrors(); |
| request_handler.StartServing(parameters); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| ASSERT_EQ(parameters.size, download->GetReceivedBytes()); |
| ASSERT_EQ(parameters.size, download->GetTotalBytes()); |
| ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( |
| parameters.pattern_generator_seed, parameters.size, |
| download->GetTargetFilePath())); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, |
| RecoverFromInitFileError) { |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(TestDownloadRequestHandler::Parameters()); |
| |
| // Setup the error injector. |
| scoped_refptr<TestFileErrorInjector> injector(TestFileErrorInjector::Create( |
| DownloadManagerForShell(initiator_shell_for_resumption()))); |
| |
| const TestFileErrorInjector::FileErrorInfo err = { |
| request_handler.url().spec(), |
| TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 0, |
| DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE}; |
| injector->AddError(err); |
| injector->InjectErrors(); |
| |
| // Start and watch for interrupt. |
| DownloadItem* download(StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url())); |
| WaitForInterrupt(download); |
| 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_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. |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, |
| RecoverFromIntermediateFileRenameError) { |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(TestDownloadRequestHandler::Parameters()); |
| |
| // Setup the error injector. |
| scoped_refptr<TestFileErrorInjector> injector(TestFileErrorInjector::Create( |
| DownloadManagerForShell(initiator_shell_for_resumption()))); |
| |
| const TestFileErrorInjector::FileErrorInfo err = { |
| request_handler.url().spec(), |
| TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY, 0, |
| DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE}; |
| injector->AddError(err); |
| injector->InjectErrors(); |
| |
| // Start and watch for interrupt. |
| DownloadItem* download(StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url())); |
| WaitForInterrupt(download); |
| 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(); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, |
| RecoverFromFinalRenameError) { |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(TestDownloadRequestHandler::Parameters()); |
| |
| // Setup the error injector. |
| scoped_refptr<TestFileErrorInjector> injector(TestFileErrorInjector::Create( |
| DownloadManagerForShell(initiator_shell_for_resumption()))); |
| |
| DownloadManagerForShell(initiator_shell_for_resumption()) |
| ->RemoveAllDownloads(); |
| TestFileErrorInjector::FileErrorInfo err = { |
| request_handler.url().spec(), |
| TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE, 0, |
| DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE}; |
| injector->AddError(err); |
| injector->InjectErrors(); |
| |
| // Start and watch for interrupt. |
| DownloadItem* download(StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url())); |
| WaitForInterrupt(download); |
| 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(); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForCompletion(download); |
| EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, Resume_Hash) { |
| using InjectedError = TestDownloadRequestHandler::InjectedError; |
| const char kExpectedHash[] = |
| "\xa7\x44\x49\x86\x24\xc6\x84\x6c\x89\xdf\xd8\xec\xa0\xe0\x61\x12\xdc\x80" |
| "\x13\xf2\x83\x49\xa9\x14\x52\x32\xf0\x95\x20\xca\x5b\x30"; |
| std::string expected_hash(kExpectedHash); |
| TestDownloadRequestHandler request_handler; |
| TestDownloadRequestHandler::Parameters parameters; |
| |
| // As a control, let's try GetHash() on an uninterrupted download. |
| request_handler.StartServing(parameters); |
| DownloadItem* uninterrupted_download(StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url())); |
| WaitForCompletion(uninterrupted_download); |
| EXPECT_EQ(expected_hash, uninterrupted_download->GetHash()); |
| |
| // Now with interruptions. |
| parameters.injected_errors.push( |
| InjectedError(100, net::ERR_CONNECTION_RESET)); |
| parameters.injected_errors.push( |
| InjectedError(211, net::ERR_CONNECTION_RESET)); |
| parameters.injected_errors.push( |
| InjectedError(337, net::ERR_CONNECTION_RESET)); |
| parameters.injected_errors.push( |
| InjectedError(400, net::ERR_CONNECTION_RESET)); |
| parameters.injected_errors.push( |
| InjectedError(512, net::ERR_CONNECTION_RESET)); |
| request_handler.StartServing(parameters); |
| |
| // Start and watch for interrupt. |
| DownloadItem* download(StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url())); |
| WaitForInterrupt(download); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForInterrupt(download); |
| |
| download->Resume(); |
| WaitForInterrupt(download); |
| |
| download->Resume(); |
| WaitForInterrupt(download); |
| |
| download->Resume(); |
| WaitForInterrupt(download); |
| |
| download->Resume(); |
| WaitForCompletion(download); |
| |
| EXPECT_EQ(expected_hash, download->GetHash()); |
| } |
| |
| // An interrupted download should remove the intermediate file when it is |
| // cancelled. |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, |
| CancelInterruptedDownload) { |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing( |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption()); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| base::FilePath intermediate_path = download->GetFullPath(); |
| ASSERT_FALSE(intermediate_path.empty()); |
| ASSERT_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_P(DownloadResumptionContentTest, |
| RemoveInterruptedDownload) { |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing( |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption()); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| base::FilePath intermediate_path = download->GetFullPath(); |
| ASSERT_FALSE(intermediate_path.empty()); |
| ASSERT_TRUE(base::PathExists(intermediate_path)); |
| |
| download->Remove(); |
| RunAllPendingInMessageLoop(BrowserThread::FILE); |
| RunAllPendingInMessageLoop(); |
| |
| // The intermediate file should now be gone. |
| EXPECT_FALSE(base::PathExists(intermediate_path)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveCompletedDownload) { |
| // A completed download shouldn't delete the downloaded file when it is |
| // removed. |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(TestDownloadRequestHandler::Parameters()); |
| scoped_ptr<DownloadTestObserver> completion_observer( |
| CreateWaiter(shell(), 1)); |
| DownloadItem* download( |
| StartDownloadAndReturnItem(shell(), request_handler.url())); |
| 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_P(DownloadResumptionContentTest, RemoveResumingDownload) { |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| 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. |
| MockDownloadManagerObserver dm_observer( |
| DownloadManagerForShell(initiator_shell_for_resumption())); |
| EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); |
| |
| TestRequestStartHandler request_start_handler; |
| parameters.on_start_handler = request_start_handler.GetOnStartHandler(); |
| request_handler.StartServing(parameters); |
| |
| PrepareToResume(); |
| download->Resume(); |
| request_start_handler.WaitForCallback(); |
| |
| // At this point, the download resumption request has been sent out, but the |
| // reponse hasn't been received yet. |
| download->Remove(); |
| |
| request_start_handler.RespondWith(std::string(), net::OK); |
| |
| // The intermediate file should now be gone. |
| RunAllPendingInMessageLoop(BrowserThread::FILE); |
| RunAllPendingInMessageLoop(); |
| EXPECT_FALSE(base::PathExists(intermediate_path)); |
| |
| parameters.ClearInjectedErrors(); |
| parameters.on_start_handler.Reset(); |
| request_handler.StartServing(parameters); |
| |
| // Start the second download and wait until it's done. This exercises the |
| // entire downloads stack and effectively flushes all of our worker threads. |
| // We are testing whether the URL request created in the previous |
| // DownloadItem::Resume() call reulted in a new download or not. |
| NavigateToURLAndWaitForDownload(shell(), request_handler.url(), |
| DownloadItem::COMPLETE); |
| EXPECT_TRUE(EnsureNoPendingDownloads()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumingDownload) { |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| 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. |
| MockDownloadManagerObserver dm_observer( |
| DownloadManagerForShell(initiator_shell_for_resumption())); |
| EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); |
| |
| TestRequestStartHandler request_start_handler; |
| parameters.on_start_handler = request_start_handler.GetOnStartHandler(); |
| request_handler.StartServing(parameters); |
| |
| PrepareToResume(); |
| download->Resume(); |
| request_start_handler.WaitForCallback(); |
| |
| // At this point, the download item has initiated a network request for the |
| // resumption attempt, but hasn't received a response yet. |
| download->Cancel(true /* user_cancel */); |
| |
| request_start_handler.RespondWith(std::string(), net::OK); |
| |
| // The intermediate file should now be gone. |
| RunAllPendingInMessageLoop(BrowserThread::IO); |
| RunAllPendingInMessageLoop(BrowserThread::FILE); |
| RunAllPendingInMessageLoop(); |
| EXPECT_FALSE(base::PathExists(intermediate_path)); |
| |
| parameters.ClearInjectedErrors(); |
| parameters.on_start_handler.Reset(); |
| request_handler.StartServing(parameters); |
| |
| // Start the second download and wait until it's done. This exercises the |
| // entire downloads stack and effectively flushes all of our worker threads. |
| // We are testing whether the URL request created in the previous |
| // DownloadItem::Resume() call reulted in a new download or not. |
| NavigateToURLAndWaitForDownload(shell(), request_handler.url(), |
| DownloadItem::COMPLETE); |
| EXPECT_TRUE(EnsureNoPendingDownloads()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumedDownload) { |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| base::FilePath intermediate_path(download->GetFullPath()); |
| base::FilePath target_path(download->GetTargetFilePath()); |
| ASSERT_FALSE(intermediate_path.empty()); |
| EXPECT_TRUE(base::PathExists(intermediate_path)); |
| EXPECT_FALSE(base::PathExists(target_path)); |
| |
| // Resume and remove download. We don't expect OnDownloadCreated() calls. |
| MockDownloadManagerObserver dm_observer( |
| DownloadManagerForShell(initiator_shell_for_resumption())); |
| EXPECT_CALL(dm_observer, OnDownloadCreated(_, _)).Times(0); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForInProgress(download); |
| |
| download->Remove(); |
| |
| // The intermediate file should now be gone. |
| RunAllPendingInMessageLoop(BrowserThread::FILE); |
| RunAllPendingInMessageLoop(); |
| EXPECT_FALSE(base::PathExists(intermediate_path)); |
| EXPECT_FALSE(base::PathExists(target_path)); |
| EXPECT_TRUE(EnsureNoPendingDownloads()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumedDownload) { |
| TestDownloadRequestHandler::Parameters parameters = |
| TestDownloadRequestHandler::Parameters::WithSingleInterruption(); |
| TestDownloadRequestHandler request_handler; |
| request_handler.StartServing(parameters); |
| |
| DownloadItem* download = StartDownloadAndReturnItem( |
| initiator_shell_for_resumption(), request_handler.url()); |
| WaitForInterrupt(download); |
| |
| base::FilePath intermediate_path(download->GetFullPath()); |
| base::FilePath target_path(download->GetTargetFilePath()); |
| ASSERT_FALSE(intermediate_path.empty()); |
| EXPECT_TRUE(base::PathExists(intermediate_path)); |
| EXPECT_FALSE(base::PathExists(target_path)); |
| |
| // Resume and remove download. We don't expect OnDownloadCreated() calls. |
| MockDownloadManagerObserver dm_observer( |
| DownloadManagerForShell(initiator_shell_for_resumption())); |
| EXPECT_CALL(dm_observer, OnDownloadCreated(_, _)).Times(0); |
| |
| PrepareToResume(); |
| download->Resume(); |
| WaitForInProgress(download); |
| |
| download->Cancel(true); |
| |
| // The intermediate file should now be gone. |
| RunAllPendingInMessageLoop(BrowserThread::FILE); |
| RunAllPendingInMessageLoop(); |
| EXPECT_FALSE(base::PathExists(intermediate_path)); |
| EXPECT_FALSE(base::PathExists(target_path)); |
| 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) { |
| net::EmbeddedTestServer origin_one; |
| net::EmbeddedTestServer origin_two; |
| ASSERT_TRUE(origin_one.Start()); |
| ASSERT_TRUE(origin_two.Start()); |
| |
| // Block third-party cookies. |
| ShellNetworkDelegate::SetAcceptAllCookies(false); |
| |
| // |url| redirects to a different origin |download| which tries to set a |
| // cookie. |
| base::StringPairs cookie_header; |
| cookie_header.push_back( |
| std::make_pair(std::string("Set-Cookie"), std::string("A=B"))); |
| origin_one.RegisterRequestHandler(CreateBasicResponseHandler( |
| "/foo", cookie_header, "application/octet-stream", "abcd")); |
| origin_two.RegisterRequestHandler( |
| CreateRedirectHandler("/bar", origin_one.GetURL("/foo"))); |
| |
| // Download the file. |
| SetupEnsureNoPendingDownloads(); |
| scoped_ptr<DownloadUrlParameters> download_parameters( |
| DownloadUrlParameters::FromWebContents(shell()->web_contents(), |
| origin_two.GetURL("/bar"))); |
| scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell(), 1)); |
| DownloadManagerForShell(shell())->DownloadUrl(std::move(download_parameters)); |
| 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(), |
| origin_one.GetURL("/"))); |
| } |
| |
| // 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) { |
| net::EmbeddedTestServer origin_one; |
| net::EmbeddedTestServer origin_two; |
| ASSERT_TRUE(origin_one.Start()); |
| ASSERT_TRUE(origin_two.Start()); |
| |
| // 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", base::StringPairs(), "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) { |
| net::EmbeddedTestServer origin_one; |
| net::EmbeddedTestServer origin_two; |
| ASSERT_TRUE(origin_one.Start()); |
| ASSERT_TRUE(origin_two.Start()); |
| |
| // 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", base::StringPairs(), "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()); |
| } |
| |
| // A request for a non-existent resource should still result in a DownloadItem |
| // that's created in an interrupted state. |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeServerError) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL download_url = |
| embedded_test_server()->GetURL("/download/does-not-exist"); |
| GURL document_url = embedded_test_server()->GetURL( |
| std::string("/download/download-attribute.html?target=") + |
| download_url.spec()); |
| |
| DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); |
| WaitForInterrupt(download); |
| |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, |
| download->GetLastReason()); |
| } |
| |
| namespace { |
| |
| void ErrorReturningRequestHandler( |
| const net::HttpRequestHeaders& headers, |
| const TestDownloadRequestHandler::OnStartResponseCallback& callback) { |
| callback.Run(std::string(), net::ERR_INTERNET_DISCONNECTED); |
| } |
| |
| } // namespace |
| |
| // A request that fails before it gets a response from the server should also |
| // result in a DownloadItem that's created in an interrupted state. |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeNetworkError) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| TestDownloadRequestHandler request_handler; |
| TestDownloadRequestHandler::Parameters parameters; |
| |
| parameters.on_start_handler = base::Bind(&ErrorReturningRequestHandler); |
| request_handler.StartServing(parameters); |
| |
| GURL document_url = embedded_test_server()->GetURL( |
| std::string("/download/download-attribute.html?target=") + |
| request_handler.url().spec()); |
| DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); |
| WaitForInterrupt(download); |
| |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, |
| download->GetLastReason()); |
| } |
| |
| // A request that fails due to it being rejected by policy should result in a |
| // DownloadItem that's marked as interrupted. |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeInvalidURL) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL document_url = embedded_test_server()->GetURL( |
| "/download/download-attribute.html?target=about:version"); |
| DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); |
| WaitForInterrupt(download); |
| |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, |
| download->GetLastReason()); |
| EXPECT_FALSE(download->CanResume()); |
| } |
| |
| // 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) { |
| GURL url = net::URLRequestMockHTTPJob::GetMockUrl("empty.bin"); |
| NavigateToURLAndWaitForDownload(shell(), url, DownloadItem::COMPLETE); |
| // That's it. This should work without crashing. |
| } |
| |
| // Make sure that sniffed MIME types are correctly passed through to the |
| // download item. |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, SniffedMimeType) { |
| GURL url = net::URLRequestMockHTTPJob::GetMockUrl("gzip-content.gz"); |
| DownloadItem* item = StartDownloadAndReturnItem(shell(), url); |
| WaitForCompletion(item); |
| |
| EXPECT_STREQ("application/x-gzip", item->GetMimeType().c_str()); |
| EXPECT_TRUE(item->GetOriginalMimeType().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DownloadContentTest, DuplicateContentDisposition) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // double-content-disposition.txt is served with two Content-Disposition |
| // headers, both of which are identical. |
| NavigateToURLAndWaitForDownload( |
| shell(), |
| embedded_test_server()->GetURL( |
| "/download/double-content-disposition.txt"), |
| DownloadItem::COMPLETE); |
| |
| std::vector<DownloadItem*> downloads; |
| DownloadManagerForShell(shell())->GetAllDownloads(&downloads); |
| ASSERT_EQ(1u, downloads.size()); |
| |
| EXPECT_EQ(FILE_PATH_LITERAL("Jumboshrimp.txt"), |
| downloads[0]->GetTargetFilePath().BaseName().value()); |
| } |
| |
| } // namespace content |