chromium/chrome/credential_provider/gaiacp/gcp_utils.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/credential_provider/gaiacp/gcp_utils.h"

#include <windows.h>
#include <winsock2.h>

#include <iphlpapi.h>
#include <wincred.h>  // For <ntsecapi.h>
#include <winternl.h>

#include <string>
#include <string_view>

#include "base/values.h"

#define _NTDEF_  // Prevent redefition errors, must come after <winternl.h>
#include <malloc.h>
#include <memory.h>
#include <ntsecapi.h>  // For LsaLookupAuthenticationPackage()
#include <sddl.h>      // For ConvertSidToStringSid()
#include <security.h>  // For NEGOSSP_NAME_A
#include <stdlib.h>
#include <wbemidl.h>

#include <algorithm>
#include <iomanip>
#include <memory>

#include "base/base64.h"
#include "base/command_line.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/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/strcat_win.h"
#include "base/strings/string_number_conversions_win.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/atl.h"
#include "base/win/current_module.h"
#include "base/win/embedded_i18n/language_selector.h"
#include "base/win/win_util.h"
#include "base/win/wmi.h"
#include "build/branding_buildflags.h"
#include "chrome/common/chrome_version.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/gaia_resources.h"
#include "chrome/credential_provider/gaiacp/gcpw_strings.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
#include "chrome/credential_provider/gaiacp/token_generator.h"
#include "chrome/installer/launcher_support/chrome_launcher_support.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/gaia/gaia_urls.h"
#include "third_party/re2/src/re2/re2.h"

namespace credential_provider {

constexpr wchar_t kDefaultProfilePictureFileExtension[] = L".jpg";

constexpr base::FilePath::CharType kCredentialProviderFolder[] =
    L"Credential Provider";

constexpr wchar_t kDefaultMdmUrl[] =
    L"https://deviceenrollmentforwindows.googleapis.com/v1/discovery";

constexpr int kMaxNumConsecutiveUploadDeviceFailures = 3;

// The following staleness time limits are set to 5 days to prevent file fetch
// operations unnecessarily by GCPW when machine is offline during weekends and
// holidays. These files are also updated by GCPW extension Windows NT service
// regularly when the device is online.
constexpr base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh =
    base::Days(5);
constexpr base::TimeDelta kMaxTimeDeltaSinceLastExperimentsFetch =
    base::Days(5);

constexpr wchar_t kGcpwExperimentsDirectory[] = L"Experiments";
constexpr wchar_t kGcpwUserExperimentsFileName[] = L"ExperimentsFetchResponse";

namespace {

// Overridden in tests to fake serial number extraction.
bool g_use_test_serial_number = false;
std::wstring& TestSerialNumber() {
  static base::NoDestructor<std::wstring> value;
  return *value;
}

// Overridden in tests to fake MAC address extraction.
bool g_use_test_mac_addresses = false;
std::vector<std::string>& TestMacAddresses() {
  static base::NoDestructor<std::vector<std::string>> value;
  return *value;
}

// Overridden in tests to fake OS version.
bool g_use_test_os_version = false;
std::string& TestOSVersion() {
  static base::NoDestructor<std::string> value;
  return *value;
}

// Overridden in tests to fake installed Chrome path.
bool g_use_test_chrome_path = false;
base::FilePath& TestChromePath() {
  static base::NoDestructor<base::FilePath> value;
  return *value;
}

constexpr wchar_t kKernelLibFile[] = L"kernel32.dll";
constexpr wchar_t kOsRegistryPath[] =
    L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
constexpr wchar_t kOsMajorName[] = L"CurrentMajorVersionNumber";
constexpr wchar_t kOsMinorName[] = L"CurrentMinorVersionNumber";
constexpr wchar_t kOsBuildName[] = L"CurrentBuildNumber";
constexpr int kVersionStringSize = 128;

// Minimum supported version of Chrome for GCPW.
constexpr char kMinimumSupportedChromeVersionStr[] = "77.0.3865.65";

constexpr char kSentinelFilename[] = "gcpw_startup.sentinel";
constexpr int64_t kMaxConsecutiveCrashCount = 5;

// L$ prefix means this secret can only be accessed locally.
constexpr wchar_t kLsaKeyDMTokenPrefix[] = L"L$GCPW-DM-Token-";

constexpr base::win::i18n::LanguageSelector::LangToOffset
    kLanguageOffsetPairs[] = {
#define HANDLE_LANGUAGE(l_, o_) {L## #l_, o_},
        DO_LANGUAGES
#undef HANDLE_LANGUAGE
};

base::FilePath GetStartupSentinelLocation(const std::wstring& version) {
  base::FilePath sentienal_path;
  if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &sentienal_path)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr);
    return base::FilePath();
  }

  sentienal_path = sentienal_path.Append(GetInstallParentDirectoryName())
                       .Append(kCredentialProviderFolder);

  return sentienal_path.Append(version).AppendASCII(kSentinelFilename);
}

const base::win::i18n::LanguageSelector& GetLanguageSelector() {
  static base::NoDestructor<base::win::i18n::LanguageSelector> instance(
      std::wstring(), kLanguageOffsetPairs);
  return *instance;
}

// Opens |path| with options that prevent the file from being read or written
// via another handle. As long as the returned object is alive, it is guaranteed
// that |path| isn't in use. It can however be deleted.
base::File GetFileLock(const base::FilePath& path) {
  return base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
                              base::File::FLAG_WIN_EXCLUSIVE_READ |
                              base::File::FLAG_WIN_EXCLUSIVE_WRITE |
                              base::File::FLAG_WIN_SHARE_DELETE);
}

