| // Copyright 2014 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 <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/bind_helpers.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/location.h" | 
 | #include "base/logging.h" | 
 | #include "base/sequenced_task_runner.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "build/build_config.h" | 
 | #if defined(OS_WIN) | 
 | #include "components/update_client/background_downloader_win.h" | 
 | #endif | 
 | #include "components/update_client/update_client_errors.h" | 
 | #include "components/update_client/url_fetcher_downloader.h" | 
 | #include "components/update_client/utils.h" | 
 |  | 
 | namespace update_client { | 
 |  | 
 | CrxDownloader::Result::Result() | 
 |     : error(0), downloaded_bytes(-1), total_bytes(-1) { | 
 | } | 
 |  | 
 | CrxDownloader::DownloadMetrics::DownloadMetrics() | 
 |     : downloader(kNone), | 
 |       error(0), | 
 |       downloaded_bytes(-1), | 
 |       total_bytes(-1), | 
 |       download_time_ms(0) { | 
 | } | 
 |  | 
 | // On Windows, the first downloader in the chain is a background downloader, | 
 | // which uses the BITS service. | 
 | std::unique_ptr<CrxDownloader> CrxDownloader::Create( | 
 |     bool is_background_download, | 
 |     net::URLRequestContextGetter* context_getter, | 
 |     const scoped_refptr<base::SequencedTaskRunner>& task_runner) { | 
 |   std::unique_ptr<CrxDownloader> url_fetcher_downloader( | 
 |       std::unique_ptr<CrxDownloader>(new UrlFetcherDownloader( | 
 |           std::unique_ptr<CrxDownloader>(), context_getter, task_runner))); | 
 | #if defined(OS_WIN) | 
 |   if (is_background_download) { | 
 |     return std::unique_ptr<CrxDownloader>(new BackgroundDownloader( | 
 |         std::move(url_fetcher_downloader), context_getter, task_runner)); | 
 |   } | 
 | #endif | 
 |  | 
 |   return url_fetcher_downloader; | 
 | } | 
 |  | 
 | CrxDownloader::CrxDownloader( | 
 |     const scoped_refptr<base::SequencedTaskRunner>& task_runner, | 
 |     std::unique_ptr<CrxDownloader> successor) | 
 |     : task_runner_(task_runner), | 
 |       main_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 
 |       successor_(std::move(successor)) {} | 
 |  | 
 | CrxDownloader::~CrxDownloader() {} | 
 |  | 
 | void CrxDownloader::set_progress_callback( | 
 |     const ProgressCallback& progress_callback) { | 
 |   progress_callback_ = progress_callback; | 
 | } | 
 |  | 
 | GURL CrxDownloader::url() const { | 
 |   return current_url_ != urls_.end() ? *current_url_ : GURL(); | 
 | } | 
 |  | 
 | const std::vector<CrxDownloader::DownloadMetrics> | 
 | CrxDownloader::download_metrics() const { | 
 |   if (!successor_) | 
 |     return download_metrics_; | 
 |  | 
 |   std::vector<DownloadMetrics> retval(successor_->download_metrics()); | 
 |   retval.insert(retval.begin(), download_metrics_.begin(), | 
 |                 download_metrics_.end()); | 
 |   return retval; | 
 | } | 
 |  | 
 | void CrxDownloader::StartDownloadFromUrl( | 
 |     const GURL& url, | 
 |     const std::string& expected_hash, | 
 |     const DownloadCallback& download_callback) { | 
 |   std::vector<GURL> urls; | 
 |   urls.push_back(url); | 
 |   StartDownload(urls, expected_hash, download_callback); | 
 | } | 
 |  | 
 | void CrxDownloader::StartDownload(const std::vector<GURL>& urls, | 
 |                                   const std::string& expected_hash, | 
 |                                   const DownloadCallback& download_callback) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  | 
 |   auto error = CrxDownloaderError::NONE; | 
 |   if (urls.empty()) { | 
 |     error = CrxDownloaderError::NO_URL; | 
 |   } else if (expected_hash.empty()) { | 
 |     error = CrxDownloaderError::NO_HASH; | 
 |   } | 
 |  | 
 |   if (error != CrxDownloaderError::NONE) { | 
 |     Result result; | 
 |     result.error = static_cast<int>(error); | 
 |     base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |         FROM_HERE, base::Bind(download_callback, result)); | 
 |     return; | 
 |   } | 
 |  | 
 |   urls_ = urls; | 
 |   expected_hash_ = expected_hash; | 
 |   current_url_ = urls_.begin(); | 
 |   download_callback_ = download_callback; | 
 |  | 
 |   DoStartDownload(*current_url_); | 
 | } | 
 |  | 
 | void CrxDownloader::OnDownloadComplete( | 
 |     bool is_handled, | 
 |     const Result& result, | 
 |     const DownloadMetrics& download_metrics) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  | 
 |   if (!result.error) | 
 |     task_runner()->PostTask( | 
 |         FROM_HERE, | 
 |         base::Bind(&CrxDownloader::VerifyResponse, base::Unretained(this), | 
 |                    is_handled, result, download_metrics)); | 
 |   else | 
 |     main_task_runner()->PostTask( | 
 |         FROM_HERE, | 
 |         base::Bind(&CrxDownloader::HandleDownloadError, base::Unretained(this), | 
 |                    is_handled, result, download_metrics)); | 
 | } | 
 |  | 
 | void CrxDownloader::OnDownloadProgress(const Result& result) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  | 
 |   if (progress_callback_.is_null()) | 
 |     return; | 
 |  | 
 |   progress_callback_.Run(result); | 
 | } | 
 |  | 
 | // The function mutates the values of the parameters |result| and | 
 | // |download_metrics|. | 
 | void CrxDownloader::VerifyResponse(bool is_handled, | 
 |                                    Result result, | 
 |                                    DownloadMetrics download_metrics) { | 
 |   DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 
 |   DCHECK_EQ(0, result.error); | 
 |   DCHECK_EQ(0, download_metrics.error); | 
 |   DCHECK(is_handled); | 
 |  | 
 |   if (VerifyFileHash256(result.response, expected_hash_)) { | 
 |     download_metrics_.push_back(download_metrics); | 
 |     main_task_runner()->PostTask(FROM_HERE, | 
 |                                  base::Bind(download_callback_, result)); | 
 |     return; | 
 |   } | 
 |  | 
 |   // The download was successful but the response is not trusted. Clean up | 
 |   // the download, mutate the result, and try the remaining fallbacks when | 
 |   // handling the error. | 
 |   result.error = static_cast<int>(CrxDownloaderError::BAD_HASH); | 
 |   download_metrics.error = result.error; | 
 |   DeleteFileAndEmptyParentDirectory(result.response); | 
 |   result.response.clear(); | 
 |  | 
 |   main_task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       base::Bind(&CrxDownloader::HandleDownloadError, base::Unretained(this), | 
 |                  is_handled, result, download_metrics)); | 
 | } | 
 |  | 
 | void CrxDownloader::HandleDownloadError( | 
 |     bool is_handled, | 
 |     const Result& result, | 
 |     const DownloadMetrics& download_metrics) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK_NE(0, result.error); | 
 |   DCHECK_NE(0, download_metrics.error); | 
 |  | 
 |   download_metrics_.push_back(download_metrics); | 
 |  | 
 |   // If an error has occured, try the next url if there is any, | 
 |   // or try the successor in the chain if there is any successor. | 
 |   // If this downloader has received a 5xx error for the current url, | 
 |   // as indicated by the |is_handled| flag, remove that url from the list of | 
 |   // urls so the url is never tried again down the chain. | 
 |   if (is_handled) { | 
 |     current_url_ = urls_.erase(current_url_); | 
 |   } else { | 
 |     ++current_url_; | 
 |   } | 
 |  | 
 |   // Try downloading from another url from the list. | 
 |   if (current_url_ != urls_.end()) { | 
 |     DoStartDownload(*current_url_); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Try downloading using the next downloader. | 
 |   if (successor_ && !urls_.empty()) { | 
 |     successor_->StartDownload(urls_, expected_hash_, download_callback_); | 
 |     return; | 
 |   } | 
 |  | 
 |   // The download ends here since there is no url nor downloader to handle this | 
 |   // download request further. | 
 |   main_task_runner()->PostTask(FROM_HERE, | 
 |                                base::Bind(download_callback_, result)); | 
 | } | 
 |  | 
 | }  // namespace update_client |