diff options
author | sorin <sorin@chromium.org> | 2015-01-23 17:15:00 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-24 01:16:25 +0000 |
commit | 52ac088530b109fa506c4491aea8cf12bcadc63d (patch) | |
tree | 64a47ef9e8d4e4662e7366aaea856b9da32ff168 /components/update_client | |
parent | 9d3bb0c75ddbeafd53f5ad32813b0fe70b7e0779 (diff) | |
download | chromium_src-52ac088530b109fa506c4491aea8cf12bcadc63d.zip chromium_src-52ac088530b109fa506c4491aea8cf12bcadc63d.tar.gz chromium_src-52ac088530b109fa506c4491aea8cf12bcadc63d.tar.bz2 |
This is a mechanical change. It is large but straightforward in its intention.
jam: please review the changes under //src/chrome/browser
blundell: please review the changes under //src/components
waffles: please review the component updater and the update client changes.
The intention here is to move most of the component updater dependencies to update_client, with the goal of creating an update_client Chrome component that encapsulates the details of talking with the update servers, downloading, and invoking installers of CRXs for both extensions and components.
The dependencies should be: component_updater->update_client.
This change just lays down some of the code that will be further used by update_client. No functionality is provided in this change. Also consider the overall goal as WIP; therefore, some of the naming and dependencies are not final. We want to have most of the code in place for future work, but want to minimize the changes to the existing production code and keep the refactoring mechanical for this change.
BUG=450337
Review URL: https://codereview.chromium.org/808773005
Cr-Commit-Position: refs/heads/master@{#312986}
Diffstat (limited to 'components/update_client')
42 files changed, 6706 insertions, 0 deletions
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn index d51bd9d..8dd5ef8 100644 --- a/components/update_client/BUILD.gn +++ b/components/update_client/BUILD.gn @@ -4,13 +4,89 @@ source_set("update_client") { sources = [ + "background_downloader_win.cc", + "background_downloader_win.h", + "component_patcher.cc", + "component_patcher.h", + "component_patcher_operation.cc", + "component_patcher_operation.h", + "component_unpacker.cc", + "component_unpacker.h", + "ping_manager.cc", + "ping_manager.h", + "utils.cc", + "utils.h", + "crx_update_item.h", + "crx_downloader.cc", + "crx_downloader.h", + "request_sender.cc", + "request_sender.h", + "update_checker.cc", + "update_checker.h", + "update_client.cc", + "update_client.h", "update_query_params.cc", "update_query_params.h", "update_query_params_delegate.cc", "update_query_params_delegate.h", + "update_response.cc", + "update_response.h", + "url_fetcher_downloader.cc", + "url_fetcher_downloader.h", ] deps = [ "//base", + "//components/crx_file", + "//courgette:courgette_lib", + "//crypto", + "//third_party/libxml", + "//third_party/zlib:zip", + "//net", + "//url", + ] +} + +source_set("test_support") { + testonly = true + sources = [ + "test/test_configurator.cc", + "test/test_configurator.h", + "test/test_installer.cc", + "test/test_installer.h", + "test/url_request_post_interceptor.cc", + "test/url_request_post_interceptor.h", + ] + + deps = [ + ":update_client", + "//base", + "//net:test_support", + "//testing/gtest", + "//testing/gmock", + "//url", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "test/component_patcher_unittest.cc", + "test/ping_manager_unittest.cc", + "test/crx_downloader_unittest.cc", + "test/request_sender_unittest.cc", + "test/update_checker_unittest.cc", + "test/update_response_unittest.cc", + ] + + deps = [ + ":update_client", + ":test_support", + "//base", + "//courgette:courgette_lib", + "//net:test_support", + "//testing/gtest", + "//testing/gmock", + "//third_party/libxml", ] } diff --git a/components/update_client/DEPS b/components/update_client/DEPS index beabace..79e0349 100644 --- a/components/update_client/DEPS +++ b/components/update_client/DEPS @@ -1,4 +1,12 @@ include_rules = [ "+base", + "+components/crx_file", + "+courgette", + "+crypto", + "+libxml", + "+net", + "+ui/base/win", + "+third_party/libxml", + "+third_party/zlib", "+testing", ] diff --git a/components/update_client/background_downloader_win.cc b/components/update_client/background_downloader_win.cc new file mode 100644 index 0000000..536fafd --- /dev/null +++ b/components/update_client/background_downloader_win.cc @@ -0,0 +1,762 @@ +// 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/background_downloader_win.h" + +#include <atlbase.h> +#include <atlcom.h> + +#include <stdint.h> +#include <functional> +#include <iomanip> +#include <limits> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_util.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/sys_string_conversions.h" +#include "base/win/scoped_co_mem.h" +#include "components/update_client/utils.h" +#include "ui/base/win/atl_module.h" +#include "url/gurl.h" + +using base::win::ScopedCoMem; +using base::win::ScopedComPtr; + +// The class BackgroundDownloader in this module is an adapter between +// the CrxDownloader interface and the BITS service interfaces. +// The interface exposed on the CrxDownloader code runs on the main thread, +// while the BITS specific code runs on a separate thread passed in by the +// client. For every url to download, a BITS job is created, unless there is +// already an existing job for that url, in which case, the downloader +// connects to it. Once a job is associated with the url, the code looks for +// changes in the BITS job state. The checks are triggered by a timer. +// The BITS job contains just one file to download. There could only be one +// download in progress at a time. If Chrome closes down before the download is +// complete, the BITS job remains active and finishes in the background, without +// any intervention. The job can be completed next time the code runs, if the +// file is still needed, otherwise it will be cleaned up on a periodic basis. +// +// To list the BITS jobs for a user, use the |bitsadmin| tool. The command line +// to do that is: "bitsadmin /list /verbose". Another useful command is +// "bitsadmin /info" and provide the job id returned by the previous /list +// command. +// +// Ignoring the suspend/resume issues since this code is not using them, the +// job state machine implemented by BITS is something like this: +// +// Suspended--->Queued--->Connecting---->Transferring--->Transferred +// | ^ | | | +// | | V V | (complete) +// +----------|---------+-----------------+-----+ V +// | | | | Acknowledged +// | V V | +// | Transient Error------->Error | +// | | | |(cancel) +// | +-------+---------+--->-+ +// | V | +// | (resume) | | +// +------<----------+ +---->Cancelled +// +// The job is created in the "suspended" state. Once |Resume| is called, +// BITS queues up the job, then tries to connect, begins transferring the +// job bytes, and moves the job to the "transferred state, after the job files +// have been transferred. When calling |Complete| for a job, the job files are +// made available to the caller, and the job is moved to the "acknowledged" +// state. +// At any point, the job can be cancelled, in which case, the job is moved +// to the "cancelled state" and the job object is removed from the BITS queue. +// Along the way, the job can encounter recoverable and non-recoverable errors. +// BITS moves the job to "transient error" or "error", depending on which kind +// of error has occured. +// If the job has reached the "transient error" state, BITS retries the +// job after a certain programmable delay. If the job can't be completed in a +// certain time interval, BITS stops retrying and errors the job out. This time +// interval is also programmable. +// If the job is in either of the error states, the job parameters can be +// adjusted to handle the error, after which the job can be resumed, and the +// whole cycle starts again. +// Jobs that are not touched in 90 days (or a value set by group policy) are +// automatically disposed off by BITS. This concludes the brief description of +// a job lifetime, according to BITS. +// +// In addition to how BITS is managing the life time of the job, there are a +// couple of special cases defined by the BackgroundDownloader. +// First, if the job encounters any of the 5xx HTTP responses, the job is +// not retried, in order to avoid DDOS-ing the servers. +// Second, there is a simple mechanism to detect stuck jobs, and allow the rest +// of the code to move on to trying other urls or trying other components. +// Last, after completing a job, irrespective of the outcome, the jobs older +// than a week are proactively cleaned up. + +namespace update_client { + +namespace { + +// All jobs created by this module have a specific description so they can +// be found at run-time or by using system administration tools. +const base::char16 kJobDescription[] = L"Chrome Component Updater"; + +// How often the code looks for changes in the BITS job state. +const int kJobPollingIntervalSec = 4; + +// How long BITS waits before retrying a job after the job encountered +// a transient error. If this value is not set, the BITS default is 10 minutes. +const int kMinimumRetryDelayMin = 1; + +// How long to wait for stuck jobs. Stuck jobs could be queued for too long, +// have trouble connecting, could be suspended for any reason, or they have +// encountered some transient error. +const int kJobStuckTimeoutMin = 15; + +// How long BITS waits before giving up on a job that could not be completed +// since the job has encountered its first transient error. If this value is +// not set, the BITS default is 14 days. +const int kSetNoProgressTimeoutDays = 1; + +// How often the jobs which were started but not completed for any reason +// are cleaned up. Reasons for jobs to be left behind include browser restarts, +// system restarts, etc. Also, the check to purge stale jobs only happens +// at most once a day. If the job clean up code is not running, the BITS +// default policy is to cancel jobs after 90 days of inactivity. +const int kPurgeStaleJobsAfterDays = 7; +const int kPurgeStaleJobsIntervalBetweenChecksDays = 1; + +// Returns the status code from a given BITS error. +int GetHttpStatusFromBitsError(HRESULT error) { + // BITS errors are defined in bitsmsg.h. Although not documented, it is + // clear that all errors corresponding to http status code have the high + // word equal to 0x8019 and the low word equal to the http status code. + const int kHttpStatusFirst = 100; // Continue. + const int kHttpStatusLast = 505; // Version not supported. + bool is_valid = HIWORD(error) == 0x8019 && + LOWORD(error) >= kHttpStatusFirst && + LOWORD(error) <= kHttpStatusLast; + return is_valid ? LOWORD(error) : 0; +} + +// Returns the files in a BITS job. +HRESULT GetFilesInJob(IBackgroundCopyJob* job, + std::vector<ScopedComPtr<IBackgroundCopyFile>>* files) { + ScopedComPtr<IEnumBackgroundCopyFiles> enum_files; + HRESULT hr = job->EnumFiles(enum_files.Receive()); + if (FAILED(hr)) + return hr; + + ULONG num_files = 0; + hr = enum_files->GetCount(&num_files); + if (FAILED(hr)) + return hr; + + for (ULONG i = 0; i != num_files; ++i) { + ScopedComPtr<IBackgroundCopyFile> file; + if (enum_files->Next(1, file.Receive(), NULL) == S_OK && file.get()) + files->push_back(file); + } + + return S_OK; +} + +// Returns the file name, the url, and some per-file progress information. +// The function out parameters can be NULL if that data is not requested. +HRESULT GetJobFileProperties(IBackgroundCopyFile* file, + base::string16* local_name, + base::string16* remote_name, + BG_FILE_PROGRESS* progress) { + if (!file) + return E_FAIL; + + HRESULT hr = S_OK; + + if (local_name) { + ScopedCoMem<base::char16> name; + hr = file->GetLocalName(&name); + if (FAILED(hr)) + return hr; + local_name->assign(name); + } + + if (remote_name) { + ScopedCoMem<base::char16> name; + hr = file->GetRemoteName(&name); + if (FAILED(hr)) + return hr; + remote_name->assign(name); + } + + if (progress) { + BG_FILE_PROGRESS bg_file_progress = {}; + hr = file->GetProgress(&bg_file_progress); + if (FAILED(hr)) + return hr; + *progress = bg_file_progress; + } + + return hr; +} + +// Returns the number of bytes downloaded and bytes to download for all files +// in the job. If the values are not known or if an error has occurred, +// a value of -1 is reported. +HRESULT GetJobByteCount(IBackgroundCopyJob* job, + int64_t* downloaded_bytes, + int64_t* total_bytes) { + *downloaded_bytes = -1; + *total_bytes = -1; + + if (!job) + return E_FAIL; + + BG_JOB_PROGRESS job_progress = {0}; + HRESULT hr = job->GetProgress(&job_progress); + if (FAILED(hr)) + return hr; + + const uint64_t kMaxNumBytes = + static_cast<uint64_t>(std::numeric_limits<int64_t>::max()); + if (job_progress.BytesTransferred <= kMaxNumBytes) + *downloaded_bytes = job_progress.BytesTransferred; + + if (job_progress.BytesTotal <= kMaxNumBytes && + job_progress.BytesTotal != BG_SIZE_UNKNOWN) + *total_bytes = job_progress.BytesTotal; + + return S_OK; +} + +HRESULT GetJobDescription(IBackgroundCopyJob* job, const base::string16* name) { + ScopedCoMem<base::char16> description; + return job->GetDescription(&description); +} + +// Returns the job error code in |error_code| if the job is in the transient +// or the final error state. Otherwise, the job error is not available and +// the function fails. +HRESULT GetJobError(IBackgroundCopyJob* job, HRESULT* error_code_out) { + *error_code_out = S_OK; + ScopedComPtr<IBackgroundCopyError> copy_error; + HRESULT hr = job->GetError(copy_error.Receive()); + if (FAILED(hr)) + return hr; + + BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE; + HRESULT error_code = S_OK; + hr = copy_error->GetError(&error_context, &error_code); + if (FAILED(hr)) + return hr; + + *error_code_out = FAILED(error_code) ? error_code : E_FAIL; + return S_OK; +} + +// Finds the component updater jobs matching the given predicate. +// Returns S_OK if the function has found at least one job, returns S_FALSE if +// no job was found, and it returns an error otherwise. +template <class Predicate> +HRESULT FindBitsJobIf(Predicate pred, + IBackgroundCopyManager* bits_manager, + std::vector<ScopedComPtr<IBackgroundCopyJob>>* jobs) { + ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs; + HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive()); + if (FAILED(hr)) + return hr; + + ULONG job_count = 0; + hr = enum_jobs->GetCount(&job_count); + if (FAILED(hr)) + return hr; + + // Iterate over jobs, run the predicate, and select the job only if + // the job description matches the component updater jobs. + for (ULONG i = 0; i != job_count; ++i) { + ScopedComPtr<IBackgroundCopyJob> current_job; + if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK && + pred(current_job.get())) { + base::string16 job_description; + hr = GetJobDescription(current_job.get(), &job_description); + if (job_description.compare(kJobDescription) == 0) + jobs->push_back(current_job); + } + } + + return jobs->empty() ? S_FALSE : S_OK; +} + +// Compares the job creation time and returns true if the job creation time +// is older than |num_days|. +struct JobCreationOlderThanDays + : public std::binary_function<IBackgroundCopyJob*, int, bool> { + bool operator()(IBackgroundCopyJob* job, int num_days) const; +}; + +bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job, + int num_days) const { + BG_JOB_TIMES times = {0}; + HRESULT hr = job->GetTimes(×); + if (FAILED(hr)) + return false; + + const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days)); + const base::Time creation_time(base::Time::FromFileTime(times.CreationTime)); + + return creation_time + time_delta < base::Time::Now(); +} + +// Compares the url of a file in a job and returns true if the remote name +// of any file in a job matches the argument. +struct JobFileUrlEqual : public std::binary_function<IBackgroundCopyJob*, + const base::string16&, + bool> { + bool operator()(IBackgroundCopyJob* job, + const base::string16& remote_name) const; +}; + +bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job, + const base::string16& remote_name) const { + std::vector<ScopedComPtr<IBackgroundCopyFile>> files; + HRESULT hr = GetFilesInJob(job, &files); + if (FAILED(hr)) + return false; + + for (size_t i = 0; i != files.size(); ++i) { + ScopedCoMem<base::char16> name; + if (SUCCEEDED(files[i]->GetRemoteName(&name)) && + remote_name.compare(name) == 0) + return true; + } + + return false; +} + +// Creates an instance of the BITS manager. +HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) { + ScopedComPtr<IBackgroundCopyManager> object; + HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager)); + if (FAILED(hr)) { + return hr; + } + *bits_manager = object.Detach(); + return S_OK; +} + +void CleanupJobFiles(IBackgroundCopyJob* job) { + std::vector<ScopedComPtr<IBackgroundCopyFile>> files; + if (FAILED(GetFilesInJob(job, &files))) + return; + for (size_t i = 0; i != files.size(); ++i) { + base::string16 local_name; + HRESULT hr(GetJobFileProperties(files[i].get(), &local_name, NULL, NULL)); + if (SUCCEEDED(hr)) + DeleteFileAndEmptyParentDirectory(base::FilePath(local_name)); + } +} + +// Cleans up incompleted jobs that are too old. +HRESULT CleanupStaleJobs( + base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) { + if (!bits_manager.get()) + return E_FAIL; + + static base::Time last_sweep; + + const base::TimeDelta time_delta( + base::TimeDelta::FromDays(kPurgeStaleJobsIntervalBetweenChecksDays)); + const base::Time current_time(base::Time::Now()); + if (last_sweep + time_delta > current_time) + return S_OK; + + last_sweep = current_time; + + std::vector<ScopedComPtr<IBackgroundCopyJob>> jobs; + HRESULT hr = FindBitsJobIf( + std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays), + bits_manager.get(), &jobs); + if (FAILED(hr)) + return hr; + + for (size_t i = 0; i != jobs.size(); ++i) { + jobs[i]->Cancel(); + CleanupJobFiles(jobs[i].get()); + } + + return S_OK; +} + +} // namespace + +BackgroundDownloader::BackgroundDownloader( + scoped_ptr<CrxDownloader> successor, + net::URLRequestContextGetter* context_getter, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) + : CrxDownloader(successor.Pass()), + main_task_runner_(base::MessageLoopProxy::current()), + context_getter_(context_getter), + task_runner_(task_runner), + is_completed_(false) { +} + +BackgroundDownloader::~BackgroundDownloader() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // The following objects have thread affinity and can't be destroyed on the + // main thread. The resources managed by these objects are acquired at the + // beginning of a download and released at the end of the download. Most of + // the time, when this destructor is called, these resources have been already + // disposed by. Releasing the ownership here is a NOP. However, if the browser + // is shutting down while a download is in progress, the timer is active and + // the interface pointers are valid. Releasing the ownership means leaking + // these objects and their associated resources. + timer_.release(); + bits_manager_.Detach(); + job_.Detach(); +} + +void BackgroundDownloader::DoStartDownload(const GURL& url) { + DCHECK(thread_checker_.CalledOnValidThread()); + + task_runner_->PostTask(FROM_HERE, + base::Bind(&BackgroundDownloader::BeginDownload, + base::Unretained(this), url)); +} + +// Called once when this class is asked to do a download. Creates or opens +// an existing bits job, hooks up the notifications, and starts the timer. +void BackgroundDownloader::BeginDownload(const GURL& url) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + DCHECK(!timer_); + + is_completed_ = false; + download_start_time_ = base::Time::Now(); + job_stuck_begin_time_ = download_start_time_; + + HRESULT hr = QueueBitsJob(url); + if (FAILED(hr)) { + EndDownload(hr); + return; + } + + // A repeating timer retains the user task. This timer can be stopped and + // reset multiple times. + timer_.reset(new base::RepeatingTimer<BackgroundDownloader>); + timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(kJobPollingIntervalSec), + this, &BackgroundDownloader::OnDownloading); +} + +// Called any time the timer fires. +void BackgroundDownloader::OnDownloading() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + DCHECK(job_.get()); + + DCHECK(!is_completed_); + if (is_completed_) + return; + + BG_JOB_STATE job_state = BG_JOB_STATE_ERROR; + HRESULT hr = job_->GetState(&job_state); + if (FAILED(hr)) { + EndDownload(hr); + return; + } + + switch (job_state) { + case BG_JOB_STATE_TRANSFERRED: + OnStateTransferred(); + return; + + case BG_JOB_STATE_ERROR: + OnStateError(); + return; + + case BG_JOB_STATE_CANCELLED: + OnStateCancelled(); + return; + + case BG_JOB_STATE_ACKNOWLEDGED: + OnStateAcknowledged(); + return; + + case BG_JOB_STATE_QUEUED: + // Fall through. + case BG_JOB_STATE_CONNECTING: + // Fall through. + case BG_JOB_STATE_SUSPENDED: + OnStateQueued(); + break; + + case BG_JOB_STATE_TRANSIENT_ERROR: + OnStateTransientError(); + break; + + case BG_JOB_STATE_TRANSFERRING: + OnStateTransferring(); + break; + + default: + break; + } +} + +// Completes the BITS download, picks up the file path of the response, and +// notifies the CrxDownloader. The function should be called only once. +void BackgroundDownloader::EndDownload(HRESULT error) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + DCHECK(!is_completed_); + is_completed_ = true; + + timer_.reset(); + + const base::Time download_end_time(base::Time::Now()); + const base::TimeDelta download_time = + download_end_time >= download_start_time_ + ? download_end_time - download_start_time_ + : base::TimeDelta(); + + int64_t downloaded_bytes = -1; + int64_t total_bytes = -1; + GetJobByteCount(job_.get(), &downloaded_bytes, &total_bytes); + + if (FAILED(error) && job_.get()) { + job_->Cancel(); + CleanupJobFiles(job_.get()); + } + + job_ = NULL; + + CleanupStaleJobs(bits_manager_); + bits_manager_ = NULL; + + // Consider the url handled if it has been successfully downloaded or a + // 5xx has been received. + const bool is_handled = + SUCCEEDED(error) || IsHttpServerError(GetHttpStatusFromBitsError(error)); + + const int error_to_report = SUCCEEDED(error) ? 0 : error; + + DownloadMetrics download_metrics; + download_metrics.url = url(); + download_metrics.downloader = DownloadMetrics::kBits; + download_metrics.error = error_to_report; + download_metrics.downloaded_bytes = downloaded_bytes; + download_metrics.total_bytes = total_bytes; + download_metrics.download_time_ms = download_time.InMilliseconds(); + + Result result; + result.error = error_to_report; + result.response = response_; + result.downloaded_bytes = downloaded_bytes; + result.total_bytes = total_bytes; + main_task_runner_->PostTask( + FROM_HERE, + base::Bind(&BackgroundDownloader::OnDownloadComplete, + base::Unretained(this), is_handled, result, download_metrics)); + + // Once the task is posted to the the main thread, this object may be deleted + // by its owner. It is not safe to access members of this object on the + // task runner from this point on. The timer is stopped and all BITS + // interface pointers have been released. +} + +// Called when the BITS job has been transferred successfully. Completes the +// BITS job by removing it from the BITS queue and making the download +// available to the caller. +void BackgroundDownloader::OnStateTransferred() { + EndDownload(CompleteJob()); +} + +// Called when the job has encountered an error and no further progress can +// be made. Cancels this job and removes it from the BITS queue. +void BackgroundDownloader::OnStateError() { + HRESULT error_code = S_OK; + HRESULT hr = GetJobError(job_.get(), &error_code); + if (FAILED(hr)) + error_code = hr; + DCHECK(FAILED(error_code)); + EndDownload(error_code); +} + +// Called when the job has encountered a transient error, such as a +// network disconnect, a server error, or some other recoverable error. +void BackgroundDownloader::OnStateTransientError() { + // If the job appears to be stuck, handle the transient error as if + // it were a final error. This causes the job to be cancelled and a specific + // error be returned, if the error was available. + if (IsStuck()) { + OnStateError(); + return; + } + + // Don't retry at all if the transient error was a 5xx. + HRESULT error_code = S_OK; + HRESULT hr = GetJobError(job_.get(), &error_code); + if (SUCCEEDED(hr) && + IsHttpServerError(GetHttpStatusFromBitsError(error_code))) { + OnStateError(); + return; + } +} + +void BackgroundDownloader::OnStateQueued() { + if (IsStuck()) + EndDownload(E_ABORT); // Return a generic error for now. +} + +void BackgroundDownloader::OnStateTransferring() { + // Resets the baseline for detecting a stuck job since the job is transferring + // data and it is making progress. + job_stuck_begin_time_ = base::Time::Now(); + + int64_t downloaded_bytes = -1; + int64_t total_bytes = -1; + HRESULT hr = GetJobByteCount(job_.get(), &downloaded_bytes, &total_bytes); + if (FAILED(hr)) + return; + + Result result; + result.downloaded_bytes = downloaded_bytes; + result.total_bytes = total_bytes; + + main_task_runner_->PostTask( + FROM_HERE, base::Bind(&BackgroundDownloader::OnDownloadProgress, + base::Unretained(this), result)); +} + +// Called when the download was cancelled. Since the observer should have +// been disconnected by now, this notification must not be seen. +void BackgroundDownloader::OnStateCancelled() { + EndDownload(E_UNEXPECTED); +} + +// Called when the download was completed. Same as above. +void BackgroundDownloader::OnStateAcknowledged() { + EndDownload(E_UNEXPECTED); +} + +// Creates or opens a job for the given url and queues it up. Tries to +// install a job observer but continues on if an observer can't be set up. +HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + HRESULT hr = S_OK; + if (bits_manager_.get() == NULL) { + hr = GetBitsManager(bits_manager_.Receive()); + if (FAILED(hr)) + return hr; + } + + hr = CreateOrOpenJob(url); + if (FAILED(hr)) + return hr; + + if (hr == S_OK) { + hr = InitializeNewJob(url); + if (FAILED(hr)) + return hr; + } + + return job_->Resume(); +} + +HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) { + std::vector<ScopedComPtr<IBackgroundCopyJob>> jobs; + HRESULT hr = FindBitsJobIf( + std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())), + bits_manager_.get(), &jobs); + if (SUCCEEDED(hr) && !jobs.empty()) { + job_ = jobs.front(); + return S_FALSE; + } + + // Use kJobDescription as a temporary job display name until the proper + // display name is initialized later on. + GUID guid = {0}; + ScopedComPtr<IBackgroundCopyJob> job; + hr = bits_manager_->CreateJob(kJobDescription, BG_JOB_TYPE_DOWNLOAD, &guid, + job.Receive()); + if (FAILED(hr)) + return hr; + + job_ = job; + return S_OK; +} + +HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) { + const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName())); + + base::FilePath tempdir; + if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"), + &tempdir)) + return E_FAIL; + + HRESULT hr = job_->AddFile(base::SysUTF8ToWide(url.spec()).c_str(), + tempdir.Append(filename).AsUTF16Unsafe().c_str()); + if (FAILED(hr)) + return hr; + + hr = job_->SetDisplayName(filename.c_str()); + if (FAILED(hr)) + return hr; + + hr = job_->SetDescription(kJobDescription); + if (FAILED(hr)) + return hr; + + hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL); + if (FAILED(hr)) + return hr; + + hr = job_->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin); + if (FAILED(hr)) + return hr; + + const int kSecondsDay = 60 * 60 * 24; + hr = job_->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays); + if (FAILED(hr)) + return hr; + + return S_OK; +} + +bool BackgroundDownloader::IsStuck() { + const base::TimeDelta job_stuck_timeout( + base::TimeDelta::FromMinutes(kJobStuckTimeoutMin)); + return job_stuck_begin_time_ + job_stuck_timeout < base::Time::Now(); +} + +HRESULT BackgroundDownloader::CompleteJob() { + HRESULT hr = job_->Complete(); + if (FAILED(hr) && hr != BG_S_UNABLE_TO_DELETE_FILES) + return hr; + + std::vector<ScopedComPtr<IBackgroundCopyFile>> files; + hr = GetFilesInJob(job_.get(), &files); + if (FAILED(hr)) + return hr; + + if (files.empty()) + return E_UNEXPECTED; + + base::string16 local_name; + BG_FILE_PROGRESS progress = {0}; + hr = GetJobFileProperties(files.front().get(), &local_name, NULL, &progress); + if (FAILED(hr)) + return hr; + + // Sanity check the post-conditions of a successful download, including + // the file and job invariants. The byte counts for a job and its file + // must match as a job only contains one file. + DCHECK(progress.Completed); + DCHECK_EQ(progress.BytesTotal, progress.BytesTransferred); + + response_ = base::FilePath(local_name); + + return S_OK; +} + +} // namespace update_client diff --git a/components/update_client/background_downloader_win.h b/components/update_client/background_downloader_win.h new file mode 100644 index 0000000..48ce6dd --- /dev/null +++ b/components/update_client/background_downloader_win.h @@ -0,0 +1,119 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_ +#define COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_ + +#include <windows.h> +#include <bits.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "base/win/scoped_comptr.h" +#include "components/update_client/crx_downloader.h" + +namespace base { +class FilePath; +class MessageLoopProxy; +class SingleThreadTaskRunner; +} + +namespace update_client { + +// Implements a downloader in terms of the BITS service. The public interface +// of this class and the CrxDownloader overrides are expected to be called +// from the main thread. The rest of the class code runs on a single thread +// task runner. This task runner must be initialized to work with COM objects. +// Instances of this class are created and destroyed in the main thread. +// See the implementation of the class destructor for details regarding the +// clean up of resources acquired in this class. +class BackgroundDownloader : public CrxDownloader { + protected: + friend class CrxDownloader; + BackgroundDownloader(scoped_ptr<CrxDownloader> successor, + net::URLRequestContextGetter* context_getter, + scoped_refptr<base::SingleThreadTaskRunner> task_runner); + virtual ~BackgroundDownloader(); + + private: + // Overrides for CrxDownloader. + virtual void DoStartDownload(const GURL& url) override; + + // Called asynchronously on the |task_runner_| at different stages during + // the download. |OnDownloading| can be called multiple times. + // |EndDownload| switches the execution flow from the |task_runner_| to the + // main thread. Accessing any data members of this object from the + // |task_runner_|after calling |EndDownload| is unsafe. + void BeginDownload(const GURL& url); + void OnDownloading(); + void EndDownload(HRESULT hr); + + // Handles the job state transitions to a final state. + void OnStateTransferred(); + void OnStateError(); + void OnStateCancelled(); + void OnStateAcknowledged(); + + // Handles the transition to a transient state where the job is in the + // queue but not actively transferring data. + void OnStateQueued(); + + // Handles the job state transition to a transient, non-final error state. + void OnStateTransientError(); + + // Handles the job state corresponding to transferring data. + void OnStateTransferring(); + + HRESULT QueueBitsJob(const GURL& url); + HRESULT CreateOrOpenJob(const GURL& url); + HRESULT InitializeNewJob(const GURL& url); + + // Returns true if at the time of the call, it appears that the job + // has not been making progress toward completion. + bool IsStuck(); + + // Makes the downloaded file available to the caller by renaming the + // temporary file to its destination and removing it from the BITS queue. + HRESULT CompleteJob(); + + // Ensures that we are running on the same thread we created the object on. + base::ThreadChecker thread_checker_; + + // Used to post responses back to the main thread. Initialized on the main + // loop but accessed from the task runner. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + net::URLRequestContextGetter* context_getter_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + // The timer and the BITS interface pointers have thread affinity. These + // members are initialized on the task runner and they must be destroyed + // on the task runner. + scoped_ptr<base::RepeatingTimer<BackgroundDownloader>> timer_; + + base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager_; + base::win::ScopedComPtr<IBackgroundCopyJob> job_; + + // Contains the time when the download of the current url has started. + base::Time download_start_time_; + + // Contains the time when the BITS job is last seen making progress. + base::Time job_stuck_begin_time_; + + // True if EndDownload was called. + bool is_completed_; + + // Contains the path of the downloaded file if the download was successful. + base::FilePath response_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundDownloader); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_ diff --git a/components/update_client/component_patcher.cc b/components/update_client/component_patcher.cc new file mode 100644 index 0000000..651756e --- /dev/null +++ b/components/update_client/component_patcher.cc @@ -0,0 +1,121 @@ +// 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/component_patcher.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/location.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "components/update_client/component_patcher_operation.h" + +namespace update_client { + +namespace { + +// Deserialize the commands file (present in delta update packages). The top +// level must be a list. +base::ListValue* ReadCommands(const base::FilePath& unpack_path) { + const base::FilePath commands = + unpack_path.Append(FILE_PATH_LITERAL("commands.json")); + if (!base::PathExists(commands)) + return NULL; + + JSONFileValueSerializer serializer(commands); + scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL)); + + return (root.get() && root->IsType(base::Value::TYPE_LIST)) + ? static_cast<base::ListValue*>(root.release()) + : NULL; +} + +} // namespace + +ComponentPatcher::ComponentPatcher( + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentInstaller* installer, + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher, + scoped_refptr<base::SequencedTaskRunner> task_runner) + : input_dir_(input_dir), + unpack_dir_(unpack_dir), + installer_(installer), + out_of_process_patcher_(out_of_process_patcher), + task_runner_(task_runner) { +} + +ComponentPatcher::~ComponentPatcher() { +} + +void ComponentPatcher::Start(const ComponentUnpacker::Callback& callback) { + callback_ = callback; + task_runner_->PostTask(FROM_HERE, + base::Bind(&ComponentPatcher::StartPatching, + scoped_refptr<ComponentPatcher>(this))); +} + +void ComponentPatcher::StartPatching() { + commands_.reset(ReadCommands(input_dir_)); + if (!commands_.get()) { + DonePatching(ComponentUnpacker::kDeltaBadCommands, 0); + } else { + next_command_ = commands_->begin(); + PatchNextFile(); + } +} + +void ComponentPatcher::PatchNextFile() { + if (next_command_ == commands_->end()) { + DonePatching(ComponentUnpacker::kNone, 0); + return; + } + if (!(*next_command_)->IsType(base::Value::TYPE_DICTIONARY)) { + DonePatching(ComponentUnpacker::kDeltaBadCommands, 0); + return; + } + const base::DictionaryValue* command_args = + static_cast<base::DictionaryValue*>(*next_command_); + + std::string operation; + if (command_args->GetString(kOp, &operation)) { + current_operation_ = + CreateDeltaUpdateOp(operation, out_of_process_patcher_); + } + + if (!current_operation_.get()) { + DonePatching(ComponentUnpacker::kDeltaUnsupportedCommand, 0); + return; + } + current_operation_->Run(command_args, input_dir_, unpack_dir_, installer_, + base::Bind(&ComponentPatcher::DonePatchingFile, + scoped_refptr<ComponentPatcher>(this)), + task_runner_); +} + +void ComponentPatcher::DonePatchingFile(ComponentUnpacker::Error error, + int extended_error) { + if (error != ComponentUnpacker::kNone) { + DonePatching(error, extended_error); + } else { + ++next_command_; + PatchNextFile(); + } +} + +void ComponentPatcher::DonePatching(ComponentUnpacker::Error error, + int extended_error) { + current_operation_ = NULL; + task_runner_->PostTask(FROM_HERE, + base::Bind(callback_, error, extended_error)); + callback_.Reset(); +} + +} // namespace update_client diff --git a/components/update_client/component_patcher.h b/components/update_client/component_patcher.h new file mode 100644 index 0000000..b7dd216 --- /dev/null +++ b/components/update_client/component_patcher.h @@ -0,0 +1,102 @@ +// 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. + +// Component updates can be either differential updates or full updates. +// Full updates come in CRX format; differential updates come in CRX-style +// archives, but have a different magic number. They contain "commands.json", a +// list of commands for the patcher to follow. The patcher uses these commands, +// the other files in the archive, and the files from the existing installation +// of the component to create the contents of a full update, which is then +// installed normally. +// Component updates are specified by the 'codebasediff' attribute of an +// updatecheck response: +// <updatecheck codebase="http://example.com/extension_1.2.3.4.crx" +// hash="12345" size="9854" status="ok" version="1.2.3.4" +// prodversionmin="2.0.143.0" +// codebasediff="http://example.com/diff_1.2.3.4.crx" +// hashdiff="123" sizediff="101" +// fp="1.123" /> +// The component updater will attempt a differential update if it is available +// and allowed to, and fall back to a full update if it fails. +// +// After installation (diff or full), the component updater records "fp", the +// fingerprint of the installed files, to later identify the existing files to +// the server so that a proper differential update can be provided next cycle. + +#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_ +#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_ + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "components/update_client/component_unpacker.h" + +namespace base { +class FilePath; +} + +namespace update_client { + +class ComponentInstaller; +class DeltaUpdateOp; +class OutOfProcessPatcher; + +// The type of a patch file. +enum PatchType { + kPatchTypeUnknown, + kPatchTypeCourgette, + kPatchTypeBsdiff, +}; + +// Encapsulates a task for applying a differential update to a component. +class ComponentPatcher : public base::RefCountedThreadSafe<ComponentPatcher> { + public: + // Takes an unpacked differential CRX (|input_dir|) and a component installer, + // and sets up the class to create a new (non-differential) unpacked CRX. + // If |in_process| is true, patching will be done completely within the + // existing process. Otherwise, some steps of patching may be done + // out-of-process. + ComponentPatcher(const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentInstaller* installer, + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher, + scoped_refptr<base::SequencedTaskRunner> task_runner); + + // Starts patching files. This member function returns immediately, after + // posting a task to do the patching. When patching has been completed, + // |callback| will be called with the error codes if any error codes were + // encountered. + void Start(const ComponentUnpacker::Callback& callback); + + private: + friend class base::RefCountedThreadSafe<ComponentPatcher>; + + virtual ~ComponentPatcher(); + + void StartPatching(); + + void PatchNextFile(); + + void DonePatchingFile(ComponentUnpacker::Error error, int extended_error); + + void DonePatching(ComponentUnpacker::Error error, int extended_error); + + const base::FilePath input_dir_; + const base::FilePath unpack_dir_; + ComponentInstaller* const installer_; + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher_; + ComponentUnpacker::Callback callback_; + scoped_ptr<base::ListValue> commands_; + base::ValueVector::const_iterator next_command_; + scoped_refptr<DeltaUpdateOp> current_operation_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(ComponentPatcher); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_ diff --git a/components/update_client/component_patcher_operation.cc b/components/update_client/component_patcher_operation.cc new file mode 100644 index 0000000..adb5b8f --- /dev/null +++ b/components/update_client/component_patcher_operation.cc @@ -0,0 +1,262 @@ +// 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/component_patcher_operation.h" + +#include <stdint.h> +#include <vector> + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/location.h" +#include "base/strings/string_number_conversions.h" +#include "components/update_client/component_patcher.h" +#include "components/update_client/update_client.h" +#include "courgette/courgette.h" +#include "courgette/third_party/bsdiff.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "crypto/signature_verifier.h" + +using crypto::SecureHash; + +namespace update_client { + +namespace { + +const char kOutput[] = "output"; +const char kSha256[] = "sha256"; + +// The integer offset disambiguates between overlapping error ranges. +const int kCourgetteErrorOffset = 300; +const int kBsdiffErrorOffset = 600; + +} // namespace + +const char kOp[] = "op"; +const char kBsdiff[] = "bsdiff"; +const char kCourgette[] = "courgette"; +const char kInput[] = "input"; +const char kPatch[] = "patch"; + +DeltaUpdateOp* CreateDeltaUpdateOp( + const std::string& operation, + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher) { + if (operation == "copy") { + return new DeltaUpdateOpCopy(); + } else if (operation == "create") { + return new DeltaUpdateOpCreate(); + } else if (operation == "bsdiff" || operation == "courgette") { + return new DeltaUpdateOpPatch(operation, out_of_process_patcher); + } + return NULL; +} + +DeltaUpdateOp::DeltaUpdateOp() { +} + +DeltaUpdateOp::~DeltaUpdateOp() { +} + +void DeltaUpdateOp::Run(const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentInstaller* installer, + const ComponentUnpacker::Callback& callback, + scoped_refptr<base::SequencedTaskRunner> task_runner) { + callback_ = callback; + task_runner_ = task_runner; + std::string output_rel_path; + if (!command_args->GetString(kOutput, &output_rel_path) || + !command_args->GetString(kSha256, &output_sha256_)) { + DoneRunning(ComponentUnpacker::kDeltaBadCommands, 0); + return; + } + + output_abs_path_ = + unpack_dir.Append(base::FilePath::FromUTF8Unsafe(output_rel_path)); + ComponentUnpacker::Error parse_result = + DoParseArguments(command_args, input_dir, installer); + if (parse_result != ComponentUnpacker::kNone) { + DoneRunning(parse_result, 0); + return; + } + + const base::FilePath parent = output_abs_path_.DirName(); + if (!base::DirectoryExists(parent)) { + if (!base::CreateDirectory(parent)) { + DoneRunning(ComponentUnpacker::kIoError, 0); + return; + } + } + + DoRun(base::Bind(&DeltaUpdateOp::DoneRunning, + scoped_refptr<DeltaUpdateOp>(this))); +} + +void DeltaUpdateOp::DoneRunning(ComponentUnpacker::Error error, + int extended_error) { + if (error == ComponentUnpacker::kNone) + error = CheckHash(); + task_runner_->PostTask(FROM_HERE, + base::Bind(callback_, error, extended_error)); + callback_.Reset(); +} + +// Uses the hash as a checksum to confirm that the file now residing in the +// output directory probably has the contents it should. +ComponentUnpacker::Error DeltaUpdateOp::CheckHash() { + std::vector<uint8_t> expected_hash; + if (!base::HexStringToBytes(output_sha256_, &expected_hash) || + expected_hash.size() != crypto::kSHA256Length) + return ComponentUnpacker::kDeltaVerificationFailure; + + base::MemoryMappedFile output_file_mmapped; + if (!output_file_mmapped.Initialize(output_abs_path_)) + return ComponentUnpacker::kDeltaVerificationFailure; + + uint8_t actual_hash[crypto::kSHA256Length] = {0}; + const scoped_ptr<SecureHash> hasher(SecureHash::Create(SecureHash::SHA256)); + hasher->Update(output_file_mmapped.data(), output_file_mmapped.length()); + hasher->Finish(actual_hash, sizeof(actual_hash)); + if (memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash))) + return ComponentUnpacker::kDeltaVerificationFailure; + + return ComponentUnpacker::kNone; +} + +scoped_refptr<base::SequencedTaskRunner> DeltaUpdateOp::GetTaskRunner() { + return task_runner_; +} + +DeltaUpdateOpCopy::DeltaUpdateOpCopy() { +} + +DeltaUpdateOpCopy::~DeltaUpdateOpCopy() { +} + +ComponentUnpacker::Error DeltaUpdateOpCopy::DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string input_rel_path; + if (!command_args->GetString(kInput, &input_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) + return ComponentUnpacker::kDeltaMissingExistingFile; + + return ComponentUnpacker::kNone; +} + +void DeltaUpdateOpCopy::DoRun(const ComponentUnpacker::Callback& callback) { + if (!base::CopyFile(input_abs_path_, output_abs_path_)) + callback.Run(ComponentUnpacker::kDeltaOperationFailure, 0); + else + callback.Run(ComponentUnpacker::kNone, 0); +} + +DeltaUpdateOpCreate::DeltaUpdateOpCreate() { +} + +DeltaUpdateOpCreate::~DeltaUpdateOpCreate() { +} + +ComponentUnpacker::Error DeltaUpdateOpCreate::DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string patch_rel_path; + if (!command_args->GetString(kPatch, &patch_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + patch_abs_path_ = + input_dir.Append(base::FilePath::FromUTF8Unsafe(patch_rel_path)); + + return ComponentUnpacker::kNone; +} + +void DeltaUpdateOpCreate::DoRun(const ComponentUnpacker::Callback& callback) { + if (!base::Move(patch_abs_path_, output_abs_path_)) + callback.Run(ComponentUnpacker::kDeltaOperationFailure, 0); + else + callback.Run(ComponentUnpacker::kNone, 0); +} + +DeltaUpdateOpPatch::DeltaUpdateOpPatch( + const std::string& operation, + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher) + : operation_(operation), out_of_process_patcher_(out_of_process_patcher) { + DCHECK(operation == kBsdiff || operation == kCourgette); +} + +DeltaUpdateOpPatch::~DeltaUpdateOpPatch() { +} + +ComponentUnpacker::Error DeltaUpdateOpPatch::DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string patch_rel_path; + std::string input_rel_path; + if (!command_args->GetString(kPatch, &patch_rel_path) || + !command_args->GetString(kInput, &input_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) + return ComponentUnpacker::kDeltaMissingExistingFile; + + patch_abs_path_ = + input_dir.Append(base::FilePath::FromUTF8Unsafe(patch_rel_path)); + + return ComponentUnpacker::kNone; +} + +void DeltaUpdateOpPatch::DoRun(const ComponentUnpacker::Callback& callback) { + if (out_of_process_patcher_.get()) { + out_of_process_patcher_->Patch( + operation_, GetTaskRunner(), input_abs_path_, patch_abs_path_, + output_abs_path_, + base::Bind(&DeltaUpdateOpPatch::DonePatching, this, callback)); + return; + } + + if (operation_ == kBsdiff) { + DonePatching(callback, + courgette::ApplyBinaryPatch(input_abs_path_, patch_abs_path_, + output_abs_path_)); + } else if (operation_ == kCourgette) { + DonePatching(callback, courgette::ApplyEnsemblePatch( + input_abs_path_.value().c_str(), + patch_abs_path_.value().c_str(), + output_abs_path_.value().c_str())); + } else { + NOTREACHED(); + } +} + +void DeltaUpdateOpPatch::DonePatching( + const ComponentUnpacker::Callback& callback, + int result) { + if (operation_ == kBsdiff) { + if (result == courgette::OK) { + callback.Run(ComponentUnpacker::kNone, 0); + } else { + callback.Run(ComponentUnpacker::kDeltaOperationFailure, + result + kBsdiffErrorOffset); + } + } else if (operation_ == kCourgette) { + if (result == courgette::C_OK) { + callback.Run(ComponentUnpacker::kNone, 0); + } else { + callback.Run(ComponentUnpacker::kDeltaOperationFailure, + result + kCourgetteErrorOffset); + } + } else { + NOTREACHED(); + } +} + +} // namespace update_client diff --git a/components/update_client/component_patcher_operation.h b/components/update_client/component_patcher_operation.h new file mode 100644 index 0000000..8f31c04 --- /dev/null +++ b/components/update_client/component_patcher_operation.h @@ -0,0 +1,184 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_ +#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_ + +#include <string> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "components/update_client/component_unpacker.h" + +namespace base { +class DictionaryValue; +} // namespace base + +namespace update_client { + +extern const char kOp[]; +extern const char kBsdiff[]; +extern const char kCourgette[]; +extern const char kInput[]; +extern const char kPatch[]; + +class ComponentInstaller; + +class DeltaUpdateOp : public base::RefCountedThreadSafe<DeltaUpdateOp> { + public: + DeltaUpdateOp(); + + // Parses, runs, and verifies the operation. Calls |callback| with the + // result of the operation. The callback is called using |task_runner|. + void Run(const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentInstaller* installer, + const ComponentUnpacker::Callback& callback, + scoped_refptr<base::SequencedTaskRunner> task_runner); + + protected: + virtual ~DeltaUpdateOp(); + + scoped_refptr<base::SequencedTaskRunner> GetTaskRunner(); + + std::string output_sha256_; + base::FilePath output_abs_path_; + + private: + friend class base::RefCountedThreadSafe<DeltaUpdateOp>; + + ComponentUnpacker::Error CheckHash(); + + // Subclasses must override DoParseArguments to parse operation-specific + // arguments. DoParseArguments returns DELTA_OK on success; any other code + // represents failure. + virtual ComponentUnpacker::Error DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) = 0; + + // Subclasses must override DoRun to actually perform the patching operation. + // They must call the provided callback when they have completed their + // operations. In practice, the provided callback is always for "DoneRunning". + virtual void DoRun(const ComponentUnpacker::Callback& callback) = 0; + + // Callback given to subclasses for when they complete their operation. + // Validates the output, and posts a task to the patching operation's + // callback. + void DoneRunning(ComponentUnpacker::Error error, int extended_error); + + ComponentUnpacker::Callback callback_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOp); +}; + +// A 'copy' operation takes a file currently residing on the disk and moves it +// into the unpacking directory: this represents "no change" in the file being +// installed. +class DeltaUpdateOpCopy : public DeltaUpdateOp { + public: + DeltaUpdateOpCopy(); + + private: + ~DeltaUpdateOpCopy() override; + + // Overrides of DeltaUpdateOp. + ComponentUnpacker::Error DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) override; + + void DoRun(const ComponentUnpacker::Callback& callback) override; + + base::FilePath input_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCopy); +}; + +// A 'create' operation takes a full file that was sent in the delta update +// archive and moves it into the unpacking directory: this represents the +// addition of a new file, or a file so different that no bandwidth could be +// saved by transmitting a differential update. +class DeltaUpdateOpCreate : public DeltaUpdateOp { + public: + DeltaUpdateOpCreate(); + + private: + ~DeltaUpdateOpCreate() override; + + // Overrides of DeltaUpdateOp. + ComponentUnpacker::Error DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) override; + + void DoRun(const ComponentUnpacker::Callback& callback) override; + + base::FilePath patch_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCreate); +}; + +// An interface an embedder may fulfill to enable out-of-process patching. +class OutOfProcessPatcher + : public base::RefCountedThreadSafe<OutOfProcessPatcher> { + public: + virtual void Patch(const std::string& operation, + scoped_refptr<base::SequencedTaskRunner> task_runner, + const base::FilePath& input_abs_path, + const base::FilePath& patch_abs_path, + const base::FilePath& output_abs_path, + base::Callback<void(int result)> callback) = 0; + + protected: + friend class base::RefCountedThreadSafe<OutOfProcessPatcher>; + + virtual ~OutOfProcessPatcher() {} +}; + +// Both 'bsdiff' and 'courgette' operations take an existing file on disk, +// and a bsdiff- or Courgette-format patch file provided in the delta update +// package, and run bsdiff or Courgette to construct an output file in the +// unpacking directory. +class DeltaUpdateOpPatch : public DeltaUpdateOp { + public: + // |out_of_process_patcher| may be NULL. + DeltaUpdateOpPatch(const std::string& operation, + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher); + + private: + ~DeltaUpdateOpPatch() override; + + // Overrides of DeltaUpdateOp. + ComponentUnpacker::Error DoParseArguments( + const base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) override; + + void DoRun(const ComponentUnpacker::Callback& callback) override; + + // |success_code| is the code that indicates a successful patch. + // |result| is the code the patching operation returned. + void DonePatching(const ComponentUnpacker::Callback& callback, int result); + + std::string operation_; + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher_; + base::FilePath patch_abs_path_; + base::FilePath input_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpPatch); +}; + +DeltaUpdateOp* CreateDeltaUpdateOp( + const std::string& operation, + scoped_refptr<OutOfProcessPatcher> out_of_process_patcher); + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_ diff --git a/components/update_client/component_unpacker.cc b/components/update_client/component_unpacker.cc new file mode 100644 index 0000000..27d75b9 --- /dev/null +++ b/components/update_client/component_unpacker.cc @@ -0,0 +1,279 @@ +// 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/component_unpacker.h" + +#include <stdint.h> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/json/json_file_value_serializer.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "components/crx_file/constants.h" +#include "components/crx_file/crx_file.h" +#include "components/update_client/component_patcher.h" +#include "components/update_client/component_patcher_operation.h" +#include "components/update_client/update_client.h" +#include "crypto/secure_hash.h" +#include "crypto/signature_verifier.h" +#include "third_party/zlib/google/zip.h" + +using crypto::SecureHash; + +namespace update_client { + +namespace { + +// This class makes sure that the CRX digital signature is valid +// and well formed. +class CRXValidator { + public: + explicit CRXValidator(FILE* crx_file) : valid_(false), is_delta_(false) { + crx_file::CrxFile::Header header; + size_t len = fread(&header, 1, sizeof(header), crx_file); + if (len < sizeof(header)) + return; + + crx_file::CrxFile::Error error; + scoped_ptr<crx_file::CrxFile> crx(crx_file::CrxFile::Parse(header, &error)); + if (!crx.get()) + return; + is_delta_ = crx_file::CrxFile::HeaderIsDelta(header); + + std::vector<uint8_t> key(header.key_size); + len = fread(&key[0], sizeof(uint8_t), header.key_size, crx_file); + if (len < header.key_size) + return; + + std::vector<uint8_t> signature(header.signature_size); + len = + fread(&signature[0], sizeof(uint8_t), header.signature_size, crx_file); + if (len < header.signature_size) + return; + + crypto::SignatureVerifier verifier; + if (!verifier.VerifyInit( + crx_file::kSignatureAlgorithm, + base::checked_cast<int>(sizeof(crx_file::kSignatureAlgorithm)), + &signature[0], base::checked_cast<int>(signature.size()), &key[0], + base::checked_cast<int>(key.size()))) { + // Signature verification initialization failed. This is most likely + // caused by a public key in the wrong format (should encode algorithm). + return; + } + + const size_t kBufSize = 8 * 1024; + scoped_ptr<uint8_t[]> buf(new uint8_t[kBufSize]); + while ((len = fread(buf.get(), 1, kBufSize, crx_file)) > 0) + verifier.VerifyUpdate(buf.get(), base::checked_cast<int>(len)); + + if (!verifier.VerifyFinal()) + return; + + public_key_.swap(key); + valid_ = true; + } + + bool valid() const { return valid_; } + + bool is_delta() const { return is_delta_; } + + const std::vector<uint8_t>& public_key() const { return public_key_; } + + private: + bool valid_; + bool is_delta_; + std::vector<uint8_t> public_key_; +}; + +} // namespace + +ComponentUnpacker::ComponentUnpacker( + const std::vector<uint8_t>& pk_hash, + const base::FilePath& path, + const std::string& fingerprint, + ComponentInstaller* installer, + scoped_refptr<OutOfProcessPatcher> oop_patcher, + scoped_refptr<base::SequencedTaskRunner> task_runner) + : pk_hash_(pk_hash), + path_(path), + is_delta_(false), + fingerprint_(fingerprint), + installer_(installer), + oop_patcher_(oop_patcher), + error_(kNone), + extended_error_(0), + task_runner_(task_runner) { +} + +// TODO(cpu): add a specific attribute check to a component json that the +// extension unpacker will reject, so that a component cannot be installed +// as an extension. +scoped_ptr<base::DictionaryValue> ReadManifest( + const base::FilePath& unpack_path) { + base::FilePath manifest = + unpack_path.Append(FILE_PATH_LITERAL("manifest.json")); + if (!base::PathExists(manifest)) + return scoped_ptr<base::DictionaryValue>(); + JSONFileValueSerializer serializer(manifest); + std::string error; + scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error)); + if (!root.get()) + return scoped_ptr<base::DictionaryValue>(); + if (!root->IsType(base::Value::TYPE_DICTIONARY)) + return scoped_ptr<base::DictionaryValue>(); + return scoped_ptr<base::DictionaryValue>( + static_cast<base::DictionaryValue*>(root.release())).Pass(); +} + +bool ComponentUnpacker::UnpackInternal() { + return Verify() && Unzip() && BeginPatching(); +} + +void ComponentUnpacker::Unpack(const Callback& callback) { + callback_ = callback; + if (!UnpackInternal()) + Finish(); +} + +bool ComponentUnpacker::Verify() { + VLOG(1) << "Verifying component: " << path_.value(); + if (pk_hash_.empty() || path_.empty()) { + error_ = kInvalidParams; + return false; + } + // First, validate the CRX header and signature. As of today + // this is SHA1 with RSA 1024. + base::ScopedFILE file(base::OpenFile(path_, "rb")); + if (!file.get()) { + error_ = kInvalidFile; + return false; + } + CRXValidator validator(file.get()); + file.reset(); + if (!validator.valid()) { + error_ = kInvalidFile; + return false; + } + is_delta_ = validator.is_delta(); + + // File is valid and the digital signature matches. Now make sure + // the public key hash matches the expected hash. If they do we fully + // trust this CRX. + uint8_t hash[32] = {}; + scoped_ptr<SecureHash> sha256(SecureHash::Create(SecureHash::SHA256)); + sha256->Update(&(validator.public_key()[0]), validator.public_key().size()); + sha256->Finish(hash, arraysize(hash)); + + if (!std::equal(pk_hash_.begin(), pk_hash_.end(), hash)) { + VLOG(1) << "Hash mismatch: " << path_.value(); + error_ = kInvalidId; + return false; + } + VLOG(1) << "Verification successful: " << path_.value(); + return true; +} + +bool ComponentUnpacker::Unzip() { + base::FilePath& destination = is_delta_ ? unpack_diff_path_ : unpack_path_; + VLOG(1) << "Unpacking in: " << destination.value(); + if (!base::CreateNewTempDirectory(base::FilePath::StringType(), + &destination)) { + VLOG(1) << "Unable to create temporary directory for unpacking."; + error_ = kUnzipPathError; + return false; + } + if (!zip::Unzip(path_, destination)) { + VLOG(1) << "Unzipping failed."; + error_ = kUnzipFailed; + return false; + } + VLOG(1) << "Unpacked successfully"; + return true; +} + +bool ComponentUnpacker::BeginPatching() { + if (is_delta_) { // Package is a diff package. + // Use a different temp directory for the patch output files. + if (!base::CreateNewTempDirectory(base::FilePath::StringType(), + &unpack_path_)) { + error_ = kUnzipPathError; + return false; + } + patcher_ = new ComponentPatcher(unpack_diff_path_, unpack_path_, installer_, + oop_patcher_, task_runner_); + task_runner_->PostTask( + FROM_HERE, + base::Bind(&ComponentPatcher::Start, patcher_, + base::Bind(&ComponentUnpacker::EndPatching, + scoped_refptr<ComponentUnpacker>(this)))); + } else { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&ComponentUnpacker::EndPatching, + scoped_refptr<ComponentUnpacker>(this), kNone, 0)); + } + return true; +} + +void ComponentUnpacker::EndPatching(Error error, int extended_error) { + error_ = error; + extended_error_ = extended_error; + patcher_ = NULL; + if (error_ != kNone) { + Finish(); + return; + } + // Optimization: clean up patch files early, in case disk space is too low to + // install otherwise. + if (!unpack_diff_path_.empty()) { + base::DeleteFile(unpack_diff_path_, true); + unpack_diff_path_.clear(); + } + Install(); + Finish(); +} + +void ComponentUnpacker::Install() { + // Write the fingerprint to disk. + if (static_cast<int>(fingerprint_.size()) != + base::WriteFile( + unpack_path_.Append(FILE_PATH_LITERAL("manifest.fingerprint")), + fingerprint_.c_str(), base::checked_cast<int>(fingerprint_.size()))) { + error_ = kFingerprintWriteFailed; + return; + } + scoped_ptr<base::DictionaryValue> manifest(ReadManifest(unpack_path_)); + if (!manifest.get()) { + error_ = kBadManifest; + return; + } + DCHECK(error_ == kNone); + if (!installer_->Install(*manifest, unpack_path_)) { + error_ = kInstallerError; + return; + } +} + +void ComponentUnpacker::Finish() { + if (!unpack_diff_path_.empty()) + base::DeleteFile(unpack_diff_path_, true); + if (!unpack_path_.empty()) + base::DeleteFile(unpack_path_, true); + callback_.Run(error_, extended_error_); +} + +ComponentUnpacker::~ComponentUnpacker() { +} + +} // namespace update_client diff --git a/components/update_client/component_unpacker.h b/components/update_client/component_unpacker.h new file mode 100644 index 0000000..7c2b6ef --- /dev/null +++ b/components/update_client/component_unpacker.h @@ -0,0 +1,162 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_ +#define COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_ + +#include <stdint.h> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/json/json_file_value_serializer.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" + +namespace update_client { + +class ComponentInstaller; +class ComponentPatcher; +class OutOfProcessPatcher; + +// Deserializes the CRX manifest. The top level must be a dictionary. +scoped_ptr<base::DictionaryValue> ReadManifest( + const base::FilePath& unpack_path); + +// In charge of unpacking the component CRX package and verifying that it is +// well formed and the cryptographic signature is correct. If there is no +// error the component specific installer will be invoked to proceed with +// the component installation or update. +// +// This class should be used only by the component updater. It is inspired by +// and overlaps with code in the extension's SandboxedUnpacker. +// The main differences are: +// - The public key hash is full SHA256. +// - Does not use a sandboxed unpacker. A valid component is fully trusted. +// - The manifest can have different attributes and resources are not +// transcoded. +// +// If the CRX is a delta CRX, the flow is: +// [ComponentUpdater] [ComponentPatcher] +// Unpack +// \_ Verify +// \_ Unzip +// \_ BeginPatching ---> DifferentialUpdatePatch +// ... +// EndPatching <------------ ... +// \_ Install +// \_ Finish +// +// For a full CRX, the flow is: +// [ComponentUpdater] +// Unpack +// \_ Verify +// \_ Unzip +// \_ BeginPatching +// | +// V +// EndPatching +// \_ Install +// \_ Finish +// +// In both cases, if there is an error at any point, the remaining steps will +// be skipped and Finish will be called. +class ComponentUnpacker : public base::RefCountedThreadSafe<ComponentUnpacker> { + public: + // Possible error conditions. + // Add only to the bottom of this enum; the order must be kept stable. + enum Error { + kNone, + kInvalidParams, + kInvalidFile, + kUnzipPathError, + kUnzipFailed, + kNoManifest, + kBadManifest, + kBadExtension, + kInvalidId, + kInstallerError, + kIoError, + kDeltaVerificationFailure, + kDeltaBadCommands, + kDeltaUnsupportedCommand, + kDeltaOperationFailure, + kDeltaPatchProcessFailure, + kDeltaMissingExistingFile, + kFingerprintWriteFailed, + }; + + typedef base::Callback<void(Error, int)> Callback; + + // Constructs an unpacker for a specific component unpacking operation. + // |pk_hash| is the expected/ public key SHA256 hash. |path| is the current + // location of the CRX. + ComponentUnpacker(const std::vector<uint8_t>& pk_hash, + const base::FilePath& path, + const std::string& fingerprint, + ComponentInstaller* installer, + scoped_refptr<OutOfProcessPatcher> oop_patcher, + scoped_refptr<base::SequencedTaskRunner> task_runner); + + // Begins the actual unpacking of the files. May invoke a patcher if the + // package is a differential update. Calls |callback| with the result. + void Unpack(const Callback& callback); + + private: + friend class base::RefCountedThreadSafe<ComponentUnpacker>; + + virtual ~ComponentUnpacker(); + + bool UnpackInternal(); + + // The first step of unpacking is to verify the file. Returns false if an + // error is encountered, the file is malformed, or the file is incorrectly + // signed. + bool Verify(); + + // The second step of unpacking is to unzip. Returns false if an error + // occurs as part of unzipping. + bool Unzip(); + + // The third step is to optionally patch files - this is a no-op for full + // (non-differential) updates. This step is asynchronous. Returns false if an + // error is encountered. + bool BeginPatching(); + + // When patching is complete, EndPatching is called before moving on to step + // four. + void EndPatching(Error error, int extended_error); + + // The fourth step is to install the unpacked component. + void Install(); + + // The final step is to do clean-up for things that can't be tidied as we go. + // If there is an error at any step, the remaining steps are skipped and + // and Finish is called. + // Finish is responsible for calling the callback provided in Start(). + void Finish(); + + std::vector<uint8_t> pk_hash_; + base::FilePath path_; + base::FilePath unpack_path_; + base::FilePath unpack_diff_path_; + bool is_delta_; + std::string fingerprint_; + scoped_refptr<ComponentPatcher> patcher_; + ComponentInstaller* installer_; + Callback callback_; + scoped_refptr<OutOfProcessPatcher> oop_patcher_; + Error error_; + int extended_error_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(ComponentUnpacker); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_ diff --git a/components/update_client/configurator.h b/components/update_client/configurator.h new file mode 100644 index 0000000..b9a86fa --- /dev/null +++ b/components/update_client/configurator.h @@ -0,0 +1,119 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_ +#define COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_ + +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +class GURL; + +namespace base { +class SingleThreadTaskRunner; +class SequencedTaskRunner; +class Version; +} + +namespace net { +class URLRequestContextGetter; +} + +namespace update_client { + +class OutOfProcessPatcher; + +// Controls the component updater behavior. +// TODO(sorin): this class will be split soon in two. One class controls +// the behavior of the update client, and the other class controls the +// behavior of the component updater. +class Configurator { + public: + virtual ~Configurator() {} + + // Delay in seconds from calling Start() to the first update check. + virtual int InitialDelay() const = 0; + + // Delay in seconds to every subsequent update check. 0 means don't check. + // This function is a mutator for testing purposes. + virtual int NextCheckDelay() = 0; + + // Delay in seconds from each task step. Used to smooth out CPU/IO usage. + virtual int StepDelay() const = 0; + + // Delay in seconds between applying updates for different components, if + // several updates are available at a given time. This function is a mutator + // for testing purposes. + virtual int StepDelayMedium() = 0; + + // Minimum delta time in seconds before checking again the same component. + virtual int MinimumReCheckWait() const = 0; + + // Minimum delta time in seconds before an on-demand check is allowed + // for the same component. + virtual int OnDemandDelay() const = 0; + + // The URLs for the update checks. The URLs are tried in order, the first one + // that succeeds wins. + virtual std::vector<GURL> UpdateUrl() const = 0; + + // The URLs for pings. Returns an empty vector if and only if pings are + // disabled. Similarly, these URLs have a fall back behavior too. + virtual std::vector<GURL> PingUrl() const = 0; + + // Version of the application. Used to compare the component manifests. + virtual base::Version GetBrowserVersion() const = 0; + + // Returns the value we use for the "updaterchannel=" and "prodchannel=" + // parameters. Possible return values include: "canary", "dev", "beta", and + // "stable". + virtual std::string GetChannel() const = 0; + + // Returns the language for the present locale. Possible return values are + // standard tags for languages, such as "en", "en-US", "de", "fr", "af", etc. + virtual std::string GetLang() const = 0; + + // Returns the OS's long name like "Windows", "Mac OS X", etc. + virtual std::string GetOSLongName() const = 0; + + // Parameters added to each url request. It can be empty if none are needed. + // The return string must be safe for insertion as an attribute in an + // XML element. + virtual std::string ExtraRequestParams() const = 0; + + // How big each update request can be. Don't go above 2000. + virtual size_t UrlSizeLimit() const = 0; + + // The source of contexts for all the url requests. + virtual net::URLRequestContextGetter* RequestContext() const = 0; + + // Returns a new out of process patcher. May be NULL for implementations + // that patch in-process. + virtual scoped_refptr<update_client::OutOfProcessPatcher> + CreateOutOfProcessPatcher() const = 0; + + // True means that this client can handle delta updates. + virtual bool DeltasEnabled() const = 0; + + // True means that the background downloader can be used for downloading + // non on-demand components. + virtual bool UseBackgroundDownloader() const = 0; + + // Gets a task runner to a blocking pool of threads suitable for worker jobs. + virtual scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() + const = 0; + + // Gets a task runner for worker jobs guaranteed to run on a single thread. + // This thread must be capable of IO. On Windows, this thread must be + // initialized for use of COM objects. + virtual scoped_refptr<base::SingleThreadTaskRunner> + GetSingleThreadTaskRunner() const = 0; +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_ diff --git a/components/update_client/crx_downloader.cc b/components/update_client/crx_downloader.cc new file mode 100644 index 0000000..ecbf301 --- /dev/null +++ b/components/update_client/crx_downloader.cc @@ -0,0 +1,157 @@ +// 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 "base/logging.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "components/update_client/url_fetcher_downloader.h" + +#if defined(OS_WIN) +#include "components/update_client/background_downloader_win.h" +#endif + +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. +CrxDownloader* CrxDownloader::Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + scoped_refptr<base::SequencedTaskRunner> url_fetcher_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> background_task_runner) { + scoped_ptr<CrxDownloader> url_fetcher_downloader( + new UrlFetcherDownloader(scoped_ptr<CrxDownloader>().Pass(), + context_getter, url_fetcher_task_runner)); +#if defined(OS_WIN) + if (is_background_download) { + return new BackgroundDownloader(url_fetcher_downloader.Pass(), + context_getter, background_task_runner); + } +#endif + + return url_fetcher_downloader.release(); +} + +CrxDownloader::CrxDownloader(scoped_ptr<CrxDownloader> successor) + : successor_(successor.Pass()) { +} + +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 DownloadCallback& download_callback) { + std::vector<GURL> urls; + urls.push_back(url); + StartDownload(urls, download_callback); +} + +void CrxDownloader::StartDownload(const std::vector<GURL>& urls, + const DownloadCallback& download_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (urls.empty()) { + // Make a result and complete the download with a generic error for now. + Result result; + result.error = -1; + download_callback.Run(result); + return; + } + + // If the urls are mutated while this downloader is active, then the + // behavior is undefined in the sense that the outcome of the download could + // be inconsistent for the list of urls. At any rate, the |current_url_| is + // reset at this point, and the iterator will be valid in all conditions. + urls_ = urls; + 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()); + + download_metrics_.push_back(download_metrics); + + if (result.error) { + // If an error has occured, in general try the next url if there is any, + // then move on to 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 retried. In both cases, move on to the + // next url. + if (!is_handled) { + ++current_url_; + } else { + current_url_ = urls_.erase(current_url_); + } + + // Try downloading from another url from the list. + if (current_url_ != urls_.end()) { + DoStartDownload(*current_url_); + return; + } + + // If there is another downloader that can accept this request, then hand + // the request over to it so that the successor can try the pruned list + // of urls. Otherwise, the request ends here since the current downloader + // has tried all urls and it can't fall back on any other downloader. + if (successor_ && !urls_.empty()) { + successor_->StartDownload(urls_, download_callback_); + return; + } + } + + download_callback_.Run(result); +} + +void CrxDownloader::OnDownloadProgress(const Result& result) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (progress_callback_.is_null()) + return; + + progress_callback_.Run(result); +} + +} // namespace update_client diff --git a/components/update_client/crx_downloader.h b/components/update_client/crx_downloader.h new file mode 100644 index 0000000..661b5c8 --- /dev/null +++ b/components/update_client/crx_downloader.h @@ -0,0 +1,156 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_ +#define COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_ + +#include <stdint.h> +#include <vector> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "url/gurl.h" + +namespace base { +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace net { +class URLRequestContextGetter; +} + +namespace update_client { + +// Defines a download interface for downloading components, with retrying on +// fallback urls in case of errors. This class implements a chain of +// responsibility design pattern. It can give successors in the chain a chance +// to handle a download request, until one of them succeeds, or there are no +// more urls or successors to try. A callback is always called at the end of +// the download, one time only. +// When multiple urls and downloaders exists, first all the urls are tried, in +// the order they are provided in the StartDownload function argument. After +// that, the download request is routed to the next downloader in the chain. +// The members of this class expect to be called from the main thread only. +class CrxDownloader { + public: + struct DownloadMetrics { + enum Downloader { kNone = 0, kUrlFetcher, kBits }; + + DownloadMetrics(); + + GURL url; + + Downloader downloader; + + int error; + + int64_t downloaded_bytes; // -1 means that the byte count is unknown. + int64_t total_bytes; + + uint64_t download_time_ms; + }; + + // Contains the progress or the outcome of the download. + struct Result { + Result(); + + // Download error: 0 indicates success. + int error; + + // Path of the downloaded file if the download was successful. + base::FilePath response; + + // Number of bytes actually downloaded, not including the bytes downloaded + // as a result of falling back on urls. + int64_t downloaded_bytes; + + // Number of bytes expected to be downloaded. + int64_t total_bytes; + }; + + // The callback fires only once, regardless of how many urls are tried, and + // how many successors in the chain of downloaders have handled the + // download. The callback interface can be extended if needed to provide + // more visibility into how the download has been handled, including + // specific error codes and download metrics. + typedef base::Callback<void(const Result& result)> DownloadCallback; + + // The callback may fire 0 or many times during a download. Since this + // class implements a chain of responsibility, the callback can fire for + // different urls and different downloaders. The number of actual downloaded + // bytes is not guaranteed to monotonically increment over time. + typedef base::Callback<void(const Result& result)> ProgressCallback; + + // Factory method to create an instance of this class and build the + // chain of responsibility. |is_background_download| specifies that a + // background downloader be used, if the platform supports it. + // |url_fetcher_task_runner| should be an IO capable task runner able to + // support UrlFetcherDownloader. |background_task_runner| should be an + // IO capable thread able to support BackgroundDownloader. + static CrxDownloader* Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + scoped_refptr<base::SequencedTaskRunner> url_fetcher_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> background_task_runner); + virtual ~CrxDownloader(); + + void set_progress_callback(const ProgressCallback& progress_callback); + + // Starts the download. One instance of the class handles one download only. + // One instance of CrxDownloader can only be started once, otherwise the + // behavior is undefined. The callback gets invoked if the download can't + // be started. + void StartDownloadFromUrl(const GURL& url, + const DownloadCallback& download_callback); + void StartDownload(const std::vector<GURL>& urls, + const DownloadCallback& download_callback); + + const std::vector<DownloadMetrics> download_metrics() const; + + protected: + explicit CrxDownloader(scoped_ptr<CrxDownloader> successor); + + // Handles the fallback in the case of multiple urls and routing of the + // download to the following successor in the chain. Derived classes must call + // this function after each attempt at downloading the urls provided + // in the StartDownload function. + // In case of errors, |is_handled| indicates that a server side error has + // occured for the current url and the url should not be retried down + // the chain to avoid DDOS of the server. This url will be removed from the + // list of url and never tried again. + void OnDownloadComplete(bool is_handled, + const Result& result, + const DownloadMetrics& download_metrics); + + // Calls the callback when progress is made. + void OnDownloadProgress(const Result& result); + + // Returns the url which is currently being downloaded from. + GURL url() const; + + private: + virtual void DoStartDownload(const GURL& url) = 0; + + base::ThreadChecker thread_checker_; + + std::vector<GURL> urls_; + scoped_ptr<CrxDownloader> successor_; + DownloadCallback download_callback_; + ProgressCallback progress_callback_; + + std::vector<GURL>::iterator current_url_; + + std::vector<DownloadMetrics> download_metrics_; + + DISALLOW_COPY_AND_ASSIGN(CrxDownloader); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_ diff --git a/components/update_client/crx_update_item.h b/components/update_client/crx_update_item.h new file mode 100644 index 0000000..ba38a1b --- /dev/null +++ b/components/update_client/crx_update_item.h @@ -0,0 +1,129 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_ +#define COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_ + +#include <string> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/version.h" +#include "components/update_client/crx_downloader.h" +#include "components/update_client/update_client.h" + +namespace update_client { + +// This is the one and only per-item state structure. Designed to be hosted +// in a std::vector or a std::list. The two main members are |component| +// which is supplied by the the component updater client and |status| which +// is modified as the item is processed by the update pipeline. The expected +// transition graph is: +// +// on-demand on-demand +// +---------------------------> kNew <--------------+-------------+ +// | | | | +// | V | | +// | +--------------------> kChecking -<-------+---|---<-----+ | +// | | | | | | | +// | | error V no | | | | +// kNoUpdate <---------------- [update?] ->---- kUpToDate kUpdated +// ^ | ^ +// | yes | | +// | diff=false V | +// | +-----------> kCanUpdate | +// | | | | +// | | V no | +// | | [differential update?]->----+ | +// | | | | | +// | | yes | | | +// | | error V | | +// | +---------<- kDownloadingDiff | | +// | | | | | +// | | | | | +// | | error V | | +// | +---------<- kUpdatingDiff ->--------|-----------+ success +// | | | +// | error V | +// +----------------------------------------- kDownloading | +// | | | +// | error V | +// +------------------------------------------ kUpdating ->----+ success +// +struct CrxUpdateItem { + enum Status { + kNew, + kChecking, + kCanUpdate, + kDownloadingDiff, + kDownloading, + kUpdatingDiff, + kUpdating, + kUpdated, + kUpToDate, + kNoUpdate, + kLastStatus + }; + + // Call CrxUpdateService::ChangeItemState to change |status|. The function may + // enforce conditions or notify observers of the change. + Status status; + + std::string id; + CrxComponent component; + + base::Time last_check; + + // A component can be made available for download from several urls. + std::vector<GURL> crx_urls; + std::vector<GURL> crx_diffurls; + + // The from/to version and fingerprint values. + Version previous_version; + Version next_version; + std::string previous_fp; + std::string next_fp; + + // True if the current update check cycle is on-demand. + bool on_demand; + + // True if the differential update failed for any reason. + bool diff_update_failed; + + // The error information for full and differential updates. + // The |error_category| contains a hint about which module in the component + // updater generated the error. The |error_code| constains the error and + // the |extra_code1| usually contains a system error, but it can contain + // any extended information that is relevant to either the category or the + // error itself. + int error_category; + int error_code; + int extra_code1; + int diff_error_category; + int diff_error_code; + int diff_extra_code1; + + std::vector<CrxDownloader::DownloadMetrics> download_metrics; + + std::vector<base::Closure> ready_callbacks; + + CrxUpdateItem(); + ~CrxUpdateItem(); + + // Function object used to find a specific component. + class FindById { + public: + explicit FindById(const std::string& id) : id_(id) {} + + bool operator()(CrxUpdateItem* item) const { return item->id == id_; } + + private: + const std::string& id_; + }; +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_ diff --git a/components/update_client/ping_manager.cc b/components/update_client/ping_manager.cc new file mode 100644 index 0000000..d3eb83c --- /dev/null +++ b/components/update_client/ping_manager.cc @@ -0,0 +1,202 @@ +// 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/ping_manager.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/configurator.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/request_sender.h" +#include "components/update_client/utils.h" +#include "net/url_request/url_fetcher.h" +#include "url/gurl.h" + +namespace update_client { + +namespace { + +// Returns a string literal corresponding to the value of the downloader |d|. +const char* DownloaderToString(CrxDownloader::DownloadMetrics::Downloader d) { + switch (d) { + case CrxDownloader::DownloadMetrics::kUrlFetcher: + return "direct"; + case CrxDownloader::DownloadMetrics::kBits: + return "bits"; + default: + return "unknown"; + } +} + +// Returns a string representing a sequence of download complete events +// corresponding to each download metrics in |item|. +std::string BuildDownloadCompleteEventElements(const CrxUpdateItem* item) { + using base::StringAppendF; + std::string download_events; + for (size_t i = 0; i != item->download_metrics.size(); ++i) { + const CrxDownloader::DownloadMetrics& metrics = item->download_metrics[i]; + std::string event("<event eventtype=\"14\""); + StringAppendF(&event, " eventresult=\"%d\"", metrics.error == 0); + StringAppendF(&event, " downloader=\"%s\"", + DownloaderToString(metrics.downloader)); + if (metrics.error) { + StringAppendF(&event, " errorcode=\"%d\"", metrics.error); + } + StringAppendF(&event, " url=\"%s\"", metrics.url.spec().c_str()); + + // -1 means that the byte counts are not known. + if (metrics.downloaded_bytes != -1) { + StringAppendF(&event, " downloaded=\"%s\"", + base::Int64ToString(metrics.downloaded_bytes).c_str()); + } + if (metrics.total_bytes != -1) { + StringAppendF(&event, " total=\"%s\"", + base::Int64ToString(metrics.total_bytes).c_str()); + } + + if (metrics.download_time_ms) { + StringAppendF(&event, " download_time_ms=\"%s\"", + base::Uint64ToString(metrics.download_time_ms).c_str()); + } + StringAppendF(&event, "/>"); + + download_events += event; + } + return download_events; +} + +// Returns a string representing one ping event xml element for an update item. +std::string BuildUpdateCompleteEventElement(const CrxUpdateItem* item) { + DCHECK(item->status == CrxUpdateItem::kNoUpdate || + item->status == CrxUpdateItem::kUpdated); + + using base::StringAppendF; + + std::string ping_event("<event eventtype=\"3\""); + const int event_result = item->status == CrxUpdateItem::kUpdated; + StringAppendF(&ping_event, " eventresult=\"%d\"", event_result); + if (item->error_category) + StringAppendF(&ping_event, " errorcat=\"%d\"", item->error_category); + if (item->error_code) + StringAppendF(&ping_event, " errorcode=\"%d\"", item->error_code); + if (item->extra_code1) + StringAppendF(&ping_event, " extracode1=\"%d\"", item->extra_code1); + if (HasDiffUpdate(item)) + StringAppendF(&ping_event, " diffresult=\"%d\"", !item->diff_update_failed); + if (item->diff_error_category) { + StringAppendF(&ping_event, " differrorcat=\"%d\"", + item->diff_error_category); + } + if (item->diff_error_code) + StringAppendF(&ping_event, " differrorcode=\"%d\"", item->diff_error_code); + if (item->diff_extra_code1) { + StringAppendF(&ping_event, " diffextracode1=\"%d\"", + item->diff_extra_code1); + } + if (!item->previous_fp.empty()) + StringAppendF(&ping_event, " previousfp=\"%s\"", item->previous_fp.c_str()); + if (!item->next_fp.empty()) + StringAppendF(&ping_event, " nextfp=\"%s\"", item->next_fp.c_str()); + StringAppendF(&ping_event, "/>"); + return ping_event; +} + +// Builds a ping message for the specified update item. +std::string BuildPing(const Configurator& config, const CrxUpdateItem* item) { + const char app_element_format[] = + "<app appid=\"%s\" version=\"%s\" nextversion=\"%s\">" + "%s" + "%s" + "</app>"; + const std::string app_element(base::StringPrintf( + app_element_format, + item->id.c_str(), // "appid" + item->previous_version.GetString().c_str(), // "version" + item->next_version.GetString().c_str(), // "nextversion" + BuildUpdateCompleteEventElement(item).c_str(), // update event + BuildDownloadCompleteEventElements(item).c_str())); // download events + + return BuildProtocolRequest(config.GetBrowserVersion().GetString(), + config.GetChannel(), config.GetLang(), + config.GetOSLongName(), app_element, ""); +} + +// Sends a fire and forget ping. The instances of this class have no +// ownership and they self-delete upon completion. One instance of this class +// can send only one ping. +class PingSender { + public: + explicit PingSender(const Configurator& config); + ~PingSender(); + + bool SendPing(const CrxUpdateItem* item); + + private: + void OnRequestSenderComplete(const net::URLFetcher* source); + + const Configurator& config_; + scoped_ptr<RequestSender> request_sender_; + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(PingSender); +}; + +PingSender::PingSender(const Configurator& config) : config_(config) { +} + +PingSender::~PingSender() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void PingSender::OnRequestSenderComplete(const net::URLFetcher* source) { + DCHECK(thread_checker_.CalledOnValidThread()); + delete this; +} + +bool PingSender::SendPing(const CrxUpdateItem* item) { + DCHECK(item); + DCHECK(thread_checker_.CalledOnValidThread()); + + std::vector<GURL> urls(config_.PingUrl()); + + if (urls.empty()) + return false; + + request_sender_.reset(new RequestSender(config_)); + request_sender_->Send( + BuildPing(config_, item), urls, + base::Bind(&PingSender::OnRequestSenderComplete, base::Unretained(this))); + return true; +} + +} // namespace + +PingManager::PingManager(const Configurator& config) : config_(config) { +} + +PingManager::~PingManager() { +} + +// Sends a fire and forget ping when the updates are complete. The ping +// sender object self-deletes after sending the ping has completed asynchrously. +void PingManager::OnUpdateComplete(const CrxUpdateItem* item) { + PingSender* ping_sender(new PingSender(config_)); + if (!ping_sender->SendPing(item)) + delete ping_sender; +} + +} // namespace update_client diff --git a/components/update_client/ping_manager.h b/components/update_client/ping_manager.h new file mode 100644 index 0000000..df2fa44 --- /dev/null +++ b/components/update_client/ping_manager.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_ +#define COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_ + +#include "base/macros.h" + +namespace update_client { + +class Configurator; +struct CrxUpdateItem; + +// Provides an event sink for completion events from ComponentUpdateService +// and sends fire-and-forget pings when handling these events. +class PingManager { + public: + explicit PingManager(const Configurator& config); + ~PingManager(); + + void OnUpdateComplete(const CrxUpdateItem* item); + + private: + const Configurator& config_; + + DISALLOW_COPY_AND_ASSIGN(PingManager); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_ diff --git a/components/update_client/request_sender.cc b/components/update_client/request_sender.cc new file mode 100644 index 0000000..726c2fa --- /dev/null +++ b/components/update_client/request_sender.cc @@ -0,0 +1,70 @@ +// 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/request_sender.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/update_client/configurator.h" +#include "components/update_client/utils.h" +#include "net/url_request/url_fetcher.h" + +namespace update_client { + +RequestSender::RequestSender(const Configurator& config) : config_(config) { +} + +RequestSender::~RequestSender() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void RequestSender::Send(const std::string& request_string, + const std::vector<GURL>& urls, + const RequestSenderCallback& request_sender_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (urls.empty()) { + request_sender_callback.Run(NULL); + return; + } + + request_string_ = request_string; + urls_ = urls; + request_sender_callback_ = request_sender_callback; + + cur_url_ = urls_.begin(); + + SendInternal(); +} + +void RequestSender::SendInternal() { + DCHECK(cur_url_ != urls_.end()); + DCHECK(cur_url_->is_valid()); + DCHECK(thread_checker_.CalledOnValidThread()); + + url_fetcher_.reset(SendProtocolRequest(*cur_url_, request_string_, this, + config_.RequestContext())); +} + +void RequestSender::OnURLFetchComplete(const net::URLFetcher* source) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (GetFetchError(*source) == 0) { + request_sender_callback_.Run(source); + return; + } + + if (++cur_url_ != urls_.end() && + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&RequestSender::SendInternal, base::Unretained(this)))) { + return; + } + + request_sender_callback_.Run(source); +} + +} // namespace update_client diff --git a/components/update_client/request_sender.h b/components/update_client/request_sender.h new file mode 100644 index 0000000..5c05350 --- /dev/null +++ b/components/update_client/request_sender.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_ +#define COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "url/gurl.h" + +namespace net { +class URLFetcher; +} + +namespace update_client { + +class Configurator; + +// Sends a request to one of the urls provided. The class implements a chain +// of responsibility design pattern, where the urls are tried in the order they +// are specified, until the request to one of them succeeds or all have failed. +class RequestSender : public net::URLFetcherDelegate { + public: + // The |source| refers to the fetcher object used to make the request. This + // parameter can be NULL in some error cases. + typedef base::Callback<void(const net::URLFetcher* source)> + RequestSenderCallback; + + explicit RequestSender(const Configurator& config); + ~RequestSender() override; + + void Send(const std::string& request_string, + const std::vector<GURL>& urls, + const RequestSenderCallback& request_sender_callback); + + private: + void SendInternal(); + + // Overrides for URLFetcherDelegate. + void OnURLFetchComplete(const net::URLFetcher* source) override; + + const Configurator& config_; + std::vector<GURL> urls_; + std::vector<GURL>::const_iterator cur_url_; + scoped_ptr<net::URLFetcher> url_fetcher_; + std::string request_string_; + RequestSenderCallback request_sender_callback_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(RequestSender); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_ diff --git a/components/update_client/test/DEPS b/components/update_client/test/DEPS new file mode 100644 index 0000000..60dbcf4 --- /dev/null +++ b/components/update_client/test/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content", +] diff --git a/components/update_client/test/component_patcher_unittest.cc b/components/update_client/test/component_patcher_unittest.cc new file mode 100644 index 0000000..ce6e7a1 --- /dev/null +++ b/components/update_client/test/component_patcher_unittest.cc @@ -0,0 +1,200 @@ +// 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 "base/base_paths.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/values.h" +#include "components/update_client/component_patcher.h" +#include "components/update_client/component_patcher_operation.h" +#include "components/update_client/test/component_patcher_unittest.h" +#include "components/update_client/test/test_installer.h" +#include "courgette/courgette.h" +#include "courgette/third_party/bsdiff.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class TestCallback { + public: + TestCallback(); + virtual ~TestCallback() {} + void Set(update_client::ComponentUnpacker::Error error, int extra_code); + + int error_; + int extra_code_; + bool called_; + + private: + DISALLOW_COPY_AND_ASSIGN(TestCallback); +}; + +TestCallback::TestCallback() : error_(-1), extra_code_(-1), called_(false) { +} + +void TestCallback::Set(update_client::ComponentUnpacker::Error error, + int extra_code) { + error_ = error; + extra_code_ = extra_code; + called_ = true; +} + +} // namespace + +namespace update_client { + +namespace { + +base::FilePath test_file(const char* file) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + return path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("update_client") + .AppendASCII(file); +} + +} // namespace + +ComponentPatcherOperationTest::ComponentPatcherOperationTest() { + EXPECT_TRUE(unpack_dir_.CreateUniqueTempDir()); + EXPECT_TRUE(input_dir_.CreateUniqueTempDir()); + EXPECT_TRUE(installed_dir_.CreateUniqueTempDir()); + installer_.reset(new ReadOnlyTestInstaller(installed_dir_.path())); + task_runner_ = base::MessageLoop::current()->task_runner(); +} + +ComponentPatcherOperationTest::~ComponentPatcherOperationTest() { +} + +// Verify that a 'create' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckCreateOperation) { + EXPECT_TRUE(base::CopyFile( + test_file("binary_output.bin"), + input_dir_.path().Append(FILE_PATH_LITERAL("binary_output.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "create"); + command_args->SetString("patch", "binary_output.bin"); + + TestCallback callback; + scoped_refptr<DeltaUpdateOp> op = new DeltaUpdateOpCreate(); + op->Run(command_args.get(), input_dir_.path(), unpack_dir_.path(), NULL, + base::Bind(&TestCallback::Set, base::Unretained(&callback)), + task_runner_); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(true, callback.called_); + EXPECT_EQ(ComponentUnpacker::kNone, callback.error_); + EXPECT_EQ(0, callback.extra_code_); + EXPECT_TRUE(base::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +// Verify that a 'copy' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckCopyOperation) { + EXPECT_TRUE(base::CopyFile( + test_file("binary_output.bin"), + installed_dir_.path().Append(FILE_PATH_LITERAL("binary_output.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "copy"); + command_args->SetString("input", "binary_output.bin"); + + TestCallback callback; + scoped_refptr<DeltaUpdateOp> op = new DeltaUpdateOpCopy(); + op->Run(command_args.get(), input_dir_.path(), unpack_dir_.path(), + installer_.get(), + base::Bind(&TestCallback::Set, base::Unretained(&callback)), + task_runner_); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(true, callback.called_); + EXPECT_EQ(ComponentUnpacker::kNone, callback.error_); + EXPECT_EQ(0, callback.extra_code_); + EXPECT_TRUE(base::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +// Verify that a 'courgette' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckCourgetteOperation) { + EXPECT_TRUE(base::CopyFile( + test_file("binary_input.bin"), + installed_dir_.path().Append(FILE_PATH_LITERAL("binary_input.bin")))); + EXPECT_TRUE(base::CopyFile(test_file("binary_courgette_patch.bin"), + input_dir_.path().Append(FILE_PATH_LITERAL( + "binary_courgette_patch.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "courgette"); + command_args->SetString("input", "binary_input.bin"); + command_args->SetString("patch", "binary_courgette_patch.bin"); + + TestCallback callback; + scoped_refptr<DeltaUpdateOp> op = + CreateDeltaUpdateOp("courgette", NULL /* out_of_process_patcher */); + op->Run(command_args.get(), input_dir_.path(), unpack_dir_.path(), + installer_.get(), + base::Bind(&TestCallback::Set, base::Unretained(&callback)), + task_runner_); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(true, callback.called_); + EXPECT_EQ(ComponentUnpacker::kNone, callback.error_); + EXPECT_EQ(0, callback.extra_code_); + EXPECT_TRUE(base::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +// Verify that a 'bsdiff' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckBsdiffOperation) { + EXPECT_TRUE(base::CopyFile( + test_file("binary_input.bin"), + installed_dir_.path().Append(FILE_PATH_LITERAL("binary_input.bin")))); + EXPECT_TRUE(base::CopyFile( + test_file("binary_bsdiff_patch.bin"), + input_dir_.path().Append(FILE_PATH_LITERAL("binary_bsdiff_patch.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "courgette"); + command_args->SetString("input", "binary_input.bin"); + command_args->SetString("patch", "binary_bsdiff_patch.bin"); + + TestCallback callback; + scoped_refptr<DeltaUpdateOp> op = + CreateDeltaUpdateOp("bsdiff", NULL /* out_of_process_patcher */); + op->Run(command_args.get(), input_dir_.path(), unpack_dir_.path(), + installer_.get(), + base::Bind(&TestCallback::Set, base::Unretained(&callback)), + task_runner_); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(true, callback.called_); + EXPECT_EQ(ComponentUnpacker::kNone, callback.error_); + EXPECT_EQ(0, callback.extra_code_); + EXPECT_TRUE(base::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +} // namespace update_client diff --git a/components/update_client/test/component_patcher_unittest.h b/components/update_client/test/component_patcher_unittest.h new file mode 100644 index 0000000..f7a4aea --- /dev/null +++ b/components/update_client/test/component_patcher_unittest.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_TEST_COMPONENT_PATCHER_UNITTEST_H_ +#define COMPONENTS_UPDATE_CLIENT_TEST_COMPONENT_PATCHER_UNITTEST_H_ + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "courgette/courgette.h" +#include "courgette/third_party/bsdiff.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace update_client { + +class MockComponentPatcher; +class ReadOnlyTestInstaller; + +const char binary_output_hash[] = + "599aba6d15a7da390621ef1bacb66601ed6aed04dadc1f9b445dcfe31296142a"; + +class ComponentPatcherOperationTest : public testing::Test { + public: + ComponentPatcherOperationTest(); + ~ComponentPatcherOperationTest() override; + + protected: + base::ScopedTempDir input_dir_; + base::ScopedTempDir installed_dir_; + base::ScopedTempDir unpack_dir_; + scoped_ptr<ReadOnlyTestInstaller> installer_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + private: + base::MessageLoopForIO loop_; +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_TEST_COMPONENT_PATCHER_UNITTEST_H_ diff --git a/components/update_client/test/crx_downloader_unittest.cc b/components/update_client/test/crx_downloader_unittest.cc new file mode 100644 index 0000000..5b01b42 --- /dev/null +++ b/components/update_client/test/crx_downloader_unittest.cc @@ -0,0 +1,382 @@ +// 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 "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/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "components/update_client/crx_downloader.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"; + +base::FilePath MakeTestFilePath(const char* file) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + return path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("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: + scoped_ptr<CrxDownloader> crx_downloader_; + + scoped_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::MessageLoopProxy::current())) { +} + +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(); + + crx_downloader_.reset(CrxDownloader::Create( + false, // Do not use the background downloader in these tests. + context_.get(), base::MessageLoopProxy::current(), + NULL)); // No |background_task_runner| because no background downloader. + crx_downloader_->set_progress_callback(progress_callback_); + + get_interceptor_.reset(new GetInterceptor(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current())); +} + +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, callback_); + RunThreadsUntilIdle(); + + EXPECT_EQ(1, num_download_complete_calls_); + EXPECT_EQ(kExpectedContext, crx_context_); + EXPECT_EQ(-1, 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, 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 specifying from 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, 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"), + 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"), + 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, 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, 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, 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 diff --git a/components/update_client/test/ping_manager_unittest.cc b/components/update_client/test/ping_manager_unittest.cc new file mode 100644 index 0000000..95539f03 --- /dev/null +++ b/components/update_client/test/ping_manager_unittest.cc @@ -0,0 +1,177 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/version.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/ping_manager.h" +#include "components/update_client/test/test_configurator.h" +#include "components/update_client/test/url_request_post_interceptor.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace update_client { + +class ComponentUpdaterPingManagerTest : public testing::Test { + public: + ComponentUpdaterPingManagerTest(); + ~ComponentUpdaterPingManagerTest() override {} + + void RunThreadsUntilIdle(); + + // Overrides from testing::Test. + void SetUp() override; + void TearDown() override; + + protected: + scoped_ptr<TestConfigurator> config_; + scoped_ptr<PingManager> ping_manager_; + + private: + base::MessageLoopForIO loop_; +}; + +ComponentUpdaterPingManagerTest::ComponentUpdaterPingManagerTest() + : config_(new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current())) { +} + +void ComponentUpdaterPingManagerTest::SetUp() { + ping_manager_.reset(new PingManager(*config_)); +} + +void ComponentUpdaterPingManagerTest::TearDown() { + ping_manager_.reset(); + config_.reset(); +} + +void ComponentUpdaterPingManagerTest::RunThreadsUntilIdle() { + base::RunLoop().RunUntilIdle(); +} + +// Test is flaky: http://crbug.com/349547 +TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { + scoped_ptr<InterceptorFactory> interceptor_factory( + new InterceptorFactory(base::MessageLoopProxy::current())); + URLRequestPostInterceptor* interceptor = + interceptor_factory->CreateInterceptor(); + EXPECT_TRUE(interceptor); + + // Test eventresult="1" is sent for successful updates. + CrxUpdateItem item; + item.id = "abc"; + item.status = CrxUpdateItem::kUpdated; + item.previous_version = base::Version("1.0"); + item.next_version = base::Version("2.0"); + + ping_manager_->OnUpdateComplete(&item); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString(); + EXPECT_NE(string::npos, + interceptor->GetRequests()[0].find( + "<app appid=\"abc\" version=\"1.0\" nextversion=\"2.0\">" + "<event eventtype=\"3\" eventresult=\"1\"/></app>")) + << interceptor->GetRequestsAsString(); + interceptor->Reset(); + + // Test eventresult="0" is sent for failed updates. + item = CrxUpdateItem(); + item.id = "abc"; + item.status = CrxUpdateItem::kNoUpdate; + item.previous_version = base::Version("1.0"); + item.next_version = base::Version("2.0"); + + ping_manager_->OnUpdateComplete(&item); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString(); + EXPECT_NE(string::npos, + interceptor->GetRequests()[0].find( + "<app appid=\"abc\" version=\"1.0\" nextversion=\"2.0\">" + "<event eventtype=\"3\" eventresult=\"0\"/></app>")) + << interceptor->GetRequestsAsString(); + interceptor->Reset(); + + // Test the error values and the fingerprints. + item = CrxUpdateItem(); + item.id = "abc"; + item.status = CrxUpdateItem::kNoUpdate; + item.previous_version = base::Version("1.0"); + item.next_version = base::Version("2.0"); + item.previous_fp = "prev fp"; + item.next_fp = "next fp"; + item.error_category = 1; + item.error_code = 2; + item.extra_code1 = -1; + item.diff_error_category = 10; + item.diff_error_code = 20; + item.diff_extra_code1 = -10; + item.diff_update_failed = true; + item.crx_diffurls.push_back(GURL("http://host/path")); + + ping_manager_->OnUpdateComplete(&item); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString(); + EXPECT_NE(string::npos, + interceptor->GetRequests()[0].find( + "<app appid=\"abc\" version=\"1.0\" nextversion=\"2.0\">" + "<event eventtype=\"3\" eventresult=\"0\" errorcat=\"1\" " + "errorcode=\"2\" extracode1=\"-1\" diffresult=\"0\" " + "differrorcat=\"10\" " + "differrorcode=\"20\" diffextracode1=\"-10\" " + "previousfp=\"prev fp\" nextfp=\"next fp\"/></app>")) + << interceptor->GetRequestsAsString(); + interceptor->Reset(); + + // Test the download metrics. + item = CrxUpdateItem(); + item.id = "abc"; + item.status = CrxUpdateItem::kUpdated; + item.previous_version = base::Version("1.0"); + item.next_version = base::Version("2.0"); + + CrxDownloader::DownloadMetrics download_metrics; + download_metrics.url = GURL("http://host1/path1"); + download_metrics.downloader = CrxDownloader::DownloadMetrics::kUrlFetcher; + download_metrics.error = -1; + download_metrics.downloaded_bytes = 123; + download_metrics.total_bytes = 456; + download_metrics.download_time_ms = 987; + item.download_metrics.push_back(download_metrics); + + download_metrics = CrxDownloader::DownloadMetrics(); + download_metrics.url = GURL("http://host2/path2"); + download_metrics.downloader = CrxDownloader::DownloadMetrics::kBits; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 1230; + download_metrics.total_bytes = 4560; + download_metrics.download_time_ms = 9870; + item.download_metrics.push_back(download_metrics); + + ping_manager_->OnUpdateComplete(&item); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString(); + EXPECT_NE( + string::npos, + interceptor->GetRequests()[0].find( + "<app appid=\"abc\" version=\"1.0\" nextversion=\"2.0\">" + "<event eventtype=\"3\" eventresult=\"1\"/>" + "<event eventtype=\"14\" eventresult=\"0\" downloader=\"direct\" " + "errorcode=\"-1\" url=\"http://host1/path1\" downloaded=\"123\" " + "total=\"456\" download_time_ms=\"987\"/>" + "<event eventtype=\"14\" eventresult=\"1\" downloader=\"bits\" " + "url=\"http://host2/path2\" downloaded=\"1230\" total=\"4560\" " + "download_time_ms=\"9870\"/></app>")) + << interceptor->GetRequestsAsString(); + interceptor->Reset(); +} + +} // namespace update_client diff --git a/components/update_client/test/request_sender_unittest.cc b/components/update_client/test/request_sender_unittest.cc new file mode 100644 index 0000000..f8ad042 --- /dev/null +++ b/components/update_client/test/request_sender_unittest.cc @@ -0,0 +1,205 @@ +// 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 "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "components/update_client/request_sender.h" +#include "components/update_client/test/test_configurator.h" +#include "components/update_client/test/url_request_post_interceptor.h" +#include "net/url_request/url_fetcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace update_client { + +namespace { + +const char kUrl1[] = "https://localhost2/path1"; +const char kUrl2[] = "https://localhost2/path2"; +const char kUrlPath1[] = "path1"; +const char kUrlPath2[] = "path2"; + +} // namespace + +class RequestSenderTest : public testing::Test { + public: + RequestSenderTest(); + ~RequestSenderTest() override; + + // Overrides from testing::Test. + void SetUp() override; + void TearDown() override; + + void RequestSenderComplete(const net::URLFetcher* source); + + protected: + void Quit(); + void RunThreads(); + void RunThreadsUntilIdle(); + + scoped_ptr<TestConfigurator> config_; + scoped_ptr<RequestSender> request_sender_; + scoped_ptr<InterceptorFactory> interceptor_factory_; + + URLRequestPostInterceptor* post_interceptor_1; // Owned by the factory. + URLRequestPostInterceptor* post_interceptor_2; // Owned by the factory. + + const net::URLFetcher* url_fetcher_source_; + + private: + base::MessageLoopForIO loop_; + base::Closure quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(RequestSenderTest); +}; + +RequestSenderTest::RequestSenderTest() + : post_interceptor_1(NULL), + post_interceptor_2(NULL), + url_fetcher_source_(NULL) { +} + +RequestSenderTest::~RequestSenderTest() { +} + +void RequestSenderTest::SetUp() { + config_.reset(new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current())); + interceptor_factory_.reset( + new InterceptorFactory(base::MessageLoopProxy::current())); + post_interceptor_1 = + interceptor_factory_->CreateInterceptorForPath(kUrlPath1); + post_interceptor_2 = + interceptor_factory_->CreateInterceptorForPath(kUrlPath2); + EXPECT_TRUE(post_interceptor_1); + EXPECT_TRUE(post_interceptor_2); + + request_sender_.reset(); +} + +void RequestSenderTest::TearDown() { + request_sender_.reset(); + + post_interceptor_1 = NULL; + post_interceptor_2 = NULL; + + interceptor_factory_.reset(); + + config_.reset(); + + RunThreadsUntilIdle(); +} + +void RequestSenderTest::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 RequestSenderTest::RunThreadsUntilIdle() { + base::RunLoop().RunUntilIdle(); +} + +void RequestSenderTest::Quit() { + if (!quit_closure_.is_null()) + quit_closure_.Run(); +} + +void RequestSenderTest::RequestSenderComplete(const net::URLFetcher* source) { + url_fetcher_source_ = source; + Quit(); +} + +// Tests that when a request to the first url succeeds, the subsequent urls are +// not tried. +TEST_F(RequestSenderTest, RequestSendSuccess) { + EXPECT_TRUE(post_interceptor_1->ExpectRequest(new PartialMatch("test"))); + + std::vector<GURL> urls; + urls.push_back(GURL(kUrl1)); + urls.push_back(GURL(kUrl2)); + request_sender_.reset(new RequestSender(*config_)); + request_sender_->Send("test", urls, + base::Bind(&RequestSenderTest::RequestSenderComplete, + base::Unretained(this))); + RunThreads(); + + EXPECT_EQ(1, post_interceptor_1->GetHitCount()) + << post_interceptor_1->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_1->GetCount()) + << post_interceptor_1->GetRequestsAsString(); + + EXPECT_STREQ("test", post_interceptor_1->GetRequests()[0].c_str()); + EXPECT_EQ(GURL(kUrl1), url_fetcher_source_->GetOriginalURL()); + EXPECT_EQ(200, url_fetcher_source_->GetResponseCode()); +} + +// Tests that the request succeeds using the second url after the first url +// has failed. +TEST_F(RequestSenderTest, RequestSendSuccessWithFallback) { + EXPECT_TRUE(post_interceptor_1->ExpectRequest(new PartialMatch("test"), 403)); + EXPECT_TRUE(post_interceptor_2->ExpectRequest(new PartialMatch("test"))); + + std::vector<GURL> urls; + urls.push_back(GURL(kUrl1)); + urls.push_back(GURL(kUrl2)); + request_sender_.reset(new RequestSender(*config_)); + request_sender_->Send("test", urls, + base::Bind(&RequestSenderTest::RequestSenderComplete, + base::Unretained(this))); + RunThreads(); + + EXPECT_EQ(1, post_interceptor_1->GetHitCount()) + << post_interceptor_1->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_1->GetCount()) + << post_interceptor_1->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_2->GetHitCount()) + << post_interceptor_2->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_2->GetCount()) + << post_interceptor_2->GetRequestsAsString(); + + EXPECT_STREQ("test", post_interceptor_1->GetRequests()[0].c_str()); + EXPECT_STREQ("test", post_interceptor_2->GetRequests()[0].c_str()); + EXPECT_EQ(GURL(kUrl2), url_fetcher_source_->GetOriginalURL()); + EXPECT_EQ(200, url_fetcher_source_->GetResponseCode()); +} + +// Tests that the request fails when both urls have failed. +TEST_F(RequestSenderTest, RequestSendFailed) { + EXPECT_TRUE(post_interceptor_1->ExpectRequest(new PartialMatch("test"), 403)); + EXPECT_TRUE(post_interceptor_2->ExpectRequest(new PartialMatch("test"), 403)); + + std::vector<GURL> urls; + urls.push_back(GURL(kUrl1)); + urls.push_back(GURL(kUrl2)); + request_sender_.reset(new RequestSender(*config_)); + request_sender_->Send("test", urls, + base::Bind(&RequestSenderTest::RequestSenderComplete, + base::Unretained(this))); + RunThreads(); + + EXPECT_EQ(1, post_interceptor_1->GetHitCount()) + << post_interceptor_1->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_1->GetCount()) + << post_interceptor_1->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_2->GetHitCount()) + << post_interceptor_2->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_2->GetCount()) + << post_interceptor_2->GetRequestsAsString(); + + EXPECT_STREQ("test", post_interceptor_1->GetRequests()[0].c_str()); + EXPECT_STREQ("test", post_interceptor_2->GetRequests()[0].c_str()); + EXPECT_EQ(GURL(kUrl2), url_fetcher_source_->GetOriginalURL()); + EXPECT_EQ(403, url_fetcher_source_->GetResponseCode()); +} + +} // namespace update_client diff --git a/components/update_client/test/test_configurator.cc b/components/update_client/test/test_configurator.cc new file mode 100644 index 0000000..27aaf5f --- /dev/null +++ b/components/update_client/test/test_configurator.cc @@ -0,0 +1,154 @@ +// 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/test/test_configurator.h" + +#include "base/run_loop.h" +#include "base/version.h" +#include "components/update_client/component_patcher_operation.h" +#include "url/gurl.h" + +namespace update_client { + +namespace { + +std::vector<GURL> MakeDefaultUrls() { + std::vector<GURL> urls; + urls.push_back(GURL(POST_INTERCEPT_SCHEME + "://" POST_INTERCEPT_HOSTNAME POST_INTERCEPT_PATH)); + return urls; +} + +} // namespace + +TestConfigurator::TestConfigurator( + const scoped_refptr<base::SequencedTaskRunner>& worker_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner) + : worker_task_runner_(worker_task_runner), + initial_time_(0), + times_(1), + recheck_time_(0), + ondemand_time_(0), + context_(new net::TestURLRequestContextGetter(network_task_runner)) { +} + +TestConfigurator::~TestConfigurator() { +} + +int TestConfigurator::InitialDelay() const { + return initial_time_; +} + +int TestConfigurator::NextCheckDelay() { + // This is called when a new full cycle of checking for updates is going + // to happen. In test we normally only test one cycle so it is a good + // time to break from the test messageloop Run() method so the test can + // finish. + if (--times_ <= 0) { + quit_closure_.Run(); + return 0; + } + return 1; +} + +int TestConfigurator::StepDelay() const { + return 0; +} + +int TestConfigurator::StepDelayMedium() { + return NextCheckDelay(); +} + +int TestConfigurator::MinimumReCheckWait() const { + return recheck_time_; +} + +int TestConfigurator::OnDemandDelay() const { + return ondemand_time_; +} + +std::vector<GURL> TestConfigurator::UpdateUrl() const { + return MakeDefaultUrls(); +} + +std::vector<GURL> TestConfigurator::PingUrl() const { + return UpdateUrl(); +} + +base::Version TestConfigurator::GetBrowserVersion() const { + // Needs to be larger than the required version in tested component manifests. + return base::Version("30.0"); +} + +std::string TestConfigurator::GetChannel() const { + return "fake_channel_string"; +} + +std::string TestConfigurator::GetLang() const { + return "fake_lang"; +} + +std::string TestConfigurator::GetOSLongName() const { + return "Fake Operating System"; +} + +std::string TestConfigurator::ExtraRequestParams() const { + return "extra=\"foo\""; +} + +size_t TestConfigurator::UrlSizeLimit() const { + return 256; +} + +net::URLRequestContextGetter* TestConfigurator::RequestContext() const { + return context_.get(); +} + +scoped_refptr<OutOfProcessPatcher> TestConfigurator::CreateOutOfProcessPatcher() + const { + return NULL; +} + +bool TestConfigurator::DeltasEnabled() const { + return true; +} + +bool TestConfigurator::UseBackgroundDownloader() const { + return false; +} + +// Set how many update checks are called, the default value is just once. +void TestConfigurator::SetLoopCount(int times) { + times_ = times; +} + +void TestConfigurator::SetRecheckTime(int seconds) { + recheck_time_ = seconds; +} + +void TestConfigurator::SetOnDemandTime(int seconds) { + ondemand_time_ = seconds; +} + +void TestConfigurator::SetQuitClosure(const base::Closure& quit_closure) { + quit_closure_ = quit_closure; +} + +void TestConfigurator::SetInitialDelay(int seconds) { + initial_time_ = seconds; +} + +scoped_refptr<base::SequencedTaskRunner> +TestConfigurator::GetSequencedTaskRunner() const { + DCHECK(worker_task_runner_.get()); + return worker_task_runner_; +} + +scoped_refptr<base::SingleThreadTaskRunner> +TestConfigurator::GetSingleThreadTaskRunner() const { + // This is NULL because tests do not use the background downloader. + return NULL; +} + +} // namespace update_client diff --git a/components/update_client/test/test_configurator.h b/components/update_client/test/test_configurator.h new file mode 100644 index 0000000..4943cca --- /dev/null +++ b/components/update_client/test/test_configurator.h @@ -0,0 +1,107 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_TEST_TEST_CONFIGURATOR_H_ +#define COMPONENTS_UPDATE_CLIENT_TEST_TEST_CONFIGURATOR_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "components/update_client/configurator.h" +#include "net/url_request/url_request_test_util.h" + +class GURL; + +namespace base { +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} // namespace base + +namespace update_client { + +#define POST_INTERCEPT_SCHEME "https" +#define POST_INTERCEPT_HOSTNAME "localhost2" +#define POST_INTERCEPT_PATH "/update2" + +struct CrxComponent; + +// component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and +// the RSA public key the following hash: +const uint8_t jebg_hash[] = {0x94, 0x16, 0x0b, 0x6d, 0x41, 0x75, 0xe9, 0xec, + 0x8e, 0xd5, 0xfa, 0x54, 0xb0, 0xd2, 0xdd, 0xa5, + 0x6e, 0x05, 0x6b, 0xe8, 0x73, 0x47, 0xf6, 0xc4, + 0x11, 0x9f, 0xbc, 0xb3, 0x09, 0xb3, 0x5b, 0x40}; +// component 2 has extension id "abagagagagagagagagagagagagagagag", and +// the RSA public key the following hash: +const uint8_t abag_hash[] = {0x01, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x01}; +// component 3 has extension id "ihfokbkgjpifnbbojhneepfflplebdkc", and +// the RSA public key the following hash: +const uint8_t ihfo_hash[] = {0x87, 0x5e, 0xa1, 0xa6, 0x9f, 0x85, 0xd1, 0x1e, + 0x97, 0xd4, 0x4f, 0x55, 0xbf, 0xb4, 0x13, 0xa2, + 0xe7, 0xc5, 0xc8, 0xf5, 0x60, 0x19, 0x78, 0x1b, + 0x6d, 0xe9, 0x4c, 0xeb, 0x96, 0x05, 0x42, 0x17}; + +class TestConfigurator : public Configurator { + public: + TestConfigurator( + const scoped_refptr<base::SequencedTaskRunner>& worker_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner); + ~TestConfigurator() override; + + // Overrrides for Configurator. + int InitialDelay() const override; + int NextCheckDelay() override; + int StepDelay() const override; + int StepDelayMedium() override; + int MinimumReCheckWait() const override; + int OnDemandDelay() const override; + std::vector<GURL> UpdateUrl() const override; + std::vector<GURL> PingUrl() const override; + base::Version GetBrowserVersion() const override; + std::string GetChannel() const override; + std::string GetLang() const override; + std::string GetOSLongName() const override; + std::string ExtraRequestParams() const override; + size_t UrlSizeLimit() const override; + net::URLRequestContextGetter* RequestContext() const override; + scoped_refptr<OutOfProcessPatcher> CreateOutOfProcessPatcher() const override; + bool DeltasEnabled() const override; + bool UseBackgroundDownloader() const override; + scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() + const override; + scoped_refptr<base::SingleThreadTaskRunner> GetSingleThreadTaskRunner() + const override; + + void SetLoopCount(int times); + void SetRecheckTime(int seconds); + void SetOnDemandTime(int seconds); + void SetQuitClosure(const base::Closure& quit_closure); + void SetInitialDelay(int seconds); + + private: + scoped_refptr<base::SequencedTaskRunner> worker_task_runner_; + scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_; + + int initial_time_; + int times_; + int recheck_time_; + int ondemand_time_; + + scoped_refptr<net::TestURLRequestContextGetter> context_; + base::Closure quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(TestConfigurator); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_TEST_TEST_CONFIGURATOR_H_ diff --git a/components/update_client/test/test_installer.cc b/components/update_client/test/test_installer.cc new file mode 100644 index 0000000..b6a3f84 --- /dev/null +++ b/components/update_client/test/test_installer.cc @@ -0,0 +1,86 @@ +// 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/test/test_installer.h" + +#include <string> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/values.h" + +namespace update_client { + +TestInstaller::TestInstaller() : error_(0), install_count_(0) { +} + +void TestInstaller::OnUpdateError(int error) { + error_ = error; +} + +bool TestInstaller::Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + ++install_count_; + return base::DeleteFile(unpack_path, true); +} + +bool TestInstaller::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + return false; +} + +int TestInstaller::error() const { + return error_; +} + +int TestInstaller::install_count() const { + return install_count_; +} + +ReadOnlyTestInstaller::ReadOnlyTestInstaller(const base::FilePath& install_dir) + : install_directory_(install_dir) { +} + +ReadOnlyTestInstaller::~ReadOnlyTestInstaller() { +} + +bool ReadOnlyTestInstaller::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + *installed_file = install_directory_.AppendASCII(file); + return true; +} + +VersionedTestInstaller::VersionedTestInstaller() { + base::CreateNewTempDirectory(FILE_PATH_LITERAL("TEST_"), &install_directory_); +} + +VersionedTestInstaller::~VersionedTestInstaller() { + base::DeleteFile(install_directory_, true); +} + +bool VersionedTestInstaller::Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + std::string version_string; + manifest.GetStringASCII("version", &version_string); + Version version(version_string.c_str()); + + base::FilePath path; + path = install_directory_.AppendASCII(version.GetString()); + base::CreateDirectory(path.DirName()); + if (!base::Move(unpack_path, path)) + return false; + current_version_ = version; + ++install_count_; + return true; +} + +bool VersionedTestInstaller::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + base::FilePath path; + path = install_directory_.AppendASCII(current_version_.GetString()); + *installed_file = path.Append(base::FilePath::FromUTF8Unsafe(file)); + return true; +} + +} // namespace update_client diff --git a/components/update_client/test/test_installer.h b/components/update_client/test/test_installer.h new file mode 100644 index 0000000..933fe21 --- /dev/null +++ b/components/update_client/test/test_installer.h @@ -0,0 +1,79 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_TEST_TEST_INSTALLER_H_ +#define COMPONENTS_UPDATE_CLIENT_TEST_TEST_INSTALLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "components/update_client/update_client.h" + +namespace base { +class DictionaryValue; +} + +namespace update_client { + +// A TestInstaller is an installer that does nothing for installation except +// increment a counter. +class TestInstaller : public ComponentInstaller { + public: + TestInstaller(); + + void OnUpdateError(int error) override; + + bool Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) override; + + bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) override; + + int error() const; + + int install_count() const; + + protected: + int error_; + int install_count_; +}; + +// A ReadOnlyTestInstaller is an installer that knows about files in an existing +// directory. It will not write to the directory. +class ReadOnlyTestInstaller : public TestInstaller { + public: + explicit ReadOnlyTestInstaller(const base::FilePath& installed_path); + + ~ReadOnlyTestInstaller() override; + + bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) override; + + private: + base::FilePath install_directory_; +}; + +// A VersionedTestInstaller is an installer that installs files into versioned +// directories (e.g. somedir/25.23.89.141/<files>). +class VersionedTestInstaller : public TestInstaller { + public: + VersionedTestInstaller(); + + ~VersionedTestInstaller() override; + + bool Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) override; + + bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) override; + + private: + base::FilePath install_directory_; + Version current_version_; +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_TEST_TEST_INSTALLER_H_ diff --git a/components/update_client/test/update_checker_unittest.cc b/components/update_client/test/update_checker_unittest.cc new file mode 100644 index 0000000..20d3e79 --- /dev/null +++ b/components/update_client/test/update_checker_unittest.cc @@ -0,0 +1,237 @@ +// 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/version.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/test/test_configurator.h" +#include "components/update_client/test/url_request_post_interceptor.h" +#include "components/update_client/update_checker.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using std::string; + +namespace update_client { + +namespace { + +base::FilePath test_file(const char* file) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + return path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("update_client") + .AppendASCII(file); +} + +} // namespace + +class UpdateCheckerTest : public testing::Test { + public: + UpdateCheckerTest(); + ~UpdateCheckerTest() override; + + // Overrides from testing::Test. + void SetUp() override; + void TearDown() override; + + void UpdateCheckComplete(const GURL& original_url, + int error, + const std::string& error_message, + const UpdateResponse::Results& results); + + protected: + void Quit(); + void RunThreads(); + void RunThreadsUntilIdle(); + + CrxUpdateItem BuildCrxUpdateItem(); + + scoped_ptr<TestConfigurator> config_; + + scoped_ptr<UpdateChecker> update_checker_; + + scoped_ptr<InterceptorFactory> interceptor_factory_; + URLRequestPostInterceptor* post_interceptor_; // Owned by the factory. + + GURL original_url_; + int error_; + std::string error_message_; + UpdateResponse::Results results_; + + private: + base::MessageLoopForIO loop_; + base::Closure quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(UpdateCheckerTest); +}; + +UpdateCheckerTest::UpdateCheckerTest() : post_interceptor_(NULL), error_(0) { +} + +UpdateCheckerTest::~UpdateCheckerTest() { +} + +void UpdateCheckerTest::SetUp() { + config_.reset(new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current())); + interceptor_factory_.reset( + new InterceptorFactory(base::MessageLoopProxy::current())); + post_interceptor_ = interceptor_factory_->CreateInterceptor(); + EXPECT_TRUE(post_interceptor_); + + update_checker_.reset(); + + error_ = 0; + error_message_.clear(); + results_ = UpdateResponse::Results(); +} + +void UpdateCheckerTest::TearDown() { + update_checker_.reset(); + + post_interceptor_ = NULL; + interceptor_factory_.reset(); + + config_.reset(); + + // The PostInterceptor requires the message loop to run to destruct correctly. + // TODO(sorin): This is fragile and should be fixed. + RunThreadsUntilIdle(); +} + +void UpdateCheckerTest::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 UpdateCheckerTest::RunThreadsUntilIdle() { + base::RunLoop().RunUntilIdle(); +} + +void UpdateCheckerTest::Quit() { + if (!quit_closure_.is_null()) + quit_closure_.Run(); +} + +void UpdateCheckerTest::UpdateCheckComplete( + const GURL& original_url, + int error, + const std::string& error_message, + const UpdateResponse::Results& results) { + original_url_ = original_url; + error_ = error; + error_message_ = error_message; + results_ = results; + Quit(); +} + +CrxUpdateItem UpdateCheckerTest::BuildCrxUpdateItem() { + CrxComponent crx_component; + crx_component.name = "test_jebg"; + crx_component.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx_component.installer = NULL; + crx_component.version = base::Version("0.9"); + crx_component.fingerprint = "fp1"; + + CrxUpdateItem crx_update_item; + crx_update_item.status = CrxUpdateItem::kNew; + crx_update_item.id = "jebgalgnebhfojomionfpkfelancnnkf"; + crx_update_item.component = crx_component; + + return crx_update_item; +} + +TEST_F(UpdateCheckerTest, UpdateCheckSuccess) { + EXPECT_TRUE(post_interceptor_->ExpectRequest( + new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml"))); + + update_checker_ = UpdateChecker::Create(*config_).Pass(); + + CrxUpdateItem item(BuildCrxUpdateItem()); + std::vector<CrxUpdateItem*> items_to_check; + items_to_check.push_back(&item); + + update_checker_->CheckForUpdates( + items_to_check, "extra=\"params\"", + base::Bind(&UpdateCheckerTest::UpdateCheckComplete, + base::Unretained(this))); + + RunThreads(); + + EXPECT_EQ(1, post_interceptor_->GetHitCount()) + << post_interceptor_->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_->GetCount()) + << post_interceptor_->GetRequestsAsString(); + + // Sanity check the request. + EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find( + "request protocol=\"3.0\" extra=\"params\"")); + EXPECT_NE( + string::npos, + post_interceptor_->GetRequests()[0].find( + "app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">" + "<updatecheck /><packages><package fp=\"fp1\"/></packages></app>")); + + EXPECT_NE(string::npos, + post_interceptor_->GetRequests()[0].find("<hw physmemory=")); + + // Sanity check the arguments of the callback after parsing. + EXPECT_EQ(config_->UpdateUrl().front(), original_url_); + EXPECT_EQ(0, error_); + EXPECT_TRUE(error_message_.empty()); + EXPECT_EQ(1ul, results_.list.size()); + EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", + results_.list[0].extension_id.c_str()); + EXPECT_STREQ("1.0", results_.list[0].manifest.version.c_str()); +} + +// Simulates a 403 server response error. +TEST_F(UpdateCheckerTest, UpdateCheckError) { + EXPECT_TRUE( + post_interceptor_->ExpectRequest(new PartialMatch("updatecheck"), 403)); + + update_checker_ = UpdateChecker::Create(*config_).Pass(); + + CrxUpdateItem item(BuildCrxUpdateItem()); + std::vector<CrxUpdateItem*> items_to_check; + items_to_check.push_back(&item); + + update_checker_->CheckForUpdates( + items_to_check, "", base::Bind(&UpdateCheckerTest::UpdateCheckComplete, + base::Unretained(this))); + + RunThreads(); + + EXPECT_EQ(1, post_interceptor_->GetHitCount()) + << post_interceptor_->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_->GetCount()) + << post_interceptor_->GetRequestsAsString(); + + EXPECT_EQ(config_->UpdateUrl().front(), original_url_); + EXPECT_EQ(403, error_); + EXPECT_STREQ("network error", error_message_.c_str()); + EXPECT_EQ(0ul, results_.list.size()); +} + +} // namespace update_client diff --git a/components/update_client/test/update_response_unittest.cc b/components/update_client/test/update_response_unittest.cc new file mode 100644 index 0000000..e106916 --- /dev/null +++ b/components/update_client/test/update_response_unittest.cc @@ -0,0 +1,302 @@ +// 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 "base/memory/scoped_vector.h" +#include "components/update_client/update_response.h" +#include "libxml/globals.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace update_client { + +const char* kValidXml = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <app appid='12345'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebase='http://example.com/'/>" + " <url codebasediff='http://diff.example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package name='extension_1_2_3_4.crx'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </app>" + "</response>"; + +const char* valid_xml_with_hash = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <app appid='12345'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebase='http://example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package name='extension_1_2_3_4.crx' hash_sha256='1234'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </app>" + "</response>"; + +const char* valid_xml_with_invalid_sizes = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <app appid='12345'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebase='http://example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package name='1' size='1234'/>" + " <package name='2' size='-1234'/>" + " <package name='3' />" + " <package name='4' size='-a'/>" + " <package name='5' size='-123467890123456789'/>" + " <package name='6' size='123467890123456789'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </app>" + "</response>"; + +const char* kInvalidValidXmlMissingCodebase = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <app appid='12345'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebasediff='http://diff.example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package namediff='extension_1_2_3_4.crx'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </app>" + "</response>"; + +const char* kMissingAppId = + "<?xml version='1.0'?>" + "<response protocol='3.0'>" + " <app>" + " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'" + " version='1.2.3.4' />" + " </app>" + "</response>"; + +const char* kInvalidCodebase = + "<?xml version='1.0'?>" + "<response protocol='3.0'>" + " <app appid='12345' status='ok'>" + " <updatecheck codebase='example.com/extension_1.2.3.4.crx'" + " version='1.2.3.4' />" + " </app>" + "</response>"; + +const char* kMissingVersion = + "<?xml version='1.0'?>" + "<response protocol='3.0'>" + " <app appid='12345' status='ok'>" + " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />" + " </app>" + "</response>"; + +const char* kInvalidVersion = + "<?xml version='1.0'?>" + "<response protocol='3.0'>" + " <app appid='12345' status='ok'>" + " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' " + " version='1.2.3.a'/>" + " </app>" + "</response>"; + +// The v3 version of the protocol is not using namespaces. However, the parser +// must be able to parse responses that include namespaces. +const char* kUsesNamespacePrefix = + "<?xml version='1.0' encoding='UTF-8'?>" + "<g:response xmlns:g='http://www.google.com/update2/response' " + "protocol='3.0'>" + " <g:app appid='12345'>" + " <g:updatecheck status='ok'>" + " <g:urls>" + " <g:url codebase='http://example.com/'/>" + " </g:urls>" + " <g:manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <g:packages>" + " <g:package name='extension_1_2_3_4.crx'/>" + " </g:packages>" + " </g:manifest>" + " </g:updatecheck>" + " </g:app>" + "</g:response>"; + +// Includes unrelated <app> tags from other xml namespaces - this should +// not cause problems. +const char* kSimilarTagnames = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response xmlns:a='http://a' protocol='3.0'>" + " <a:app appid='12345'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebase='http://example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package name='extension_1_2_3_4.crx'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </a:app>" + " <b:app appid='xyz' xmlns:b='http://b'>" + " <updatecheck status='noupdate'/>" + " </b:app>" + "</response>"; + +// Includes a <daystart> tag. +const char* kWithDaystart = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <daystart elapsed_seconds='456' />" + " <app appid='12345'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebase='http://example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package name='extension_1_2_3_4.crx'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </app>" + "</response>"; + +// Indicates no updates available - this should not be a parse error. +const char* kNoUpdate = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <app appid='12345'>" + " <updatecheck status='noupdate' />" + " </app>" + "</response>"; + +// Includes two <app> tags, one with an error. +const char* kTwoAppsOneError = + "<?xml version='1.0' encoding='UTF-8'?>" + "<response protocol='3.0'>" + " <app appid='aaaaaaaa' status='error-unknownApplication'>" + " <updatecheck status='error-unknownapplication'/>" + " </app>" + " <app appid='bbbbbbbb'>" + " <updatecheck status='ok'>" + " <urls>" + " <url codebase='http://example.com/'/>" + " </urls>" + " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>" + " <packages>" + " <package name='extension_1_2_3_4.crx'/>" + " </packages>" + " </manifest>" + " </updatecheck>" + " </app>" + "</response>"; + +TEST(ComponentUpdaterUpdateResponseTest, TestParser) { + UpdateResponse parser; + + // Test parsing of a number of invalid xml cases + EXPECT_FALSE(parser.Parse(std::string())); + EXPECT_FALSE(parser.errors().empty()); + + EXPECT_TRUE(parser.Parse(kMissingAppId)); + EXPECT_TRUE(parser.results().list.empty()); + EXPECT_FALSE(parser.errors().empty()); + + EXPECT_TRUE(parser.Parse(kInvalidCodebase)); + EXPECT_TRUE(parser.results().list.empty()); + EXPECT_FALSE(parser.errors().empty()); + + EXPECT_TRUE(parser.Parse(kMissingVersion)); + EXPECT_TRUE(parser.results().list.empty()); + EXPECT_FALSE(parser.errors().empty()); + + EXPECT_TRUE(parser.Parse(kInvalidVersion)); + EXPECT_TRUE(parser.results().list.empty()); + EXPECT_FALSE(parser.errors().empty()); + + EXPECT_TRUE(parser.Parse(kInvalidValidXmlMissingCodebase)); + EXPECT_TRUE(parser.results().list.empty()); + EXPECT_FALSE(parser.errors().empty()); + + // Parse some valid XML, and check that all params came out as expected + EXPECT_TRUE(parser.Parse(kValidXml)); + EXPECT_TRUE(parser.errors().empty()); + EXPECT_EQ(1u, parser.results().list.size()); + const UpdateResponse::Result* firstResult = &parser.results().list[0]; + EXPECT_EQ(1u, firstResult->crx_urls.size()); + EXPECT_EQ(GURL("http://example.com/"), firstResult->crx_urls[0]); + EXPECT_EQ(GURL("http://diff.example.com/"), firstResult->crx_diffurls[0]); + EXPECT_EQ("1.2.3.4", firstResult->manifest.version); + EXPECT_EQ("2.0.143.0", firstResult->manifest.browser_min_version); + EXPECT_EQ(1u, firstResult->manifest.packages.size()); + EXPECT_EQ("extension_1_2_3_4.crx", firstResult->manifest.packages[0].name); + + // Parse some xml that uses namespace prefixes. + EXPECT_TRUE(parser.Parse(kUsesNamespacePrefix)); + EXPECT_TRUE(parser.errors().empty()); + EXPECT_TRUE(parser.Parse(kSimilarTagnames)); + EXPECT_TRUE(parser.errors().empty()); + xmlCleanupGlobals(); + + // Parse xml with hash value + EXPECT_TRUE(parser.Parse(valid_xml_with_hash)); + EXPECT_TRUE(parser.errors().empty()); + EXPECT_FALSE(parser.results().list.empty()); + firstResult = &parser.results().list[0]; + EXPECT_FALSE(firstResult->manifest.packages.empty()); + EXPECT_EQ("1234", firstResult->manifest.packages[0].hash_sha256); + + // Parse xml with package size value + EXPECT_TRUE(parser.Parse(valid_xml_with_invalid_sizes)); + EXPECT_TRUE(parser.errors().empty()); + EXPECT_FALSE(parser.results().list.empty()); + firstResult = &parser.results().list[0]; + EXPECT_FALSE(firstResult->manifest.packages.empty()); + EXPECT_EQ(1234, firstResult->manifest.packages[0].size); + EXPECT_EQ(-1234, firstResult->manifest.packages[1].size); + EXPECT_EQ(0, firstResult->manifest.packages[2].size); + EXPECT_EQ(0, firstResult->manifest.packages[3].size); + EXPECT_EQ(0, firstResult->manifest.packages[4].size); + EXPECT_EQ(0, firstResult->manifest.packages[5].size); + + // Parse xml with a <daystart> element. + EXPECT_TRUE(parser.Parse(kWithDaystart)); + EXPECT_TRUE(parser.errors().empty()); + EXPECT_FALSE(parser.results().list.empty()); + EXPECT_EQ(parser.results().daystart_elapsed_seconds, 456); + + // Parse a no-update response. + EXPECT_TRUE(parser.Parse(kNoUpdate)); + EXPECT_TRUE(parser.errors().empty()); + EXPECT_FALSE(parser.results().list.empty()); + firstResult = &parser.results().list[0]; + EXPECT_EQ(firstResult->extension_id, "12345"); + EXPECT_EQ(firstResult->manifest.version, ""); + + // Parse xml with one error and one success <app> tag. + EXPECT_TRUE(parser.Parse(kTwoAppsOneError)); + EXPECT_FALSE(parser.errors().empty()); + EXPECT_EQ(1u, parser.results().list.size()); + firstResult = &parser.results().list[0]; + EXPECT_EQ(firstResult->extension_id, "bbbbbbbb"); +} + +} // namespace update_client diff --git a/components/update_client/test/url_request_post_interceptor.cc b/components/update_client/test/url_request_post_interceptor.cc new file mode 100644 index 0000000..4aeb7a7 --- /dev/null +++ b/components/update_client/test/url_request_post_interceptor.cc @@ -0,0 +1,301 @@ +// 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/test/url_request_post_interceptor.h" + +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/stringprintf.h" +#include "components/update_client/test/test_configurator.h" +#include "net/base/upload_bytes_element_reader.h" +#include "net/base/upload_data_stream.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_filter.h" +#include "net/url_request/url_request_interceptor.h" +#include "net/url_request/url_request_simple_job.h" +#include "net/url_request/url_request_test_util.h" + +namespace update_client { + +// Returns a canned response. +class URLRequestMockJob : public net::URLRequestSimpleJob { + public: + URLRequestMockJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + int response_code, + const std::string& response_body) + : net::URLRequestSimpleJob(request, network_delegate), + response_code_(response_code), + response_body_(response_body) {} + + protected: + int GetResponseCode() const override { return response_code_; } + + int GetData(std::string* mime_type, + std::string* charset, + std::string* data, + const net::CompletionCallback& callback) const override { + mime_type->assign("text/plain"); + charset->assign("US-ASCII"); + data->assign(response_body_); + return net::OK; + } + + private: + ~URLRequestMockJob() override {} + + int response_code_; + std::string response_body_; + DISALLOW_COPY_AND_ASSIGN(URLRequestMockJob); +}; + +URLRequestPostInterceptor::URLRequestPostInterceptor( + const GURL& url, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) + : url_(url), io_task_runner_(io_task_runner), hit_count_(0) { +} + +URLRequestPostInterceptor::~URLRequestPostInterceptor() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + ClearExpectations(); +} + +void URLRequestPostInterceptor::ClearExpectations() { + while (!expectations_.empty()) { + Expectation expectation(expectations_.front()); + delete expectation.first; + expectations_.pop(); + } +} + +GURL URLRequestPostInterceptor::GetUrl() const { + return url_; +} + +bool URLRequestPostInterceptor::ExpectRequest( + class RequestMatcher* request_matcher) { + expectations_.push(std::make_pair(request_matcher, + ExpectationResponse(kResponseCode200, ""))); + return true; +} + +bool URLRequestPostInterceptor::ExpectRequest( + class RequestMatcher* request_matcher, + int response_code) { + expectations_.push( + std::make_pair(request_matcher, ExpectationResponse(response_code, ""))); + return true; +} + +bool URLRequestPostInterceptor::ExpectRequest( + class RequestMatcher* request_matcher, + const base::FilePath& filepath) { + std::string response; + if (filepath.empty() || !base::ReadFileToString(filepath, &response)) + return false; + + expectations_.push(std::make_pair( + request_matcher, ExpectationResponse(kResponseCode200, response))); + return true; +} + +int URLRequestPostInterceptor::GetHitCount() const { + base::AutoLock auto_lock(interceptor_lock_); + return hit_count_; +} + +int URLRequestPostInterceptor::GetCount() const { + base::AutoLock auto_lock(interceptor_lock_); + return static_cast<int>(requests_.size()); +} + +std::vector<std::string> URLRequestPostInterceptor::GetRequests() const { + base::AutoLock auto_lock(interceptor_lock_); + return requests_; +} + +std::string URLRequestPostInterceptor::GetRequestsAsString() const { + std::vector<std::string> requests(GetRequests()); + + std::string s = "Requests are:"; + + int i = 0; + for (std::vector<std::string>::const_iterator it = requests.begin(); + it != requests.end(); ++it) { + s.append(base::StringPrintf("\n (%d): %s", ++i, it->c_str())); + } + + return s; +} + +void URLRequestPostInterceptor::Reset() { + base::AutoLock auto_lock(interceptor_lock_); + hit_count_ = 0; + requests_.clear(); + ClearExpectations(); +} + +class URLRequestPostInterceptor::Delegate : public net::URLRequestInterceptor { + public: + Delegate(const std::string& scheme, + const std::string& hostname, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) + : scheme_(scheme), hostname_(hostname), io_task_runner_(io_task_runner) {} + + void Register() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( + scheme_, hostname_, scoped_ptr<net::URLRequestInterceptor>(this)); + } + + void Unregister() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + for (InterceptorMap::iterator it = interceptors_.begin(); + it != interceptors_.end(); ++it) + delete (*it).second; + net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(scheme_, + hostname_); + } + + void OnCreateInterceptor(URLRequestPostInterceptor* interceptor) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(interceptors_.find(interceptor->GetUrl()) == interceptors_.end()); + + interceptors_.insert(std::make_pair(interceptor->GetUrl(), interceptor)); + } + + private: + ~Delegate() override {} + + net::URLRequestJob* MaybeInterceptRequest( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + // Only intercepts POST. + if (!request->has_upload()) + return NULL; + + GURL url = request->url(); + if (url.has_query()) { + GURL::Replacements replacements; + replacements.ClearQuery(); + url = url.ReplaceComponents(replacements); + } + + InterceptorMap::const_iterator it(interceptors_.find(url)); + if (it == interceptors_.end()) + return NULL; + + // There is an interceptor hooked up for this url. Read the request body, + // check the existing expectations, and handle the matching case by + // popping the expectation off the queue, counting the match, and + // returning a mock object to serve the canned response. + URLRequestPostInterceptor* interceptor(it->second); + + const net::UploadDataStream* stream = request->get_upload(); + const net::UploadBytesElementReader* reader = + (*stream->GetElementReaders())[0]->AsBytesReader(); + const int size = reader->length(); + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(size)); + const std::string request_body(reader->bytes()); + + { + base::AutoLock auto_lock(interceptor->interceptor_lock_); + interceptor->requests_.push_back(request_body); + if (interceptor->expectations_.empty()) + return NULL; + const URLRequestPostInterceptor::Expectation& expectation( + interceptor->expectations_.front()); + if (expectation.first->Match(request_body)) { + const int response_code(expectation.second.response_code); + const std::string response_body(expectation.second.response_body); + delete expectation.first; + interceptor->expectations_.pop(); + ++interceptor->hit_count_; + + return new URLRequestMockJob(request, network_delegate, response_code, + response_body); + } + } + + return NULL; + } + + typedef std::map<GURL, URLRequestPostInterceptor*> InterceptorMap; + InterceptorMap interceptors_; + + const std::string scheme_; + const std::string hostname_; + scoped_refptr<base::SequencedTaskRunner> io_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +URLRequestPostInterceptorFactory::URLRequestPostInterceptorFactory( + const std::string& scheme, + const std::string& hostname, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) + : scheme_(scheme), + hostname_(hostname), + io_task_runner_(io_task_runner), + delegate_(new URLRequestPostInterceptor::Delegate(scheme, + hostname, + io_task_runner)) { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&URLRequestPostInterceptor::Delegate::Register, + base::Unretained(delegate_))); +} + +URLRequestPostInterceptorFactory::~URLRequestPostInterceptorFactory() { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&URLRequestPostInterceptor::Delegate::Unregister, + base::Unretained(delegate_))); +} + +URLRequestPostInterceptor* URLRequestPostInterceptorFactory::CreateInterceptor( + const base::FilePath& filepath) { + const GURL base_url( + base::StringPrintf("%s://%s", scheme_.c_str(), hostname_.c_str())); + GURL absolute_url(base_url.Resolve(filepath.MaybeAsASCII())); + URLRequestPostInterceptor* interceptor( + new URLRequestPostInterceptor(absolute_url, io_task_runner_)); + bool res = io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&URLRequestPostInterceptor::Delegate::OnCreateInterceptor, + base::Unretained(delegate_), base::Unretained(interceptor))); + if (!res) { + delete interceptor; + return NULL; + } + + return interceptor; +} + +bool PartialMatch::Match(const std::string& actual) const { + return actual.find(expected_) != std::string::npos; +} + +InterceptorFactory::InterceptorFactory( + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) + : URLRequestPostInterceptorFactory(POST_INTERCEPT_SCHEME, + POST_INTERCEPT_HOSTNAME, + io_task_runner) { +} + +InterceptorFactory::~InterceptorFactory() { +} + +URLRequestPostInterceptor* InterceptorFactory::CreateInterceptor() { + return CreateInterceptorForPath(POST_INTERCEPT_PATH); +} + +URLRequestPostInterceptor* InterceptorFactory::CreateInterceptorForPath( + const char* url_path) { + return URLRequestPostInterceptorFactory::CreateInterceptor( + base::FilePath::FromUTF8Unsafe(url_path)); +} + +} // namespace update_client diff --git a/components/update_client/test/url_request_post_interceptor.h b/components/update_client/test/url_request_post_interceptor.h new file mode 100644 index 0000000..d261160 --- /dev/null +++ b/components/update_client/test/url_request_post_interceptor.h @@ -0,0 +1,165 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_TEST_URL_REQUEST_POST_INTERCEPTOR_H_ +#define COMPONENTS_UPDATE_CLIENT_TEST_URL_REQUEST_POST_INTERCEPTOR_H_ + +#include <stdint.h> +#include <map> +#include <queue> +#include <string> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "url/gurl.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +} + +namespace net { +class URLRequest; +} + +namespace update_client { + +// Intercepts requests to a file path, counts them, and captures the body of +// the requests. Optionally, for each request, it can return a canned response +// from a given file. The class maintains a queue of expectations, and returns +// one and only one response for each request that matches and it is +// intercepted. +class URLRequestPostInterceptor { + public: + // Allows a generic string maching interface when setting up expectations. + class RequestMatcher { + public: + virtual bool Match(const std::string& actual) const = 0; + virtual ~RequestMatcher() {} + }; + + // Returns the url that is intercepted. + GURL GetUrl() const; + + // Sets an expection for the body of the POST request and optionally, + // provides a canned response identified by a |file_path| to be returned when + // the expectation is met. If no |file_path| is provided, then an empty + // response body is served. If |response_code| is provided, then an empty + // response body with that response code is returned. + // Returns |true| if the expectation was set. This class takes ownership of + // the |request_matcher| object. + bool ExpectRequest(class RequestMatcher* request_matcher); + bool ExpectRequest(class RequestMatcher* request_matcher, int response_code); + bool ExpectRequest(class RequestMatcher* request_matcher, + const base::FilePath& filepath); + + // Returns how many requests have been intercepted and matched by + // an expectation. One expectation can only be matched by one request. + int GetHitCount() const; + + // Returns how many requests in total have been captured by the interceptor. + int GetCount() const; + + // Returns all requests that have been intercepted, matched or not. + std::vector<std::string> GetRequests() const; + + // Returns all requests as a string for debugging purposes. + std::string GetRequestsAsString() const; + + // Resets the state of the interceptor so that new expectations can be set. + void Reset(); + + class Delegate; + + private: + friend class URLRequestPostInterceptorFactory; + + static const int kResponseCode200 = 200; + + struct ExpectationResponse { + ExpectationResponse(int code, const std::string& body) + : response_code(code), response_body(body) {} + const int response_code; + const std::string response_body; + }; + typedef std::pair<const RequestMatcher*, ExpectationResponse> Expectation; + + URLRequestPostInterceptor( + const GURL& url, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner); + ~URLRequestPostInterceptor(); + + void ClearExpectations(); + + const GURL url_; + scoped_refptr<base::SequencedTaskRunner> io_task_runner_; + + mutable base::Lock interceptor_lock_; + mutable int hit_count_; + mutable std::vector<std::string> requests_; + mutable std::queue<Expectation> expectations_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestPostInterceptor); +}; + +class URLRequestPostInterceptorFactory { + public: + URLRequestPostInterceptorFactory( + const std::string& scheme, + const std::string& hostname, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner); + ~URLRequestPostInterceptorFactory(); + + // Creates an interceptor object for the specified url path. Returns NULL + // in case of errors or a valid interceptor object otherwise. The caller + // does not own the returned object. + URLRequestPostInterceptor* CreateInterceptor(const base::FilePath& filepath); + + private: + const std::string scheme_; + const std::string hostname_; + scoped_refptr<base::SequencedTaskRunner> io_task_runner_; + + // After creation, |delegate_| lives on the IO thread and it is owned by + // a URLRequestFilter after registration. A task to unregister it and + // implicitly destroy it is posted from ~URLRequestPostInterceptorFactory(). + URLRequestPostInterceptor::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestPostInterceptorFactory); +}; + +// Intercepts HTTP POST requests sent to "localhost2". +class InterceptorFactory : public URLRequestPostInterceptorFactory { + public: + explicit InterceptorFactory( + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner); + ~InterceptorFactory(); + + // Creates an interceptor for the url path defined by POST_INTERCEPT_PATH. + URLRequestPostInterceptor* CreateInterceptor(); + + // Creates an interceptor for the given url path. + URLRequestPostInterceptor* CreateInterceptorForPath(const char* url_path); + + private: + DISALLOW_COPY_AND_ASSIGN(InterceptorFactory); +}; + +class PartialMatch : public URLRequestPostInterceptor::RequestMatcher { + public: + explicit PartialMatch(const std::string& expected) : expected_(expected) {} + bool Match(const std::string& actual) const override; + + private: + const std::string expected_; + + DISALLOW_COPY_AND_ASSIGN(PartialMatch); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_TEST_URL_REQUEST_POST_INTERCEPTOR_H_ diff --git a/components/update_client/update_checker.cc b/components/update_client/update_checker.cc new file mode 100644 index 0000000..9a38a5a --- /dev/null +++ b/components/update_client/update_checker.cc @@ -0,0 +1,163 @@ +// 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/update_checker.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/configurator.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/request_sender.h" +#include "components/update_client/utils.h" +#include "net/url_request/url_fetcher.h" +#include "url/gurl.h" + +namespace update_client { + +namespace { + +// Builds an update check request for |components|. |additional_attributes| is +// serialized as part of the <request> element of the request to customize it +// with data that is not platform or component specific. For each |item|, a +// corresponding <app> element is created and inserted as a child node of +// the <request>. +// +// An app element looks like this: +// <app appid="hnimpnehoodheedghdeeijklkeaacbdc" +// version="0.1.2.3" installsource="ondemand"> +// <updatecheck /> +// <packages> +// <package fp="abcd" /> +// </packages> +// </app> +std::string BuildUpdateCheckRequest(const Configurator& config, + const std::vector<CrxUpdateItem*>& items, + const std::string& additional_attributes) { + std::string app_elements; + for (size_t i = 0; i != items.size(); ++i) { + const CrxUpdateItem* item = items[i]; + std::string app("<app "); + base::StringAppendF(&app, "appid=\"%s\" version=\"%s\"", item->id.c_str(), + item->component.version.GetString().c_str()); + if (item->on_demand) + base::StringAppendF(&app, " installsource=\"ondemand\""); + base::StringAppendF(&app, ">"); + base::StringAppendF(&app, "<updatecheck />"); + if (!item->component.fingerprint.empty()) { + base::StringAppendF(&app, + "<packages>" + "<package fp=\"%s\"/>" + "</packages>", + item->component.fingerprint.c_str()); + } + base::StringAppendF(&app, "</app>"); + app_elements.append(app); + VLOG(1) << "Appending to update request: " << app; + } + + return BuildProtocolRequest(config.GetBrowserVersion().GetString(), + config.GetChannel(), config.GetLang(), + config.GetOSLongName(), app_elements, + additional_attributes); +} + +class UpdateCheckerImpl : public UpdateChecker { + public: + explicit UpdateCheckerImpl(const Configurator& config); + ~UpdateCheckerImpl() override; + + // Overrides for UpdateChecker. + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override; + + private: + void OnRequestSenderComplete(const net::URLFetcher* source); + + const Configurator& config_; + UpdateCheckCallback update_check_callback_; + scoped_ptr<RequestSender> request_sender_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(UpdateCheckerImpl); +}; + +UpdateCheckerImpl::UpdateCheckerImpl(const Configurator& config) + : config_(config) { +} + +UpdateCheckerImpl::~UpdateCheckerImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +bool UpdateCheckerImpl::CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (request_sender_.get()) { + NOTREACHED(); + return false; // Another update check is in progress. + } + + update_check_callback_ = update_check_callback; + + request_sender_.reset(new RequestSender(config_)); + request_sender_->Send( + BuildUpdateCheckRequest(config_, items_to_check, additional_attributes), + config_.UpdateUrl(), + base::Bind(&UpdateCheckerImpl::OnRequestSenderComplete, + base::Unretained(this))); + return true; +} + +void UpdateCheckerImpl::OnRequestSenderComplete(const net::URLFetcher* source) { + DCHECK(thread_checker_.CalledOnValidThread()); + + const GURL original_url(source->GetOriginalURL()); + VLOG(1) << "Update check request went to: " << original_url.spec(); + + int error = 0; + std::string error_message; + UpdateResponse update_response; + + if (FetchSuccess(*source)) { + std::string xml; + source->GetResponseAsString(&xml); + if (!update_response.Parse(xml)) { + error = -1; + error_message = update_response.errors(); + VLOG(1) << "Update request failed: " << error_message; + } + } else { + error = GetFetchError(*source); + error_message.assign("network error"); + VLOG(1) << "Update request failed: network error"; + } + + request_sender_.reset(); + update_check_callback_.Run(original_url, error, error_message, + update_response.results()); +} + +} // namespace + +scoped_ptr<UpdateChecker> UpdateChecker::Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new UpdateCheckerImpl(config)); +} + +} // namespace update_client diff --git a/components/update_client/update_checker.h b/components/update_client/update_checker.h new file mode 100644 index 0000000..1ac6426 --- /dev/null +++ b/components/update_client/update_checker.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_ +#define COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/update_client/update_response.h" +#include "url/gurl.h" + +class GURL; + +namespace net { +class URLRequestContextGetter; +} + +namespace update_client { + +class Configurator; +struct CrxUpdateItem; + +class UpdateChecker { + public: + typedef base::Callback<void(const GURL& original_url, + int error, + const std::string& error_message, + const UpdateResponse::Results& results)> + UpdateCheckCallback; + + virtual ~UpdateChecker() {} + + // Initiates an update check for the |items_to_check|. |additional_attributes| + // provides a way to customize the <request> element. This value is inserted + // as-is, therefore it must be well-formed as an XML attribute string. + virtual bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) = 0; + + static scoped_ptr<UpdateChecker> Create(const Configurator& config); + + protected: + UpdateChecker() {} + + private: + DISALLOW_COPY_AND_ASSIGN(UpdateChecker); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_ diff --git a/components/update_client/update_client.cc b/components/update_client/update_client.cc new file mode 100644 index 0000000..b7fff2b --- /dev/null +++ b/components/update_client/update_client.cc @@ -0,0 +1,33 @@ +// 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/update_client.h" + +#include "components/update_client/crx_update_item.h" + +namespace update_client { + +CrxUpdateItem::CrxUpdateItem() + : status(kNew), + on_demand(false), + diff_update_failed(false), + error_category(0), + error_code(0), + extra_code1(0), + diff_error_category(0), + diff_error_code(0), + diff_extra_code1(0) { +} + +CrxUpdateItem::~CrxUpdateItem() { +} + +CrxComponent::CrxComponent() + : installer(NULL), allow_background_download(true) { +} + +CrxComponent::~CrxComponent() { +} + +} // namespace update_client diff --git a/components/update_client/update_client.h b/components/update_client/update_client.h new file mode 100644 index 0000000..07b5617 --- /dev/null +++ b/components/update_client/update_client.h @@ -0,0 +1,71 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_ +#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_ + +#include <stdint.h> +#include <string> +#include <vector> + +#include "base/version.h" + +namespace base { +class DictionaryValue; +class FilePath; +} + +namespace update_client { + +// Component specific installers must derive from this class and implement +// OnUpdateError() and Install(). A valid instance of this class must be +// given to ComponentUpdateService::RegisterComponent(). +class ComponentInstaller { + public: + // Called by the component updater on the main thread when there was a + // problem unpacking or verifying the component. |error| is a non-zero + // value which is only meaningful to the component updater. + virtual void OnUpdateError(int error) = 0; + + // Called by the component updater when a component has been unpacked + // and is ready to be installed. |manifest| contains the CRX manifest + // json dictionary and |unpack_path| contains the temporary directory + // with all the unpacked CRX files. This method may be called from + // a thread other than the main thread. + virtual bool Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) = 0; + + // Set |installed_file| to the full path to the installed |file|. |file| is + // the filename of the file in this component's CRX. Returns false if this is + // not possible (the file has been removed or modified, or its current + // location is unknown). Otherwise, returns true. + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) = 0; + + virtual ~ComponentInstaller() {} +}; + +// Describes a particular component that can be installed or updated. This +// structure is required to register a component with the component updater. +// |pk_hash| is the SHA256 hash of the component's public key. If the component +// is to be installed then version should be "0" or "0.0", else it should be +// the current version. |fingerprint|, and |name| are optional. +// |allow_background_download| specifies that the component can be background +// downloaded in some cases. The default for this value is |true| and the value +// can be overriden at the registration time. This is a temporary change until +// the issue 340448 is resolved. +struct CrxComponent { + std::vector<uint8_t> pk_hash; + ComponentInstaller* installer; + Version version; + std::string fingerprint; + std::string name; + bool allow_background_download; + CrxComponent(); + ~CrxComponent(); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_ diff --git a/components/update_client/update_response.cc b/components/update_client/update_response.cc new file mode 100644 index 0000000..8677fe6 --- /dev/null +++ b/components/update_client/update_response.cc @@ -0,0 +1,349 @@ +// 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/update_response.h" + +#include <algorithm> + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/version.h" +#include "libxml/tree.h" +#include "third_party/libxml/chromium/libxml_utils.h" + +namespace update_client { + +static const char* kExpectedResponseProtocol = "3.0"; + +UpdateResponse::UpdateResponse() { +} +UpdateResponse::~UpdateResponse() { +} + +UpdateResponse::Results::Results() : daystart_elapsed_seconds(kNoDaystart) { +} +UpdateResponse::Results::~Results() { +} + +UpdateResponse::Result::Result() { +} + +UpdateResponse::Result::~Result() { +} + +UpdateResponse::Result::Manifest::Manifest() { +} +UpdateResponse::Result::Manifest::~Manifest() { +} + +UpdateResponse::Result::Manifest::Package::Package() : size(0), sizediff(0) { +} +UpdateResponse::Result::Manifest::Package::~Package() { +} + +void UpdateResponse::ParseError(const char* details, ...) { + va_list args; + va_start(args, details); + + if (!errors_.empty()) { + errors_ += "\r\n"; + } + + base::StringAppendV(&errors_, details, args); + va_end(args); +} + +// Checks whether a given node's name matches |expected_name|. +static bool TagNameEquals(const xmlNode* node, const char* expected_name) { + return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name)); +} + +// Returns child nodes of |root| with name |name|. +static std::vector<xmlNode*> GetChildren(xmlNode* root, const char* name) { + std::vector<xmlNode*> result; + for (xmlNode* child = root->children; child != NULL; child = child->next) { + if (!TagNameEquals(child, name)) { + continue; + } + result.push_back(child); + } + return result; +} + +// Returns the value of a named attribute, or the empty string. +static std::string GetAttribute(xmlNode* node, const char* attribute_name) { + const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name); + for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) { + if (!xmlStrcmp(attr->name, name) && attr->children && + attr->children->content) { + return std::string( + reinterpret_cast<const char*>(attr->children->content)); + } + } + return std::string(); +} + +// This is used for the xml parser to report errors. This assumes the context +// is a pointer to a std::string where the error message should be appended. +static void XmlErrorFunc(void* context, const char* message, ...) { + va_list args; + va_start(args, message); + std::string* error = static_cast<std::string*>(context); + base::StringAppendV(error, message, args); + va_end(args); +} + +// Utility class for cleaning up the xml document when leaving a scope. +class ScopedXmlDocument { + public: + explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {} + ~ScopedXmlDocument() { + if (document_) + xmlFreeDoc(document_); + } + + xmlDocPtr get() { return document_; } + + private: + xmlDocPtr document_; +}; + +// Parses the <package> tag. +bool ParsePackageTag(xmlNode* package, + UpdateResponse::Result* result, + std::string* error) { + UpdateResponse::Result::Manifest::Package p; + p.name = GetAttribute(package, "name"); + if (p.name.empty()) { + *error = "Missing name for package."; + return false; + } + + p.namediff = GetAttribute(package, "namediff"); + + // package_fingerprint is optional. It identifies the package, preferably + // with a modified sha256 hash of the package in hex format. + p.fingerprint = GetAttribute(package, "fp"); + + p.hash_sha256 = GetAttribute(package, "hash_sha256"); + int size = 0; + if (base::StringToInt(GetAttribute(package, "size"), &size)) { + p.size = size; + } + + p.hashdiff_sha256 = GetAttribute(package, "hashdiff_sha256"); + int sizediff = 0; + if (base::StringToInt(GetAttribute(package, "sizediff"), &sizediff)) { + p.sizediff = sizediff; + } + + result->manifest.packages.push_back(p); + + return true; +} + +// Parses the <manifest> tag. +bool ParseManifestTag(xmlNode* manifest, + UpdateResponse::Result* result, + std::string* error) { + // Get the version. + result->manifest.version = GetAttribute(manifest, "version"); + if (result->manifest.version.empty()) { + *error = "Missing version for manifest."; + return false; + } + Version version(result->manifest.version); + if (!version.IsValid()) { + *error = "Invalid version: '"; + *error += result->manifest.version; + *error += "'."; + return false; + } + + // Get the minimum browser version (not required). + result->manifest.browser_min_version = + GetAttribute(manifest, "prodversionmin"); + if (result->manifest.browser_min_version.length()) { + Version browser_min_version(result->manifest.browser_min_version); + if (!browser_min_version.IsValid()) { + *error = "Invalid prodversionmin: '"; + *error += result->manifest.browser_min_version; + *error += "'."; + return false; + } + } + + // Get the <packages> node. + std::vector<xmlNode*> packages = GetChildren(manifest, "packages"); + if (packages.empty()) { + *error = "Missing packages tag on manifest."; + return false; + } + + // Parse each of the <package> tags. + std::vector<xmlNode*> package = GetChildren(packages[0], "package"); + for (size_t i = 0; i != package.size(); ++i) { + if (!ParsePackageTag(package[i], result, error)) + return false; + } + + return true; +} + +// Parses the <urls> tag and its children in the <updatecheck>. +bool ParseUrlsTag(xmlNode* urls, + UpdateResponse::Result* result, + std::string* error) { + // Get the url nodes. + std::vector<xmlNode*> url = GetChildren(urls, "url"); + if (url.empty()) { + *error = "Missing url tags on urls."; + return false; + } + + // Get the list of urls for full and optionally, for diff updates. + // There can only be either a codebase or a codebasediff attribute in a tag. + for (size_t i = 0; i != url.size(); ++i) { + // Find the url to the crx file. + const GURL crx_url(GetAttribute(url[i], "codebase")); + if (crx_url.is_valid()) { + result->crx_urls.push_back(crx_url); + continue; + } + const GURL crx_diffurl(GetAttribute(url[i], "codebasediff")); + if (crx_diffurl.is_valid()) { + result->crx_diffurls.push_back(crx_diffurl); + continue; + } + } + + // Expect at least one url for full update. + if (result->crx_urls.empty()) { + *error = "Missing valid url for full update."; + return false; + } + + return true; +} + +// Parses the <updatecheck> tag. +bool ParseUpdateCheckTag(xmlNode* updatecheck, + UpdateResponse::Result* result, + std::string* error) { + if (GetAttribute(updatecheck, "status") == "noupdate") { + return true; + } + + // Get the <urls> tag. + std::vector<xmlNode*> urls = GetChildren(updatecheck, "urls"); + if (urls.empty()) { + *error = "Missing urls on updatecheck."; + return false; + } + + if (!ParseUrlsTag(urls[0], result, error)) { + return false; + } + + std::vector<xmlNode*> manifests = GetChildren(updatecheck, "manifest"); + if (urls.empty()) { + *error = "Missing urls on updatecheck."; + return false; + } + + return ParseManifestTag(manifests[0], result, error); +} + +// Parses a single <app> tag. +bool ParseAppTag(xmlNode* app, + UpdateResponse::Result* result, + std::string* error) { + // Read the crx id. + result->extension_id = GetAttribute(app, "appid"); + if (result->extension_id.empty()) { + *error = "Missing appid on app node"; + return false; + } + + // Get the <updatecheck> tag. + std::vector<xmlNode*> updates = GetChildren(app, "updatecheck"); + if (updates.empty()) { + *error = "Missing updatecheck on app."; + return false; + } + + return ParseUpdateCheckTag(updates[0], result, error); +} + +bool UpdateResponse::Parse(const std::string& response_xml) { + results_.daystart_elapsed_seconds = kNoDaystart; + results_.list.clear(); + errors_.clear(); + + if (response_xml.length() < 1) { + ParseError("Empty xml"); + return false; + } + + std::string xml_errors; + ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc); + + // Start up the xml parser with the manifest_xml contents. + ScopedXmlDocument document( + xmlParseDoc(reinterpret_cast<const xmlChar*>(response_xml.c_str()))); + if (!document.get()) { + ParseError("%s", xml_errors.c_str()); + return false; + } + + xmlNode* root = xmlDocGetRootElement(document.get()); + if (!root) { + ParseError("Missing root node"); + return false; + } + + if (!TagNameEquals(root, "response")) { + ParseError("Missing response tag"); + return false; + } + + // Check for the response "protocol" attribute. + if (GetAttribute(root, "protocol") != kExpectedResponseProtocol) { + ParseError( + "Missing/incorrect protocol on response tag " + "(expected '%s')", + kExpectedResponseProtocol); + return false; + } + + // Parse the first <daystart> if it is present. + std::vector<xmlNode*> daystarts = GetChildren(root, "daystart"); + if (!daystarts.empty()) { + xmlNode* first = daystarts[0]; + std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds"); + int parsed_elapsed = kNoDaystart; + if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) { + results_.daystart_elapsed_seconds = parsed_elapsed; + } + } + + // Parse each of the <app> tags. + std::vector<xmlNode*> apps = GetChildren(root, "app"); + for (size_t i = 0; i != apps.size(); ++i) { + Result result; + std::string error; + if (ParseAppTag(apps[i], &result, &error)) { + results_.list.push_back(result); + } else { + ParseError("%s", error.c_str()); + } + } + + return true; +} + +} // namespace update_client diff --git a/components/update_client/update_response.h b/components/update_client/update_response.h new file mode 100644 index 0000000..003474f --- /dev/null +++ b/components/update_client/update_response.h @@ -0,0 +1,135 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_RESPONSE_H_ +#define COMPONENTS_UPDATE_CLIENT_UPDATE_RESPONSE_H_ + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "url/gurl.h" + +namespace update_client { + +// Parses responses for the update protocol version 3. +// (http://code.google.com/p/omaha/wiki/ServerProtocol) +// +// An update response looks like this: +// +// <?xml version="1.0" encoding="UTF-8"?> +// <response protocol="3.0" server="prod"> +// <daystart elapsed_seconds="56508"/> +// <app appid="{430FD4D0-B729-4F61-AA34-91526481799D}" status="ok"> +// <updatecheck status="noupdate"/> +// <ping status="ok"/> +// </app> +// <app appid="{D0AB2EBC-931B-4013-9FEB-C9C4C2225C8C}" status="ok"> +// <updatecheck status="ok"> +// <urls> +// <url codebase="http://host/edgedl/chrome/install/782.112/" +// <url codebasediff="http://fallback/chrome/diff/782.112/"/> +// </urls> +// <manifest version="13.0.782.112" prodversionmin="2.0.143.0"> +// <packages> +// <package name="component.crx" +// namediff="diff_1.2.3.4.crx" +// fp="1.123" +// hash_sha256="9830b4245c4..." size="23963192" +// hashdiff_sha256="cfb6caf3d0..." sizediff="101"/> +// </packages> +// </manifest> +// </updatecheck> +// <ping status="ok"/> +// </app> +// </response> +// +// The <daystart> tag contains a "elapsed_seconds" attribute which refers to +// the server's notion of how many seconds it has been since midnight. +// +// The "appid" attribute of the <app> tag refers to the unique id of the +// extension. The "codebase" attribute of the <updatecheck> tag is the url to +// fetch the updated crx file, and the "prodversionmin" attribute refers to +// the minimum version of the chrome browser that the update applies to. +// +// The diff data members correspond to the differential update package, if +// a differential update is specified in the response. +class UpdateResponse { + public: + // The result of parsing one <app> tag in an xml update check response. + struct Result { + struct Manifest { + struct Package { + Package(); + ~Package(); + + std::string fingerprint; + + // Attributes for the full update. + std::string name; + std::string hash_sha256; + int size; + + // Attributes for the differential update. + std::string namediff; + std::string hashdiff_sha256; + int sizediff; + }; + + Manifest(); + ~Manifest(); + + std::string version; + std::string browser_min_version; + std::vector<Package> packages; + }; + + Result(); + ~Result(); + + std::string extension_id; + + // The list of fallback urls, for full and diff updates respectively. + // These urls are base urls; they don't include the filename. + std::vector<GURL> crx_urls; + std::vector<GURL> crx_diffurls; + + Manifest manifest; + }; + + static const int kNoDaystart = -1; + struct Results { + Results(); + ~Results(); + + // This will be >= 0, or kNoDaystart if the <daystart> tag was not present. + int daystart_elapsed_seconds; + std::vector<Result> list; + }; + + UpdateResponse(); + ~UpdateResponse(); + + // Parses an update response xml string into Result data. Returns a bool + // indicating success or failure. On success, the results are available by + // calling results(). The details for any failures are available by calling + // errors(). + bool Parse(const std::string& manifest_xml); + + const Results& results() const { return results_; } + const std::string& errors() const { return errors_; } + + private: + Results results_; + std::string errors_; + + // Adds parse error details to |errors_| string. + void ParseError(const char* details, ...); + + DISALLOW_COPY_AND_ASSIGN(UpdateResponse); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_RESPONSE_H_ diff --git a/components/update_client/url_fetcher_downloader.cc b/components/update_client/url_fetcher_downloader.cc new file mode 100644 index 0000000..fd7f3da --- /dev/null +++ b/components/update_client/url_fetcher_downloader.cc @@ -0,0 +1,109 @@ +// 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/url_fetcher_downloader.h" + +#include <stdint.h> + +#include "base/logging.h" +#include "base/sequenced_task_runner.h" +#include "components/update_client/utils.h" +#include "net/base/load_flags.h" +#include "net/url_request/url_fetcher.h" +#include "url/gurl.h" + +namespace update_client { + +UrlFetcherDownloader::UrlFetcherDownloader( + scoped_ptr<CrxDownloader> successor, + net::URLRequestContextGetter* context_getter, + scoped_refptr<base::SequencedTaskRunner> task_runner) + : CrxDownloader(successor.Pass()), + context_getter_(context_getter), + task_runner_(task_runner), + downloaded_bytes_(-1), + total_bytes_(-1) { +} + +UrlFetcherDownloader::~UrlFetcherDownloader() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void UrlFetcherDownloader::DoStartDownload(const GURL& url) { + DCHECK(thread_checker_.CalledOnValidThread()); + + url_fetcher_.reset( + net::URLFetcher::Create(0, url, net::URLFetcher::GET, this)); + url_fetcher_->SetRequestContext(context_getter_); + url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | + net::LOAD_DO_NOT_SAVE_COOKIES | + net::LOAD_DISABLE_CACHE); + url_fetcher_->SetAutomaticallyRetryOn5xx(false); + url_fetcher_->SaveResponseToTemporaryFile(task_runner_); + + VLOG(1) << "Starting background download: " << url.spec(); + url_fetcher_->Start(); + + download_start_time_ = base::Time::Now(); + + downloaded_bytes_ = -1; + total_bytes_ = -1; +} + +void UrlFetcherDownloader::OnURLFetchComplete(const net::URLFetcher* source) { + DCHECK(thread_checker_.CalledOnValidThread()); + + const base::Time download_end_time(base::Time::Now()); + const base::TimeDelta download_time = + download_end_time >= download_start_time_ + ? download_end_time - download_start_time_ + : base::TimeDelta(); + + // Consider a 5xx response from the server as an indication to terminate + // the request and avoid overloading the server in this case. + // is not accepting requests for the moment. + const int fetch_error(GetFetchError(*url_fetcher_)); + const bool is_handled = fetch_error == 0 || IsHttpServerError(fetch_error); + + Result result; + result.error = fetch_error; + if (!fetch_error) { + source->GetResponseAsFilePath(true, &result.response); + } + result.downloaded_bytes = downloaded_bytes_; + result.total_bytes = total_bytes_; + + DownloadMetrics download_metrics; + download_metrics.url = url(); + download_metrics.downloader = DownloadMetrics::kUrlFetcher; + download_metrics.error = fetch_error; + download_metrics.downloaded_bytes = downloaded_bytes_; + download_metrics.total_bytes = total_bytes_; + download_metrics.download_time_ms = download_time.InMilliseconds(); + + base::FilePath local_path_; + source->GetResponseAsFilePath(false, &local_path_); + VLOG(1) << "Downloaded " << downloaded_bytes_ << " bytes in " + << download_time.InMilliseconds() << "ms from " + << source->GetURL().spec() << " to " << local_path_.value(); + CrxDownloader::OnDownloadComplete(is_handled, result, download_metrics); +} + +void UrlFetcherDownloader::OnURLFetchDownloadProgress( + const net::URLFetcher* source, + int64_t current, + int64_t total) { + DCHECK(thread_checker_.CalledOnValidThread()); + + downloaded_bytes_ = current; + total_bytes_ = total; + + Result result; + result.downloaded_bytes = downloaded_bytes_; + result.total_bytes = total_bytes_; + + OnDownloadProgress(result); +} + +} // namespace update_client diff --git a/components/update_client/url_fetcher_downloader.h b/components/update_client/url_fetcher_downloader.h new file mode 100644 index 0000000..7a8e3b2 --- /dev/null +++ b/components/update_client/url_fetcher_downloader.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_ +#define COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "components/update_client/crx_downloader.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace update_client { + +// Implements a CRX downloader in top of the URLFetcher. +class UrlFetcherDownloader : public CrxDownloader, + public net::URLFetcherDelegate { + protected: + friend class CrxDownloader; + UrlFetcherDownloader(scoped_ptr<CrxDownloader> successor, + net::URLRequestContextGetter* context_getter, + scoped_refptr<base::SequencedTaskRunner> task_runner); + ~UrlFetcherDownloader() override; + + private: + // Overrides for CrxDownloader. + void DoStartDownload(const GURL& url) override; + + // Overrides for URLFetcherDelegate. + void OnURLFetchComplete(const net::URLFetcher* source) override; + void OnURLFetchDownloadProgress(const net::URLFetcher* source, + int64_t current, + int64_t total) override; + scoped_ptr<net::URLFetcher> url_fetcher_; + net::URLRequestContextGetter* context_getter_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + base::Time download_start_time_; + + int64_t downloaded_bytes_; + int64_t total_bytes_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(UrlFetcherDownloader); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_ diff --git a/components/update_client/utils.cc b/components/update_client/utils.cc new file mode 100644 index 0000000..47bb437 --- /dev/null +++ b/components/update_client/utils.cc @@ -0,0 +1,195 @@ +// 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/utils.h" + +#include <stdint.h> +#include <cmath> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/guid.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/sys_info.h" +#include "base/win/windows_version.h" +#include "components/crx_file/id_util.h" +#include "components/update_client/configurator.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/update_client.h" +#include "components/update_client/update_query_params.h" +#include "net/base/load_flags.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_status.h" + +namespace update_client { + +namespace { + +// Returns the amount of physical memory in GB, rounded to the nearest GB. +int GetPhysicalMemoryGB() { + const double kOneGB = 1024 * 1024 * 1024; + const int64_t phys_mem = base::SysInfo::AmountOfPhysicalMemory(); + return static_cast<int>(std::floor(0.5 + phys_mem / kOneGB)); +} + +// Produces an extension-like friendly id. +std::string HexStringToID(const std::string& hexstr) { + std::string id; + for (size_t i = 0; i < hexstr.size(); ++i) { + int val = 0; + if (base::HexStringToInt( + base::StringPiece(hexstr.begin() + i, hexstr.begin() + i + 1), + &val)) { + id.append(1, val + 'a'); + } else { + id.append(1, 'a'); + } + } + + DCHECK(crx_file::id_util::IdIsValid(id)); + + return id; +} + +} // namespace + +std::string BuildProtocolRequest(const std::string& browser_version, + const std::string& channel, + const std::string& lang, + const std::string& os_long_name, + const std::string& request_body, + const std::string& additional_attributes) { + const std::string prod_id( + UpdateQueryParams::GetProdIdString(UpdateQueryParams::CHROME)); + + std::string request( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<request protocol=\"3.0\" "); + + if (!additional_attributes.empty()) + base::StringAppendF(&request, "%s ", additional_attributes.c_str()); + + // Chrome version and platform information. + base::StringAppendF( + &request, + "version=\"%s-%s\" prodversion=\"%s\" " + "requestid=\"{%s}\" lang=\"%s\" updaterchannel=\"%s\" prodchannel=\"%s\" " + "os=\"%s\" arch=\"%s\" nacl_arch=\"%s\"", + prod_id.c_str(), + browser_version.c_str(), // "version" + browser_version.c_str(), // "prodversion" + base::GenerateGUID().c_str(), // "requestid" + lang.c_str(), // "lang", + channel.c_str(), // "updaterchannel" + channel.c_str(), // "prodchannel" + UpdateQueryParams::GetOS(), // "os" + UpdateQueryParams::GetArch(), // "arch" + UpdateQueryParams::GetNaclArch()); // "nacl_arch" +#if defined(OS_WIN) + const bool is_wow64(base::win::OSInfo::GetInstance()->wow64_status() == + base::win::OSInfo::WOW64_ENABLED); + if (is_wow64) + base::StringAppendF(&request, " wow64=\"1\""); +#endif + base::StringAppendF(&request, ">"); + + // HW platform information. + base::StringAppendF(&request, "<hw physmemory=\"%d\"/>", + GetPhysicalMemoryGB()); // "physmem" in GB. + + // OS version and platform information. + base::StringAppendF( + &request, "<os platform=\"%s\" version=\"%s\" arch=\"%s\"/>", + os_long_name.c_str(), // "platform" + base::SysInfo().OperatingSystemVersion().c_str(), // "version" + base::SysInfo().OperatingSystemArchitecture().c_str()); // "arch" + + // The actual payload of the request. + base::StringAppendF(&request, "%s</request>", request_body.c_str()); + + return request; +} + +net::URLFetcher* SendProtocolRequest( + const GURL& url, + const std::string& protocol_request, + net::URLFetcherDelegate* url_fetcher_delegate, + net::URLRequestContextGetter* url_request_context_getter) { + net::URLFetcher* url_fetcher(net::URLFetcher::Create( + 0, url, net::URLFetcher::POST, url_fetcher_delegate)); + + url_fetcher->SetUploadData("application/xml", protocol_request); + url_fetcher->SetRequestContext(url_request_context_getter); + url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | + net::LOAD_DO_NOT_SAVE_COOKIES | + net::LOAD_DISABLE_CACHE); + url_fetcher->SetAutomaticallyRetryOn5xx(false); + url_fetcher->Start(); + + return url_fetcher; +} + +bool FetchSuccess(const net::URLFetcher& fetcher) { + return GetFetchError(fetcher) == 0; +} + +int GetFetchError(const net::URLFetcher& fetcher) { + const net::URLRequestStatus::Status status(fetcher.GetStatus().status()); + switch (status) { + case net::URLRequestStatus::IO_PENDING: + case net::URLRequestStatus::CANCELED: + // Network status is a small positive number. + return status; + + case net::URLRequestStatus::SUCCESS: { + // Response codes are positive numbers, greater than 100. + const int response_code(fetcher.GetResponseCode()); + if (response_code == 200) + return 0; + else + return response_code ? response_code : -1; + } + + case net::URLRequestStatus::FAILED: { + // Network errors are small negative numbers. + const int error = fetcher.GetStatus().error(); + return error ? error : -1; + } + + default: + return -1; + } +} + +bool HasDiffUpdate(const CrxUpdateItem* update_item) { + return !update_item->crx_diffurls.empty(); +} + +bool IsHttpServerError(int status_code) { + return 500 <= status_code && status_code < 600; +} + +bool DeleteFileAndEmptyParentDirectory(const base::FilePath& filepath) { + if (!base::DeleteFile(filepath, false)) + return false; + + const base::FilePath dirname(filepath.DirName()); + if (!base::IsDirectoryEmpty(dirname)) + return true; + + return base::DeleteFile(dirname, false); +} + +std::string GetCrxComponentID(const CrxComponent& component) { + const size_t kCrxIdSize = 16; + CHECK_GE(component.pk_hash.size(), kCrxIdSize); + return HexStringToID(base::StringToLowerASCII( + base::HexEncode(&component.pk_hash[0], kCrxIdSize))); +} + +} // namespace update_client diff --git a/components/update_client/utils.h b/components/update_client/utils.h new file mode 100644 index 0000000..297eb97 --- /dev/null +++ b/components/update_client/utils.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef COMPONENTS_UPDATE_CLIENT_UTILS_H_ +#define COMPONENTS_UPDATE_CLIENT_UTILS_H_ + +#include <string> + +class GURL; + +namespace base { +class FilePath; +} + +namespace net { +class URLFetcher; +class URLFetcherDelegate; +class URLRequestContextGetter; +} + +namespace update_client { + +class Configurator; +struct CrxComponent; +struct CrxUpdateItem; + +// An update protocol request starts with a common preamble which includes +// version and platform information for Chrome and the operating system, +// followed by a request body, which is the actual payload of the request. +// For example: +// +// <?xml version="1.0" encoding="UTF-8"?> +// <request protocol="3.0" version="chrome-32.0.1.0" prodversion="32.0.1.0" +// requestid="{7383396D-B4DD-46E1-9104-AAC6B918E792}" +// updaterchannel="canary" arch="x86" nacl_arch="x86-64" +// ADDITIONAL ATTRIBUTES> +// <hw physmemory="16"/> +// <os platform="win" version="6.1" arch="x86"/> +// ... REQUEST BODY ... +// </request> + +// Builds a protocol request string by creating the outer envelope for +// the request and including the request body specified as a parameter. +// If specified, |additional_attributes| are appended as attributes of the +// request element. The additional attributes have to be well-formed for +// insertion in the request element. +std::string BuildProtocolRequest(const std::string& browser_version, + const std::string& channel, + const std::string& lang, + const std::string& os_long_name, + const std::string& request_body, + const std::string& additional_attributes); + +// Sends a protocol request to the the service endpoint specified by |url|. +// The body of the request is provided by |protocol_request| and it is +// expected to contain XML data. The caller owns the returned object. +net::URLFetcher* SendProtocolRequest( + const GURL& url, + const std::string& protocol_request, + net::URLFetcherDelegate* url_fetcher_delegate, + net::URLRequestContextGetter* url_request_context_getter); + +// Returns true if the url request of |fetcher| was succesful. +bool FetchSuccess(const net::URLFetcher& fetcher); + +// Returns the error code which occured during the fetch. The function returns 0 +// if the fetch was successful. If errors happen, the function could return a +// network error, an http response code, or the status of the fetch, if the +// fetch is pending or canceled. +int GetFetchError(const net::URLFetcher& fetcher); + +// Returns true if the |update_item| contains a valid differential update url. +bool HasDiffUpdate(const CrxUpdateItem* update_item); + +// Returns true if the |status_code| represents a server error 5xx. +bool IsHttpServerError(int status_code); + +// Deletes the file and its directory, if the directory is empty. If the +// parent directory is not empty, the function ignores deleting the directory. +// Returns true if the file and the empty directory are deleted. +bool DeleteFileAndEmptyParentDirectory(const base::FilePath& filepath); + +// Returns the component id of the |component|. The component id is in a +// format similar with the format of an extension id. +std::string GetCrxComponentID(const CrxComponent& component); + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_UTILS_H_ |