// Deletes a specific GCP version from the disk.
void DeleteVersionDirectory(const base::FilePath& version_path) {
  // Lock all exes and dlls for exclusive access while allowing deletes.  Mark
  // the files for deletion and release them, causing them to actually be
  // deleted.  This allows the deletion of the version path itself.
  std::vector<base::File> locks;
  const int types = base::FileEnumerator::FILES;
  base::FileEnumerator enumerator_version(version_path, false, types,
                                          FILE_PATH_LITERAL("*"));
  bool all_deletes_succeeded = true;
  for (base::FilePath path = enumerator_version.Next(); !path.empty();
       path = enumerator_version.Next()) {
    if (!path.MatchesExtension(FILE_PATH_LITERAL(".exe")) &&
        !path.MatchesExtension(FILE_PATH_LITERAL(".dll"))) {
      continue;
    }

    // Open the file for exclusive access while allowing deletes.
    locks.push_back(GetFileLock(path));
    if (!locks.back().IsValid()) {
      HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
      LOGFN(ERROR) << "Could not lock " << path << " hr=" << putHR(hr);
      all_deletes_succeeded = false;
      continue;
    }

    // Mark the file for deletion.
    HRESULT hr = base::DeleteFile(path);
    if (FAILED(hr)) {
      LOGFN(ERROR) << "Could not delete " << path;
      all_deletes_succeeded = false;
    }
  }

  // Release the locks, actually deleting the files.  It is now possible to
  // delete the version path.
  locks.clear();
  if (all_deletes_succeeded && !base::DeletePathRecursively(version_path))
    LOGFN(ERROR) << "Could not delete version " << version_path.BaseName();
}

// Reads the dm token for |sid| from lsa store and writes into |token| output
// parameter. If |refresh| is true, token is re-generated before returning.
HRESULT GetGCPWDmTokenInternal(const std::wstring& sid,
                               std::wstring* token,
                               bool refresh) {
  DCHECK(token);

  std::wstring store_key = kLsaKeyDMTokenPrefix + sid;

  auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS);

  if (!policy) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "ScopedLsaPolicy::Create hr=" << putHR(hr);
    return hr;
  }

  if (refresh) {
    if (policy->PrivateDataExists(store_key.c_str())) {
      HRESULT hr = policy->RemovePrivateData(store_key.c_str());
      if (FAILED(hr)) {
        LOGFN(ERROR) << "ScopedLsaPolicy::RemovePrivateData hr=" << putHR(hr);
        return hr;
      }
    }

    std::wstring new_token =
        base::UTF8ToWide(TokenGenerator::Get()->GenerateToken());

    HRESULT hr = policy->StorePrivateData(store_key.c_str(), new_token.c_str());
    if (FAILED(hr)) {
      LOGFN(ERROR) << "ScopedLsaPolicy::StorePrivateData hr=" << putHR(hr);
      return hr;
    }

    *token = new_token;
  } else {
    wchar_t dm_token_lsa_data[1024];
    HRESULT hr = policy->RetrievePrivateData(
        store_key.c_str(), dm_token_lsa_data, std::size(dm_token_lsa_data));
    if (FAILED(hr)) {
      LOGFN(ERROR) << "ScopedLsaPolicy::RetrievePrivateData hr=" << putHR(hr);
      return hr;
    }

    *token = dm_token_lsa_data;
  }

  return S_OK;
}

// Get the path to the directory under DIR_COMMON_APP_DATA with the given |sid|
// and |file_dir|.
base::FilePath GetDirectoryFilePath(const std::wstring& sid,
                                    const std::wstring& file_dir) {
  base::FilePath path;
  if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &path)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr);
    return base::FilePath();
  }
  path = path.Append(GetInstallParentDirectoryName())
             .Append(kCredentialProviderFolder)
             .Append(file_dir)
             .Append(sid);
  return path;
}

}  // namespace

// GoogleRegistrationDataForTesting //////////////////////////////////////////

GoogleRegistrationDataForTesting::GoogleRegistrationDataForTesting(
    std::wstring serial_number) {
  g_use_test_serial_number = true;
  TestSerialNumber() = serial_number;
}

GoogleRegistrationDataForTesting::~GoogleRegistrationDataForTesting() {
  g_use_test_serial_number = false;
  TestSerialNumber() = L"";
}

// GoogleRegistrationDataForTesting //////////////////////////////////////////

// GemDeviceDetailsForTesting //////////////////////////////////////////

GemDeviceDetailsForTesting::GemDeviceDetailsForTesting(
    std::vector<std::string>& mac_addresses,
    std::string os_version) {
  g_use_test_mac_addresses = true;
  g_use_test_os_version = true;
  TestMacAddresses() = mac_addresses;
  TestOSVersion() = os_version;
}

GemDeviceDetailsForTesting::~GemDeviceDetailsForTesting() {
  g_use_test_mac_addresses = false;
  g_use_test_os_version = false;
}

// GemDeviceDetailsForTesting //////////////////////////////////////////

// GoogleChromePathForTesting ////////////////////////////////////////////////

GoogleChromePathForTesting::GoogleChromePathForTesting(
    base::FilePath file_path) {
  g_use_test_chrome_path = true;
  TestChromePath() = file_path;
}

GoogleChromePathForTesting::~GoogleChromePathForTesting() {
  g_use_test_chrome_path = false;
  TestChromePath() = base::FilePath(L"");
}

// GoogleChromePathForTesting /////////////////////////////////////////////////

base::FilePath GetInstallDirectory() {
  base::FilePath dest_path;
  if (!base::PathService::Get(base::DIR_PROGRAM_FILES, &dest_path)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "PathService::Get(DIR_PROGRAM_FILES) hr=" << putHR(hr);
    return base::FilePath();
  }

  dest_path = dest_path.Append(GetInstallParentDirectoryName())
                  .Append(kCredentialProviderFolder);

  return dest_path;
}

void DeleteVersionsExcept(const base::FilePath& gcp_path,
                          const std::wstring& product_version) {
  base::FilePath version = base::FilePath(product_version);
  const int types = base::FileEnumerator::DIRECTORIES;
  base::FileEnumerator enumerator(gcp_path, false, types,
                                  FILE_PATH_LITERAL("*"));
  for (base::FilePath name = enumerator.Next(); !name.empty();
       name = enumerator.Next()) {
    base::FilePath basename = name.BaseName();
    if (version == basename)
      continue;

    // Found an older version on the machine that can be deleted.  This is
    // best effort only.  If any errors occurred they are logged by
    // DeleteVersionDirectory().
    DeleteVersionDirectory(gcp_path.Append(basename));
    DeleteStartupSentinelForVersion(basename.value());
  }
}

// StdParentHandles ///////////////////////////////////////////////////////////

StdParentHandles::StdParentHandles() {}

StdParentHandles::~StdParentHandles() {}

// ScopedStartupInfo //////////////////////////////////////////////////////////

ScopedStartupInfo::ScopedStartupInfo() {
  memset(&info_, 0, sizeof(info_));
  info_.hStdInput = INVALID_HANDLE_VALUE;
  info_.hStdOutput = INVALID_HANDLE_VALUE;
  info_.hStdError = INVALID_HANDLE_VALUE;
  info_.cb = sizeof(info_);
}

