chromium/chrome/updater/win/installer/installer_win_unittest.cc

// 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/installer/installer.h"

#include <shlobj.h>

#include <optional>
#include <string>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/strcat.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/win/installer/exit_code.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
constexpr char install_switch[] = "--install=";
constexpr char enable_logging_switch[] = "--enable-logging";
constexpr char logging_module_switch[] = "--vmodule=";

void ExpectExactlyOneOccurrence(const std::wstring& search_string,
                                const std::wstring& substring) {
  auto pos = search_string.find(substring);
  EXPECT_NE(pos, std::wstring::npos);
  pos = search_string.find(substring, pos + substring.size());
  EXPECT_EQ(pos, std::wstring::npos);
}

void ExpectSwitchValue(const std::wstring& cmd_line,
                       const std::wstring& switch_str,
                       const std::wstring& expected_value) {
  auto pos = cmd_line.find(switch_str);
  EXPECT_NE(pos, std::wstring::npos);
  pos += switch_str.size();
  EXPECT_LE(pos + expected_value.size(), cmd_line.size());
  EXPECT_EQ(cmd_line.substr(pos, expected_value.size()), expected_value);
}
}  // namespace

// Tests that `HandleRunElevated` returns `UNEXPECTED_ELEVATION_LOOP` when
// not elevated and called with `kCmdLineExpectElevated` argument.
TEST(InstallerTest, HandleRunElevated) {
  if (::IsUserAnAdmin()) {
    return;
  }

  base::CommandLine command_line(
      base::FilePath(FILE_PATH_LITERAL("UpdaterSetup.exe")));
  command_line.AppendSwitch(updater::kInstallSwitch);
  command_line.AppendSwitch(updater::kSystemSwitch);
  command_line.AppendSwitch(updater::kCmdLineExpectElevated);

  updater::ProcessExitResult exit_result =
      updater::HandleRunElevated(command_line);
  EXPECT_EQ(exit_result.exit_code, updater::UNEXPECTED_ELEVATION_LOOP);
  EXPECT_EQ(exit_result.windows_error, 0U);
}

TEST(InstallerTest, FindOfflineDir) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  base::FilePath unpack_path = temp_dir.GetPath();
  base::FilePath metainstall_dir = unpack_path.Append(L"bin");
  ASSERT_TRUE(base::CreateDirectory(metainstall_dir));

  EXPECT_FALSE(updater::FindOfflineDir(unpack_path).has_value());

  base::FilePath offline_install_dir =
      metainstall_dir.Append(L"Offline")
          .Append(L"{8D5D0563-F2A0-40E3-932D-AFEAE261A9D1}");
  ASSERT_TRUE(base::CreateDirectory(offline_install_dir));

  std::optional<base::FilePath> offline_dir =
      updater::FindOfflineDir(unpack_path);
  EXPECT_TRUE(offline_dir.has_value());
  EXPECT_EQ(offline_dir->BaseName(),
            base::FilePath(L"{8D5D0563-F2A0-40E3-932D-AFEAE261A9D1}"));
}

TEST(BuildInstallerCommandLineArgumentsTest, EnableLoggingSwitch) {
  // Test that --enable-logging switch is added if none is provided.
  updater::CommandString cmd_line_args;
  std::wstring command_line_str(L"UpdaterSetup.exe");
  // Add a tag switch to bypass attempting to parse a tag.
  command_line_str = base::SysUTF8ToWide(
      base::StrCat({"UpdaterSetup.exe ", install_switch, "fake_tag"}));
  updater::ProcessExitResult exit_result =
      updater::BuildInstallerCommandLineArguments(command_line_str.c_str(),
                                                  cmd_line_args.get(),
                                                  cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::SUCCESS_EXIT_CODE);
  ExpectExactlyOneOccurrence(std::wstring(cmd_line_args.get()),
                             base::SysUTF8ToWide(enable_logging_switch));

  // Test that no --enable-logging switch is added if one is provided.
  cmd_line_args.clear();
  // Add a tag switch to bypass attempting to parse a tag.
  command_line_str =
      base::SysUTF8ToWide(base::StrCat({"UpdaterSetup.exe ", install_switch,
                                        "fake_tag ", enable_logging_switch}));
  exit_result = updater::BuildInstallerCommandLineArguments(
      command_line_str.c_str(), cmd_line_args.get(), cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::SUCCESS_EXIT_CODE);
  ExpectExactlyOneOccurrence(std::wstring(cmd_line_args.get()),
                             base::SysUTF8ToWide(enable_logging_switch));
}

