// 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.

#include "chrome/updater/util/win_util.h"

#include <objbase.h>

#include <windows.h>

#include <regstr.h>
#include <sddl.h>
#include <shellapi.h>
#include <shlobj.h>

#include <optional>
#include <string>
#include <vector>

#include "base/command_line.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/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/uuid.h"
#include "base/win/access_token.h"
#include "base/win/registry.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_localalloc.h"
#include "base/win/security_descriptor.h"
#include "base/win/sid.h"
#include "base/win/win_util.h"
#include "chrome/updater/test/integration_tests_impl.h"
#include "chrome/updater/test/test_scope.h"
#include "chrome/updater/test/unit_test_util.h"
#include "chrome/updater/test/unit_test_util_win.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/win/scoped_impersonation.h"
#include "chrome/updater/win/test/test_executables.h"
#include "chrome/updater/win/test/test_strings.h"
#include "chrome/updater/win/win_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace updater::test {

using ::testing::EndsWith;

namespace {

constexpr char kTestAppID[] = "{D07D2B56-F583-4631-9E8E-9942F63765BE}";

// Returns `sid` stringified by the SDDL writer used by
// `SecurityDescriptor::ToSddl`, which substitutes well-known aliases (e.g. `LA`
// for the built-in local Administrator) that `Sid::ToSddlString` does not.
std::optional<std::wstring> SidInSddlForm(const base::win::Sid& sid) {
  base::win::SecurityDescriptor sd;
  sd.set_owner(sid);
  std::optional<std::wstring> sddl = sd.ToSddl(OWNER_SECURITY_INFORMATION);
  if (!sddl || !sddl->starts_with(L"O:")) {
    return std::nullopt;
  }
  return sddl->substr(2);
}

}  // namespace

class WinUtilServiceNameTest : public ::testing::TestWithParam<std::string> {
 protected:
  base::Version version() const { return base::Version(GetParam()); }
};

INSTANTIATE_TEST_SUITE_P(WinUtilServiceNameTestCases,
                         WinUtilServiceNameTest,
                         ::testing::Values(kUpdaterVersion,
                                           "1.2.3.4",
                                           "199.28537.11717"));

TEST_P(WinUtilServiceNameTest, GetServiceName) {
  for (const bool is_internal_service : {true, false}) {
    EXPECT_EQ(base::StrCat({base::UTF8ToWide(PRODUCT_FULLNAME_STRING),
                            is_internal_service ? kWindowsInternalServiceName
                                                : kWindowsServiceName,
                            base::UTF8ToWide(version().GetString())}),
              GetServiceName(is_internal_service, version()));
  }
}

TEST(WinUtil, BuildMsiCommandLine) {
  EXPECT_EQ(L"", BuildMsiCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
                                     base::FilePath(L"NotMsi.exe")));
  EXPECT_EQ(
      L"msiexec arg1 arg2 arg3 REBOOT=ReallySuppress /qn /i \"c:\\my "
      L"path\\YesMsi.msi\" /log \"c:\\my path\\YesMsi.msi.log\"",
      BuildMsiCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
                          base::FilePath(L"c:\\my path\\YesMsi.msi")));
  EXPECT_EQ(
      L"msiexec arg1 arg2 arg3 INSTALLERDATA=\"c:\\my path\\installer data "
      L"file.dat\" REBOOT=ReallySuppress /qn /i \"c:\\my "
      L"path\\YesMsi.msi\" /log \"c:\\my path\\YesMsi.msi.log\"",
      BuildMsiCommandLine(
          std::wstring(L"arg1 arg2 arg3"),
          base::FilePath(L"c:\\my path\\installer data file.dat"),
          base::FilePath(L"c:\\my path\\YesMsi.msi")));
}

TEST(WinUtil, BuildExeCommandLine) {
  EXPECT_EQ(L"", BuildExeCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
                                     base::FilePath(L"NotExe.msi")));
  EXPECT_EQ(L"\"c:\\my path\\YesExe.exe\" arg1 arg2 arg3",
            BuildExeCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
                                base::FilePath(L"c:\\my path\\YesExe.exe")));
  EXPECT_EQ(
      L"\"c:\\my path\\YesExe.exe\" arg1 arg2 arg3 --installerdata=\"c:\\my "
      L"path\\installer data file.dat\"",
      BuildExeCommandLine(
          std::wstring(L"arg1 arg2 arg3"),
          base::FilePath(L"c:\\my path\\installer data file.dat"),
          base::FilePath(L"c:\\my path\\YesExe.exe")));
}

