chromium/chrome/browser/google/google_update_win.cc

// Copyright 2012 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/browser/google/google_update_win.h"

#include <objbase.h>

#include <stdint.h>
#include <string.h>

#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/sequenced_task_runner_helpers.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/version.h"
#include "base/win/atl.h"
#include "base/win/scoped_bstr.h"
#include "base/win/win_util.h"
#include "chrome/browser/google/switches.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/google_update_settings.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/win/atl_module.h"

namespace {

struct UpdateCheckResult {
  GoogleUpdateErrorCode error_code = GOOGLE_UPDATE_NO_ERROR;
  HRESULT hresult = S_OK;
};

// The status of the upgrade. These values are used for a histogram. Do not
// reorder.
enum GoogleUpdateUpgradeStatus {
  // The upgrade has started. DEPRECATED.
  // UPGRADE_STARTED = 0,
  // A check for upgrade has been initiated. DEPRECATED.
  // UPGRADE_CHECK_STARTED = 1,
  // An update is available.
  UPGRADE_IS_AVAILABLE = 2,
  // The upgrade happened successfully.
  UPGRADE_SUCCESSFUL = 3,
  // No need to upgrade, Chrome is up to date.
  UPGRADE_ALREADY_UP_TO_DATE = 4,
  // An error occurred.
  UPGRADE_ERROR = 5,
  NUM_UPGRADE_STATUS
};

GoogleUpdate3ClassFactory* g_google_update_factory = nullptr;
base::SingleThreadTaskRunner* g_update_driver_task_runner = nullptr;

// The time interval, in milliseconds, between polls to Google Update. This
// value was chosen unscientificaly during an informal discussion.
const int64_t kGoogleUpdatePollIntervalMs = 250;

const int kGoogleAllowedRetries = 1;
const int kGoogleRetryIntervalSeconds = 5;

// Constants from Google Update.
const HRESULT GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY = 0x80040813;
const HRESULT GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL = 0x8004081f;
const HRESULT GOOPDATE_E_APP_USING_EXTERNAL_UPDATER = 0xA043081D;
const HRESULT GOOPDATEINSTALL_E_INSTALLER_FAILED = 0x80040902;

// Older versions of GoogleUpdate require elevation for system-level updates.
bool IsElevationRequiredForSystemLevelUpdates() {
  const base::Version kMinGUVersionNoElevationRequired("1.3.29.1");
  const base::Version current_version(
      GoogleUpdateSettings::GetGoogleUpdateVersion(true));

  return !current_version.IsValid() ||
         current_version < kMinGUVersionNoElevationRequired;
}

// Check if the currently running instance can be updated by Google Update.
// Returns GOOGLE_UPDATE_NO_ERROR only if the instance running is a Google
// Chrome distribution installed in a standard location.
GoogleUpdateErrorCode CanUpdateCurrentChrome(
    const base::FilePath& chrome_exe_path,
    bool system_level_install) {
  DCHECK_NE(InstallUtil::IsPerUserInstall(), system_level_install);

  // The currently-running browser can only be updated by Google Update if it
  // is running from the same directory as the currently-installed browser
  // being managed by Google Update at the desired install level.
  const base::FilePath install_dir =
      installer::GetInstalledDirectory(system_level_install);
  return (!install_dir.empty() &&
          base::FilePath::CompareEqualIgnoreCase(chrome_exe_path.value(),
                                                 install_dir.value()))
             ? GOOGLE_UPDATE_NO_ERROR
             : CANNOT_UPGRADE_CHROME_IN_THIS_DIRECTORY;
}

// Explicitly allow the Google Update service to impersonate the client since
// some COM code elsewhere in the browser process may have previously used
// CoInitializeSecurity to set the impersonation level to something other than
// the default. Ignore errors since an attempt to use Google Update may succeed
// regardless.
void ConfigureProxyBlanket(IUnknown* interface_pointer) {
  ::CoSetProxyBlanket(interface_pointer,
                      RPC_C_AUTHN_DEFAULT,
                      RPC_C_AUTHZ_DEFAULT,
                      COLE_DEFAULT_PRINCIPAL,
                      RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                      RPC_C_IMP_LEVEL_IMPERSONATE,
                      nullptr,
                      EOAC_DYNAMIC_CLOAKING);
}

// Creates a class factory for a COM Local Server class using the Elevation
// moniker. |hwnd| must refer to a foregound window in order to get the UAC
// prompt to appear in the foreground if running on Vista+. It can also be NULL
// if background UAC prompts are desired.
HRESULT CoGetClassObjectAsAdmin(gfx::AcceleratedWidget hwnd,
                                REFCLSID class_id,
                                REFIID interface_id,
                                void** interface_ptr) {
  if (!interface_ptr)
    return E_POINTER;

  // For Vista+, need to instantiate the class factory via the elevation
  // moniker. This ensures that the UAC dialog shows up.
  const std::wstring elevation_moniker_name =
      L"Elevation:Administrator!clsid:" + base::win::WStringFromGUID(class_id);

  BIND_OPTS3 bind_opts;
  // An explicit memset is needed rather than relying on value initialization
  // since BIND_OPTS3 is not an aggregate (it is a derived type).
  memset(&bind_opts, 0, sizeof(bind_opts));
  bind_opts.cbStruct = sizeof(bind_opts);
  bind_opts.dwClassContext = CLSCTX_LOCAL_SERVER;
  bind_opts.hwnd = hwnd;

  return ::CoGetObject(elevation_moniker_name.c_str(), &bind_opts, interface_id,
                       interface_ptr);
}

HRESULT CreateGoogleUpdate3WebClass(
    bool system_level_install,
    bool install_update_if_possible,
    gfx::AcceleratedWidget elevation_window,
    Microsoft::WRL::ComPtr<IGoogleUpdate3Web>* google_update) {
  if (g_google_update_factory)
    return g_google_update_factory->Run(google_update);

  const CLSID& google_update_clsid = system_level_install
                                         ? CLSID_GoogleUpdate3WebSystemClass
                                         : CLSID_GoogleUpdate3WebUserClass;
  Microsoft::WRL::ComPtr<IClassFactory> class_factory;
  HRESULT hresult = S_OK;

  // For a user-level install, update checks and updates can both be done by a
  // normal user with the UserClass. For a system-level install, update checks
  // can be done by a normal user with the MachineClass. Newer versions of
  // GoogleUpdate allow normal users to also install system-level updates
  // without requiring elevation.
  if (!system_level_install ||
      !install_update_if_possible ||
      !IsElevationRequiredForSystemLevelUpdates()) {
    hresult = ::CoGetClassObject(google_update_clsid, CLSCTX_ALL, nullptr,
                                 IID_PPV_ARGS(&class_factory));
  } else {
    // With older versions of GoogleUpdate, a system-level update requires Admin
    // privileges. Elevate while instantiating the MachineClass.
    hresult = CoGetClassObjectAsAdmin(elevation_window, google_update_clsid,
                                      IID_PPV_ARGS(&class_factory));
  }
  if (FAILED(hresult))
    return hresult;

  ConfigureProxyBlanket(class_factory.Get());

  Microsoft::WRL::ComPtr<IUnknown> unknown;
  hresult = class_factory->CreateInstance(nullptr, IID_PPV_ARGS(&unknown));
  if (FAILED(hresult)) {
    return hresult;
  }

  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
  // Without this change, marshaling can load the typelib from the wrong hive
  // (HKCU instead of HKLM, or vice-versa).
  hresult =
      unknown.CopyTo(system_level_install ? __uuidof(IGoogleUpdate3WebSystem)
                                          : __uuidof(IGoogleUpdate3WebUser),
                     IID_PPV_ARGS_Helper(&(*google_update)));
  return SUCCEEDED(hresult) ? hresult : unknown.As(&(*google_update));
}

// Returns the process-wide storage for the state of the last update check.
std::optional<UpdateState>* GetLastUpdateStateStorage() {
  static base::NoDestructor<std::optional<UpdateState>> storage;
  return storage.get();
}

// Checks if --simulate-update-hresult is present in the command line and
// returns either: nullopt if switch not present, or E_FAIL if the switch
// was present without the value, or the value of the switch as an HRESULT.
// Additionally the returned structure contains the default error code
// GOOGLE_UPDATE_ERROR_UPDATING or the value of --simulate-update-error-code.
std::optional<UpdateCheckResult> GetSimulatedErrorForDebugging() {
  const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
  if (!cmd_line.HasSwitch(switches::kSimulateUpdateHresult))
    return std::nullopt;

  uint32_t error_from_string = 0;
  std::string error_switch_value =
      cmd_line.GetSwitchValueASCII(switches::kSimulateUpdateHresult);
  HRESULT hresult = E_FAIL;
  if (base::HexStringToUInt(error_switch_value, &error_from_string))
    hresult = error_from_string;

  GoogleUpdateErrorCode error_code = GOOGLE_UPDATE_ERROR_UPDATING;
  error_switch_value =
      cmd_line.GetSwitchValueASCII(switches::kSimulateUpdateErrorCode);
  int32_t error_code_value = 0;
  if (base::StringToInt(error_switch_value, &error_code_value) &&
      error_code_value >= 0 && error_code_value < NUM_ERROR_CODES) {
    error_code = static_cast<GoogleUpdateErrorCode>(error_code_value);
  }

  return {{error_code, hresult}};
}

// UpdateCheckDriver -----------------------------------------------------------

// A driver that is created and destroyed on the caller's thread and drives
// Google Update on another.
class UpdateCheckDriver {
 public:
  UpdateCheckDriver(const UpdateCheckDriver&) = delete;
  UpdateCheckDriver& operator=(const UpdateCheckDriver&) = delete;

