| // 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. |
| |
| #include "components/update_client/update_engine.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/guid.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/optional.h" |
| #include "base/stl_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/update_client/component.h" |
| #include "components/update_client/configurator.h" |
| #include "components/update_client/crx_update_item.h" |
| #include "components/update_client/persisted_data.h" |
| #include "components/update_client/protocol_parser.h" |
| #include "components/update_client/update_checker.h" |
| #include "components/update_client/update_client_errors.h" |
| #include "components/update_client/utils.h" |
| |
| namespace update_client { |
| |
| UpdateContext::UpdateContext( |
| scoped_refptr<Configurator> config, |
| bool is_foreground, |
| const std::vector<std::string>& ids, |
| UpdateClient::CrxDataCallback crx_data_callback, |
| const UpdateEngine::NotifyObserversCallback& notify_observers_callback, |
| UpdateEngine::Callback callback, |
| CrxDownloader::Factory crx_downloader_factory) |
| : config(config), |
| is_foreground(is_foreground), |
| enabled_component_updates(config->EnabledComponentUpdates()), |
| ids(ids), |
| crx_data_callback(std::move(crx_data_callback)), |
| notify_observers_callback(notify_observers_callback), |
| callback(std::move(callback)), |
| crx_downloader_factory(crx_downloader_factory), |
| session_id(base::StrCat({"{", base::GenerateGUID(), "}"})) { |
| for (const auto& id : ids) { |
| components.insert( |
| std::make_pair(id, std::make_unique<Component>(*this, id))); |
| } |
| } |
| |
| UpdateContext::~UpdateContext() {} |
| |
| UpdateEngine::UpdateEngine( |
| scoped_refptr<Configurator> config, |
| UpdateChecker::Factory update_checker_factory, |
| CrxDownloader::Factory crx_downloader_factory, |
| scoped_refptr<PingManager> ping_manager, |
| const NotifyObserversCallback& notify_observers_callback) |
| : config_(config), |
| update_checker_factory_(update_checker_factory), |
| crx_downloader_factory_(crx_downloader_factory), |
| ping_manager_(ping_manager), |
| metadata_( |
| std::make_unique<PersistedData>(config->GetPrefService(), |
| config->GetActivityDataService())), |
| notify_observers_callback_(notify_observers_callback) {} |
| |
| UpdateEngine::~UpdateEngine() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| } |
| |
| void UpdateEngine::Update(bool is_foreground, |
| const std::vector<std::string>& ids, |
| UpdateClient::CrxDataCallback crx_data_callback, |
| Callback callback) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (ids.empty()) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), Error::INVALID_ARGUMENT)); |
| return; |
| } |
| |
| if (IsThrottled(is_foreground)) { |
| // TODO(xiaochu): remove this log after https://crbug.com/851151 is fixed. |
| VLOG(1) << "Background update is throttled for following components:"; |
| for (const auto& id : ids) { |
| VLOG(1) << "id:" << id; |
| } |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), Error::RETRY_LATER)); |
| return; |
| } |
| |
| const auto update_context = base::MakeRefCounted<UpdateContext>( |
| config_, is_foreground, ids, std::move(crx_data_callback), |
| notify_observers_callback_, std::move(callback), crx_downloader_factory_); |
| DCHECK(!update_context->session_id.empty()); |
| |
| const auto result = update_contexts_.insert( |
| std::make_pair(update_context->session_id, update_context)); |
| DCHECK(result.second); |
| |
| // Calls out to get the corresponding CrxComponent data for the CRXs in this |
| // update context. |
| const auto crx_components = |
| std::move(update_context->crx_data_callback).Run(update_context->ids); |
| DCHECK_EQ(update_context->ids.size(), crx_components.size()); |
| |
| for (size_t i = 0; i != update_context->ids.size(); ++i) { |
| const auto& id = update_context->ids[i]; |
| |
| DCHECK(update_context->components[id]->state() == ComponentState::kNew); |
| |
| const auto crx_component = crx_components[i]; |
| if (crx_component) { |
| // This component can be checked for updates. |
| DCHECK_EQ(id, GetCrxComponentID(*crx_component)); |
| auto& component = update_context->components[id]; |
| component->set_crx_component(*crx_component); |
| component->set_previous_version(component->crx_component()->version); |
| component->set_previous_fp(component->crx_component()->fingerprint); |
| update_context->components_to_check_for_updates.push_back(id); |
| } else { |
| // |CrxDataCallback| did not return a CrxComponent instance for this |
| // component, which most likely, has been uninstalled. This component |
| // is going to be transitioned to an error state when the its |Handle| |
| // method is called later on by the engine. |
| update_context->component_queue.push(id); |
| } |
| } |
| |
| if (update_context->components_to_check_for_updates.empty()) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent, |
| base::Unretained(this), update_context)); |
| return; |
| } |
| |
| for (const auto& id : update_context->components_to_check_for_updates) |
| update_context->components[id]->Handle( |
| base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesStart, |
| base::Unretained(this), update_context, id)); |
| } |
| |
| void UpdateEngine::ComponentCheckingForUpdatesStart( |
| scoped_refptr<UpdateContext> update_context, |
| const std::string& id) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| DCHECK(update_context->components.at(id)); |
| |
| // Handle |kChecking| state. |
| auto& component = *update_context->components.at(id); |
| component.Handle( |
| base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesComplete, |
| base::Unretained(this), update_context)); |
| |
| ++update_context->num_components_ready_to_check; |
| if (update_context->num_components_ready_to_check < |
| update_context->components_to_check_for_updates.size()) { |
| return; |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateEngine::DoUpdateCheck, |
| base::Unretained(this), update_context)); |
| } |
| |
| void UpdateEngine::DoUpdateCheck(scoped_refptr<UpdateContext> update_context) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| update_context->update_checker = |
| update_checker_factory_(config_, metadata_.get()); |
| |
| update_context->update_checker->CheckForUpdates( |
| update_context->session_id, |
| update_context->components_to_check_for_updates, |
| update_context->components, config_->ExtraRequestParams(), |
| update_context->enabled_component_updates, |
| base::BindOnce(&UpdateEngine::UpdateCheckResultsAvailable, |
| base::Unretained(this), update_context)); |
| } |
| |
| void UpdateEngine::UpdateCheckResultsAvailable( |
| scoped_refptr<UpdateContext> update_context, |
| const base::Optional<ProtocolParser::Results>& results, |
| ErrorCategory error_category, |
| int error, |
| int retry_after_sec) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| update_context->retry_after_sec = retry_after_sec; |
| |
| // Only positive values for throttle_sec are effective. 0 means that no |
| // throttling occurs and it resets |throttle_updates_until_|. |
| // Negative values are not trusted and are ignored. |
| constexpr int kMaxRetryAfterSec = 24 * 60 * 60; // 24 hours. |
| const int throttle_sec = |
| std::min(update_context->retry_after_sec, kMaxRetryAfterSec); |
| if (throttle_sec >= 0) { |
| throttle_updates_until_ = |
| throttle_sec ? base::TimeTicks::Now() + |
| base::TimeDelta::FromSeconds(throttle_sec) |
| : base::TimeTicks(); |
| } |
| |
| update_context->update_check_error = error; |
| |
| if (error) { |
| DCHECK(!results); |
| for (const auto& id : update_context->components_to_check_for_updates) { |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| auto& component = update_context->components.at(id); |
| component->SetUpdateCheckResult(base::nullopt, |
| ErrorCategory::kUpdateCheck, error); |
| } |
| return; |
| } |
| |
| DCHECK(results); |
| DCHECK_EQ(0, error); |
| |
| std::map<std::string, ProtocolParser::Result> id_to_result; |
| for (const auto& result : results->list) |
| id_to_result[result.extension_id] = result; |
| |
| for (const auto& id : update_context->components_to_check_for_updates) { |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| auto& component = update_context->components.at(id); |
| const auto& it = id_to_result.find(id); |
| if (it != id_to_result.end()) { |
| const auto result = it->second; |
| const auto error = [](const std::string& status) { |
| // First, handle app status literals which can be folded down as an |
| // updatecheck status |
| if (status == "error-unknownApplication") |
| return std::make_pair(ErrorCategory::kUpdateCheck, |
| ProtocolError::UNKNOWN_APPLICATION); |
| if (status == "restricted") |
| return std::make_pair(ErrorCategory::kUpdateCheck, |
| ProtocolError::RESTRICTED_APPLICATION); |
| if (status == "error-invalidAppId") |
| return std::make_pair(ErrorCategory::kUpdateCheck, |
| ProtocolError::INVALID_APPID); |
| // If the parser has return a valid result and the status is not one of |
| // the literals above, then this must be a success an not a parse error. |
| return std::make_pair(ErrorCategory::kNone, ProtocolError::NONE); |
| }(result.status); |
| component->SetUpdateCheckResult(result, error.first, |
| static_cast<int>(error.second)); |
| } else { |
| component->SetUpdateCheckResult( |
| base::nullopt, ErrorCategory::kUpdateCheck, |
| static_cast<int>(ProtocolError::UPDATE_RESPONSE_NOT_FOUND)); |
| } |
| } |
| } |
| |
| void UpdateEngine::ComponentCheckingForUpdatesComplete( |
| scoped_refptr<UpdateContext> update_context) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| ++update_context->num_components_checked; |
| if (update_context->num_components_checked < |
| update_context->components_to_check_for_updates.size()) { |
| return; |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateEngine::UpdateCheckComplete, |
| base::Unretained(this), update_context)); |
| } |
| |
| void UpdateEngine::UpdateCheckComplete( |
| scoped_refptr<UpdateContext> update_context) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| for (const auto& id : update_context->components_to_check_for_updates) |
| update_context->component_queue.push(id); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent, |
| base::Unretained(this), update_context)); |
| } |
| |
| void UpdateEngine::HandleComponent( |
| scoped_refptr<UpdateContext> update_context) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| auto& queue = update_context->component_queue; |
| |
| if (queue.empty()) { |
| const Error error = update_context->update_check_error |
| ? Error::UPDATE_CHECK_ERROR |
| : Error::NONE; |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&UpdateEngine::UpdateComplete, base::Unretained(this), |
| update_context, error)); |
| return; |
| } |
| |
| const auto& id = queue.front(); |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| const auto& component = update_context->components.at(id); |
| DCHECK(component); |
| |
| auto& next_update_delay = update_context->next_update_delay; |
| if (!next_update_delay.is_zero() && component->IsUpdateAvailable()) { |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&UpdateEngine::HandleComponent, base::Unretained(this), |
| update_context), |
| next_update_delay); |
| next_update_delay = base::TimeDelta(); |
| |
| notify_observers_callback_.Run( |
| UpdateClient::Observer::Events::COMPONENT_WAIT, id); |
| return; |
| } |
| |
| component->Handle(base::BindOnce(&UpdateEngine::HandleComponentComplete, |
| base::Unretained(this), update_context)); |
| } |
| |
| void UpdateEngine::HandleComponentComplete( |
| scoped_refptr<UpdateContext> update_context) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| auto& queue = update_context->component_queue; |
| DCHECK(!queue.empty()); |
| |
| const auto& id = queue.front(); |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| const auto& component = update_context->components.at(id); |
| DCHECK(component); |
| |
| if (component->IsHandled()) { |
| update_context->next_update_delay = component->GetUpdateDuration(); |
| |
| if (!component->events().empty()) { |
| ping_manager_->SendPing(*component, |
| base::BindOnce([](int, const std::string&) {})); |
| } |
| |
| queue.pop(); |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent, |
| base::Unretained(this), update_context)); |
| } |
| |
| void UpdateEngine::UpdateComplete(scoped_refptr<UpdateContext> update_context, |
| Error error) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(update_context); |
| |
| const auto num_erased = update_contexts_.erase(update_context->session_id); |
| DCHECK_EQ(1u, num_erased); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_context->callback), error)); |
| } |
| |
| bool UpdateEngine::GetUpdateState(const std::string& id, |
| CrxUpdateItem* update_item) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| for (const auto& context : update_contexts_) { |
| const auto& components = context.second->components; |
| const auto it = components.find(id); |
| if (it != components.end()) { |
| *update_item = it->second->GetCrxUpdateItem(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool UpdateEngine::IsThrottled(bool is_foreground) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (is_foreground || throttle_updates_until_.is_null()) |
| return false; |
| |
| const auto now(base::TimeTicks::Now()); |
| |
| // Throttle the calls in the interval (t - 1 day, t) to limit the effect of |
| // unset clocks or clock drift. |
| return throttle_updates_until_ - base::TimeDelta::FromDays(1) < now && |
| now < throttle_updates_until_; |
| } |
| |
| void UpdateEngine::SendUninstallPing(const std::string& id, |
| const base::Version& version, |
| int reason, |
| Callback callback) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| const auto update_context = base::MakeRefCounted<UpdateContext>( |
| config_, false, std::vector<std::string>{id}, |
| UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(), |
| std::move(callback), nullptr); |
| DCHECK(!update_context->session_id.empty()); |
| |
| const auto result = update_contexts_.insert( |
| std::make_pair(update_context->session_id, update_context)); |
| DCHECK(result.second); |
| |
| DCHECK(update_context); |
| DCHECK_EQ(1u, update_context->ids.size()); |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| const auto& component = update_context->components.at(id); |
| |
| component->Uninstall(version, reason); |
| |
| update_context->component_queue.push(id); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent, |
| base::Unretained(this), update_context)); |
| } |
| |
| } // namespace update_client |