TEST(WinUtil, ShellExecuteAndWait) {
  EXPECT_THAT(ShellExecuteAndWait(base::FilePath(L"NonExistent.Exe"), {}, {}),
              base::test::ErrorIs(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)));

  EXPECT_THAT(
      ShellExecuteAndWait(GetTestProcessCommandLine(GetUpdaterScopeForTesting(),
                                                    test::GetTestName())
                              .GetProgram(),
                          {}, {}),
      base::test::ValueIs(DWORD{0}));
}

TEST(WinUtil, RunElevated) {
  if (!::IsUserAnAdmin()) {
    return;
  }
  const base::CommandLine test_process_cmd_line = GetTestProcessCommandLine(
      GetUpdaterScopeForTesting(), test::GetTestName());
  EXPECT_THAT(RunElevated(test_process_cmd_line.GetProgram(),
                          test_process_cmd_line.GetArgumentsString()),
              base::test::ValueIs(DWORD{0}));
}

TEST(WinUtil, RunDeElevatedCmdLine_Exe) {
  // Create a shared event to be waited for in this process and signaled in the
  // test process to confirm that the test process is running at medium
  // integrity.
  test::EventHolder event_holder(IsElevatedWithUACOn()
                                     ? CreateEveryoneWaitableEventForTest()
                                     : test::CreateWaitableEventForTest());
  ASSERT_NE(event_holder.event.handle(), nullptr);

  base::CommandLine test_process_cmd_line = GetTestProcessCommandLine(
      GetUpdaterScopeForTesting(), test::GetTestName());
  test_process_cmd_line.AppendSwitchNative(
      IsElevatedWithUACOn() ? kTestEventToSignalIfMediumIntegrity
                            : kTestEventToSignal,
      event_holder.name);
  EXPECT_HRESULT_SUCCEEDED(
      RunDeElevatedCmdLine(test_process_cmd_line.GetCommandLineString()));
  EXPECT_TRUE(event_holder.event.TimedWait(TestTimeouts::action_max_timeout()));

  EXPECT_TRUE(test::WaitFor(
      [] { return test::FindProcesses(kTestProcessExecutableName).empty(); }));
}

TEST(WinUtil, GetOSVersion) {
  std::optional<OSVERSIONINFOEX> rtl_os_version = GetOSVersion();
  ASSERT_NE(rtl_os_version, std::nullopt);

  // Compare to the version from `::GetVersionEx`.
  OSVERSIONINFOEX os = {};
  os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  EXPECT_TRUE(::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&os)));
#pragma clang diagnostic pop

  EXPECT_EQ(rtl_os_version->dwOSVersionInfoSize, os.dwOSVersionInfoSize);
  EXPECT_EQ(rtl_os_version->dwMajorVersion, os.dwMajorVersion);
  EXPECT_EQ(rtl_os_version->dwMinorVersion, os.dwMinorVersion);
  EXPECT_EQ(rtl_os_version->dwBuildNumber, os.dwBuildNumber);
  EXPECT_EQ(rtl_os_version->dwPlatformId, os.dwPlatformId);
  EXPECT_STREQ(rtl_os_version->szCSDVersion, os.szCSDVersion);
  EXPECT_EQ(rtl_os_version->wServicePackMajor, os.wServicePackMajor);
  EXPECT_EQ(rtl_os_version->wServicePackMinor, os.wServicePackMinor);
  EXPECT_EQ(rtl_os_version->wSuiteMask, os.wSuiteMask);
  EXPECT_EQ(rtl_os_version->wProductType, os.wProductType);
}

TEST(WinUtil, CompareOSVersions_SameAsCurrent) {
  std::optional<OSVERSIONINFOEX> this_os = GetOSVersion();
  ASSERT_NE(this_os, std::nullopt);

  EXPECT_TRUE(CompareOSVersions(this_os.value(), VER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(this_os.value(), VER_GREATER_EQUAL));
  EXPECT_FALSE(CompareOSVersions(this_os.value(), VER_GREATER));
  EXPECT_FALSE(CompareOSVersions(this_os.value(), VER_LESS));
  EXPECT_TRUE(CompareOSVersions(this_os.value(), VER_LESS_EQUAL));
}

TEST(WinUtil, CompareOSVersions_NewBuildNumber) {
  std::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
  ASSERT_NE(prior_os, std::nullopt);
  ASSERT_GT(prior_os->dwBuildNumber, 0UL);
  --prior_os->dwBuildNumber;

  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}

TEST(WinUtil, CompareOSVersions_NewMajor) {
  std::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
  ASSERT_NE(prior_os, std::nullopt);
  ASSERT_GT(prior_os->dwMajorVersion, 0UL);
  --prior_os->dwMajorVersion;

  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}

TEST(WinUtil, CompareOSVersions_NewMinor) {
  std::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
  ASSERT_NE(prior_os, std::nullopt);

  // This test only runs if the current OS has a minor version.
  if (prior_os->dwMinorVersion >= 1) {
    --prior_os->dwMinorVersion;

    EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
    EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
    EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
    EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
    EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
  }
}

TEST(WinUtil, CompareOSVersions_NewMajorWithLowerMinor) {
  std::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
  ASSERT_NE(prior_os, std::nullopt);
  ASSERT_GT(prior_os->dwMajorVersion, 0UL);
  --prior_os->dwMajorVersion;
  ++prior_os->dwMinorVersion;

  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}

TEST(WinUtil, CompareOSVersions_OldMajor) {
  std::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
  ASSERT_NE(prior_os, std::nullopt);
  ++prior_os->dwMajorVersion;

  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
  EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS));
  EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}