  // Runs an update check, invoking methods of |delegate| on the caller's thread
  // to report progress and final results.
  static void RunUpdateCheck(
      const std::string& locale,
      bool install_update_if_possible,
      gfx::AcceleratedWidget elevation_window,
      const base::WeakPtr<UpdateCheckDelegate>& delegate);

 private:
  friend class base::DeleteHelper<UpdateCheckDriver>;

  UpdateCheckDriver(
      const std::string& locale,
      bool install_update_if_possible,
      gfx::AcceleratedWidget elevation_window,
      const base::WeakPtr<UpdateCheckDelegate>& delegate);

  // Invokes a completion or error method on all delegates, as appropriate.
  ~UpdateCheckDriver();

  // If an UpdateCheckDriver is already running, the delegate is added to the
  // existing one instead of creating a new one.
  void AddDelegate(const base::WeakPtr<UpdateCheckDelegate>& delegate);

  // Notifies delegates of an update's progress. |progress|, a number between 0
  // and 100 (inclusive), is an estimation as to what percentage of the upgrade
  // has completed. |new_version| indicates the version that is being download
  // and installed.
  void NotifyUpgradeProgress(int progress, const std::u16string& new_version);

  // Starts an update check.
  void BeginUpdateCheck();

  // Returns the result of initiating an update check. On failure, the instance
  // is left in a consistent state so that this method can be invoked later to
  // retry the steps that failed.
  UpdateCheckResult BeginUpdateCheckInternal();