ScopedStartupInfo::ScopedStartupInfo(const wchar_t* desktop)
    : ScopedStartupInfo() {
  DCHECK(desktop);
  desktop_.assign(desktop);
  info_.lpDesktop = const_cast<wchar_t*>(desktop_.c_str());
}

ScopedStartupInfo::~ScopedStartupInfo() {
  Shutdown();
}

HRESULT ScopedStartupInfo::SetStdHandles(base::win::ScopedHandle* hstdin,
                                         base::win::ScopedHandle* hstdout,
                                         base::win::ScopedHandle* hstderr) {
  if ((info_.dwFlags & STARTF_USESTDHANDLES) == STARTF_USESTDHANDLES) {
    LOGFN(ERROR) << "Already set";
    return E_UNEXPECTED;
  }

  // CreateProcessWithTokenW will fail if any of the std handles provided are
  // invalid and the STARTF_USESTDHANDLES flag is set. So supply the default
  // standard handle if no handle is given for some of the handles. This tells
  // the process it can create its own local handles for these pipes as needed.
  info_.dwFlags |= STARTF_USESTDHANDLES;
  if (hstdin && hstdin->IsValid()) {
    info_.hStdInput = hstdin->Take();
  } else {
    info_.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);
  }
  if (hstdout && hstdout->IsValid()) {
    info_.hStdOutput = hstdout->Take();
  } else {
    info_.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
  }
  if (hstderr && hstderr->IsValid()) {
    info_.hStdError = hstderr->Take();
  } else {
    info_.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
  }

  return S_OK;
}

void ScopedStartupInfo::Shutdown() {
  if ((info_.dwFlags & STARTF_USESTDHANDLES) == STARTF_USESTDHANDLES) {
    info_.dwFlags &= ~STARTF_USESTDHANDLES;

    if (info_.hStdInput != ::GetStdHandle(STD_INPUT_HANDLE))
      ::CloseHandle(info_.hStdInput);
    if (info_.hStdOutput != ::GetStdHandle(STD_OUTPUT_HANDLE))
      ::CloseHandle(info_.hStdOutput);
    if (info_.hStdError != ::GetStdHandle(STD_ERROR_HANDLE))
      ::CloseHandle(info_.hStdError);
    info_.hStdInput = INVALID_HANDLE_VALUE;
    info_.hStdOutput = INVALID_HANDLE_VALUE;
    info_.hStdError = INVALID_HANDLE_VALUE;
  }
}

// Waits for a process to terminate while capturing output from |output_handle|
// to the buffer |output_buffer| of length |buffer_size|. The buffer is expected
// to be relatively small.  The exit code of the process is written to
// |exit_code|.
HRESULT WaitForProcess(base::win::ScopedHandle::Handle process_handle,
                       const StdParentHandles& parent_handles,
                       DWORD* exit_code,
                       char* output_buffer,
                       int buffer_size) {
  LOGFN(VERBOSE);
  DCHECK(exit_code);
  DCHECK_GT(buffer_size, 0);

  output_buffer[0] = 0;

  HANDLE output_handle = parent_handles.hstdout_read.Get();

  for (bool is_done = false; !is_done;) {
    char buffer[80];
    DWORD length = std::size(buffer) - 1;
    HRESULT hr = S_OK;

    const DWORD kThreeMinutesInMs = 3 * 60 * 1000;
    DWORD ret = ::WaitForSingleObject(output_handle,
                                      kThreeMinutesInMs);  // timeout ms
    switch (ret) {
      case WAIT_OBJECT_0: {
        int index = ret - WAIT_OBJECT_0;
        LOGFN(VERBOSE) << "WAIT_OBJECT_" << index;
        if (!::ReadFile(output_handle, buffer, length, &length, nullptr)) {
          hr = HRESULT_FROM_WIN32(::GetLastError());
          if (hr != HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE))
            LOGFN(ERROR) << "ReadFile(" << index << ") hr=" << putHR(hr);
        } else {
          LOGFN(VERBOSE) << "ReadFile(" << index << ") length=" << length;
          buffer[length] = 0;
        }
        break;
      }
      case WAIT_IO_COMPLETION:
        // This is normal.  Just ignore.
        LOGFN(VERBOSE) << "WaitForMultipleObjectsEx WAIT_IO_COMPLETION";
        break;
      case WAIT_TIMEOUT: {
        // User took too long to log in, so kill UI process.
        LOGFN(VERBOSE) << "WaitForMultipleObjectsEx WAIT_TIMEOUT, killing UI";
        ::TerminateProcess(process_handle, kUiecTimeout);
        is_done = true;
        break;
      }
      case WAIT_FAILED:
      default: {
        HRESULT last_error_hr = HRESULT_FROM_WIN32(::GetLastError());
        LOGFN(ERROR) << "WaitForMultipleObjectsEx hr=" << putHR(last_error_hr);
        is_done = true;
        break;
      }
    }

    // Copy the read buffer to the output buffer. If the pipe was broken,
    // we can break our loop and wait for the process to die.
    if (hr == HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)) {
      LOGFN(VERBOSE) << "Stop waiting for output buffer";
      break;
    } else {
      strcat_s(output_buffer, buffer_size, buffer);
    }
  }

  // At this point both stdout and stderr have been closed.  Wait on the process
  // handle for the process to terminate, getting the exit code.  If the
  // process does not terminate gracefully, kill it before returning.
  DWORD ret = ::WaitForSingleObject(process_handle, 10000);
  if (ret == 0) {
    if (::GetExitCodeProcess(process_handle, exit_code)) {
      LOGFN(VERBOSE) << "Process terminated with exit code " << *exit_code;
    } else {
      LOGFN(WARNING) << "Process terminated without exit code";
      *exit_code = kUiecAbort;
    }
  } else {
    LOGFN(WARNING) << "UI did not terminiate within 10 seconds, killing now";
    ::TerminateProcess(process_handle, kUiecKilled);
    *exit_code = kUiecKilled;
  }

  return S_OK;
}