TEST(WinUtil, CompareOSVersions_OldMajorWithHigherMinor) {
  std::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
  ASSERT_NE(prior_os, std::nullopt);

  // This test only runs if the current OS has a minor version.
  if (prior_os->dwMinorVersion >= 1) {
    ++prior_os->dwMajorVersion;
    --prior_os->dwMinorVersion;

    EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
    EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
    EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER));
    EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS));
    EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
  }
}

TEST(WinUtil, IsCOMCallerAdmin) {
  EXPECT_THAT(IsCOMCallerAdmin(), base::test::ValueIs(::IsUserAnAdmin()));
}

TEST(WinUtil, EnableSecureDllLoading) {
  EXPECT_TRUE(EnableSecureDllLoading());
}

TEST(WinUtil, EnableProcessHeapMetadataProtection) {
  EXPECT_TRUE(EnableProcessHeapMetadataProtection());
}

TEST(WinUtil, CreateSecureTempDir) {
  std::optional<base::ScopedTempDir> temp_dir = CreateSecureTempDir();
  ASSERT_TRUE(temp_dir);
  ASSERT_TRUE(temp_dir->IsValid());

  base::FilePath expected_parent;
  if (::IsUserAnAdmin()) {
    ASSERT_TRUE(
        base::PathService::Get(base::DIR_SYSTEM_TEMP, &expected_parent));
  } else {
    ASSERT_TRUE(base::GetTempDir(&expected_parent));
  }
  EXPECT_TRUE(expected_parent.IsParent(temp_dir->GetPath()));
}

TEST(WinUtil, SignalShutdownEvent) {
  {
    const base::ScopedClosureRunner reset_shutdown_event(
        SignalShutdownEvent(GetUpdaterScopeForTesting()));

    // Expect that the legacy GoogleUpdate shutdown event is signaled.
    EXPECT_TRUE(IsShutdownEventSignaled(GetUpdaterScopeForTesting()))
        << "Unexpected shutdown event not signaled";
  }

  // Expect that the legacy GoogleUpdate shutdown event is invalid now.
  EXPECT_FALSE(IsShutdownEventSignaled(GetUpdaterScopeForTesting()))
      << "Unexpected shutdown event signaled";
}

TEST(WinUtil, StopProcessesUnderPath) {
  base::FilePath exe_dir;
  ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &exe_dir));
  exe_dir = exe_dir.AppendUTF8(test::GetTestName());

  base::CommandLine command_line = GetTestProcessCommandLine(
      GetUpdaterScopeForTesting(), test::GetTestName());
  command_line.AppendSwitchUTF8(
      updater::kTestSleepSecondsSwitch,
      base::NumberToString(TestTimeouts::action_timeout().InSeconds() / 4));

  std::vector<base::Process> processes;
  for (const base::FilePath& dir :
       {exe_dir, exe_dir.Append(L"1"), exe_dir.Append(L"2")}) {
    ASSERT_TRUE(base::CreateDirectory(dir));

    for (const std::wstring exe_name : {L"random1.exe", L"random2.exe"}) {
      const base::FilePath exe(dir.Append(exe_name));
      ASSERT_TRUE(base::CopyFile(command_line.GetProgram(), exe));

      base::Process process = base::LaunchProcess(
          base::StrCat(
              {base::CommandLine::QuoteForCommandLineToArgvW(exe.value()), L" ",
               command_line.GetArgumentsString()}),
          {});
      ASSERT_TRUE(process.IsValid());
      processes.push_back(std::move(process));
    }
  }

  StopProcessesUnderPath(exe_dir, TestTimeouts::action_timeout());
  base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());

  for (const base::Process& process : processes) {
    EXPECT_FALSE(process.IsRunning()) << process.Pid();
  }

  EXPECT_TRUE(WaitFor([&] { return base::DeletePathRecursively(exe_dir); }));
}

