1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/update_client/crx_downloader.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/path_service.h"
15 #include "base/run_loop.h"
16 #include "base/test/bind_test_util.h"
17 #include "base/test/task_environment.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "build/build_config.h"
20 #include "components/update_client/net/network_chromium.h"
21 #include "components/update_client/update_client_errors.h"
22 #include "components/update_client/utils.h"
23 #include "net/base/net_errors.h"
24 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
25 #include "services/network/test/test_url_loader_factory.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 
28 using base::ContentsEqual;
29 
30 namespace update_client {
31 
32 namespace {
33 
34 const char kTestFileName[] = "jebgalgnebhfojomionfpkfelancnnkf.crx";
35 
36 const char hash_jebg[] =
37     "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498";
38 
MakeTestFilePath(const char * file)39 base::FilePath MakeTestFilePath(const char* file) {
40   base::FilePath path;
41   base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
42   return path.AppendASCII("components/test/data/update_client")
43       .AppendASCII(file);
44 }
45 
46 }  // namespace
47 
48 class CrxDownloaderTest : public testing::Test {
49  public:
50   CrxDownloaderTest();
51   ~CrxDownloaderTest() override;
52 
53   // Overrides from testing::Test.
54   void SetUp() override;
55   void TearDown() override;
56 
57   void Quit();
58   void RunThreads();
59   void RunThreadsUntilIdle();
60 
61   void DownloadComplete(int crx_context, const CrxDownloader::Result& result);
62 
63   void DownloadProgress(int crx_context);
64 
GetInterceptorCount()65   int GetInterceptorCount() { return interceptor_count_; }
66 
67   void AddResponse(const GURL& url,
68                    const base::FilePath& file_path,
69                    int net_error);
70 
71  protected:
72   std::unique_ptr<CrxDownloader> crx_downloader_;
73 
74   network::TestURLLoaderFactory test_url_loader_factory_;
75 
76   CrxDownloader::DownloadCallback callback_;
77   CrxDownloader::ProgressCallback progress_callback_;
78 
79   int crx_context_;
80 
81   int num_download_complete_calls_;
82   CrxDownloader::Result download_complete_result_;
83 
84   // These members are updated by DownloadProgress.
85   int num_progress_calls_;
86 
87   // Accumulates the number of loads triggered.
88   int interceptor_count_ = 0;
89 
90   // A magic value for the context to be used in the tests.
91   static const int kExpectedContext = 0xaabb;
92 
93  private:
94   base::test::TaskEnvironment task_environment_;
95   scoped_refptr<network::SharedURLLoaderFactory>
96       test_shared_url_loader_factory_;
97   base::OnceClosure quit_closure_;
98 };
99 
100 const int CrxDownloaderTest::kExpectedContext;
101 
CrxDownloaderTest()102 CrxDownloaderTest::CrxDownloaderTest()
103     : callback_(base::BindOnce(&CrxDownloaderTest::DownloadComplete,
104                                base::Unretained(this),
105                                kExpectedContext)),
106       progress_callback_(
107           base::BindRepeating(&CrxDownloaderTest::DownloadProgress,
108                               base::Unretained(this),
109                               kExpectedContext)),
110       crx_context_(0),
111       num_download_complete_calls_(0),
112       num_progress_calls_(0),
113       task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
114       test_shared_url_loader_factory_(
115           base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
116               &test_url_loader_factory_)) {}
117 
118 CrxDownloaderTest::~CrxDownloaderTest() = default;
119 
SetUp()120 void CrxDownloaderTest::SetUp() {
121   num_download_complete_calls_ = 0;
122   download_complete_result_ = CrxDownloader::Result();
123   num_progress_calls_ = 0;
124 
125   // Do not use the background downloader in these tests.
126   crx_downloader_ = CrxDownloader::Create(
127       false, base::MakeRefCounted<NetworkFetcherChromiumFactory>(
128                  test_shared_url_loader_factory_,
129                  base::BindRepeating([](const GURL& url) { return false; })));
130   crx_downloader_->set_progress_callback(progress_callback_);
131 
132   test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
133       [&](const network::ResourceRequest& request) { interceptor_count_++; }));
134 }
135 
TearDown()136 void CrxDownloaderTest::TearDown() {
137   crx_downloader_.reset();
138 }
139 
Quit()140 void CrxDownloaderTest::Quit() {
141   if (!quit_closure_.is_null())
142     std::move(quit_closure_).Run();
143 }
144 
DownloadComplete(int crx_context,const CrxDownloader::Result & result)145 void CrxDownloaderTest::DownloadComplete(int crx_context,
146                                          const CrxDownloader::Result& result) {
147   ++num_download_complete_calls_;
148   crx_context_ = crx_context;
149   download_complete_result_ = result;
150   Quit();
151 }
152 
DownloadProgress(int crx_context)153 void CrxDownloaderTest::DownloadProgress(int crx_context) {
154   ++num_progress_calls_;
155 }
156 
AddResponse(const GURL & url,const base::FilePath & file_path,int net_error)157 void CrxDownloaderTest::AddResponse(const GURL& url,
158                                     const base::FilePath& file_path,
159                                     int net_error) {
160   if (net_error == net::OK) {
161     std::string data;
162     EXPECT_TRUE(base::ReadFileToString(file_path, &data));
163     auto head = network::mojom::URLResponseHead::New();
164     head->content_length = data.size();
165     network::URLLoaderCompletionStatus status(net_error);
166     status.decoded_body_length = data.size();
167     test_url_loader_factory_.AddResponse(url, std::move(head), data, status);
168     return;
169   }
170 
171   EXPECT_NE(net_error, net::OK);
172   test_url_loader_factory_.AddResponse(
173       url, network::mojom::URLResponseHead::New(), std::string(),
174       network::URLLoaderCompletionStatus(net_error));
175 }
176 
RunThreads()177 void CrxDownloaderTest::RunThreads() {
178   base::RunLoop runloop;
179   quit_closure_ = runloop.QuitClosure();
180   runloop.Run();
181 
182   // Since some tests need to drain currently enqueued tasks such as network
183   // intercepts on the IO thread, run the threads until they are
184   // idle. The component updater service won't loop again until the loop count
185   // is set and the service is started.
186   RunThreadsUntilIdle();
187 }
188 
RunThreadsUntilIdle()189 void CrxDownloaderTest::RunThreadsUntilIdle() {
190   task_environment_.RunUntilIdle();
191   base::RunLoop().RunUntilIdle();
192 }
193 
194 // Tests that starting a download without a url results in an error.
TEST_F(CrxDownloaderTest,NoUrl)195 TEST_F(CrxDownloaderTest, NoUrl) {
196   std::vector<GURL> urls;
197   crx_downloader_->StartDownload(urls, std::string("abcd"),
198                                  std::move(callback_));
199   RunThreadsUntilIdle();
200 
201   EXPECT_EQ(1, num_download_complete_calls_);
202   EXPECT_EQ(kExpectedContext, crx_context_);
203   EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_URL),
204             download_complete_result_.error);
205   EXPECT_TRUE(download_complete_result_.response.empty());
206   EXPECT_EQ(0, num_progress_calls_);
207 }
208 
209 // Tests that starting a download without providing a hash results in an error.
TEST_F(CrxDownloaderTest,NoHash)210 TEST_F(CrxDownloaderTest, NoHash) {
211   std::vector<GURL> urls(1, GURL("http://somehost/somefile"));
212 
213   crx_downloader_->StartDownload(urls, std::string(), std::move(callback_));
214   RunThreadsUntilIdle();
215 
216   EXPECT_EQ(1, num_download_complete_calls_);
217   EXPECT_EQ(kExpectedContext, crx_context_);
218   EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_HASH),
219             download_complete_result_.error);
220   EXPECT_TRUE(download_complete_result_.response.empty());
221   EXPECT_EQ(0, num_progress_calls_);
222 }
223 
224 // Tests that downloading from one url is successful.
TEST_F(CrxDownloaderTest,OneUrl)225 TEST_F(CrxDownloaderTest, OneUrl) {
226   const GURL expected_crx_url =
227       GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
228 
229   const base::FilePath test_file(MakeTestFilePath(kTestFileName));
230   AddResponse(expected_crx_url, test_file, net::OK);
231 
232   crx_downloader_->StartDownloadFromUrl(
233       expected_crx_url, std::string(hash_jebg), std::move(callback_));
234   RunThreads();
235 
236   EXPECT_EQ(1, GetInterceptorCount());
237 
238   EXPECT_EQ(1, num_download_complete_calls_);
239   EXPECT_EQ(kExpectedContext, crx_context_);
240   EXPECT_EQ(0, download_complete_result_.error);
241   EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
242 
243   EXPECT_TRUE(
244       DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
245 
246   EXPECT_LE(1, num_progress_calls_);
247 }
248 
249 // Tests that downloading from one url fails if the actual hash of the file
250 // does not match the expected hash.
TEST_F(CrxDownloaderTest,OneUrlBadHash)251 TEST_F(CrxDownloaderTest, OneUrlBadHash) {
252   const GURL expected_crx_url =
253       GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
254 
255   const base::FilePath test_file(MakeTestFilePath(kTestFileName));
256   AddResponse(expected_crx_url, test_file, net::OK);
257 
258   crx_downloader_->StartDownloadFromUrl(
259       expected_crx_url,
260       std::string(
261           "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9"),
262       std::move(callback_));
263   RunThreads();
264 
265   EXPECT_EQ(1, GetInterceptorCount());
266 
267   EXPECT_EQ(1, num_download_complete_calls_);
268   EXPECT_EQ(kExpectedContext, crx_context_);
269   EXPECT_EQ(static_cast<int>(CrxDownloaderError::BAD_HASH),
270             download_complete_result_.error);
271   EXPECT_TRUE(download_complete_result_.response.empty());
272 
273   EXPECT_LE(1, num_progress_calls_);
274 }
275 
276 // Tests that specifying two urls has no side effects. Expect a successful
277 // download, and only one download request be made.
TEST_F(CrxDownloaderTest,TwoUrls)278 TEST_F(CrxDownloaderTest, TwoUrls) {
279   const GURL expected_crx_url =
280       GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
281 
282   const base::FilePath test_file(MakeTestFilePath(kTestFileName));
283   AddResponse(expected_crx_url, test_file, net::OK);
284 
285   std::vector<GURL> urls;
286   urls.push_back(expected_crx_url);
287   urls.push_back(expected_crx_url);
288 
289   crx_downloader_->StartDownload(urls, std::string(hash_jebg),
290                                  std::move(callback_));
291   RunThreads();
292 
293   EXPECT_EQ(1, GetInterceptorCount());
294 
295   EXPECT_EQ(1, num_download_complete_calls_);
296   EXPECT_EQ(kExpectedContext, crx_context_);
297   EXPECT_EQ(0, download_complete_result_.error);
298   EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
299 
300   EXPECT_TRUE(
301       DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
302 
303   EXPECT_LE(1, num_progress_calls_);
304 }
305 
306 // Tests that the fallback to a valid url is successful.
TEST_F(CrxDownloaderTest,TwoUrls_FirstInvalid)307 TEST_F(CrxDownloaderTest, TwoUrls_FirstInvalid) {
308   const GURL expected_crx_url =
309       GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
310   const GURL no_file_url =
311       GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
312 
313   const base::FilePath test_file(MakeTestFilePath(kTestFileName));
314   AddResponse(expected_crx_url, test_file, net::OK);
315   AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
316 
317   std::vector<GURL> urls;
318   urls.push_back(no_file_url);
319   urls.push_back(expected_crx_url);
320 
321   crx_downloader_->StartDownload(urls, std::string(hash_jebg),
322                                  std::move(callback_));
323   RunThreads();
324 
325   EXPECT_EQ(2, GetInterceptorCount());
326 
327   EXPECT_EQ(1, num_download_complete_calls_);
328   EXPECT_EQ(kExpectedContext, crx_context_);
329   EXPECT_EQ(0, download_complete_result_.error);
330   EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
331 
332   EXPECT_TRUE(
333       DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
334 
335   // Expect at least some progress reported by the loader.
336   EXPECT_LE(1, num_progress_calls_);
337 
338   const auto download_metrics = crx_downloader_->download_metrics();
339   ASSERT_EQ(2u, download_metrics.size());
340   EXPECT_EQ(no_file_url, download_metrics[0].url);
341   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
342   EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
343   EXPECT_EQ(-1, download_metrics[0].total_bytes);
344   EXPECT_EQ(expected_crx_url, download_metrics[1].url);
345   EXPECT_EQ(0, download_metrics[1].error);
346   EXPECT_EQ(1015, download_metrics[1].downloaded_bytes);
347   EXPECT_EQ(1015, download_metrics[1].total_bytes);
348 }
349 
350 // Tests that the download succeeds if the first url is correct and the
351 // second bad url does not have a side-effect.
TEST_F(CrxDownloaderTest,TwoUrls_SecondInvalid)352 TEST_F(CrxDownloaderTest, TwoUrls_SecondInvalid) {
353   const GURL expected_crx_url =
354       GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
355   const GURL no_file_url =
356       GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
357 
358   const base::FilePath test_file(MakeTestFilePath(kTestFileName));
359   AddResponse(expected_crx_url, test_file, net::OK);
360   AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
361 
362   std::vector<GURL> urls;
363   urls.push_back(expected_crx_url);
364   urls.push_back(no_file_url);
365 
366   crx_downloader_->StartDownload(urls, std::string(hash_jebg),
367                                  std::move(callback_));
368   RunThreads();
369 
370   EXPECT_EQ(1, GetInterceptorCount());
371 
372   EXPECT_EQ(1, num_download_complete_calls_);
373   EXPECT_EQ(kExpectedContext, crx_context_);
374   EXPECT_EQ(0, download_complete_result_.error);
375   EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
376 
377   EXPECT_TRUE(
378       DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
379 
380   EXPECT_LE(1, num_progress_calls_);
381 
382   EXPECT_EQ(1u, crx_downloader_->download_metrics().size());
383 }
384 
385 // Tests that the download fails if both urls don't serve content.
TEST_F(CrxDownloaderTest,TwoUrls_BothInvalid)386 TEST_F(CrxDownloaderTest, TwoUrls_BothInvalid) {
387   const GURL expected_crx_url =
388       GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
389 
390   AddResponse(expected_crx_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
391 
392   std::vector<GURL> urls;
393   urls.push_back(expected_crx_url);
394   urls.push_back(expected_crx_url);
395 
396   crx_downloader_->StartDownload(urls, std::string(hash_jebg),
397                                  std::move(callback_));
398   RunThreads();
399 
400   EXPECT_EQ(2, GetInterceptorCount());
401 
402   EXPECT_EQ(1, num_download_complete_calls_);
403   EXPECT_EQ(kExpectedContext, crx_context_);
404   EXPECT_NE(0, download_complete_result_.error);
405   EXPECT_TRUE(download_complete_result_.response.empty());
406 
407   const auto download_metrics = crx_downloader_->download_metrics();
408   ASSERT_EQ(2u, download_metrics.size());
409   EXPECT_EQ(expected_crx_url, download_metrics[0].url);
410   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
411   EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
412   EXPECT_EQ(-1, download_metrics[0].total_bytes);
413   EXPECT_EQ(expected_crx_url, download_metrics[1].url);
414   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[1].error);
415   EXPECT_EQ(-1, download_metrics[1].downloaded_bytes);
416   EXPECT_EQ(-1, download_metrics[1].total_bytes);
417 }
418 
419 }  // namespace update_client
420