HRESULT CreateLogonToken(const wchar_t* domain,
                         const wchar_t* username,
                         const wchar_t* password,
                         bool interactive,
                         base::win::ScopedHandle* token) {
  DCHECK(domain);
  DCHECK(username);
  DCHECK(password);
  DCHECK(token);

  DWORD logon_type =
      interactive ? LOGON32_LOGON_INTERACTIVE : LOGON32_LOGON_BATCH;
  base::win::ScopedHandle::Handle handle;

  if (!::LogonUserW(username, domain, password, logon_type,
                    LOGON32_PROVIDER_DEFAULT, &handle)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "LogonUserW hr=" << putHR(hr);
    return hr;
  }
  base::win::ScopedHandle primary_token(handle);

  if (!::CreateRestrictedToken(primary_token.Get(), DISABLE_MAX_PRIVILEGE, 0,
                               nullptr, 0, nullptr, 0, nullptr, &handle)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "CreateRestrictedToken hr=" << putHR(hr);
    return hr;
  }
  token->Set(handle);
  return S_OK;
}

HRESULT CreateJobForSignin(base::win::ScopedHandle* job) {
  LOGFN(VERBOSE);
  DCHECK(job);

  job->Set(::CreateJobObject(nullptr, nullptr));
  if (!job->IsValid()) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "CreateJobObject hr=" << putHR(hr);
    return hr;
  }

  JOBOBJECT_BASIC_UI_RESTRICTIONS ui;
  ui.UIRestrictionsClass =
      JOB_OBJECT_UILIMIT_DESKTOP |           // Create/switch desktops.
      JOB_OBJECT_UILIMIT_HANDLES |           // Only access own handles.
      JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS |  // Cannot set sys params.
      JOB_OBJECT_UILIMIT_WRITECLIPBOARD;     // Cannot write to clipboard.
  if (!::SetInformationJobObject(job->Get(), JobObjectBasicUIRestrictions, &ui,
                                 sizeof(ui))) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "SetInformationJobObject hr=" << putHR(hr);
    return hr;
  }

  return S_OK;
}