TEST(WinUtil, IsGuid) {
  EXPECT_FALSE(IsGuid(L"c:\\test\\dir"));
  EXPECT_FALSE(IsGuid(L"a"));
  EXPECT_FALSE(IsGuid(L"CA3045BFA6B14fb8A0EFA615CEFE452C"));

  // Missing {}.
  EXPECT_FALSE(IsGuid(L"CA3045BF-A6B1-4fb8-A0EF-A615CEFE452C"));

  // Invalid char X.
  EXPECT_FALSE(IsGuid(L"{XA3045BF-A6B1-4fb8-A0EF-A615CEFE452C}"));

  // Invalid binary char 0x200.
  EXPECT_FALSE(IsGuid(L"{\0x200a3045bf-a6b1-4fb8-a0ef-a615cefe452c}"));

  // Missing -.
  EXPECT_FALSE(IsGuid(L"{CA3045BFA6B14fb8A0EFA615CEFE452C}"));

  // Double quotes.
  EXPECT_FALSE(IsGuid(L"\"{ca3045bf-a6b1-4fb8-a0ef-a615cefe452c}\""));

  EXPECT_TRUE(IsGuid(L"{CA3045BF-A6B1-4fb8-A0EF-A615CEFE452C}"));
  EXPECT_TRUE(IsGuid(L"{ca3045bf-a6b1-4fb8-a0ef-a615cefe452c}"));
}

TEST(WinUtil, ForEachRegistryRunValueWithPrefix) {
  static constexpr int kRunEntries = 6;
  const std::wstring kRunEntryPrefix(base::UTF8ToWide(test::GetTestName()));

  base::win::RegKey key;
  ASSERT_EQ(key.Open(HKEY_CURRENT_USER, REGSTR_PATH_RUN, KEY_READ | KEY_WRITE),
            ERROR_SUCCESS);

  for (int count = 0; count < kRunEntries; ++count) {
    std::wstring entry_name(kRunEntryPrefix);
    entry_name.push_back(L'0' + count);
    ASSERT_EQ(key.WriteValue(entry_name.c_str(), entry_name.c_str()),
              ERROR_SUCCESS);
  }

  int count_entries = 0;
  ForEachRegistryRunValueWithPrefix(
      kRunEntryPrefix,
      [&key, &count_entries, kRunEntryPrefix](const std::wstring& run_name) {
        EXPECT_TRUE(run_name.starts_with(kRunEntryPrefix));
        ++count_entries;
        EXPECT_EQ(key.DeleteValue(run_name.c_str()), ERROR_SUCCESS);
      });
  EXPECT_EQ(count_entries, kRunEntries);
}

TEST(WinUtil, DeleteRegValue) {
  static constexpr int kRegValues = 6;
  const std::wstring kRegValuePrefix(base::UTF8ToWide(test::GetTestName()));

  base::win::RegKey key;
  ASSERT_EQ(key.Open(HKEY_CURRENT_USER, REGSTR_PATH_RUN, KEY_READ | KEY_WRITE),
            ERROR_SUCCESS);

  for (int count = 0; count < kRegValues; ++count) {
    std::wstring entry_name(kRegValuePrefix);
    entry_name.push_back(L'0' + count);
    ASSERT_EQ(key.WriteValue(entry_name.c_str(), entry_name.c_str()),
              ERROR_SUCCESS);

    EXPECT_TRUE(key.HasValue(entry_name.c_str()));
    EXPECT_TRUE(DeleteRegValue(HKEY_CURRENT_USER, REGSTR_PATH_RUN, entry_name));
    EXPECT_FALSE(key.HasValue(entry_name.c_str()));
    EXPECT_TRUE(DeleteRegValue(HKEY_CURRENT_USER, REGSTR_PATH_RUN, entry_name));
  }
}

TEST(WinUtil, ForEachServiceWithPrefix) {
  if (!::IsUserAnAdmin()) {
    return;
  }

  static constexpr int kNumServices = 6;
  const std::wstring kServiceNamePrefix(base::UTF8ToWide(test::GetTestName()));

  for (int count = 0; count < kNumServices; ++count) {
    std::wstring service_name(kServiceNamePrefix);
    service_name.push_back(L'0' + count);
    EXPECT_TRUE(
        CreateService(service_name, service_name, L"C:\\temp\\temp.exe"));
  }

  int count_entries = 0;
  ForEachServiceWithPrefix(
      kServiceNamePrefix, kServiceNamePrefix,
      [&count_entries, kServiceNamePrefix](const std::wstring& service_name) {
        EXPECT_TRUE(service_name.starts_with(kServiceNamePrefix));
        ++count_entries;
        EXPECT_TRUE(DeleteService(service_name));
      });
  EXPECT_EQ(count_entries, kNumServices);
}

