|  | // Copyright 2013 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "components/update_client/crx_downloader.h" | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/path_service.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "build/build_config.h" | 
|  | #include "components/update_client/update_client_errors.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/url_request/test_url_request_interceptor.h" | 
|  | #include "net/url_request/url_request_test_util.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using base::ContentsEqual; | 
|  |  | 
|  | namespace update_client { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Intercepts HTTP GET requests sent to "localhost". | 
|  | typedef net::LocalHostTestURLRequestInterceptor GetInterceptor; | 
|  |  | 
|  | const char kTestFileName[] = "jebgalgnebhfojomionfpkfelancnnkf.crx"; | 
|  |  | 
|  | const char hash_jebg[] = | 
|  | "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87"; | 
|  |  | 
|  | base::FilePath MakeTestFilePath(const char* file) { | 
|  | base::FilePath path; | 
|  | PathService::Get(base::DIR_SOURCE_ROOT, &path); | 
|  | return path.AppendASCII("components/test/data/update_client") | 
|  | .AppendASCII(file); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class CrxDownloaderTest : public testing::Test { | 
|  | public: | 
|  | CrxDownloaderTest(); | 
|  | ~CrxDownloaderTest() override; | 
|  |  | 
|  | // Overrides from testing::Test. | 
|  | void SetUp() override; | 
|  | void TearDown() override; | 
|  |  | 
|  | void Quit(); | 
|  | void RunThreads(); | 
|  | void RunThreadsUntilIdle(); | 
|  |  | 
|  | void DownloadComplete(int crx_context, const CrxDownloader::Result& result); | 
|  |  | 
|  | void DownloadProgress(int crx_context, const CrxDownloader::Result& result); | 
|  |  | 
|  | protected: | 
|  | std::unique_ptr<CrxDownloader> crx_downloader_; | 
|  |  | 
|  | std::unique_ptr<GetInterceptor> get_interceptor_; | 
|  |  | 
|  | CrxDownloader::DownloadCallback callback_; | 
|  | CrxDownloader::ProgressCallback progress_callback_; | 
|  |  | 
|  | int crx_context_; | 
|  |  | 
|  | int num_download_complete_calls_; | 
|  | CrxDownloader::Result download_complete_result_; | 
|  |  | 
|  | // These members are updated by DownloadProgress. | 
|  | int num_progress_calls_; | 
|  | CrxDownloader::Result download_progress_result_; | 
|  |  | 
|  | // A magic value for the context to be used in the tests. | 
|  | static const int kExpectedContext = 0xaabb; | 
|  |  | 
|  | private: | 
|  | base::MessageLoopForIO loop_; | 
|  | scoped_refptr<net::TestURLRequestContextGetter> context_; | 
|  | base::Closure quit_closure_; | 
|  | }; | 
|  |  | 
|  | const int CrxDownloaderTest::kExpectedContext; | 
|  |  | 
|  | CrxDownloaderTest::CrxDownloaderTest() | 
|  | : callback_(base::Bind(&CrxDownloaderTest::DownloadComplete, | 
|  | base::Unretained(this), | 
|  | kExpectedContext)), | 
|  | progress_callback_(base::Bind(&CrxDownloaderTest::DownloadProgress, | 
|  | base::Unretained(this), | 
|  | kExpectedContext)), | 
|  | crx_context_(0), | 
|  | num_download_complete_calls_(0), | 
|  | num_progress_calls_(0), | 
|  | context_(new net::TestURLRequestContextGetter( | 
|  | base::ThreadTaskRunnerHandle::Get())) { | 
|  | } | 
|  |  | 
|  | CrxDownloaderTest::~CrxDownloaderTest() { | 
|  | context_ = NULL; | 
|  |  | 
|  | // The GetInterceptor requires the message loop to run to destruct correctly. | 
|  | get_interceptor_.reset(); | 
|  | RunThreadsUntilIdle(); | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::SetUp() { | 
|  | num_download_complete_calls_ = 0; | 
|  | download_complete_result_ = CrxDownloader::Result(); | 
|  | num_progress_calls_ = 0; | 
|  | download_progress_result_ = CrxDownloader::Result(); | 
|  |  | 
|  | // Do not use the background downloader in these tests. | 
|  | crx_downloader_.reset( | 
|  | CrxDownloader::Create(false, context_.get(), | 
|  | base::ThreadTaskRunnerHandle::Get()) | 
|  | .release()); | 
|  | crx_downloader_->set_progress_callback(progress_callback_); | 
|  |  | 
|  | get_interceptor_.reset( | 
|  | new GetInterceptor(base::ThreadTaskRunnerHandle::Get(), | 
|  | base::ThreadTaskRunnerHandle::Get())); | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::TearDown() { | 
|  | crx_downloader_.reset(); | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::Quit() { | 
|  | if (!quit_closure_.is_null()) | 
|  | quit_closure_.Run(); | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::DownloadComplete(int crx_context, | 
|  | const CrxDownloader::Result& result) { | 
|  | ++num_download_complete_calls_; | 
|  | crx_context_ = crx_context; | 
|  | download_complete_result_ = result; | 
|  | Quit(); | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::DownloadProgress(int crx_context, | 
|  | const CrxDownloader::Result& result) { | 
|  | ++num_progress_calls_; | 
|  | download_progress_result_ = result; | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::RunThreads() { | 
|  | base::RunLoop runloop; | 
|  | quit_closure_ = runloop.QuitClosure(); | 
|  | runloop.Run(); | 
|  |  | 
|  | // Since some tests need to drain currently enqueued tasks such as network | 
|  | // intercepts on the IO thread, run the threads until they are | 
|  | // idle. The component updater service won't loop again until the loop count | 
|  | // is set and the service is started. | 
|  | RunThreadsUntilIdle(); | 
|  | } | 
|  |  | 
|  | void CrxDownloaderTest::RunThreadsUntilIdle() { | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Tests that starting a download without a url results in an error. | 
|  | TEST_F(CrxDownloaderTest, NoUrl) { | 
|  | std::vector<GURL> urls; | 
|  | crx_downloader_->StartDownload(urls, std::string("abcd"), callback_); | 
|  | RunThreadsUntilIdle(); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_URL), | 
|  | download_complete_result_.error); | 
|  | EXPECT_TRUE(download_complete_result_.response.empty()); | 
|  | EXPECT_EQ(-1, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(-1, download_complete_result_.total_bytes); | 
|  | EXPECT_EQ(0, num_progress_calls_); | 
|  | } | 
|  |  | 
|  | // Tests that starting a download without providing a hash results in an error. | 
|  | TEST_F(CrxDownloaderTest, NoHash) { | 
|  | std::vector<GURL> urls(1, GURL("http://somehost/somefile")); | 
|  |  | 
|  | crx_downloader_->StartDownload(urls, std::string(), callback_); | 
|  | RunThreadsUntilIdle(); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_HASH), | 
|  | download_complete_result_.error); | 
|  | EXPECT_TRUE(download_complete_result_.response.empty()); | 
|  | EXPECT_EQ(-1, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(-1, download_complete_result_.total_bytes); | 
|  | EXPECT_EQ(0, num_progress_calls_); | 
|  | } | 
|  |  | 
|  | // Tests that downloading from one url is successful. | 
|  | TEST_F(CrxDownloaderTest, OneUrl) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | crx_downloader_->StartDownloadFromUrl(expected_crx_url, | 
|  | std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(1, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(0, download_complete_result_.error); | 
|  | EXPECT_EQ(1843, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_complete_result_.total_bytes); | 
|  | EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file)); | 
|  |  | 
|  | EXPECT_TRUE(base::DeleteFile(download_complete_result_.response, false)); | 
|  |  | 
|  | EXPECT_LE(1, num_progress_calls_); | 
|  | EXPECT_EQ(1843, download_progress_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_progress_result_.total_bytes); | 
|  | } | 
|  |  | 
|  | // Tests that downloading from one url fails if the actual hash of the file | 
|  | // does not match the expected hash. | 
|  | TEST_F(CrxDownloaderTest, OneUrlBadHash) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | crx_downloader_->StartDownloadFromUrl( | 
|  | expected_crx_url, | 
|  | std::string( | 
|  | "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9"), | 
|  | callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(1, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(static_cast<int>(CrxDownloaderError::BAD_HASH), | 
|  | download_complete_result_.error); | 
|  | EXPECT_EQ(1843, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_complete_result_.total_bytes); | 
|  | EXPECT_TRUE(download_complete_result_.response.empty()); | 
|  |  | 
|  | EXPECT_LE(1, num_progress_calls_); | 
|  | EXPECT_EQ(1843, download_progress_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_progress_result_.total_bytes); | 
|  | } | 
|  |  | 
|  | // Tests that specifying two urls has no side effects. Expect a successful | 
|  | // download, and only one download request be made. | 
|  | // This test is flaky on Android. crbug.com/329883 | 
|  | #if defined(OS_ANDROID) | 
|  | #define MAYBE_TwoUrls DISABLED_TwoUrls | 
|  | #else | 
|  | #define MAYBE_TwoUrls TwoUrls | 
|  | #endif | 
|  | TEST_F(CrxDownloaderTest, MAYBE_TwoUrls) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | std::vector<GURL> urls; | 
|  | urls.push_back(expected_crx_url); | 
|  | urls.push_back(expected_crx_url); | 
|  |  | 
|  | crx_downloader_->StartDownload(urls, std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(1, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(0, download_complete_result_.error); | 
|  | EXPECT_EQ(1843, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_complete_result_.total_bytes); | 
|  | EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file)); | 
|  |  | 
|  | EXPECT_TRUE(base::DeleteFile(download_complete_result_.response, false)); | 
|  |  | 
|  | EXPECT_LE(1, num_progress_calls_); | 
|  | EXPECT_EQ(1843, download_progress_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_progress_result_.total_bytes); | 
|  | } | 
|  |  | 
|  | // Tests that an invalid host results in a download error. | 
|  | TEST_F(CrxDownloaderTest, OneUrl_InvalidHost) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | crx_downloader_->StartDownloadFromUrl( | 
|  | GURL("http://no.such.host" | 
|  | "/download/jebgalgnebhfojomionfpkfelancnnkf.crx"), | 
|  | std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(0, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_NE(0, download_complete_result_.error); | 
|  | EXPECT_TRUE(download_complete_result_.response.empty()); | 
|  | } | 
|  |  | 
|  | // Tests that an invalid path results in a download error. | 
|  | TEST_F(CrxDownloaderTest, OneUrl_InvalidPath) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | crx_downloader_->StartDownloadFromUrl(GURL("http://localhost/no/such/file"), | 
|  | std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(0, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_NE(0, download_complete_result_.error); | 
|  | EXPECT_TRUE(download_complete_result_.response.empty()); | 
|  | } | 
|  |  | 
|  | // Tests that the fallback to a valid url is successful. | 
|  | // This test is flaky on Android. crbug.com/329883 | 
|  | #if defined(OS_ANDROID) | 
|  | #define MAYBE_TwoUrls_FirstInvalid DISABLED_TwoUrls_FirstInvalid | 
|  | #else | 
|  | #define MAYBE_TwoUrls_FirstInvalid TwoUrls_FirstInvalid | 
|  | #endif | 
|  | TEST_F(CrxDownloaderTest, MAYBE_TwoUrls_FirstInvalid) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | std::vector<GURL> urls; | 
|  | urls.push_back(GURL("http://localhost/no/such/file")); | 
|  | urls.push_back(expected_crx_url); | 
|  |  | 
|  | crx_downloader_->StartDownload(urls, std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(1, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(0, download_complete_result_.error); | 
|  | EXPECT_EQ(1843, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_complete_result_.total_bytes); | 
|  | EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file)); | 
|  |  | 
|  | EXPECT_TRUE(base::DeleteFile(download_complete_result_.response, false)); | 
|  |  | 
|  | EXPECT_LE(1, num_progress_calls_); | 
|  | EXPECT_EQ(1843, download_progress_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_progress_result_.total_bytes); | 
|  | } | 
|  |  | 
|  | // Tests that the download succeeds if the first url is correct and the | 
|  | // second bad url does not have a side-effect. | 
|  | TEST_F(CrxDownloaderTest, TwoUrls_SecondInvalid) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | std::vector<GURL> urls; | 
|  | urls.push_back(expected_crx_url); | 
|  | urls.push_back(GURL("http://localhost/no/such/file")); | 
|  |  | 
|  | crx_downloader_->StartDownload(urls, std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(1, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_EQ(0, download_complete_result_.error); | 
|  | EXPECT_EQ(1843, download_complete_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_complete_result_.total_bytes); | 
|  | EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file)); | 
|  |  | 
|  | EXPECT_TRUE(base::DeleteFile(download_complete_result_.response, false)); | 
|  |  | 
|  | EXPECT_LE(1, num_progress_calls_); | 
|  | EXPECT_EQ(1843, download_progress_result_.downloaded_bytes); | 
|  | EXPECT_EQ(1843, download_progress_result_.total_bytes); | 
|  | } | 
|  |  | 
|  | // Tests that the download fails if both urls are bad. | 
|  | TEST_F(CrxDownloaderTest, TwoUrls_BothInvalid) { | 
|  | const GURL expected_crx_url = | 
|  | GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"); | 
|  |  | 
|  | const base::FilePath test_file(MakeTestFilePath(kTestFileName)); | 
|  | get_interceptor_->SetResponse(expected_crx_url, test_file); | 
|  |  | 
|  | std::vector<GURL> urls; | 
|  | urls.push_back(GURL("http://localhost/no/such/file")); | 
|  | urls.push_back(GURL( | 
|  | "http://no.such.host/" | 
|  | "/download/jebgalgnebhfojomionfpkfelancnnkf.crx")); | 
|  |  | 
|  | crx_downloader_->StartDownload(urls, std::string(hash_jebg), callback_); | 
|  | RunThreads(); | 
|  |  | 
|  | EXPECT_EQ(0, get_interceptor_->GetHitCount()); | 
|  |  | 
|  | EXPECT_EQ(1, num_download_complete_calls_); | 
|  | EXPECT_EQ(kExpectedContext, crx_context_); | 
|  | EXPECT_NE(0, download_complete_result_.error); | 
|  | EXPECT_TRUE(download_complete_result_.response.empty()); | 
|  | } | 
|  |  | 
|  | }  // namespace update_client |