HRESULT CreatePipeForChildProcess(bool child_reads,
                                  bool use_nul,
                                  base::win::ScopedHandle* reading,
                                  base::win::ScopedHandle* writing) {
  // Make sure that all handles created here are inheritable.  It is important
  // that the child side handle is inherited.
  SECURITY_ATTRIBUTES sa;
  sa.nLength = sizeof(sa);
  sa.bInheritHandle = TRUE;
  sa.lpSecurityDescriptor = nullptr;

  if (use_nul) {
    base::win::ScopedHandle h(
        ::CreateFileW(L"nul:", FILE_GENERIC_READ | FILE_GENERIC_WRITE,
                      FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                      &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
    if (!h.IsValid()) {
      HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
      LOGFN(ERROR) << "CreateFile(nul) hr=" << putHR(hr);
      return hr;
    }

    if (child_reads) {
      reading->Set(h.Take());
    } else {
      writing->Set(h.Take());
    }
  } else {
    base::win::ScopedHandle::Handle temp_handle1;
    base::win::ScopedHandle::Handle temp_handle2;
    if (!::CreatePipe(&temp_handle1, &temp_handle2, &sa, 0)) {
      HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
      LOGFN(ERROR) << "CreatePipe(reading) hr=" << putHR(hr);
      return hr;
    }
    reading->Set(temp_handle1);
    writing->Set(temp_handle2);

    // Make sure parent side is not inherited.
    if (!::SetHandleInformation(child_reads ? writing->Get() : reading->Get(),
                                HANDLE_FLAG_INHERIT, 0)) {
      HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
      LOGFN(ERROR) << "SetHandleInformation(parent) hr=" << putHR(hr);
      return hr;
    }
  }

  return S_OK;
}

HRESULT InitializeStdHandles(CommDirection direction,
                             StdHandlesToCreate to_create,
                             ScopedStartupInfo* startupinfo,
                             StdParentHandles* parent_handles) {
  LOGFN(VERBOSE);
  DCHECK(startupinfo);
  DCHECK(parent_handles);

  base::win::ScopedHandle hstdin_read;
  base::win::ScopedHandle hstdin_write;
  if ((to_create & kStdInput) != 0) {
    HRESULT hr = CreatePipeForChildProcess(
        true,                                            // child reads
        direction == CommDirection::kChildToParentOnly,  // use nul
        &hstdin_read, &hstdin_write);
    if (FAILED(hr)) {
      LOGFN(ERROR) << "CreatePipeForChildProcess(stdin) hr=" << putHR(hr);
      return hr;
    }
  }

  base::win::ScopedHandle hstdout_read;
  base::win::ScopedHandle hstdout_write;
  if ((to_create & kStdOutput) != 0) {
    HRESULT hr = CreatePipeForChildProcess(
        false,                                           // child reads
        direction == CommDirection::kParentToChildOnly,  // use nul
        &hstdout_read, &hstdout_write);
    if (FAILED(hr)) {
      LOGFN(ERROR) << "CreatePipeForChildProcess(stdout) hr=" << putHR(hr);
      return hr;
    }
  }

  base::win::ScopedHandle hstderr_read;
  base::win::ScopedHandle hstderr_write;
  if ((to_create & kStdError) != 0) {
    HRESULT hr = CreatePipeForChildProcess(
        false,                                           // child reads
        direction == CommDirection::kParentToChildOnly,  // use nul
        &hstderr_read, &hstderr_write);
    if (FAILED(hr)) {
      LOGFN(ERROR) << "CreatePipeForChildProcess(stderr) hr=" << putHR(hr);
      return hr;
    }
  }

  HRESULT hr =
      startupinfo->SetStdHandles(&hstdin_read, &hstdout_write, &hstderr_write);
  if (FAILED(hr)) {
    LOGFN(ERROR) << "startupinfo->SetStdHandles hr=" << putHR(hr);
    return hr;
  }

  parent_handles->hstdin_write.Set(hstdin_write.Take());
  parent_handles->hstdout_read.Set(hstdout_read.Take());
  parent_handles->hstderr_read.Set(hstderr_read.Take());
  return S_OK;
}

HRESULT GetPathToDllFromHandle(HINSTANCE dll_handle,
                               base::FilePath* path_to_dll) {
  wchar_t path[MAX_PATH];
  DWORD length = std::size(path);
  length = ::GetModuleFileName(dll_handle, path, length);
  if (length == 0 || length >= std::size(path)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "GetModuleFileNameW hr=" << putHR(hr);
    return hr;
  }

  *path_to_dll = base::FilePath(std::wstring_view(path, length));
  return S_OK;
}

HRESULT GetEntryPointArgumentForRunDll(HINSTANCE dll_handle,
                                       const wchar_t* entrypoint,
                                       std::wstring* entrypoint_arg) {
  DCHECK(entrypoint);
  DCHECK(entrypoint_arg);

  entrypoint_arg->clear();

  // rundll32 expects the first command line argument to be the path to the
  // DLL, followed by a comma and the name of the function to call.  There can
  // be no spaces around the comma. The dll path is quoted because short names
  // may be disabled in the system and path can not have space otherwise. It is
  // recommended to use the short path name of the DLL.
  base::FilePath path_to_dll;
  HRESULT hr = GetPathToDllFromHandle(dll_handle, &path_to_dll);
  if (FAILED(hr))
    return hr;

  wchar_t short_path[MAX_PATH];
  DWORD short_length = std::size(short_path);
  short_length =
      ::GetShortPathName(path_to_dll.value().c_str(), short_path, short_length);
  if (short_length >= std::size(short_path)) {
    hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "GetShortPathNameW hr=" << putHR(hr);
    return hr;
  }

  *entrypoint_arg = base::StrCat({L"\"", short_path, L"\",", entrypoint});

  // In tests, the current module is the unittest exe, not the real dll.
  // The unittest exe does not expose entrypoints, so return S_FALSE as a hint
  // that this will not work.  The command line is built anyway though so
  // tests of the command line construction can be written.
  return wcsicmp(wcsrchr(path_to_dll.value().c_str(), L'.'), L".dll") == 0
             ? S_OK
             : S_FALSE;
}

HRESULT GetCommandLineForEntrypoint(HINSTANCE dll_handle,
                                    const wchar_t* entrypoint,
                                    base::CommandLine* command_line) {
  DCHECK(entrypoint);
  DCHECK(command_line);

  // Build the full path to rundll32.
  base::FilePath system_dir;
  if (!base::PathService::Get(base::DIR_SYSTEM, &system_dir))
    return HRESULT_FROM_WIN32(::GetLastError());

  command_line->SetProgram(
      system_dir.Append(FILE_PATH_LITERAL("rundll32.exe")));

  std::wstring entrypoint_arg;
  HRESULT hr =
      GetEntryPointArgumentForRunDll(dll_handle, entrypoint, &entrypoint_arg);
  if (SUCCEEDED(hr))
    command_line->AppendArgNative(entrypoint_arg);

  return hr;
}

// Gets localized name for builtin administrator account. Extracting
// localized name for builtin administrator account requires DomainSid
// to be passed onto the CreateWellKnownSid function unlike any other
// WellKnownSid as per microsoft documentation. That's why we need to
// first extract the DomainSid (even for local accounts) and pass it as
// a parameter to the CreateWellKnownSid function call.
HRESULT GetLocalizedNameBuiltinAdministratorAccount(
    std::wstring* builtin_localized_admin_name) {
  LSA_HANDLE PolicyHandle;
  LSA_OBJECT_ATTRIBUTES oa = {sizeof(oa)};
  NTSTATUS status =
      LsaOpenPolicy(0, &oa, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle);
  if (status >= 0) {
    PPOLICY_ACCOUNT_DOMAIN_INFO ppadi;
    status = LsaQueryInformationPolicy(
        PolicyHandle, PolicyAccountDomainInformation, (void**)&ppadi);
    if (status >= 0) {
      BYTE well_known_sid[SECURITY_MAX_SID_SIZE];
      DWORD size_local_users_group_sid = std::size(well_known_sid);
      if (CreateWellKnownSid(::WinAccountAdministratorSid, ppadi->DomainSid,
                             well_known_sid, &size_local_users_group_sid)) {
        return LookupLocalizedNameBySid(well_known_sid,
                                        builtin_localized_admin_name);
      } else {
        status = GetLastError();
      }
      LsaFreeMemory(ppadi);
    }
    LsaClose(PolicyHandle);
  }
  return status >= 0 ? S_OK : E_FAIL;
}

HRESULT LookupLocalizedNameBySid(PSID sid, std::wstring* localized_name) {
  DCHECK(localized_name);
  std::vector<wchar_t> localized_name_buffer;
  DWORD group_name_size = 0;
  std::vector<wchar_t> domain_buffer;
  DWORD domain_size = 0;
  SID_NAME_USE use;

  // Get the localized name of the local users group. The function
  // NetLocalGroupAddMembers only accepts the name of the group and it
  // may be localized on the system.
  if (!::LookupAccountSidW(nullptr, sid, nullptr, &group_name_size, nullptr,
                           &domain_size, &use)) {
    if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
      HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
      LOGFN(ERROR) << "LookupAccountSidW hr=" << putHR(hr);
      return hr;
    }

    localized_name_buffer.resize(group_name_size);
    domain_buffer.resize(domain_size);
    if (!::LookupAccountSidW(nullptr, sid, localized_name_buffer.data(),
                             &group_name_size, domain_buffer.data(),
                             &domain_size, &use)) {
      HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
      LOGFN(ERROR) << "LookupAccountSidW hr=" << putHR(hr);
      return hr;
    }
  }

  if (localized_name_buffer.empty()) {
    LOGFN(ERROR) << "Empty localized name";
    return E_UNEXPECTED;
  }
  *localized_name = std::wstring(localized_name_buffer.data(),
                                 localized_name_buffer.size() - 1);

  return S_OK;
}

HRESULT LookupLocalizedNameForWellKnownSid(WELL_KNOWN_SID_TYPE sid_type,
                                           std::wstring* localized_name) {
  BYTE well_known_sid[SECURITY_MAX_SID_SIZE];
  DWORD size_local_users_group_sid = std::size(well_known_sid);

  // Get the sid for the well known local users group.
  if (!::CreateWellKnownSid(sid_type, nullptr, well_known_sid,
                            &size_local_users_group_sid)) {
    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
    LOGFN(ERROR) << "CreateWellKnownSid hr=" << putHR(hr);
    return hr;
  }

  return LookupLocalizedNameBySid(well_known_sid, localized_name);
}

bool WriteToStartupSentinel() {
  LOGFN(VERBOSE);
  // Always try to write to the startup sentinel file. If writing or opening
  // fails for any reason (file locked, no access etc) consider this a failure.
  // If no sentinel file path can be found this probably means that we are
  // running in a unit test so just let the verification pass in this case.
  // Each process will only write once to startup sentinel file.

  static volatile long sentinel_initialized = 0;
  if (::InterlockedCompareExchange(&sentinel_initialized, 1, 0))
    return true;

  base::FilePath startup_sentinel_path =
      GetStartupSentinelLocation(TEXT(CHROME_VERSION_STRING));
  if (!startup_sentinel_path.empty()) {
    base::FilePath startup_sentinel_directory = startup_sentinel_path.DirName();
    if (!base::DirectoryExists(startup_sentinel_directory)) {
      base::File::Error error;
      if (!base::CreateDirectoryAndGetError(startup_sentinel_directory,
                                            &error)) {
        LOGFN(ERROR) << "Could not create sentinel directory='"
                     << startup_sentinel_directory << "' error=" << error;
        return false;
      }
    }
    base::File startup_sentinel(
        startup_sentinel_path,
        base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);

    // Keep writing to the sentinel file until we have reached
    // |kMaxConsecutiveCrashCount| at which point it is assumed that GCPW
    // is crashing continuously and should be disabled.
    if (!startup_sentinel.IsValid()) {
      LOGFN(ERROR) << "Could not open the sentinel path "
                   << startup_sentinel_path.value();
      return false;
    }

    if (startup_sentinel.GetLength() >= kMaxConsecutiveCrashCount) {
      LOGFN(ERROR) << "Sentinel file length indicates "
                   << startup_sentinel.GetLength() << " possible crashes";
      return false;
    }

    LOGFN(VERBOSE) << "Writing to sentinel. Current length="
                   << startup_sentinel.GetLength();
    return startup_sentinel.WriteAtCurrentPos("0", 1) == 1;
  }

  return true;
}

void DeleteStartupSentinel() {
  DeleteStartupSentinelForVersion(TEXT(CHROME_VERSION_STRING));
}

void DeleteStartupSentinelForVersion(const std::wstring& version) {
  LOGFN(VERBOSE) << "Deleting sentinel for version " << version;
  base::FilePath startup_sentinel_path = GetStartupSentinelLocation(version);
  if (base::PathExists(startup_sentinel_path) &&
      !base::DeleteFile(startup_sentinel_path)) {
    LOGFN(ERROR) << "Failed to delete sentinel file: " << startup_sentinel_path;
  }
}

std::wstring GetStringResource(UINT base_message_id) {
  std::wstring localized_string;

  UINT message_id =
      static_cast<UINT>(base_message_id + GetLanguageSelector().offset());
  const ATLSTRINGRESOURCEIMAGE* image =
      AtlGetStringResourceImage(_AtlBaseModule.GetModuleInstance(), message_id);
  if (image) {
    localized_string = std::wstring(image->achString, image->nLength);
  } else {
    NOTREACHED_IN_MIGRATION() << "Unable to find resource id " << message_id;
  }

  return localized_string;
}

std::wstring GetStringResource(UINT base_message_id,
                               const std::vector<std::wstring>& subst) {
  std::wstring format_string = GetStringResource(base_message_id);
  std::wstring formatted =
      base::ReplaceStringPlaceholders(format_string, subst, nullptr);

  return formatted;
}

std::wstring GetSelectedLanguage() {
  return GetLanguageSelector().matched_candidate();
}

void SecurelyClearDictionaryValue(base::optional_ref<base::Value::Dict> dict) {
  SecurelyClearDictionaryValueWithKey(dict, kKeyPassword);
}

void SecurelyClearDictionaryValueWithKey(
    base::optional_ref<base::Value::Dict> dict,
    const std::string& password_key) {
  if (!dict.has_value()) {
    return;
  }

  if (auto* password_value = dict->FindString(password_key)) {
    SecurelyClearString(*password_value);
  }

  dict->clear();
}

void SecurelyClearString(std::wstring& str) {
  SecurelyClearBuffer(const_cast<wchar_t*>(str.data()),
                      str.size() * sizeof(decltype(str[0])));
}

void SecurelyClearString(std::string& str) {
  SecurelyClearBuffer(const_cast<char*>(str.data()), str.size());
}

void SecurelyClearBuffer(void* buffer, size_t length) {
  if (buffer)
    ::RtlSecureZeroMemory(buffer, length);
}

std::string SearchForKeyInStringDictUTF8(
    const std::string& json_string,
    const std::initializer_list<std::string_view>& path) {
  DCHECK_GT(path.size(), 0UL);

  std::optional<base::Value::Dict> json_obj =
      base::JSONReader::ReadDict(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
  if (!json_obj) {
    LOGFN(ERROR) << "base::JSONReader::Read failed to translate to JSON";
    return std::string();
  }
  const std::string* value =
      json_obj->FindStringByDottedPath(base::JoinString(path, "."));
  return value ? *value : std::string();
}

std::wstring GetDictString(const base::Value::Dict& dict, const char* name) {
  DCHECK(name);
  const std::string* value = dict.FindString(name);
  return value ? base::UTF8ToWide(*value) : std::wstring();
}

std::string GetDictStringUTF8(const base::Value::Dict& dict, const char* name) {
  DCHECK(name);
  const std::string* value = dict.FindString(name);
  return value ? *value : std::string();
}

HRESULT SearchForListInStringDictUTF8(
    const std::string& list_key,
    const std::string& json_string,
    const std::initializer_list<std::string_view>& path,
    std::vector<std::string>* output) {
  DCHECK_GT(path.size(), 0UL);

  std::optional<base::Value::Dict> json_obj =
      base::JSONReader::ReadDict(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
  if (!json_obj) {
    LOGFN(ERROR) << "base::JSONReader::Read failed to translate to JSON";
    return E_FAIL;
  }

  auto* value = json_obj->FindListByDottedPath(base::JoinString(path, "."));
  if (value) {
    for (const base::Value& entry_val : *value) {
      const base::Value::Dict& entry = entry_val.GetDict();
      const std::string* list_key_str = entry.FindString(list_key);
      if (list_key_str) {
        output->push_back(*list_key_str);
      } else {
        return E_FAIL;
      }
    }
  }
  return S_OK;
}

base::FilePath::StringType GetInstallParentDirectoryName() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  return FILE_PATH_LITERAL("Google");
#else
  return FILE_PATH_LITERAL("Chromium");
#endif
}

std::wstring GetWindowsVersion() {
  wchar_t release_id[32];
  ULONG length = std::size(release_id);
  HRESULT hr =
      GetMachineRegString(L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
                          L"ReleaseId", release_id, &length);
  if (SUCCEEDED(hr))
    return release_id;

  return L"Unknown";
}

base::Version GetMinimumSupportedChromeVersion() {
  return base::Version(kMinimumSupportedChromeVersionStr);
}

bool ExtractKeysFromDict(
    const base::Value::Dict& dict,
    const std::vector<std::pair<std::string, std::string*>>& needed_outputs) {
  for (const std::pair<std::string, std::string*>& output : needed_outputs) {
    const std::string* output_value = dict.FindString(output.first);
    if (!output_value) {
      LOGFN(ERROR) << "Could not extract value '" << output.first
                   << "' from server response";
      return false;
    }
    DCHECK(output.second);
    *output.second = *output_value;
  }
  return true;
}

std::wstring GetSerialNumber() {
  if (g_use_test_serial_number) {
    return TestSerialNumber();
  }
  return base::win::WmiComputerSystemInfo::Get().serial_number();
}

// This approach was inspired by:
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365917(v=vs.85).aspx
std::vector<std::string> GetMacAddresses() {
  // Used for unit tests.
  if (g_use_test_mac_addresses) {
    return TestMacAddresses();
  }

  PIP_ADAPTER_INFO pAdapter;
  ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
  IP_ADAPTER_INFO* pAdapterInfo =
      new IP_ADAPTER_INFO[ulOutBufLen / sizeof(IP_ADAPTER_INFO)];
  // Get the right buffer size in case of overflow.
  if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
    delete[] pAdapterInfo;
    pAdapterInfo =
        new IP_ADAPTER_INFO[ulOutBufLen / sizeof(IP_ADAPTER_INFO) + 1];
  }
  std::vector<std::string> mac_addresses;
  if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_SUCCESS) {
    pAdapter = pAdapterInfo;
    while (pAdapter) {
      if (pAdapter->AddressLength == 6) {
        char mac_address[17 + 1];
        snprintf(mac_address, sizeof(mac_address),
                 "%02X-%02X-%02X-%02X-%02X-%02X",
                 static_cast<unsigned int>(pAdapter->Address[0]),
                 static_cast<unsigned int>(pAdapter->Address[1]),
                 static_cast<unsigned int>(pAdapter->Address[2]),
                 static_cast<unsigned int>(pAdapter->Address[3]),
                 static_cast<unsigned int>(pAdapter->Address[4]),
                 static_cast<unsigned int>(pAdapter->Address[5]));
        mac_addresses.push_back(mac_address);
      }
      pAdapter = pAdapter->Next;
    }
  }
  delete[] pAdapterInfo;
  return mac_addresses;
}

