|  | // 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/component_updater/component_installer.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/files/file_enumerator.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_temp_dir.h" | 
|  | #include "base/location.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/path_service.h" | 
|  | #include "base/sequenced_task_runner.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/task/post_task.h" | 
|  | #include "base/task/task_traits.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "base/values.h" | 
|  | #include "base/version.h" | 
|  | #include "build/build_config.h" | 
|  | #include "components/component_updater/component_updater_paths.h" | 
|  | #include "components/component_updater/component_updater_service.h" | 
|  | #include "components/crx_file/crx_verifier.h" | 
|  | #include "components/update_client/component_unpacker.h" | 
|  | #include "components/update_client/update_client.h" | 
|  | #include "components/update_client/update_client_errors.h" | 
|  | #include "components/update_client/utils.h" | 
|  |  | 
|  | namespace component_updater { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Version "0" corresponds to no installed version. By the server's conventions, | 
|  | // we represent it as a dotted quad. | 
|  | const char kNullVersion[] = "0.0.0.0"; | 
|  |  | 
|  | using Result = update_client::CrxInstaller::Result; | 
|  | using InstallError = update_client::InstallError; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | ComponentInstallerPolicy::~ComponentInstallerPolicy() {} | 
|  |  | 
|  | ComponentInstaller::RegistrationInfo::RegistrationInfo() | 
|  | : version(kNullVersion) {} | 
|  |  | 
|  | ComponentInstaller::RegistrationInfo::~RegistrationInfo() = default; | 
|  |  | 
|  | ComponentInstaller::ComponentInstaller( | 
|  | std::unique_ptr<ComponentInstallerPolicy> installer_policy) | 
|  | : current_version_(kNullVersion), | 
|  | main_task_runner_(base::ThreadTaskRunnerHandle::Get()) { | 
|  | installer_policy_ = std::move(installer_policy); | 
|  | } | 
|  |  | 
|  | ComponentInstaller::~ComponentInstaller() {} | 
|  |  | 
|  | void ComponentInstaller::Register(ComponentUpdateService* cus, | 
|  | base::OnceClosure callback) { | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  |  | 
|  | // Some components may affect user visible features, hence USER_VISIBLE. | 
|  | task_runner_ = base::CreateSequencedTaskRunnerWithTraits( | 
|  | {base::MayBlock(), base::TaskPriority::USER_VISIBLE, | 
|  | base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); | 
|  |  | 
|  | if (!installer_policy_) { | 
|  | LOG(ERROR) << "A ComponentInstaller has been created but " | 
|  | << "has no installer policy."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto registration_info = base::MakeRefCounted<RegistrationInfo>(); | 
|  | task_runner_->PostTaskAndReply( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&ComponentInstaller::StartRegistration, this, | 
|  | registration_info), | 
|  | base::BindOnce(&ComponentInstaller::FinishRegistration, this, | 
|  | registration_info, cus, std::move(callback))); | 
|  | } | 
|  |  | 
|  | void ComponentInstaller::OnUpdateError(int error) { | 
|  | LOG(ERROR) << "Component update error: " << error; | 
|  | } | 
|  |  | 
|  | Result ComponentInstaller::InstallHelper( | 
|  | const base::FilePath& unpack_path, | 
|  | std::unique_ptr<base::DictionaryValue>* manifest, | 
|  | base::Version* version, | 
|  | base::FilePath* install_path) { | 
|  | auto local_manifest = update_client::ReadManifest(unpack_path); | 
|  | if (!local_manifest) | 
|  | return Result(InstallError::BAD_MANIFEST); | 
|  |  | 
|  | std::string version_ascii; | 
|  | local_manifest->GetStringASCII("version", &version_ascii); | 
|  | const base::Version manifest_version(version_ascii); | 
|  |  | 
|  | VLOG(1) << "Install: version=" << manifest_version.GetString() | 
|  | << " current version=" << current_version_.GetString(); | 
|  |  | 
|  | if (!manifest_version.IsValid()) | 
|  | return Result(InstallError::INVALID_VERSION); | 
|  | if (current_version_.CompareTo(manifest_version) > 0) | 
|  | return Result(InstallError::VERSION_NOT_UPGRADED); | 
|  | base::FilePath local_install_path; | 
|  | if (!base::PathService::Get(DIR_COMPONENT_USER, &local_install_path)) | 
|  | return Result(InstallError::NO_DIR_COMPONENT_USER); | 
|  | local_install_path = | 
|  | local_install_path.Append(installer_policy_->GetRelativeInstallDir()) | 
|  | .AppendASCII(manifest_version.GetString()); | 
|  | if (base::PathExists(local_install_path)) { | 
|  | if (!base::DeleteFile(local_install_path, true)) | 
|  | return Result(InstallError::CLEAN_INSTALL_DIR_FAILED); | 
|  | } | 
|  |  | 
|  | VLOG(1) << "unpack_path=" << unpack_path.AsUTF8Unsafe() | 
|  | << " install_path=" << local_install_path.AsUTF8Unsafe(); | 
|  |  | 
|  | if (!base::Move(unpack_path, local_install_path)) { | 
|  | PLOG(ERROR) << "Move failed."; | 
|  | base::DeleteFile(local_install_path, true); | 
|  | return Result(InstallError::MOVE_FILES_ERROR); | 
|  | } | 
|  |  | 
|  | // Acquire the ownership of the |local_install_path|. | 
|  | base::ScopedTempDir install_path_owner; | 
|  | ignore_result(install_path_owner.Set(local_install_path)); | 
|  |  | 
|  | #if defined(OS_CHROMEOS) | 
|  | if (!base::SetPosixFilePermissions(local_install_path, 0755)) { | 
|  | PLOG(ERROR) << "SetPosixFilePermissions failed: " | 
|  | << local_install_path.value(); | 
|  | return Result(InstallError::SET_PERMISSIONS_FAILED); | 
|  | } | 
|  | #endif  // defined(OS_CHROMEOS) | 
|  |  | 
|  | DCHECK(!base::PathExists(unpack_path)); | 
|  | DCHECK(base::PathExists(local_install_path)); | 
|  |  | 
|  | const Result result = | 
|  | installer_policy_->OnCustomInstall(*local_manifest, local_install_path); | 
|  | if (result.error) | 
|  | return result; | 
|  |  | 
|  | if (!installer_policy_->VerifyInstallation(*local_manifest, | 
|  | local_install_path)) | 
|  | return Result(InstallError::INSTALL_VERIFICATION_FAILED); | 
|  |  | 
|  | *manifest = std::move(local_manifest); | 
|  | *version = manifest_version; | 
|  | *install_path = install_path_owner.Take(); | 
|  |  | 
|  | return Result(InstallError::NONE); | 
|  | } | 
|  |  | 
|  | void ComponentInstaller::Install(const base::FilePath& unpack_path, | 
|  | const std::string& /*public_key*/, | 
|  | Callback callback) { | 
|  | std::unique_ptr<base::DictionaryValue> manifest; | 
|  | base::Version version; | 
|  | base::FilePath install_path; | 
|  | const Result result = | 
|  | InstallHelper(unpack_path, &manifest, &version, &install_path); | 
|  | base::DeleteFile(unpack_path, true); | 
|  | if (result.error) { | 
|  | main_task_runner_->PostTask(FROM_HERE, | 
|  | base::BindOnce(std::move(callback), result)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | current_version_ = version; | 
|  | current_install_dir_ = install_path; | 
|  |  | 
|  | // Invoke |ComponentReady| on the main thread, then after this task has | 
|  | // completed, post a task to call the lamda below using the task scheduler. | 
|  | // The task scheduler PostTaskAndReply call requires the caller to run on | 
|  | // a sequence. This code is not running on a sequence, therefore, there | 
|  | // are two tasks posted to the main thread runner, to ensure that | 
|  | // the |callback| is invoked by the task scheduler after |ComponentReady| has | 
|  | // returned. | 
|  | main_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(&ComponentInstaller::ComponentReady, this, | 
|  | std::move(manifest))); | 
|  | main_task_runner_->PostTask(FROM_HERE, | 
|  | base::BindOnce(std::move(callback), result)); | 
|  | } | 
|  |  | 
|  | bool ComponentInstaller::GetInstalledFile(const std::string& file, | 
|  | base::FilePath* installed_file) { | 
|  | if (current_version_ == base::Version(kNullVersion)) | 
|  | return false;  // No component has been installed yet. | 
|  | *installed_file = current_install_dir_.AppendASCII(file); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ComponentInstaller::Uninstall() { | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  | task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&ComponentInstaller::UninstallOnTaskRunner, this)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ComponentInstaller::FindPreinstallation( | 
|  | const base::FilePath& root, | 
|  | scoped_refptr<RegistrationInfo> registration_info) { | 
|  | base::FilePath path = root.Append(installer_policy_->GetRelativeInstallDir()); | 
|  | if (!base::PathExists(path)) { | 
|  | DVLOG(1) << "Relative install dir does not exist: " << path.MaybeAsASCII(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> manifest = | 
|  | update_client::ReadManifest(path); | 
|  | if (!manifest) { | 
|  | DVLOG(1) << "Manifest does not exist: " << path.MaybeAsASCII(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!installer_policy_->VerifyInstallation(*manifest, path)) { | 
|  | DVLOG(1) << "Installation verification failed: " << path.MaybeAsASCII(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::string version_lexical; | 
|  | if (!manifest->GetStringASCII("version", &version_lexical)) { | 
|  | DVLOG(1) << "Failed to get component version from the manifest."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const base::Version version(version_lexical); | 
|  | if (!version.IsValid()) { | 
|  | DVLOG(1) << "Version in the manifest is invalid:" << version_lexical; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | VLOG(1) << "Preinstalled component found for " << installer_policy_->GetName() | 
|  | << " at " << path.MaybeAsASCII() << " with version " << version | 
|  | << "."; | 
|  |  | 
|  | registration_info->install_dir = path; | 
|  | registration_info->version = version; | 
|  | registration_info->manifest = std::move(manifest); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ComponentInstaller::StartRegistration( | 
|  | scoped_refptr<RegistrationInfo> registration_info) { | 
|  | VLOG(1) << __func__ << " for " << installer_policy_->GetName(); | 
|  | DCHECK(task_runner_); | 
|  | DCHECK(task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | base::Version latest_version(kNullVersion); | 
|  |  | 
|  | // First check for an installation set up alongside Chrome itself. | 
|  | base::FilePath root; | 
|  | if (base::PathService::Get(DIR_COMPONENT_PREINSTALLED, &root) && | 
|  | FindPreinstallation(root, registration_info)) { | 
|  | latest_version = registration_info->version; | 
|  | } | 
|  |  | 
|  | // If there is a distinct alternate root, check there as well, and override | 
|  | // anything found in the basic root. | 
|  | base::FilePath root_alternate; | 
|  | if (base::PathService::Get(DIR_COMPONENT_PREINSTALLED_ALT, &root_alternate) && | 
|  | root != root_alternate && | 
|  | FindPreinstallation(root_alternate, registration_info)) { | 
|  | latest_version = registration_info->version; | 
|  | } | 
|  |  | 
|  | // Then check for a higher-versioned user-wide installation. | 
|  | base::FilePath latest_path; | 
|  | std::unique_ptr<base::DictionaryValue> latest_manifest; | 
|  | base::FilePath base_component_dir; | 
|  | if (!base::PathService::Get(DIR_COMPONENT_USER, &base_component_dir)) | 
|  | return; | 
|  | base::FilePath base_dir = | 
|  | base_component_dir.Append(installer_policy_->GetRelativeInstallDir()); | 
|  | if (!base::PathExists(base_dir) && !base::CreateDirectory(base_dir)) { | 
|  | PLOG(ERROR) << "Could not create the base directory for " | 
|  | << installer_policy_->GetName() << " (" | 
|  | << base_dir.MaybeAsASCII() << ")."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | #if defined(OS_CHROMEOS) | 
|  | base::FilePath base_dir_ = base_component_dir; | 
|  | std::vector<base::FilePath::StringType> components; | 
|  | installer_policy_->GetRelativeInstallDir().GetComponents(&components); | 
|  | for (const base::FilePath::StringType component : components) { | 
|  | base_dir_ = base_dir_.Append(component); | 
|  | if (!base::SetPosixFilePermissions(base_dir_, 0755)) { | 
|  | PLOG(ERROR) << "SetPosixFilePermissions failed: " << base_dir.value(); | 
|  | return; | 
|  | } | 
|  | } | 
|  | #endif  // defined(OS_CHROMEOS) | 
|  |  | 
|  | std::vector<base::FilePath> older_paths; | 
|  | base::FileEnumerator file_enumerator(base_dir, false, | 
|  | base::FileEnumerator::DIRECTORIES); | 
|  | for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); | 
|  | path = file_enumerator.Next()) { | 
|  | base::Version version(path.BaseName().MaybeAsASCII()); | 
|  |  | 
|  | // Ignore folders that don't have valid version names. These folders are not | 
|  | // managed by component installer so do not try to remove them. | 
|  | if (!version.IsValid()) | 
|  | continue; | 
|  |  | 
|  | // |version| not newer than the latest found version (kNullVersion if no | 
|  | // version has been found yet) is marked for removal. | 
|  | if (version.CompareTo(latest_version) <= 0) { | 
|  | older_paths.push_back(path); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> manifest = | 
|  | update_client::ReadManifest(path); | 
|  | if (!manifest || !installer_policy_->VerifyInstallation(*manifest, path)) { | 
|  | PLOG(ERROR) << "Failed to read manifest or verify installation for " | 
|  | << installer_policy_->GetName() << " (" << path.MaybeAsASCII() | 
|  | << ")."; | 
|  | older_paths.push_back(path); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // New valid |version| folder found! | 
|  |  | 
|  | if (!latest_path.empty()) | 
|  | older_paths.push_back(latest_path); | 
|  |  | 
|  | latest_path = path; | 
|  | latest_version = version; | 
|  | latest_manifest = std::move(manifest); | 
|  | } | 
|  |  | 
|  | if (latest_manifest) { | 
|  | registration_info->version = latest_version; | 
|  | registration_info->manifest = std::move(latest_manifest); | 
|  | registration_info->install_dir = latest_path; | 
|  | base::ReadFileToString(latest_path.AppendASCII("manifest.fingerprint"), | 
|  | ®istration_info->fingerprint); | 
|  | } | 
|  |  | 
|  | // Remove older versions of the component. None should be in use during | 
|  | // browser startup. | 
|  | for (const auto& older_path : older_paths) | 
|  | base::DeleteFile(older_path, true); | 
|  | } | 
|  |  | 
|  | void ComponentInstaller::UninstallOnTaskRunner() { | 
|  | DCHECK(task_runner_); | 
|  | DCHECK(task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | // Only try to delete any files that are in our user-level install path. | 
|  | base::FilePath userInstallPath; | 
|  | if (!base::PathService::Get(DIR_COMPONENT_USER, &userInstallPath)) | 
|  | return; | 
|  | if (!userInstallPath.IsParent(current_install_dir_)) | 
|  | return; | 
|  |  | 
|  | const base::FilePath base_dir = current_install_dir_.DirName(); | 
|  | base::FileEnumerator file_enumerator(base_dir, false, | 
|  | base::FileEnumerator::DIRECTORIES); | 
|  | for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); | 
|  | path = file_enumerator.Next()) { | 
|  | base::Version version(path.BaseName().MaybeAsASCII()); | 
|  |  | 
|  | // Ignore folders that don't have valid version names. These folders are not | 
|  | // managed by the component installer, so do not try to remove them. | 
|  | if (!version.IsValid()) | 
|  | continue; | 
|  |  | 
|  | if (!base::DeleteFile(path, true)) | 
|  | DLOG(ERROR) << "Couldn't delete " << path.value(); | 
|  | } | 
|  |  | 
|  | // Delete the base directory if it's empty now. | 
|  | if (base::IsDirectoryEmpty(base_dir)) { | 
|  | if (!base::DeleteFile(base_dir, false)) | 
|  | DLOG(ERROR) << "Couldn't delete " << base_dir.value(); | 
|  | } | 
|  |  | 
|  | // Customized operations for individual component. | 
|  | installer_policy_->OnCustomUninstall(); | 
|  | } | 
|  |  | 
|  | void ComponentInstaller::FinishRegistration( | 
|  | scoped_refptr<RegistrationInfo> registration_info, | 
|  | ComponentUpdateService* cus, | 
|  | base::OnceClosure callback) { | 
|  | VLOG(1) << __func__ << " for " << installer_policy_->GetName(); | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  |  | 
|  | current_install_dir_ = registration_info->install_dir; | 
|  | current_version_ = registration_info->version; | 
|  | current_fingerprint_ = registration_info->fingerprint; | 
|  |  | 
|  | update_client::CrxComponent crx; | 
|  | installer_policy_->GetHash(&crx.pk_hash); | 
|  | crx.installer = this; | 
|  | crx.version = current_version_; | 
|  | crx.fingerprint = current_fingerprint_; | 
|  | crx.name = installer_policy_->GetName(); | 
|  | crx.installer_attributes = installer_policy_->GetInstallerAttributes(); | 
|  | crx.requires_network_encryption = | 
|  | installer_policy_->RequiresNetworkEncryption(); | 
|  | crx.crx_format_requirement = | 
|  | crx_file::VerifierFormat::CRX3_WITH_PUBLISHER_PROOF; | 
|  | crx.handled_mime_types = installer_policy_->GetMimeTypes(); | 
|  | crx.supports_group_policy_enable_component_updates = | 
|  | installer_policy_->SupportsGroupPolicyEnabledComponentUpdates(); | 
|  |  | 
|  | if (!cus->RegisterComponent(crx)) { | 
|  | LOG(ERROR) << "Component registration failed for " | 
|  | << installer_policy_->GetName(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (registration_info->manifest) { | 
|  | ComponentReady(std::move(registration_info->manifest)); | 
|  | } else { | 
|  | DVLOG(1) << "No component found for " << installer_policy_->GetName(); | 
|  | } | 
|  |  | 
|  | if (!callback.is_null()) | 
|  | std::move(callback).Run(); | 
|  | } | 
|  |  | 
|  | void ComponentInstaller::ComponentReady( | 
|  | std::unique_ptr<base::DictionaryValue> manifest) { | 
|  | VLOG(1) << "Component ready, version " << current_version_.GetString() | 
|  | << " in " << current_install_dir_.value(); | 
|  | installer_policy_->ComponentReady(current_version_, current_install_dir_, | 
|  | std::move(manifest)); | 
|  | } | 
|  |  | 
|  | }  // namespace component_updater |