  // Sets status_ to UPGRADE_ERROR, update_state_.error_code to |error_code|,
  // update_state_.hresult to |check_result.hresult|,
  // update_state_.installer_exit_code to |installer_exit_code|,
  // and html_error_message_ to a composition of all values suitable for display
  // to the user. This call should be followed by deletion of the driver, which
  // will result in callers being notified via their delegates.
  void OnUpgradeError(UpdateCheckResult check_result,
                      std::optional<int> installer_exit_code,
                      const std::u16string& error_string);

  // Returns true if |current_state| and |state_value| can be obtained from the
  // ongoing update check. Otherwise, populates |hresult| with the reason they
  // could not be obtained.
  bool GetCurrentState(Microsoft::WRL::ComPtr<ICurrentState>* current_state,
                       CurrentState* state_value,
                       HRESULT* hresult) const;

  // Returns true if |current_state| and |state_value| constitute an error state
  // for the ongoing update check, in which case |error_code| is populated with
  // one of GOOGLE_UPDATE_ERROR_UPDATING, GOOGLE_UPDATE_DISABLED_BY_POLICY, or
  // GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR. |hresult| is populated with
  // the most relevant HRESULT (which may be a value from Google Update; see
  // https://code.google.com/p/omaha/source/browse/trunk/base/error.h). In case
  // Chrome's installer failed during execution, |installer_exit_code| may be
  // populated with its process exit code (see enum installer::InstallStatus in
  // chrome/installer/util/util_constants.h). |error_string| will be populated
  // with a completion message if one is provided by Google Update.
  bool IsErrorState(const Microsoft::WRL::ComPtr<ICurrentState>& current_state,
                    CurrentState state_value,
                    GoogleUpdateErrorCode* error_code,
                    HRESULT* hresult,
                    std::optional<int>* installer_exit_code,
                    std::u16string* error_string) const;

  // Returns true if |current_state| and |state_value| constitute a final state
  // for the ongoing update check, in which case |upgrade_status| is populated
  // with one of UPGRADE_ALREADY_UP_TO_DATE or UPGRADE_IS_AVAILABLE (in case a
  // pure check is being performed rather than an update) or UPGRADE_SUCCESSFUL
  // (in case an update is being performed). For the UPGRADE_IS_AVAILABLE case,
  // |new_version| will be populated with the available version, if provided by
  // Google Update.
  bool IsFinalState(const Microsoft::WRL::ComPtr<ICurrentState>& current_state,
                    CurrentState state_value,
                    GoogleUpdateUpgradeStatus* upgrade_status,
                    std::u16string* new_version) const;

  // Returns true if |current_state| and |state_value| constitute an
  // intermediate state for the ongoing update check. |new_version| will be
  // populated with the version to be installed if it is provided by Google
  // Update for the current state. |progress| will be populated with a number
  // between 0 and 100 according to how far Google Update has progressed in the
  // download and install process.
  bool IsIntermediateState(
      const Microsoft::WRL::ComPtr<ICurrentState>& current_state,
      CurrentState state_value,
      std::u16string* new_version,
      int* progress) const;