void GetOsVersionFallback(std::string* version) {
  int buffer_size = GetFileVersionInfoSize(kKernelLibFile, nullptr);
  if (buffer_size) {
    std::vector<wchar_t> buffer(buffer_size, 0);
    if (GetFileVersionInfo(kKernelLibFile, 0, buffer_size, buffer.data())) {
      UINT size;
      void* fixed_version_info_raw;
      if (VerQueryValue(buffer.data(), L"\\", &fixed_version_info_raw, &size)) {
        VS_FIXEDFILEINFO* fixed_version_info =
            static_cast<VS_FIXEDFILEINFO*>(fixed_version_info_raw);
        // https://stackoverflow.com/questions/38068477
        int major = HIWORD(fixed_version_info->dwProductVersionMS);
        int minor = LOWORD(fixed_version_info->dwProductVersionMS);
        int build = HIWORD(fixed_version_info->dwProductVersionLS);
        char version_buffer[kVersionStringSize];
        snprintf(version_buffer, kVersionStringSize, "%d.%d.%d", major, minor,
                 build);
        *version = version_buffer;
      }
    }
  }
}

// The current solution is based on registries or the version of the
// "kernel32.dll" file. A cleaner alternative would be to use the GetVersionEx
// API. However, since Windows 8.1 the values returned by that API are dependent
// on how the application is manifested, and might not be the actual OS version.
// The format of the OS version is <Major>.<Minor>.<BuildNumber>. Eg: 10.0.18363
void GetOsVersion(std::string* version) {
  if (g_use_test_os_version) {
    *version = TestOSVersion();
    return;
  }

  // Fetching Windows version from registries.
  // https://stackoverflow.com/questions/32729244
  // https://stackoverflow.com/questions/31072543
  DWORD major;
  DWORD minor;
  wchar_t build[32];
  ULONG length = std::size(build);

  HRESULT hr1 = GetMachineRegDWORD(kOsRegistryPath, kOsMajorName, &major);
  HRESULT hr2 = GetMachineRegDWORD(kOsRegistryPath, kOsMinorName, &minor);
  HRESULT hr3 =
      GetMachineRegString(kOsRegistryPath, kOsBuildName, build, &length);

  if (SUCCEEDED(hr1) && SUCCEEDED(hr2) && SUCCEEDED(hr3)) {
    char version_buffer[kVersionStringSize];
    snprintf(version_buffer, kVersionStringSize, "%lu.%lu.%ls", major, minor,
             build);
    *version = version_buffer;
    return;
  }
  LOGFN(ERROR) << "Error while fetching Os version hr=" << hr1 << "," << hr2
               << "," << hr3;

  // Try getting the version from kernel.dll in case there is a issue with
  // getting OS version from registries.
  GetOsVersionFallback(version);
}

