// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/manifest_util.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/version.h"
#include "base/win/windows_version.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/protocol_parser_xml.h"
#include "components/update_client/protocol_parser.h"
#include "components/update_client/utils.h"
namespace updater {
namespace {
constexpr char kArchAmd64Omaha3[] = "x64";
std::optional<base::FilePath> GetOfflineManifest(
const base::FilePath& offline_dir,
const std::string& app_id) {
// Check manifest with fixed name first.
base::FilePath manifest_path = offline_dir.AppendASCII("OfflineManifest.gup");
if (base::PathExists(manifest_path)) {
return manifest_path;
}
// Then check the legacy app specific manifest.
manifest_path =
offline_dir.AppendASCII(app_id).AddExtension(FILE_PATH_LITERAL(".gup"));
return base::PathExists(manifest_path)
? std::optional<base::FilePath>(manifest_path)
: std::nullopt;
}
std::unique_ptr<ProtocolParserXML> ParseOfflineManifest(
const base::FilePath& offline_dir,
const std::string& app_id) {
std::optional<base::FilePath> manifest_path =
GetOfflineManifest(offline_dir, app_id);
if (!manifest_path) {
VLOG(2) << "Cannot find manifest file in: " << offline_dir;
return nullptr;
}
int64_t file_size = 0;
if (!base::GetFileSize(manifest_path.value(), &file_size)) {
VLOG(2) << "Cannot determine manifest file size.";
return nullptr;
}
constexpr int64_t kMaxManifestSize = 1024 * 1024;
if (file_size > kMaxManifestSize) {
VLOG(2) << "Manifest file is too large.";
return nullptr;
}
std::string contents(file_size, '\0');
if (base::ReadFile(manifest_path.value(), &contents[0], file_size) == -1) {
VLOG(2) << "Failed to load manifest file: " << manifest_path.value();
return nullptr;
}
auto xml_parser = std::make_unique<ProtocolParserXML>();
if (!xml_parser->Parse(contents)) {
VLOG(2) << "Failed to parse XML manifest file: " << manifest_path.value();
return nullptr;
}
return xml_parser;
}
} // namespace
void ReadInstallCommandFromManifest(
const std::wstring& offline_dir_guid,
const std::string& app_id,
const std::string& install_data_index,
update_client::ProtocolParser::Results& results,
std::string& installer_version,
base::FilePath& installer_path,
std::string& install_args,
std::string& install_data) {
if (offline_dir_guid.empty()) {
VLOG(1) << "Unexpected: offline install without an offline directory.";
return;
}
if (!IsGuid(offline_dir_guid)) {
VLOG(1) << "Unexpected: offline directory needs to be a GUID: "
<< offline_dir_guid;
return;
}
const base::FilePath offline_dir = [&offline_dir_guid] {
base::FilePath offline_dir;
return base::PathService::Get(base::DIR_EXE, &offline_dir)
? offline_dir.Append(L"Offline").Append(offline_dir_guid)
: base::FilePath();
}();
if (offline_dir.empty()) {
VLOG(1) << "Unexpected: offline directory empty.";
return;
}
const std::unique_ptr<ProtocolParserXML> manifest_parser =
ParseOfflineManifest(offline_dir, app_id);
if (!manifest_parser) {
return;
}
results = manifest_parser->results();
const std::vector<update_client::ProtocolParser::Result>& app_list =
manifest_parser->results().list;
auto it = base::ranges::find_if(
app_list, [&app_id](const update_client::ProtocolParser::Result& result) {
return base::EqualsCaseInsensitiveASCII(result.extension_id, app_id);
});
if (it == std::end(app_list)) {
VLOG(2) << "No manifest data for app: " << app_id;
return;
}
installer_version = it->manifest.version;
installer_path = [&offline_dir, &app_id, &it] {
const base::FilePath app_dir(offline_dir.AppendASCII(app_id));
const base::FilePath path(app_dir.AppendASCII(it->manifest.run));
return base::PathExists(path)
? path
: base::FileEnumerator(
app_dir, false, base::FileEnumerator::FILES, {},
base::FileEnumerator::FolderSearchPolicy::ALL,
base::FileEnumerator::ErrorPolicy::IGNORE_ERRORS)
.Next();
}();
install_args = it->manifest.arguments;
if (!install_data_index.empty()) {
auto data_iter = base::ranges::find(
it->data, install_data_index,
&update_client::ProtocolParser::Result::Data::install_data_index);
if (data_iter == std::end(it->data)) {
VLOG(2) << "Install data index not found: " << install_data_index;
return;
}
install_data = data_iter->text;
}
}
bool IsArchitectureSupported(const std::string& arch,
const std::string& current_architecture) {
if (arch.empty()) {
return true;
}
// This code accounts for Omaha 3 Offline manifests having `arch` as "x64",
// but `GetArchitecture` returning "x86_64" for amd64.
if (arch == current_architecture ||
(arch == kArchAmd64Omaha3 &&
current_architecture == update_client::kArchAmd64)) {
return true;
}
using IsWow64GuestMachineSupportedFunc = HRESULT(WINAPI*)(USHORT, BOOL*);
const IsWow64GuestMachineSupportedFunc is_wow64_guest_machine_supported =
reinterpret_cast<IsWow64GuestMachineSupportedFunc>(::GetProcAddress(
::GetModuleHandle(L"kernel32.dll"), "IsWow64GuestMachineSupported"));
if (is_wow64_guest_machine_supported) {
const base::flat_map<std::string, int> kNativeArchitectureStringsToImages =
{
{update_client::kArchIntel, IMAGE_FILE_MACHINE_I386},
{kArchAmd64Omaha3, IMAGE_FILE_MACHINE_AMD64},
{update_client::kArchAmd64, IMAGE_FILE_MACHINE_AMD64},
{update_client::kArchArm64, IMAGE_FILE_MACHINE_ARM64},
};
const auto image = kNativeArchitectureStringsToImages.find(arch);
if (image != kNativeArchitectureStringsToImages.end()) {
BOOL is_machine_supported = false;
if (SUCCEEDED(is_wow64_guest_machine_supported(
static_cast<USHORT>(image->second), &is_machine_supported))) {
return is_machine_supported;
}
}
}
return arch == update_client::kArchIntel;
}
bool IsPlatformCompatible(const std::string& platform) {
return platform.empty() || base::ToLowerASCII(platform) == "win";
}
bool IsArchitectureCompatible(const std::string& arch_list,
const std::string& current_architecture) {
std::vector<std::string> architectures = base::SplitString(
arch_list, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (architectures.empty()) {
return true;
}
base::ranges::sort(architectures);
if (base::ranges::find_if(
architectures, [¤t_architecture](const std::string& narch) {
if (narch[0] != '-') {
return false;
}
const std::string arch = narch.substr(1);
// This code accounts for Omaha 3 Offline manifests having `arch` as
// "x64", but `GetArchitecture` returning "x86_64" for amd64.
return (arch == current_architecture ||
(arch == kArchAmd64Omaha3 &&
current_architecture == update_client::kArchAmd64));
}) != architectures.end()) {
return false;
}
std::erase_if(architectures,
[](const std::string& arch) { return arch[0] == '-'; });
return architectures.empty() ||
base::ranges::find_if(
architectures, [¤t_architecture](const std::string& arch) {
return IsArchitectureSupported(arch, current_architecture);
}) != architectures.end();
}
bool IsOSVersionCompatible(const std::string& min_os_version) {
if (min_os_version.empty()) {
return true;
}
// `base::win::OSInfo` gets the `major`, `minor` and `build` from
// `::GetVersionEx`, and the `patch` from the `UBR` value under the registry
// path `HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion`.
const base::win::OSInfo::VersionNumber& version_number =
base::win::OSInfo::GetInstance()->version_number();
const base::Version current_version(
{version_number.major, version_number.minor, version_number.build,
version_number.patch});
const base::Version other_version(min_os_version);
return current_version.IsValid() && other_version.IsValid() &&
(current_version.CompareTo(other_version) >= 0);
}
bool IsOsSupported(const update_client::ProtocolParser::Results& results) {
const update_client::ProtocolParser::SystemRequirements& system_requirements =
results.system_requirements;
return IsPlatformCompatible(system_requirements.platform) &&
IsArchitectureCompatible(system_requirements.arch,
update_client::GetArchitecture()) &&
IsOSVersionCompatible(system_requirements.min_os_version);
}
} // namespace updater