  // Polls Google Update to determine the state of the ongoing check or
  // update. If the process has reached a terminal state, this instance will be
  // deleted and the caller will be notified of the final status. Otherwise, the
  // caller will be notified of the intermediate state (iff it differs from a
  // previous notification) and another future poll will be scheduled.
  void PollGoogleUpdate();

  // The global driver instance. Accessed only on the caller's thread.
  static UpdateCheckDriver* driver_;

  // The task runner on which the update checks runs.
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

  // The caller's task runner, on which methods of the |delegates_| will be
  // invoked.
  scoped_refptr<base::SequencedTaskRunner> result_runner_;

  // The UI locale.
  std::string locale_;

  // False to only check for an update; true to also install one if available.
  bool install_update_if_possible_;

  // A parent window in case any UX is required (e.g., an elevation prompt).
  gfx::AcceleratedWidget elevation_window_;

  // Contains all delegates by which feedback is conveyed. Accessed only on the
  // caller's thread.
  std::vector<base::WeakPtr<UpdateCheckDelegate>> delegates_;

  // Number of remaining retries allowed when errors occur.
  int allowed_retries_;

  // True if operating on a per-machine installation rather than a per-user one.
  bool system_level_install_;

  // The on-demand updater that is doing the work.
  Microsoft::WRL::ComPtr<IGoogleUpdate3Web> google_update_;

  // An app bundle containing the application being updated.
  Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle_;

  // The application being updated (Chrome, Chrome Binaries, or Chrome SxS).
  Microsoft::WRL::ComPtr<IAppWeb> app_;

  // The progress value reported most recently to the caller.
  int last_reported_progress_;

  // The results of the update check to be logged via UMA and/or reported to the
  // caller.
  GoogleUpdateUpgradeStatus status_;
  UpdateState update_state_;
  std::u16string html_error_message_;
};

UpdateCheckDriver* UpdateCheckDriver::driver_ = nullptr;

// static
void UpdateCheckDriver::RunUpdateCheck(
    const std::string& locale,
    bool install_update_if_possible,
    gfx::AcceleratedWidget elevation_window,
    const base::WeakPtr<UpdateCheckDelegate>& delegate) {
  // Create the driver if it doesn't exist, or add the delegate to the existing
  // one.
  if (!driver_) {
    // The driver is owned by itself, and will self-destruct when its work is
    // done.
    driver_ = new UpdateCheckDriver(locale, install_update_if_possible,
                                    elevation_window, delegate);
    driver_->task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&UpdateCheckDriver::BeginUpdateCheck,
                                  base::Unretained(driver_)));
  } else {
    driver_->AddDelegate(delegate);
  }
}