HRESULT GenerateDeviceId(std::string* device_id) {
  // Build the json data encapsulating different device ids.
  base::Value::Dict device_ids_dict;

  // Add the serial number to the dictionary.
  std::wstring serial_number = GetSerialNumber();
  if (!serial_number.empty()) {
    device_ids_dict.Set("serial_number", base::WideToUTF8(serial_number));
  }

  // Add machine_guid to the dictionary.
  std::wstring machine_guid;
  HRESULT hr = GetMachineGuid(&machine_guid);
  if (SUCCEEDED(hr) && !machine_guid.empty()) {
    device_ids_dict.Set("machine_guid", base::WideToUTF8(machine_guid));
  }

  std::string device_id_str;
  bool json_write_result =
      base::JSONWriter::Write(device_ids_dict, &device_id_str);
  if (!json_write_result) {
    LOGFN(ERROR) << "JSONWriter::Write(device_ids_dict)";
    return E_FAIL;
  }

  // Store the base64encoded device id json blob in the output.
  *device_id = base::Base64Encode(device_id_str);
  return S_OK;
}

HRESULT SetGaiaEndpointCommandLineIfNeeded(const wchar_t* override_registry_key,
                                           const std::string& default_endpoint,
                                           bool provide_deviceid,
                                           bool show_tos,
                                           base::CommandLine* command_line) {
  // Registry specified endpoint.
  wchar_t endpoint_url_setting[256];
  ULONG endpoint_url_length = std::size(endpoint_url_setting);
  HRESULT hr = GetGlobalFlag(override_registry_key, endpoint_url_setting,
                             &endpoint_url_length);
  if (SUCCEEDED(hr) && endpoint_url_setting[0]) {
    GURL endpoint_url(base::AsStringPiece16(endpoint_url_setting));
    if (endpoint_url.is_valid()) {
      command_line->AppendSwitchASCII(switches::kGaiaUrl,
                                      endpoint_url.GetWithEmptyPath().spec());
      command_line->AppendSwitchASCII(kGcpwEndpointPathSwitch,
                                      endpoint_url.path().substr(1));
    }
    return S_OK;
  }

  if (provide_deviceid || show_tos) {
    std::string device_id;
    hr = GenerateDeviceId(&device_id);
    if (SUCCEEDED(hr)) {
      command_line->AppendSwitchASCII(
          kGcpwEndpointPathSwitch,
          base::StringPrintf("%s?device_id=%s&show_tos=%d",
                             default_endpoint.c_str(), device_id.c_str(),
                             show_tos ? 1 : 0));
    } else if (show_tos) {
      command_line->AppendSwitchASCII(
          kGcpwEndpointPathSwitch,
          base::StringPrintf("%s?show_tos=1", default_endpoint.c_str()));
    }
  }
  return S_OK;
}

