// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_UPDATER_UTIL_UTIL_H_
#define CHROME_UPDATER_UTIL_UTIL_H_

#include <cmath>
#include <concepts>
#include <limits>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/functional/function_ref.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/tag.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/path_util.h"  // IWYU pragma: export

class GURL;

namespace base {

class CommandLine;
class FilePath;

// Enables insertion of optional `base` types. Must be in the `base` namespace
// for insertion into gTest expectations to work.
template <class T>
inline std::ostream& operator<<(std::ostream& os, std::optional<T> opt) {
  if (!opt.has_value()) {
    return os << "std::nullopt";
  }
  return os << opt.value();
}

}  // namespace base

namespace updater {

// Converts an unsigned integral to a signed one. Returns -1 if the value is
// out of the range of the target type.
template <std::unsigned_integral T>
[[nodiscard]] auto ToSignedIntegral(T value) {
  using Result = std::make_signed_t<T>;
  return value <= std::numeric_limits<Result>::max()
             ? static_cast<Result>(value)
             : -1;
}

// Inserts an enum value as the underlying type.
template <typename T>
  requires(std::is_enum_v<T>)
inline std::ostream& operator<<(std::ostream& os, const T& e) {
  return os << std::to_underlying(e);
}

// Returns the versioned install directory under which the program stores its
// executables. For example, on macOS this function may return
// ~/Library/Google/GoogleUpdater/88.0.4293.0 (/Library for system). Does not
// create the directory if it does not exist.
std::optional<base::FilePath> GetVersionedInstallDirectory(
    UpdaterScope scope,
    const base::Version& version);

// Simpler form of GetVersionedInstallDirectory for the currently running
// version of the updater.
std::optional<base::FilePath> GetVersionedInstallDirectory(UpdaterScope scope);

// Returns the path where cached CRX files should be stored, common to all
// versions of the updater. Does not create the directory if it does not exist.
std::optional<base::FilePath> GetCrxCacheDirectory(UpdaterScope scope);

// Returns the temporary directory used by the updater.
std::optional<base::FilePath> GetUpdaterTempDir();

#if BUILDFLAG(IS_MAC)
// For example: ~/Library/Google/GoogleUpdater/88.0.4293.0/GoogleUpdater.app
std::optional<base::FilePath> GetUpdaterAppBundlePath(UpdaterScope scope);
#endif  // BUILDFLAG(IS_MAC)

// For user installations:
// ~/Library/Google/GoogleUpdater/88.0.4293.0/GoogleUpdater.app/Contents/
//    MacOS/GoogleUpdater
// For system installations:
// /Library/Google/GoogleUpdater/88.0.4293.0/GoogleUpdater.app/Contents/
//    MacOS/GoogleUpdater
std::optional<base::FilePath> GetUpdaterExecutablePath(
    UpdaterScope scope,
    const base::Version& version);

// Simpler form of GetUpdaterExecutablePath for the currently running version
// of the updater.
std::optional<base::FilePath> GetUpdaterExecutablePath(UpdaterScope scope);

// Returns a relative path to the executable from GetVersionedInstallDirectory.
// "GoogleUpdater.app/Contents/MacOS/GoogleUpdater" on macOS.
// "updater.exe" on Win.
base::FilePath GetExecutableRelativePath();

// Returns the path to the crashpad database directory. The directory is not
// created if it does not exist.
std::optional<base::FilePath> GetCrashDatabasePath(UpdaterScope scope);

// Returns the path to the crashpad database, creating it if it does not exist.
std::optional<base::FilePath> EnsureCrashDatabasePath(UpdaterScope scope);

// Contains the parsed values from the tag. The tag is provided as a command
// line argument to the `--install` or the `--handoff` switch.
struct TagParsingResult {
  TagParsingResult();
  TagParsingResult(std::optional<tagging::TagArgs> tag_args,
                   tagging::ErrorCode error);
  ~TagParsingResult();
  TagParsingResult(const TagParsingResult&);
  TagParsingResult& operator=(const TagParsingResult&);
  std::optional<tagging::TagArgs> tag_args;
  tagging::ErrorCode error = tagging::ErrorCode::kSuccess;
};

// These functions return {} if there was no tag at all. An error is set if the
// tag fails to parse.
TagParsingResult GetTagArgsForCommandLine(
    const base::CommandLine& command_line);
TagParsingResult GetTagArgs();

std::optional<tagging::AppArgs> GetAppArgs(const std::string& app_id);

std::string GetTagLanguage();

std::string GetDecodedInstallDataFromAppArgs(const std::string& app_id);

std::string GetInstallDataIndexFromAppArgs(const std::string& app_id);

// Initializes logging for an executable.
void InitLogging(UpdaterScope updater_scope);

// Returns HTTP user-agent value.
std::string GetUpdaterUserAgent(
    const base::Version& updater_version = base::Version(kUpdaterVersion));

// Returns a new GURL by appending the given query parameter name and the
// value. Unsafe characters in the name and the value are escaped like
// %XX%XX. The original query component is preserved if it's present.
//
// Examples:
//
// AppendQueryParameter(GURL("http://example.com"), "name", "value").spec()
// => "http://example.com?name=value"
// AppendQueryParameter(GURL("http://example.com?x=y"), "name", "value").spec()
// => "http://example.com?x=y&name=value"
GURL AppendQueryParameter(const GURL& url,
                          const std::string& name,
                          const std::string& value);

#if BUILDFLAG(IS_MAC)
// Recursively update the permissions of a path to 0755 or 0644, depending on
// whether the file is already executable (by any user) or is a directory.
// Returns false if and only if there is a failure lstating or setting a
// permission, except for failures to set permissions on symbolic links.
bool SetFilePermissionsRecursive(const base::FilePath& root_path);
#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)

// Returns the versioned task name prefix in the following format:
// "{ProductName}Task{System/User}{UpdaterVersion}".
// For instance: "ChromiumUpdaterTaskSystem92.0.0.1".
std::wstring GetTaskNamePrefix(
    UpdaterScope scope,
    const base::Version& version = base::Version(kUpdaterVersion));

// Returns the versioned task display name in the following format:
// "{ProductName} Task {System/User} {UpdaterVersion}".
// For instance: "ChromiumUpdater Task System 92.0.0.1".
std::wstring GetTaskDisplayName(
    UpdaterScope scope,
    const base::Version& version = base::Version(kUpdaterVersion));

// Parses the command line string in legacy format into `base::CommandLine`.
// The string must be in format like:
//   program.exe /switch1 value1 /switch2 /switch3 value3
// Returns empty if a Chromium style switch is found.
std::optional<base::CommandLine> CommandLineForLegacyFormat(
    const std::wstring& cmd_string);

// Returns the command line for current process, either in legacy style, or
// in Chromium style.
base::CommandLine GetCommandLineLegacyCompatible();

#endif  // BUILDFLAG(IS_WIN)

// Writes the provided string prefixed with the UTF8 byte order mark to a
// temporary file. The temporary file is created in the specified `directory`.
std::optional<base::FilePath> WriteInstallerDataToTempFile(
    const base::FilePath& directory,
    const std::string& installer_data);

// Creates and starts a thread pool for this process.
void InitializeThreadPool(const char* name);

// Returns whether the user currently running the program is the right user for
// the scope. This can be useful to avoid installing system updaters that are
// owned by non-root accounts, or avoiding the installation of a user level
// updater as root.
bool WrongUser(UpdaterScope scope);

// Returns whether a user has previously accepted a EULA / ToS for at least one
// of the listed apps.
bool EulaAccepted(const std::vector<std::string>& app_ids);

// Imports metadata from legacy updaters.
bool MigrateLegacyUpdaters(
    UpdaterScope scope,
    base::RepeatingCallback<void(const RegistrationRequest&)>
        register_callback);

// Delete everything other than `except` under `except.DirName()`.
[[nodiscard]] bool DeleteExcept(std::optional<base::FilePath> except);

// Returns the quotient of dividing two integer numbers (m/n) rounded up.
template <typename T>
  requires(std::integral<T>)
[[nodiscard]] constexpr T CeilingDivide(T m, T n) {
  return std::ceil(static_cast<double>(m) / n);
}

// Returns a value in the [0, 100] range or -1 if the progress could not
// be computed.
[[nodiscard]] int GetDownloadProgress(int64_t downloaded_bytes,
                                      int64_t total_bytes);

// Returns the absolute path to the enterprise companion app executable bundled
// with the updater.
[[nodiscard]] std::optional<base::FilePath>
GetBundledEnterpriseCompanionExecutablePath(UpdaterScope scope);

// Finds files that match `predicate` under `dir`.
std::vector<base::FilePath> GetFilesWithPredicate(
    const base::FilePath& dir,
    base::FunctionRef<bool(const base::FilePath&)> predicate);

// Enumerates and calls `callback` for each update client temp directory found
// for `scope`.
void EnumerateUpdateClientTempDirectories(
    UpdaterScope scope,
    base::FunctionRef<void(const base::FilePath& dir)> callback);

// Returns true if the App ID is valid (not empty, not too long, does not
// contain path separators '/' or '\', and does not contain path traversal
// components).
bool IsValidAppId(std::string_view app_id);
bool IsValidAppId(std::wstring_view app_id);

}  // namespace updater

#endif  // CHROME_UPDATER_UTIL_UTIL_H_