// Runs on the caller's thread.
UpdateCheckDriver::UpdateCheckDriver(
    const std::string& locale,
    bool install_update_if_possible,
    gfx::AcceleratedWidget elevation_window,
    const base::WeakPtr<UpdateCheckDelegate>& delegate)
    : task_runner_(
          g_update_driver_task_runner
              ? g_update_driver_task_runner
              : base::ThreadPool::CreateCOMSTATaskRunner(
                    {base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
      result_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      locale_(locale),
      install_update_if_possible_(install_update_if_possible),
      elevation_window_(elevation_window),
      delegates_(1, delegate),
      allowed_retries_(kGoogleAllowedRetries),
      system_level_install_(false),
      last_reported_progress_(0),
      status_(UPGRADE_ERROR) {}

UpdateCheckDriver::~UpdateCheckDriver() {
  DCHECK(result_runner_->RunsTasksInCurrentSequence());
  // If there is an error, then error_code must not be blank, and vice versa.
  DCHECK_NE(status_ == UPGRADE_ERROR,
            update_state_.error_code == GOOGLE_UPDATE_NO_ERROR);

  *GetLastUpdateStateStorage() = update_state_;

  base::UmaHistogramEnumeration("GoogleUpdate.UpgradeResult", status_,
                                NUM_UPGRADE_STATUS);
  if (status_ == UPGRADE_ERROR) {
    base::UmaHistogramEnumeration("GoogleUpdate.UpdateErrorCode",
                                  update_state_.error_code, NUM_ERROR_CODES);
    if (FAILED(update_state_.hresult)) {
      base::UmaHistogramSparse("GoogleUpdate.ErrorHresult",
                               update_state_.hresult);
    }
  }

  // Clear the driver before calling the delegates because they might call
  // BeginUpdateCheck() and they must not add themselves to the current
  // instance of UpdateCheckDriver, which is being destroyed.
  driver_ = nullptr;

  for (const auto& delegate : delegates_) {
    if (delegate) {
      if (status_ == UPGRADE_ERROR) {
        delegate->OnError(update_state_.error_code, html_error_message_,
                          update_state_.new_version);
      } else if (install_update_if_possible_) {
        delegate->OnUpgradeComplete(update_state_.new_version);
      } else {
        delegate->OnUpdateCheckComplete(update_state_.new_version);
      }
    }
  }
}

void UpdateCheckDriver::AddDelegate(
    const base::WeakPtr<UpdateCheckDelegate>& delegate) {
  DCHECK(result_runner_->RunsTasksInCurrentSequence());
  delegates_.push_back(delegate);
}

void UpdateCheckDriver::NotifyUpgradeProgress(
    int progress,
    const std::u16string& new_version) {
  DCHECK(result_runner_->RunsTasksInCurrentSequence());

  for (const auto& delegate : delegates_) {
    if (delegate)
      delegate->OnUpgradeProgress(progress, new_version);
  }
}

void UpdateCheckDriver::BeginUpdateCheck() {
  UpdateCheckResult result = BeginUpdateCheckInternal();
  if (SUCCEEDED(result.hresult)) {
    // Start polling.
    task_runner_->PostTask(FROM_HERE,
                           base::BindOnce(&UpdateCheckDriver::PollGoogleUpdate,
                                          base::Unretained(this)));
    return;
  }
  if (result.hresult == GOOPDATE_E_APP_USING_EXTERNAL_UPDATER) {
    // This particular transient error is worth retrying.
    if (allowed_retries_) {
      --allowed_retries_;
      task_runner_->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&UpdateCheckDriver::BeginUpdateCheck,
                         base::Unretained(this)),
          base::Seconds(kGoogleRetryIntervalSeconds));
      return;
    }
  }

  DCHECK(FAILED(result.hresult));
  OnUpgradeError(result, std::nullopt, std::u16string());
  result_runner_->DeleteSoon(FROM_HERE, this);
}

UpdateCheckResult UpdateCheckDriver::BeginUpdateCheckInternal() {
  const auto simulated_error = GetSimulatedErrorForDebugging();
  if (simulated_error.has_value())
    return simulated_error.value();

  HRESULT hresult = S_OK;

  // Instantiate GoogleUpdate3Web{Machine,User}Class.
  if (!google_update_) {
    base::FilePath chrome_exe;
    if (!base::PathService::Get(base::DIR_EXE, &chrome_exe))
      NOTREACHED_IN_MIGRATION();

    system_level_install_ = !InstallUtil::IsPerUserInstall();

    // Make sure ATL is initialized in this module.
    ui::win::CreateATLModuleIfNeeded();

    const GoogleUpdateErrorCode error_code =
        CanUpdateCurrentChrome(chrome_exe, system_level_install_);
    if (error_code != GOOGLE_UPDATE_NO_ERROR)
      return {error_code, E_FAIL};

    hresult = CreateGoogleUpdate3WebClass(system_level_install_,
                                          install_update_if_possible_,
                                          elevation_window_, &google_update_);
    if (FAILED(hresult))
      return {GOOGLE_UPDATE_ONDEMAND_CLASS_NOT_FOUND, hresult};

    ConfigureProxyBlanket(google_update_.Get());
  }

  // The class was created, so all subsequent errors are reported as:
  constexpr GoogleUpdateErrorCode error_code =
      GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR;

  // Create an app bundle.
  if (!app_bundle_) {
    Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
    Microsoft::WRL::ComPtr<IDispatch> dispatch;
    hresult = google_update_->createAppBundleWeb(&dispatch);
    if (FAILED(hresult))
      return {error_code, hresult};

    hresult =
        dispatch.CopyTo(system_level_install_ ? __uuidof(IAppBundleWebSystem)
                                              : __uuidof(IAppBundleWebUser),
                        IID_PPV_ARGS_Helper(&app_bundle));
    if (FAILED(hresult)) {
      hresult = dispatch.As(&app_bundle);
      if (FAILED(hresult)) {
        return {error_code, hresult};
      }
    }

    dispatch.Reset();

    ConfigureProxyBlanket(app_bundle.Get());

    if (!locale_.empty()) {
      // Ignore the result of this since, while setting the display language is
      // nice to have, a failure to do so does not affect the likelihood that
      // the update check and/or install will succeed.
      app_bundle->put_displayLanguage(
          base::win::ScopedBstr(base::UTF8ToWide(locale_)).Get());
    }

    hresult = app_bundle->initialize();
    if (FAILED(hresult))
      return {error_code, hresult};
    if (elevation_window_) {
      // Likewise, a failure to set the parent window need not block an update
      // check.
      app_bundle->put_parentHWND(
          reinterpret_cast<ULONG_PTR>(elevation_window_));
    }
    app_bundle_.Swap(app_bundle);
  }

  // Get a reference to the Chrome app in the bundle.
  if (!app_) {
    const wchar_t* app_guid = install_static::GetAppGuid();
    DCHECK(app_guid);
    DCHECK(*app_guid);

    Microsoft::WRL::ComPtr<IDispatch> dispatch;
    // It is common for this call to fail with APP_USING_EXTERNAL_UPDATER if
    // an auto update is in progress.
    hresult =
        app_bundle_->createInstalledApp(base::win::ScopedBstr(app_guid).Get());
    if (FAILED(hresult))
      return {error_code, hresult};
    // Move the IAppBundleWeb reference into a local now so that failures from
    // this point onward result in it being released.
    Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
    app_bundle.Swap(app_bundle_);
    hresult = app_bundle->get_appWeb(0, &dispatch);
    if (FAILED(hresult))
      return {error_code, hresult};
    Microsoft::WRL::ComPtr<IAppWeb> app;

    // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
    // Without this change, marshaling can load the typelib from the wrong hive
    // (HKCU instead of HKLM, or vice-versa).
    hresult = dispatch.CopyTo(
        system_level_install_ ? __uuidof(IAppWebSystem) : __uuidof(IAppWebUser),
        IID_PPV_ARGS_Helper(&app));
    if (FAILED(hresult)) {
      hresult = dispatch.As(&app);
      if (FAILED(hresult)) {
        return {error_code, hresult};
      }
    }

    ConfigureProxyBlanket(app.Get());
    hresult = app_bundle->checkForUpdate();
    if (FAILED(hresult))
      return {error_code, hresult};
    app_bundle_.Swap(app_bundle);
    app_.Swap(app);
  }

  return {GOOGLE_UPDATE_NO_ERROR, hresult};
}

bool UpdateCheckDriver::GetCurrentState(
    Microsoft::WRL::ComPtr<ICurrentState>* current_state,
    CurrentState* state_value,
    HRESULT* hresult) const {
  Microsoft::WRL::ComPtr<IDispatch> dispatch;
  *hresult = app_->get_currentState(&dispatch);
  if (FAILED(*hresult))
    return false;

  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
  // Without this change, marshaling can load the typelib from the wrong hive
  // (HKCU instead of HKLM, or vice-versa).
  *hresult =
      dispatch.CopyTo(system_level_install_ ? __uuidof(ICurrentStateSystem)
                                            : __uuidof(ICurrentStateUser),
                      IID_PPV_ARGS_Helper(&(*current_state)));
  if (FAILED(*hresult)) {
    *hresult = dispatch.As(&(*current_state));
    if (FAILED(*hresult)) {
      return false;
    }
  }
  ConfigureProxyBlanket(current_state->Get());
  LONG value = 0;
  *hresult = (*current_state)->get_stateValue(&value);
  if (FAILED(*hresult))
    return false;
  *state_value = static_cast<CurrentState>(value);
  return true;
}

bool UpdateCheckDriver::IsErrorState(
    const Microsoft::WRL::ComPtr<ICurrentState>& current_state,
    CurrentState state_value,
    GoogleUpdateErrorCode* error_code,
    HRESULT* hresult,
    std::optional<int>* installer_exit_code,
    std::u16string* error_string) const {
  if (state_value == STATE_ERROR) {
    // In general, errors reported by Google Update fall under this category
    // (see special case below).
    *error_code = GOOGLE_UPDATE_ERROR_UPDATING;

    // In general, the exit code of Chrome's installer is unknown (see special
    // case below).
    installer_exit_code->reset();

    // Report the error_code provided by Google Update if possible, or the
    // reason it wasn't possible otherwise.
    LONG long_value = 0;
    *hresult = current_state->get_errorCode(&long_value);
    if (SUCCEEDED(*hresult))
      *hresult = long_value;

    // Special cases:
    // - Use a custom error code if Google Update repoted that the update was
    //   disabled by a Group Policy setting.
    // - Extract the exit code of Chrome's installer if Google Update repoted
    //   that the update failed because of a failure in the installer.
    LONG code = 0;
    if (*hresult == GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY) {
      *error_code = GOOGLE_UPDATE_DISABLED_BY_POLICY;
    } else if (*hresult == GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL) {
      *error_code = GOOGLE_UPDATE_DISABLED_BY_POLICY_AUTO_ONLY;
    } else if (*hresult == GOOPDATEINSTALL_E_INSTALLER_FAILED &&
               SUCCEEDED(current_state->get_installerResultCode(&code))) {
      *installer_exit_code = code;
    }

    base::win::ScopedBstr message;
    if (SUCCEEDED(current_state->get_completionMessage(message.Receive())))
      error_string->assign(base::as_u16cstr(message.Get()), message.Length());

    return true;
  }
  if (state_value == STATE_UPDATE_AVAILABLE && install_update_if_possible_) {
    *hresult = app_bundle_->install();
    if (FAILED(*hresult)) {
      // Report a failure to start the install as a general error while trying
      // to interact with Google Update.
      *error_code = GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR;
      installer_exit_code->reset();
      return true;
    }
    // Return false for handling in IsIntermediateState.
  }
  return false;
}

bool UpdateCheckDriver::IsFinalState(
    const Microsoft::WRL::ComPtr<ICurrentState>& current_state,
    CurrentState state_value,
    GoogleUpdateUpgradeStatus* upgrade_status,
    std::u16string* new_version) const {
  if (state_value == STATE_UPDATE_AVAILABLE && !install_update_if_possible_) {
    base::win::ScopedBstr version;
    *upgrade_status = UPGRADE_IS_AVAILABLE;
    if (SUCCEEDED(current_state->get_availableVersion(version.Receive())))
      new_version->assign(base::as_u16cstr(version.Get()), version.Length());
    return true;
  }
  if (state_value == STATE_INSTALL_COMPLETE) {
    DCHECK(install_update_if_possible_);
    *upgrade_status = UPGRADE_SUCCESSFUL;
    return true;
  }
  if (state_value == STATE_NO_UPDATE) {
    *upgrade_status = UPGRADE_ALREADY_UP_TO_DATE;
    return true;
  }
  return false;
}

bool UpdateCheckDriver::IsIntermediateState(
    const Microsoft::WRL::ComPtr<ICurrentState>& current_state,
    CurrentState state_value,
    std::u16string* new_version,
    int* progress) const {
  // ERROR will have been handled in IsErrorState. UPDATE_AVAILABLE, and
  // NO_UPDATE will have been handled in IsFinalState if not doing an install,
  // as will STATE_INSTALL_COMPLETE when doing an install. All other states
  // following UPDATE_AVAILABLE will only happen when an install is to be done.
  DCHECK(state_value < STATE_UPDATE_AVAILABLE || install_update_if_possible_);
  *progress = 0;

  switch (state_value) {
    case STATE_INIT:
    case STATE_WAITING_TO_CHECK_FOR_UPDATE:
    case STATE_CHECKING_FOR_UPDATE:
      // There is no news to report yet.
      break;

    case STATE_UPDATE_AVAILABLE: {
      base::win::ScopedBstr version;
      if (SUCCEEDED(current_state->get_availableVersion(version.Receive())))
        new_version->assign(base::as_u16cstr(version.Get()), version.Length());
      break;
    }

    case STATE_WAITING_TO_DOWNLOAD:
    case STATE_RETRYING_DOWNLOAD:
      break;

    case STATE_DOWNLOADING: {
      ULONG bytes_downloaded = 0;
      ULONG total_bytes = 0;
      if (SUCCEEDED(current_state->get_bytesDownloaded(&bytes_downloaded)) &&
          SUCCEEDED(current_state->get_totalBytesToDownload(&total_bytes)) &&
          total_bytes) {
        // 0-50 is downloading.
        *progress = base::ClampFloor((static_cast<double>(bytes_downloaded) /
                                      static_cast<double>(total_bytes)) *
                                     50.0);
      }
      break;
    }

    case STATE_DOWNLOAD_COMPLETE:
    case STATE_EXTRACTING:
    case STATE_APPLYING_DIFFERENTIAL_PATCH:
    case STATE_READY_TO_INSTALL:
    case STATE_WAITING_TO_INSTALL:
      *progress = 50;
      break;

    case STATE_INSTALLING: {
      *progress = 50;
      LONG install_progress = 0;
      if (SUCCEEDED(current_state->get_installProgress(&install_progress)) &&
          install_progress >= 0 && install_progress <= 100) {
        // 50-100 is installing.
        *progress = (50 + install_progress / 2);
      }
      break;
    }

    case STATE_INSTALL_COMPLETE:
    case STATE_PAUSED:
    case STATE_NO_UPDATE:
    case STATE_ERROR:
    default:
      NOTREACHED_IN_MIGRATION();
      return false;
  }
  return true;
}

void UpdateCheckDriver::PollGoogleUpdate() {
  Microsoft::WRL::ComPtr<ICurrentState> state;
  CurrentState state_value = STATE_INIT;
  HRESULT hresult = S_OK;
  GoogleUpdateErrorCode error_code = GOOGLE_UPDATE_NO_ERROR;
  std::optional<int> installer_exit_code;
  std::u16string error_string;
  GoogleUpdateUpgradeStatus upgrade_status = UPGRADE_ERROR;
  std::u16string new_version;
  int progress = 0;

  if (!GetCurrentState(&state, &state_value, &hresult)) {
    OnUpgradeError({GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR, hresult},
                   std::nullopt, std::u16string());
  } else if (IsErrorState(state, state_value, &error_code, &hresult,
                          &installer_exit_code, &error_string)) {
    OnUpgradeError({error_code, hresult}, installer_exit_code, error_string);
  } else if (IsFinalState(state, state_value, &upgrade_status, &new_version)) {
    status_ = upgrade_status;
    update_state_.error_code = GOOGLE_UPDATE_NO_ERROR;
    html_error_message_.clear();
    if (!new_version.empty())
      update_state_.new_version = new_version;
    update_state_.hresult = S_OK;
    update_state_.installer_exit_code.reset();
  } else if (IsIntermediateState(state, state_value, &new_version, &progress)) {
    bool got_new_version =
        update_state_.new_version.empty() && !new_version.empty();
    if (got_new_version)
      update_state_.new_version = new_version;
    // Give the caller this status update if it differs from the last one given.
    if (got_new_version || progress != last_reported_progress_) {
      last_reported_progress_ = progress;

      // It is safe to post this task with an unretained pointer since the task
      // is guaranteed to run before a subsequent DeleteSoon is handled.
      result_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&UpdateCheckDriver::NotifyUpgradeProgress,
                         base::Unretained(this), last_reported_progress_,
                         update_state_.new_version));
    }

    // Schedule the next check.
    task_runner_->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&UpdateCheckDriver::PollGoogleUpdate,
                       base::Unretained(this)),
        base::Milliseconds(kGoogleUpdatePollIntervalMs));
    // Early return for this non-terminal state.
    return;
  }

  // Release the reference on the COM objects before bouncing back to the
  // caller's thread.
  state.Reset();
  app_.Reset();
  app_bundle_.Reset();
  google_update_.Reset();

  result_runner_->DeleteSoon(FROM_HERE, this);
}