TEST(WinUtil, DeleteService) {
  if (!::IsUserAnAdmin()) {
    return;
  }

  static constexpr int kNumServices = 6;
  const std::wstring kServiceNamePrefix(base::UTF8ToWide(test::GetTestName()));

  for (int count = 0; count < kNumServices; ++count) {
    std::wstring service_name(kServiceNamePrefix);
    service_name.push_back(L'0' + count);
    ASSERT_TRUE(
        CreateService(service_name, service_name, L"C:\\temp\\temp.exe"));
    EXPECT_TRUE(DeleteService(service_name));
  }
}

TEST(WinUtil, LogClsidEntries) {
  CLSID clsid = {};
  EXPECT_HRESULT_SUCCEEDED(
      ::CLSIDFromProgID(L"InternetExplorer.Application", &clsid));
  LogClsidEntries(clsid);
}

TEST(WinUtil, GetAppAPValue) {
  std::string ap(GetAppAPValue(GetUpdaterScopeForTesting(), kTestAppID));
  EXPECT_EQ(ap, "");

  base::win::RegKey client_state_key(CreateAppClientStateKey(
      GetUpdaterScopeForTesting(), base::UTF8ToWide(kTestAppID)));
  EXPECT_EQ(client_state_key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS);

  ap = GetAppAPValue(GetUpdaterScopeForTesting(), kTestAppID);
  EXPECT_EQ(ap, "TestAP");

  DeleteAppClientStateKey(GetUpdaterScopeForTesting(),
                          base::UTF8ToWide(kTestAppID));
}

struct WinUtilGetRegKeyContentsTestCase {
  const std::wstring reg_key;
  const std::wstring expected_substring;
};

class WinUtilGetRegKeyContentsTest
    : public ::testing::TestWithParam<WinUtilGetRegKeyContentsTestCase> {};

INSTANTIATE_TEST_SUITE_P(
    WinUtilGetRegKeyContentsTestCases,
    WinUtilGetRegKeyContentsTest,
    ::testing::ValuesIn(std::vector<WinUtilGetRegKeyContentsTestCase>{
        {L"HKLM\\SOFTWARE\\Classes\\CLSID\\{00020424-0000-0000-C000-"
         L"000000000046}",
         L"{00020424-0000-0000-C000-000000000046}"},
        {L"HKLM\\SOFTWARE\\WOW6432Node\\Classes\\CLSID\\{00020424-0000-0000-"
         L"C000-000000000046}",
         L"{00020424-0000-0000-C000-000000000046}"},
        {L"HKCR\\CLSID\\{00020424-0000-0000-C000-000000000046}",
         L"{00020424-0000-0000-C000-000000000046}"},
        {L"HKCR\\WOW6432Node\\CLSID\\{00020424-0000-0000-C000-000000000046}",
         L"{00020424-0000-0000-C000-000000000046}"},
    }));

TEST_P(WinUtilGetRegKeyContentsTest, TestCases) {
  std::optional<std::wstring> contents = GetRegKeyContents(GetParam().reg_key);
  ASSERT_TRUE(contents);
  ASSERT_TRUE(contents->contains(GetParam().expected_substring));
}

TEST(WinUtil, GetTextForSystemError) {
  EXPECT_EQ(GetTextForSystemError(2),
            L"The system cannot find the file specified. ");
  EXPECT_EQ(GetTextForSystemError(0x80070002),
            L"The system cannot find the file specified. ");
  EXPECT_EQ(GetTextForSystemError(12007),
            L"The server name or address could not be resolved ");
  EXPECT_EQ(GetTextForSystemError(0x80072ee7),
            L"The server name or address could not be resolved ");
  EXPECT_EQ(GetTextForSystemError(-2147012889),
            L"The server name or address could not be resolved ");
  EXPECT_EQ(
      GetTextForSystemError(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200)),
      L"0x80040200");
}

TEST(WinUtil, GetLoggedOnUserToken) {
  if (!::IsUserAnAdmin() || !IsUACOn()) {
    return;
  }

  ASSERT_TRUE(::IsUserAnAdmin());
  std::optional<base::win::AccessToken> token = GetLoggedOnUserToken();
  ASSERT_TRUE(token.has_value());

  ScopedImpersonation impersonate;
  ASSERT_TRUE(SUCCEEDED(impersonate.Impersonate(token->get())));
  ASSERT_FALSE(::IsUserAnAdmin());
}

TEST(WinUtil, IsAuditMode) {
  if (!::IsUserAnAdmin()) {
    GTEST_SKIP();
  }
  ASSERT_FALSE(IsAuditMode());
  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
                .WriteValue(L"ImageState", L"IMAGE_STATE_UNDEPLOYABLE"),
            ERROR_SUCCESS);
  ASSERT_TRUE(IsAuditMode());
  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
                .DeleteValue(L"ImageState"),
            ERROR_SUCCESS);
}