base::FilePath GetChromePath() {
  base::FilePath gls_path = GetSystemChromePath();

  wchar_t custom_gls_path_value[MAX_PATH];
  ULONG path_len = std::size(custom_gls_path_value);
  HRESULT hr = GetGlobalFlag(kRegGlsPath, custom_gls_path_value, &path_len);
  if (SUCCEEDED(hr)) {
    base::FilePath custom_gls_path(custom_gls_path_value);
    if (base::PathExists(custom_gls_path)) {
      gls_path = custom_gls_path;
    } else {
      LOGFN(ERROR) << "Specified gls path ('" << custom_gls_path.value()
                   << "') does not exist, using default gls path.";
    }
  }

  return gls_path;
}

base::FilePath GetSystemChromePath() {
  if (g_use_test_chrome_path) {
    return TestChromePath();
  }

  return chrome_launcher_support::GetChromePathForInstallationLevel(
      chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION, false);
}

HRESULT GenerateGCPWDmToken(const std::wstring& sid) {
  std::wstring dm_token;
  return GetGCPWDmTokenInternal(sid, &dm_token, true);
}

HRESULT GetGCPWDmToken(const std::wstring& sid, std::wstring* token) {
  return GetGCPWDmTokenInternal(sid, token, false);
}

FakesForTesting::FakesForTesting() {}

FakesForTesting::~FakesForTesting() {}

GURL GetGcpwServiceUrl() {
  std::wstring dev = GetGlobalFlagOrDefault(kRegDeveloperMode, L"");
  if (!dev.empty())
    return GURL(
        base::AsStringPiece16(GetDevelopmentUrl(kDefaultGcpwServiceUrl, dev)));

  return GURL(base::AsStringPiece16(kDefaultGcpwServiceUrl));
}

std::wstring GetDevelopmentUrl(const std::wstring& url,
                               const std::wstring& dev) {
  std::string project;
  std::string final_part;
  if (re2::RE2::FullMatch(base::WideToUTF8(url),
                          "https://(.*).(googleapis.com.*)", &project,
                          &final_part)) {
    std::string url_prefix = "https://" + base::WideToUTF8(dev) + "-";
    return base::UTF8ToWide(
        base::JoinString({url_prefix + project, "sandbox", final_part}, "."));
  }
  return url;
}

std::unique_ptr<base::File> GetOpenedFileForUser(
    const std::wstring& sid,
    uint32_t open_flags,
    const std::wstring& file_dir,
    const std::wstring& file_name) {
  base::FilePath experiments_dir = GetDirectoryFilePath(sid, file_dir);
  if (!base::DirectoryExists(experiments_dir)) {
    base::File::Error error;
    if (!CreateDirectoryAndGetError(experiments_dir, &error)) {
      LOGFN(ERROR) << "Experiments data directory could not be created for "
                   << sid << " Error: " << error;
      return nullptr;
    }
  }

  base::FilePath experiments_file_path = experiments_dir.Append(file_name);
  std::unique_ptr<base::File> experiments_file(
      new base::File(experiments_file_path, open_flags));

  if (!experiments_file->IsValid()) {
    LOGFN(ERROR) << "Error opening experiments file for user " << sid
                 << " with flags " << open_flags
                 << " Error: " << experiments_file->error_details();
    return nullptr;
  }

  base::File::Error lock_error =
      experiments_file->Lock(base::File::LockMode::kExclusive);
  if (lock_error != base::File::FILE_OK) {
    LOGFN(ERROR)
        << "Failed to obtain exclusive lock on experiments file! Error: "
        << lock_error;
    return nullptr;
  }

  return experiments_file;
}

base::TimeDelta GetTimeDeltaSinceLastFetch(const std::wstring& sid,
                                           const std::wstring& flag) {
  wchar_t last_fetch_millis[512];
  ULONG last_fetch_size = std::size(last_fetch_millis);
  HRESULT hr = GetUserProperty(sid, flag, last_fetch_millis, &last_fetch_size);

  if (FAILED(hr)) {
    return base::TimeDelta::Max();
  }

  int64_t last_fetch_millis_int64;
  base::StringToInt64(last_fetch_millis, &last_fetch_millis_int64);

  int64_t time_delta_from_last_fetch_ms =
      base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds() -
      last_fetch_millis_int64;

  return base::Milliseconds(time_delta_from_last_fetch_ms);
}

}  // namespace credential_provider