void UpdateCheckDriver::OnUpgradeError(UpdateCheckResult check_result,
                                       std::optional<int> installer_exit_code,
                                       const std::u16string& error_string) {
  status_ = UPGRADE_ERROR;
  update_state_.error_code = check_result.error_code;
  update_state_.hresult = check_result.hresult;
  update_state_.installer_exit_code = installer_exit_code;

  // Some specific result codes have dedicated messages.
  if (check_result.hresult == GOOPDATE_E_APP_USING_EXTERNAL_UPDATER) {
    html_error_message_ = l10n_util::GetStringUTF16(
        IDS_ABOUT_BOX_EXTERNAL_UPDATE_IS_RUNNING);
    return;
  }

  std::u16string html_error_msg = base::UTF8ToUTF16(base::StringPrintf(
      "%d: <a href='%s%#lX' target=_blank>%#lX</a>", update_state_.error_code,
      chrome::kUpgradeHelpCenterBaseURL, update_state_.hresult,
      update_state_.hresult));
  if (update_state_.installer_exit_code) {
    html_error_msg +=
        u": " + base::NumberToString16(*update_state_.installer_exit_code);
  }
  if (system_level_install_)
    html_error_msg += u" -- system level";
  if (error_string.empty()) {
    html_error_message_ = l10n_util::GetStringFUTF16(
        IDS_ABOUT_BOX_ERROR_UPDATE_CHECK_FAILED, html_error_msg);
  } else {
    html_error_message_ = l10n_util::GetStringFUTF16(
        IDS_ABOUT_BOX_GOOGLE_UPDATE_ERROR, error_string, html_error_msg);
  }
}

}  // namespace