TEST(WinUtil, OemInstallState) {
  if (!::IsUserAnAdmin()) {
    GTEST_SKIP();
  }
  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
                .WriteValue(L"ImageState", L"IMAGE_STATE_UNDEPLOYABLE"),
            ERROR_SUCCESS);
  ASSERT_TRUE(SetOemInstallState());
  ASSERT_TRUE(IsOemInstalling());

  DWORD oem_install_time_minutes = 0;
  ASSERT_EQ(
      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
                        Wow6432(KEY_QUERY_VALUE))
          .ReadValueDW(kRegValueOemInstallTimeMin, &oem_install_time_minutes),
      ERROR_SUCCESS);

  // Rewind to 71 hours and 58 minutes before now.
  ASSERT_EQ(
      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY, Wow6432(KEY_SET_VALUE))
          .WriteValue(
              kRegValueOemInstallTimeMin,
              (base::Minutes(oem_install_time_minutes + 2) - kMinOemModeTime)
                  .InMinutes()),
      ERROR_SUCCESS);
  ASSERT_TRUE(IsOemInstalling());

  // Rewind to 72 hours and 2 minutes before now.
  ASSERT_EQ(
      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY, Wow6432(KEY_SET_VALUE))
          .WriteValue(
              kRegValueOemInstallTimeMin,
              (base::Minutes(oem_install_time_minutes - 2) - kMinOemModeTime)
                  .InMinutes()),
      ERROR_SUCCESS);
  ASSERT_FALSE(IsOemInstalling());

  ASSERT_TRUE(ResetOemInstallState());
  ASSERT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, kSetupStateKey, KEY_SET_VALUE)
                .DeleteValue(L"ImageState"),
            ERROR_SUCCESS);
}

TEST(WinUtil, StringFromGuid) {
  GUID guid = {0};
  EXPECT_HRESULT_SUCCEEDED(::CoCreateGuid(&guid));
  EXPECT_EQ(base::win::WStringFromGUID(guid), StringFromGuid(guid));
}

TEST(WinUtil, SetEulaAccepted) {
  // This will set `eulaaccepted=0` in the registry.
  EXPECT_TRUE(
      SetEulaAccepted(GetUpdaterScopeForTesting(), /*eula_accepted=*/false));
  DWORD eula_accepted = 0;
  const HKEY root = UpdaterScopeToHKeyRoot(GetUpdaterScopeForTesting());
  EXPECT_EQ(base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ))
                .ReadValueDW(L"eulaaccepted", &eula_accepted),
            ERROR_SUCCESS);
  EXPECT_EQ(eula_accepted, 0ul);

  // This will delete the `eulaaccepted` value in the registry.
  EXPECT_TRUE(
      SetEulaAccepted(GetUpdaterScopeForTesting(), /*eula_accepted=*/true));
  EXPECT_FALSE(base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ))
                   .HasValue(L"eulaaccepted"));
}

TEST(WinUtil, IsServicePresent_IsServiceEnabled) {
  if (!::IsUserAnAdmin()) {
    GTEST_SKIP();
  }

  GUID random_guid = {0};
  EXPECT_HRESULT_SUCCEEDED(::CoCreateGuid(&random_guid));
  const std::wstring service_name = base::StrCat(
      {base::UTF8ToWide(test::GetTestName()), StringFromGuid(random_guid)});

  EXPECT_FALSE(IsServicePresent(service_name));
  EXPECT_FALSE(IsServiceEnabled(service_name));

  ASSERT_TRUE(CreateService(service_name, service_name, L"C:\\temp\\temp.exe"));
  EXPECT_TRUE(IsServicePresent(service_name));
  EXPECT_TRUE(IsServiceEnabled(service_name));

  EXPECT_TRUE(DisableService(service_name));
  EXPECT_TRUE(IsServicePresent(service_name));
  EXPECT_FALSE(IsServiceEnabled(service_name));

  EXPECT_TRUE(DeleteService(service_name));
  EXPECT_FALSE(IsServicePresent(service_name));
  EXPECT_FALSE(IsServiceEnabled(service_name));
}

TEST(WinUtil, IsServicePresent_IsServiceEnabled_NonAdmin) {
  EXPECT_TRUE(IsServicePresent(L"Schedule"));
  EXPECT_TRUE(IsServiceEnabled(L"Schedule"));
  EXPECT_FALSE(IsServicePresent(L"ScheduleFooBar"));
  EXPECT_FALSE(IsServiceEnabled(L"ScheduleFooBar"));
}