TEST(BuildInstallerCommandLineArgumentsTest, LoggingModuleSwitch) {
  // Test that --vmodule switch is added if none is provided.
  updater::CommandString cmd_line_args;
  std::wstring command_line_str(L"UpdaterSetup.exe");
  // Add a tag switch to bypass attempting to parse a tag.
  command_line_str = base::SysUTF8ToWide(
      base::StrCat({"UpdaterSetup.exe ", install_switch, "fake_tag"}));
  updater::ProcessExitResult exit_result =
      updater::BuildInstallerCommandLineArguments(command_line_str.c_str(),
                                                  cmd_line_args.get(),
                                                  cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::SUCCESS_EXIT_CODE);
  ExpectExactlyOneOccurrence(std::wstring(cmd_line_args.get()),
                             base::SysUTF8ToWide(logging_module_switch));

  // Test that no --vmodule switch is added if one is provided.
  cmd_line_args.clear();
  // Add a tag switch to bypass attempting to parse a tag.
  command_line_str = base::SysUTF8ToWide(
      base::StrCat({"UpdaterSetup.exe ", install_switch, "fake_tag ",
                    logging_module_switch, "fake_module"}));
  exit_result = updater::BuildInstallerCommandLineArguments(
      command_line_str.c_str(), cmd_line_args.get(), cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::SUCCESS_EXIT_CODE);
  ExpectExactlyOneOccurrence(std::wstring(cmd_line_args.get()),
                             base::SysUTF8ToWide(logging_module_switch));
  ExpectSwitchValue(command_line_str,
                    base::SysUTF8ToWide(logging_module_switch),
                    std::wstring(L"fake_module"));
}

TEST(BuildInstallerCommandLineArgumentsTest, CommandStringOverflow) {
  updater::CommandString cmd_line_args;
  std::wstring command_line_str(L"UpdaterSetup.exe");
  std::string long_tag(updater::kInstallerMaxCommandString + 1, 'A');
  command_line_str = base::SysUTF8ToWide(
      base::StrCat({"UpdaterSetup.exe ", install_switch, long_tag}));
  updater::ProcessExitResult exit_result =
      updater::BuildInstallerCommandLineArguments(command_line_str.c_str(),
                                                  cmd_line_args.get(),
                                                  cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::COMMAND_STRING_OVERFLOW);
}

TEST(BuildInstallerCommandLineArgumentsTest, NoArguments) {
  // Passing in no arguments on the command line will attempt to
  // extract the embedded tag, but since the test executable is not
  // tagged this should not add any --tag switches.
  updater::CommandString cmd_line_args;
  std::wstring command_line_str(L"UpdaterSetup.exe");
  updater::ProcessExitResult exit_result =
      updater::BuildInstallerCommandLineArguments(command_line_str.c_str(),
                                                  cmd_line_args.get(),
                                                  cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::INVALID_OPTION);
}

TEST(BuildInstallerCommandLineArgumentsTest, LegacyCommandLine) {
  std::optional<base::CommandLine> cmd_line =
      updater::CommandLineForLegacyFormat(
          L"UpdaterSetup.exe /install "
          L"\"appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&appname=Google%"
          L"20Chrome&needsadmin=Prefers&lang=en\"");
  ASSERT_TRUE(cmd_line.has_value());
  updater::CommandString cmd_line_args;
  updater::ProcessExitResult exit_result =
      updater::BuildInstallerCommandLineArguments(
          cmd_line->GetCommandLineString().c_str(), cmd_line_args.get(),
          cmd_line_args.capacity());
  EXPECT_EQ(exit_result.exit_code, updater::SUCCESS_EXIT_CODE);
  const base::CommandLine command_line = base::CommandLine::FromString(
      base::StrCat({L"exe.exe ", cmd_line_args.get()}));
  EXPECT_EQ(command_line.GetSwitchValueASCII(updater::kInstallSwitch),
            "appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&appname=Google%"
            "20Chrome&needsadmin=Prefers&lang=en");
}