1 // Copyright 2014 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/url_fetcher_downloader.h"
6 
7 #include <stdint.h>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/files/file_util.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/sequenced_task_runner.h"
15 #include "base/task/thread_pool.h"
16 #include "components/update_client/network.h"
17 #include "components/update_client/task_traits.h"
18 #include "components/update_client/utils.h"
19 #include "url/gurl.h"
20 
21 namespace update_client {
22 
UrlFetcherDownloader(scoped_refptr<CrxDownloader> successor,scoped_refptr<NetworkFetcherFactory> network_fetcher_factory)23 UrlFetcherDownloader::UrlFetcherDownloader(
24     scoped_refptr<CrxDownloader> successor,
25     scoped_refptr<NetworkFetcherFactory> network_fetcher_factory)
26     : CrxDownloader(std::move(successor)),
27       network_fetcher_factory_(network_fetcher_factory) {}
28 
29 UrlFetcherDownloader::~UrlFetcherDownloader() = default;
30 
DoStartDownload(const GURL & url)31 void UrlFetcherDownloader::DoStartDownload(const GURL& url) {
32   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
33   base::ThreadPool::PostTaskAndReply(
34       FROM_HERE, kTaskTraits,
35       base::BindOnce(&UrlFetcherDownloader::CreateDownloadDir, this),
36       base::BindOnce(&UrlFetcherDownloader::StartURLFetch, this, url));
37 }
38 
CreateDownloadDir()39 void UrlFetcherDownloader::CreateDownloadDir() {
40   base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_url_fetcher_"),
41                                &download_dir_);
42 }
43 
StartURLFetch(const GURL & url)44 void UrlFetcherDownloader::StartURLFetch(const GURL& url) {
45   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
46 
47   if (download_dir_.empty()) {
48     Result result;
49     result.error = -1;
50 
51     DownloadMetrics download_metrics;
52     download_metrics.url = url;
53     download_metrics.downloader = DownloadMetrics::kUrlFetcher;
54     download_metrics.error = -1;
55     download_metrics.downloaded_bytes = -1;
56     download_metrics.total_bytes = -1;
57     download_metrics.download_time_ms = 0;
58 
59     main_task_runner()->PostTask(
60         FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
61                                   this, false, result, download_metrics));
62     return;
63   }
64 
65   file_path_ = download_dir_.AppendASCII(url.ExtractFileName());
66   network_fetcher_ = network_fetcher_factory_->Create();
67   network_fetcher_->DownloadToFile(
68       url, file_path_,
69       base::BindOnce(&UrlFetcherDownloader::OnResponseStarted, this),
70       base::BindRepeating(&UrlFetcherDownloader::OnDownloadProgress, this),
71       base::BindOnce(&UrlFetcherDownloader::OnNetworkFetcherComplete, this));
72 
73   download_start_time_ = base::TimeTicks::Now();
74 }
75 
OnNetworkFetcherComplete(int net_error,int64_t content_size)76 void UrlFetcherDownloader::OnNetworkFetcherComplete(int net_error,
77                                                     int64_t content_size) {
78   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
79 
80   const base::TimeTicks download_end_time(base::TimeTicks::Now());
81   const base::TimeDelta download_time =
82       download_end_time >= download_start_time_
83           ? download_end_time - download_start_time_
84           : base::TimeDelta();
85 
86   // Consider a 5xx response from the server as an indication to terminate
87   // the request and avoid overloading the server in this case.
88   // is not accepting requests for the moment.
89   int error = -1;
90   if (!net_error && response_code_ == 200)
91     error = 0;
92   else if (response_code_ != -1)
93     error = response_code_;
94   else
95     error = net_error;
96 
97   const bool is_handled = error == 0 || IsHttpServerError(error);
98 
99   Result result;
100   result.error = error;
101   if (!error)
102     result.response = file_path_;
103 
104   DownloadMetrics download_metrics;
105   download_metrics.url = url();
106   download_metrics.downloader = DownloadMetrics::kUrlFetcher;
107   download_metrics.error = error;
108   // Tests expected -1, in case of failures and no content is available.
109   download_metrics.downloaded_bytes = error ? -1 : content_size;
110   download_metrics.total_bytes = total_bytes_;
111   download_metrics.download_time_ms = download_time.InMilliseconds();
112 
113   VLOG(1) << "Downloaded " << content_size << " bytes in "
114           << download_time.InMilliseconds() << "ms from " << url().spec()
115           << " to " << result.response.value();
116 
117   // Delete the download directory in the error cases.
118   if (error && !download_dir_.empty()) {
119     base::ThreadPool::PostTask(
120         FROM_HERE, kTaskTraits,
121         base::BindOnce(IgnoreResult(&base::DeletePathRecursively),
122                        download_dir_));
123   }
124 
125   main_task_runner()->PostTask(
126       FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete, this,
127                                 is_handled, result, download_metrics));
128   network_fetcher_ = nullptr;
129 }
130 
131 // This callback is used to indicate that a download has been started.
OnResponseStarted(int response_code,int64_t content_length)132 void UrlFetcherDownloader::OnResponseStarted(int response_code,
133                                              int64_t content_length) {
134   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
135 
136   VLOG(1) << "url fetcher response started for: " << url().spec();
137 
138   response_code_ = response_code;
139   total_bytes_ = content_length;
140 }
141 
OnDownloadProgress(int64_t current)142 void UrlFetcherDownloader::OnDownloadProgress(int64_t current) {
143   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
144   CrxDownloader::OnDownloadProgress(current, total_bytes_);
145 }
146 
147 }  // namespace update_client
148