TEST(WinUtil, GetCommandLineForPid) {
  const HResultOr<std::wstring> cmd_line_for_pid =
      GetCommandLineForPid(::GetCurrentProcessId());
  ASSERT_TRUE(cmd_line_for_pid.has_value());
  EXPECT_STREQ(cmd_line_for_pid->c_str(), ::GetCommandLine());
}

TEST(WinUtil, AddCurrentUserAllowedAce) {
  std::optional<base::win::AccessToken> token =
      base::win::AccessToken::FromCurrentProcess();
  ASSERT_TRUE(token.has_value());
  std::optional<std::wstring> sid_str = SidInSddlForm(token->User());
  ASSERT_TRUE(sid_str.has_value());

  // Empty SDDL should produce a DACL with the user's ACE.
  std::optional<std::wstring> new_sddl =
      AddCurrentUserAllowedAce(L"", GENERIC_ALL, 0);
  ASSERT_TRUE(new_sddl);
  EXPECT_TRUE(new_sddl->contains(L"D:"));
  EXPECT_TRUE(new_sddl->contains(*sid_str));

  // Adding to DACL with existing BA ACE should preserve both.
  new_sddl = AddCurrentUserAllowedAce(L"D:(A;;GA;;;BA)", GENERIC_ALL, 0);
  ASSERT_TRUE(new_sddl);
  EXPECT_TRUE(new_sddl->contains(*sid_str));
  EXPECT_TRUE(new_sddl->contains(L"BA)"));

  // Owner and group should be preserved.
  new_sddl =
      AddCurrentUserAllowedAce(L"O:AOG:BAD:(A;;GA;;;S-1-0-0)", GENERIC_ALL, 0);
  ASSERT_TRUE(new_sddl);
  EXPECT_TRUE(new_sddl->contains(L"O:AO"));
  EXPECT_TRUE(new_sddl->contains(L"G:BA"));
  EXPECT_TRUE(new_sddl->contains(*sid_str));

  // Complex SDDL with object ACEs and SACL.
  new_sddl = AddCurrentUserAllowedAce(
      L"O:BAG:BAD:(A;;RPWPCCDCLCRCWOWDSDSW;;;SY)(A;;RPWPCCDCLCRCWOWDSDSW;;;BA)("
      L"OA;;CCDC;aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb;;AO)(OA;;CCDC;bbbbbbbb-"
      L"1111-2222-3333-cccccccccccc;;AO)(OA;;CCDC;cccccccc-2222-3333-4444-"
      L"dddddddddddd;;AO)(OA;;CCDC;dddddddd-3333-4444-5555-eeeeeeeeeeee;;PO)(A;"
      L";RPLCRC;;;AU)S:(AU;SAFA;WDWOSDWPCCDCSW;;;WD)",
      GENERIC_ALL, 0);
  ASSERT_TRUE(new_sddl);

  // Verify key properties: owner, group, user SID, SACL all preserved.
  EXPECT_TRUE(new_sddl->contains(L"O:BA"));
  EXPECT_TRUE(new_sddl->contains(L"G:BA"));
  EXPECT_TRUE(new_sddl->contains(*sid_str));
  EXPECT_TRUE(new_sddl->contains(L"S:"));
  // The result must be valid SDDL and parse back.
  PSECURITY_DESCRIPTOR raw_sd = nullptr;
  EXPECT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor(
      new_sddl->c_str(), SDDL_REVISION_1, &raw_sd, nullptr));
  base::win::ScopedLocalAlloc sd_holder(raw_sd);

  // The SDDL must be stable/idempotent: repeated calls produce the same result.
  const std::wstring stable_sddl = *new_sddl;
  for (int i = 0; i < 100; ++i) {
    new_sddl = AddCurrentUserAllowedAce(*new_sddl, GENERIC_ALL, 0);
    ASSERT_TRUE(new_sddl);
    EXPECT_EQ(*new_sddl, stable_sddl);
  }
}

TEST(WinUtil, GetCurrentUserDefaultSecurityDescriptor) {
  std::optional<std::wstring> sddl = GetCurrentUserDefaultSecurityDescriptor();
  ASSERT_TRUE(sddl.has_value());
  EXPECT_FALSE(sddl->empty());

  // The SDDL must contain owner (O:), group (G:), and DACL (D:) sections.
  EXPECT_TRUE(sddl->contains(L"O:"));
  EXPECT_TRUE(sddl->contains(L"G:"));
  EXPECT_TRUE(sddl->contains(L"D:"));

  // The SDDL must contain the current user's SID.
  auto token = base::win::AccessToken::FromCurrentProcess();
  ASSERT_TRUE(token);
  auto user_sddl = SidInSddlForm(token->User());
  ASSERT_TRUE(user_sddl);
  EXPECT_TRUE(sddl->contains(*user_sddl));

  // The result must be a valid security descriptor.
  PSECURITY_DESCRIPTOR raw_sd = nullptr;
  EXPECT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor(
      sddl->c_str(), SDDL_REVISION_1, &raw_sd, nullptr));
  base::win::ScopedLocalAlloc sd_holder(raw_sd);
}