// Globals ---------------------------------------------------------------------

void BeginUpdateCheck(
    const std::string& locale,
    bool install_update_if_possible,
    gfx::AcceleratedWidget elevation_window,
    const base::WeakPtr<UpdateCheckDelegate>& delegate) {
  UpdateCheckDriver::RunUpdateCheck(locale, install_update_if_possible,
                                    elevation_window, delegate);
}

// UpdateState -----------------------------------------------------------------

UpdateState::UpdateState() = default;
UpdateState::UpdateState(const UpdateState&) = default;
UpdateState::UpdateState(UpdateState&&) = default;
UpdateState& UpdateState::operator=(UpdateState&&) = default;
UpdateState::~UpdateState() = default;

std::optional<UpdateState> GetLastUpdateState() {
  return *GetLastUpdateStateStorage();
}

// Private API exposed for testing. --------------------------------------------

void SetGoogleUpdateFactoryForTesting(
    GoogleUpdate3ClassFactory google_update_factory) {
  if (g_google_update_factory) {
    delete g_google_update_factory;
    g_google_update_factory = nullptr;
  }
  if (!google_update_factory.is_null()) {
    g_google_update_factory =
        new GoogleUpdate3ClassFactory(std::move(google_update_factory));
  }
}

// TODO(calamity): Remove once a MockTimer is implemented in
// TaskEnvironment. See https://crbug.com/708584.
void SetUpdateDriverTaskRunnerForTesting(
    base::SingleThreadTaskRunner* task_runner) {
  g_update_driver_task_runner = task_runner;
}