#include "chrome/updater/util/util.h"
#include <algorithm>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#if BUILDFLAG(IS_WIN)
#include <initguid.h>
#include <windows.h>
#include "base/logging_win.h"
#endif
#include <algorithm>
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/functional/function_ref.h"
#include "base/logging.h"
#include "base/logging/logging_settings.h"
#include "base/path_service.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/tag.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "components/update_client/utils.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_LINUX)
#include "chrome/updater/util/linux_util.h"
#elif BUILDFLAG(IS_MAC)
#import "chrome/updater/util/mac_util.h"
#endif
#if BUILDFLAG(IS_POSIX)
#include <pwd.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
namespace updater {
namespace {
constexpr int64_t kLogRotateAtSize = 1024 * 1024;
}
std::optional<base::FilePath> GetVersionedInstallDirectory(
UpdaterScope scope,
const base::Version& version) {
const std::optional<base::FilePath> path = GetInstallDirectory(scope);
if (!path) {
return std::nullopt;
}
return path->AppendUTF8(version.GetString());
}
std::optional<base::FilePath> GetVersionedInstallDirectory(UpdaterScope scope) {
return GetVersionedInstallDirectory(scope, base::Version(kUpdaterVersion));
}
std::optional<base::FilePath> GetUpdaterExecutablePath(
UpdaterScope scope,
const base::Version& version) {
std::optional<base::FilePath> path =
GetVersionedInstallDirectory(scope, version);
if (!path) {
return std::nullopt;
}
return path->Append(GetExecutableRelativePath());
}
std::optional<base::FilePath> GetCrxCacheDirectory(UpdaterScope scope) {
const std::optional<base::FilePath> cache_path(GetInstallDirectory(scope));
if (!cache_path) {
return std::nullopt;
}
return std::optional<base::FilePath>(cache_path->AppendUTF8("crx_cache"));
}
std::optional<base::FilePath> GetUpdaterExecutablePath(UpdaterScope scope) {
return GetUpdaterExecutablePath(scope, base::Version(kUpdaterVersion));
}
std::optional<base::FilePath> GetCrashDatabasePath(UpdaterScope scope) {
const std::optional<base::FilePath> path(GetVersionedInstallDirectory(scope));
return path ? std::optional<base::FilePath>(path->AppendUTF8("Crashpad"))
: std::nullopt;
}
std::optional<base::FilePath> EnsureCrashDatabasePath(UpdaterScope scope) {
const std::optional<base::FilePath> database_path(
GetCrashDatabasePath(scope));
return database_path && base::CreateDirectory(*database_path) ? database_path
: std::nullopt;
}
TagParsingResult::TagParsingResult() = default;
TagParsingResult::TagParsingResult(std::optional<tagging::TagArgs> tag_args,
tagging::ErrorCode error)
: tag_args(tag_args), error(error) {}
TagParsingResult::~TagParsingResult() = default;
TagParsingResult::TagParsingResult(const TagParsingResult&) = default;
TagParsingResult& TagParsingResult::operator=(const TagParsingResult&) =
default;
TagParsingResult GetTagArgsForCommandLine(
const base::CommandLine& command_line) {
std::string tag = command_line.HasSwitch(kInstallSwitch)
? command_line.GetSwitchValueUTF8(kInstallSwitch)
: command_line.GetSwitchValueUTF8(kHandoffSwitch);
if (tag.empty()) {
return {};
}
tagging::TagArgs tag_args;
const tagging::ErrorCode error = tagging::Parse(
tag, command_line.GetSwitchValueUTF8(kAppArgsSwitch), tag_args);
VLOG_IF(1, error != tagging::ErrorCode::kSuccess)
<< "Tag parsing returned " << error << ".";
return {tag_args, error};
}
TagParsingResult GetTagArgs() {
return GetTagArgsForCommandLine(*base::CommandLine::ForCurrentProcess());
}
std::optional<tagging::AppArgs> GetAppArgs(const std::string& app_id) {
const std::optional<tagging::TagArgs> tag_args = GetTagArgs().tag_args;
if (!tag_args || tag_args->apps.empty()) {
return std::nullopt;
}
const std::vector<tagging::AppArgs>& apps_args = tag_args->apps;
std::vector<tagging::AppArgs>::const_iterator it = std::ranges::find_if(
apps_args, [&app_id](const tagging::AppArgs& app_args) {
return base::EqualsCaseInsensitiveASCII(app_args.app_id, app_id);
});
return it != std::end(apps_args) ? std::optional<tagging::AppArgs>(*it)
: std::nullopt;
}
std::string GetTagLanguage() {
std::optional<tagging::TagArgs> tag_args = GetTagArgs().tag_args;
return tag_args ? tag_args->language : "";
}
std::string GetDecodedInstallDataFromAppArgs(const std::string& app_id) {
const std::optional<tagging::AppArgs> app_args = GetAppArgs(app_id);
if (!app_args) {
return std::string();
}
std::string decoded_installer_data;
const bool result = base::UnescapeBinaryURLComponentSafe(
app_args->encoded_installer_data,
false, &decoded_installer_data);
VLOG_IF(1, !result) << "Failed to decode encoded installer data: ["
<< app_args->encoded_installer_data << "]";
return decoded_installer_data;
}
std::string GetInstallDataIndexFromAppArgs(const std::string& app_id) {
const std::optional<tagging::AppArgs> app_args = GetAppArgs(app_id);
return app_args ? app_args->install_data_index : std::string();
}
std::optional<base::FilePath> GetLogFilePath(UpdaterScope scope) {
const std::optional<base::FilePath> log_dir = GetInstallDirectory(scope);
if (log_dir) {
return log_dir->Append(FILE_PATH_LITERAL("updater.log"));
}
return std::nullopt;
}
void InitLogging(UpdaterScope updater_scope) {
std::optional<base::FilePath> log_file = GetLogFilePath(updater_scope);
if (!log_file) {
LOG(ERROR) << "Error getting base dir.";
return;
}
base::CreateDirectory(log_file->DirName());
std::optional<int64_t> size = base::GetFileSize(*log_file);
if (size.has_value() && size.value() >= kLogRotateAtSize) {
base::ReplaceFile(
*log_file, log_file->AddExtension(FILE_PATH_LITERAL(".old")), nullptr);
}
logging::LoggingSettings settings;
settings.log_file_path = log_file->value().c_str();
settings.logging_dest = logging::LOG_TO_ALL;
#if BUILDFLAG(IS_WIN)
settings.logging_dest &= ~logging::LOG_TO_SYSTEM_DEBUG_LOG;
#endif
logging::InitLogging(settings);
logging::SetLogItems(true,
true,
true,
false);
#if BUILDFLAG(IS_WIN)
static constexpr GUID kUpdaterETWProviderName = {
0x4d7d9607,
0x78b6,
0x4583,
{0xa1, 0x88, 0x21, 0x36, 0xab, 0x85, 0xf5, 0xf1}};
logging::LogEventProvider::Initialize(kUpdaterETWProviderName);
#endif
}
std::string GetUpdaterUserAgent(const base::Version& updater_version) {
return base::StrCat(
{PRODUCT_FULLNAME_STRING, " ", updater_version.GetString()});
}
GURL AppendQueryParameter(const GURL& url,
const std::string& name,
const std::string& value) {
std::string query(url.GetQuery());
if (!query.empty()) {
query += "&";
}
query += (base::EscapeQueryParamValue(name, true) + "=" +
base::EscapeQueryParamValue(value, true));
GURL::Replacements replacements;
replacements.SetQueryStr(query);
return url.ReplaceComponents(replacements);
}
#if BUILDFLAG(IS_WIN)
std::wstring GetTaskNamePrefix(UpdaterScope scope,
const base::Version& version) {
std::wstring task_name = GetTaskDisplayName(scope, version);
std::erase_if(task_name, base::IsAsciiWhitespace<wchar_t>);
return task_name;
}
std::wstring GetTaskDisplayName(UpdaterScope scope,
const base::Version& version) {
return base::StrCat({base::UTF8ToWide(PRODUCT_FULLNAME_STRING), L" Task ",
IsSystemInstall(scope) ? L"System " : L"User ",
base::UTF8ToWide(version.GetString())});
}
base::CommandLine GetCommandLineLegacyCompatible() {
const std::wstring cmd_string = ::GetCommandLine();
std::optional<base::CommandLine> cmd_line =
CommandLineForLegacyFormat(cmd_string);
return cmd_line ? *cmd_line : base::CommandLine::FromString(cmd_string);
}
#endif
std::optional<base::FilePath> WriteInstallerDataToTempFile(
const base::FilePath& directory,
const std::string& installer_data) {
VLOG(2) << __func__ << ": " << directory << ": " << installer_data;
if (!base::DirectoryExists(directory)) {
return std::nullopt;
}
if (installer_data.empty()) {
return std::nullopt;
}
base::FilePath path;
base::File file = base::CreateAndOpenTemporaryFileInDir(directory, &path);
if (!file.IsValid()) {
return std::nullopt;
}
if (!file.WriteAndCheck(
0, base::as_byte_span(base::StrCat({kUTF8BOM, installer_data})))) {
VLOG(2) << __func__ << " failed to write file";
return std::nullopt;
}
return path;
}
void InitializeThreadPool(const char* name) {
base::ThreadPoolInstance::Create(name);
const size_t max_num_foreground_threads =
static_cast<size_t>(std::max(3, base::SysInfo::NumberOfProcessors() - 1));
base::ThreadPoolInstance::InitParams init_params(max_num_foreground_threads);
#if BUILDFLAG(IS_WIN)
init_params.common_thread_pool_environment = base::ThreadPoolInstance::
InitParams::CommonThreadPoolEnvironment::COM_MTA;
#endif
base::ThreadPoolInstance::Get()->Start(init_params);
}
bool DeleteExcept(std::optional<base::FilePath> except) {
if (!except) {
return false;
}
bool delete_success = true;
base::FileEnumerator(
except->DirName(), false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES)
.ForEach([&](const base::FilePath& item) {
if (item != *except) {
VLOG(2) << "DeleteExcept deleting: " << item;
const bool success = update_client::RetryFileOperation(
&base::DeletePathRecursively, item, 2,
base::Milliseconds(100));
VPLOG_IF(1, !success) << "DeleteExcept failed to delete: " << item;
}
});
return delete_success;
}
int GetDownloadProgress(int64_t downloaded_bytes, int64_t total_bytes) {
if (downloaded_bytes == -1 || total_bytes == -1 || total_bytes == 0) {
return -1;
}
return 100 * std::clamp(static_cast<double>(downloaded_bytes) / total_bytes,
0.0, 1.0);
}
std::vector<base::FilePath> GetFilesWithPredicate(
const base::FilePath& dir,
base::FunctionRef<bool(const base::FilePath&)> predicate) {
if (dir.empty()) {
return {};
}
std::vector<base::FilePath> files;
base::FileEnumerator(dir, true, base::FileEnumerator::FILES)
.ForEach([&](const base::FilePath& item) {
if (predicate(item)) {
files.push_back(item);
}
});
return files;
}
void EnumerateUpdateClientTempDirectories(
UpdaterScope scope,
base::FunctionRef<void(const base::FilePath& dir)> callback) {
base::FilePath temp_dir;
#if BUILDFLAG(IS_WIN)
if (!base::GetSecureTempDirectory(&temp_dir)) {
return;
}
#else
if (!base::GetTempDir(&temp_dir)) {
return;
}
#endif
for (const auto& matcher :
{FILE_PATH_LITERAL("*chrome_url_fetcher_*"),
FILE_PATH_LITERAL("*chrome_Unpacker_BeginUnzipping*"),
FILE_PATH_LITERAL("*chrome_BITS_*")}) {
base::FileEnumerator(temp_dir,
false, base::FileEnumerator::DIRECTORIES,
matcher)
.ForEach([&callback](const base::FilePath& dir) { callback(dir); });
}
}
}