TEST(WinUtil, GetAdminDaclSecurityDescriptor) {
  std::wstring sddl = GetAdminDaclSecurityDescriptor(GENERIC_ALL);
  EXPECT_FALSE(sddl.empty());

  // Must have owner (BA), group (BA), and DACL sections.
  EXPECT_TRUE(sddl.contains(L"O:BA"));
  EXPECT_TRUE(sddl.contains(L"G:BA"));
  EXPECT_TRUE(sddl.contains(L"D:"));

  // Must grant access to System (SY) and Admins (BA).
  EXPECT_TRUE(sddl.contains(L";;;SY)"));
  EXPECT_TRUE(sddl.contains(L";;;BA)"));

  // The result must be a valid security descriptor.
  PSECURITY_DESCRIPTOR raw_sd = nullptr;
  EXPECT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor(
      sddl.c_str(), SDDL_REVISION_1, &raw_sd, nullptr));
  base::win::ScopedLocalAlloc sd_holder(raw_sd);

  // Different access masks should produce different SDDLs.
  std::wstring sddl2 = GetAdminDaclSecurityDescriptor(GENERIC_READ);
  EXPECT_NE(sddl, sddl2);
}

TEST(WinUtil, NamedObjectAttributes_WithValidSddl) {
  const std::wstring kName = L"TestObject";
  NamedObjectAttributes attrs(kName, L"D:(A;;GA;;;BA)");

  EXPECT_EQ(attrs.name, kName);
  EXPECT_NE(attrs.sa.lpSecurityDescriptor, nullptr);
  EXPECT_EQ(attrs.sa.nLength, sizeof(SECURITY_ATTRIBUTES));
  EXPECT_FALSE(attrs.sa.bInheritHandle);
}

TEST(WinUtil, NamedObjectAttributes_WithEmptySddl) {
  const std::wstring kName = L"TestObject";
  NamedObjectAttributes attrs(kName, std::wstring());

  EXPECT_EQ(attrs.name, kName);
  EXPECT_EQ(attrs.sa.lpSecurityDescriptor, nullptr);
}

TEST(WinUtil, AddCurrentUserAllowedAce_InvalidSddl) {
  // Invalid SDDL should return nullopt.
  EXPECT_FALSE(
      AddCurrentUserAllowedAce(L"INVALID_SDDL", GENERIC_ALL, 0).has_value());
}

TEST(WinUtil, AddCurrentUserAllowedAce_DenyBeforeAllow) {
  // An SDDL with both deny and allow ACEs should maintain canonical ordering
  // (deny before allow) after adding the current user's ACE.
  std::optional<base::win::AccessToken> token =
      base::win::AccessToken::FromCurrentProcess();
  ASSERT_TRUE(token.has_value());
  std::optional<std::wstring> sid_str = SidInSddlForm(token->User());
  ASSERT_TRUE(sid_str.has_value());

  std::optional<std::wstring> new_sddl = AddCurrentUserAllowedAce(
      L"D:(D;;GA;;;S-1-0-0)(A;;GA;;;BA)", GENERIC_ALL, 0);
  ASSERT_TRUE(new_sddl);

  // The deny ACE (D;) should appear before any allow ACE (A;) in the result.
  size_t deny_pos = new_sddl->find(L"(D;");
  size_t allow_pos = new_sddl->find(L"(A;");
  ASSERT_TRUE(new_sddl->contains(L"(D;"));
  ASSERT_TRUE(new_sddl->contains(L"(A;"));
  EXPECT_LT(deny_pos, allow_pos);

  // The current user's ACE should be present.
  EXPECT_TRUE(new_sddl->contains(*sid_str));

  // The result must be a valid security descriptor.
  PSECURITY_DESCRIPTOR raw_sd = nullptr;
  EXPECT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor(
      new_sddl->c_str(), SDDL_REVISION_1, &raw_sd, nullptr));
  base::win::ScopedLocalAlloc sd_holder(raw_sd);
}

TEST(WinUtil, RegistryKeyHelpersSanitizeInvalidAppIds) {
  EXPECT_THAT(GetAppClientsKey(L"a\\b"), EndsWith(L"a_b"));
  EXPECT_THAT(GetAppClientStateKey(L"a\\b"), EndsWith(L"a_b"));
  EXPECT_THAT(GetAppClientStateMediumKey(L"a\\b"), EndsWith(L"a_b"));
}

}  // namespace updater::test
