| // Copyright 2020 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/credential_provider/setup/setup_utils.h" |
| |
| #include <Windows.h> |
| |
| #include <string> |
| |
| #include "base/files/file_util.h" |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/registry.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/gcp_utils.h" |
| #include "chrome/credential_provider/gaiacp/logging.h" |
| #include "chrome/credential_provider/gaiacp/reg_utils.h" |
| #include "chrome/credential_provider/setup/gcpw_files.h" |
| |
| namespace credential_provider { |
| |
| namespace switches { |
| |
| const char kParentHandle[] = "parent-handle"; |
| const char kInstallPath[] = "install-path"; |
| const char kUninstall[] = "uninstall"; |
| |
| const char kEnableStats[] = "enable-stats"; |
| const char kDisableStats[] = "disable-stats"; |
| |
| const char kInstallerData[] = "installerdata"; |
| |
| const char kStandaloneInstall[] = "standalone"; |
| } // namespace switches |
| |
| namespace { |
| |
| // Path to the msi json value inside the dictionary which is parsed from |
| // installer data argument in the command line. |
| const char kMsiJsonPath[] = "distribution.msi"; |
| |
| // The registry name which is saved to indicate installation source. |
| const wchar_t kMsiInstall[] = L"msi"; |
| |
| // Parses the json data and returns it as a dictionary. If the json data isn't |
| // valid, returns nullptr. |
| base::DictionaryValue* ParseDistributionPreferences( |
| const std::string& json_data) { |
| JSONStringValueDeserializer json(json_data); |
| std::string error; |
| std::unique_ptr<base::Value> root(json.Deserialize(nullptr, &error)); |
| if (!root.get()) { |
| LOGFN(WARNING) << "Failed to parse initial prefs file: " << error; |
| return nullptr; |
| } |
| if (!root->is_dict()) { |
| LOGFN(WARNING) << "Failed to parse installer data file"; |
| return nullptr; |
| } |
| return static_cast<base::DictionaryValue*>(root.release()); |
| } |
| |
| } // namespace |
| |
| StandaloneInstallerConfigurator::StandaloneInstallerConfigurator() |
| : is_msi_installation_(false) {} |
| |
| StandaloneInstallerConfigurator::~StandaloneInstallerConfigurator() {} |
| |
| // static |
| StandaloneInstallerConfigurator** |
| StandaloneInstallerConfigurator::GetInstanceStorage() { |
| static StandaloneInstallerConfigurator* instance = |
| new StandaloneInstallerConfigurator(); |
| |
| return &instance; |
| } |
| |
| // static |
| StandaloneInstallerConfigurator* StandaloneInstallerConfigurator::Get() { |
| return *GetInstanceStorage(); |
| } |
| |
| // Sets the installer source for GCPW. When installed through MSI, |
| // contains installer data file name as argument. |
| void StandaloneInstallerConfigurator::ConfigureInstallationType( |
| const base::CommandLine& cmdline) { |
| // There are following scenarios for installations: |
| // First time install from MSI |
| // First time install from EXE |
| // MSIs before this kMsiInstall registry gets auto-updated |
| // MSIs with kMsiInstall registry gets auto-updated |
| // EXEs with kMsiInstall registry gets auto-updated |
| |
| // |kStandaloneInstall| indicates fresh installation. |
| if (cmdline.HasSwitch(switches::kStandaloneInstall)) { |
| base::Value* is_msi = nullptr; |
| if (cmdline.HasSwitch(switches::kInstallerData)) { |
| base::FilePath prefs_path( |
| cmdline.GetSwitchValuePath(switches::kInstallerData)); |
| |
| if (InitializeFromInstallerData(prefs_path)) |
| is_msi = installer_data_dictionary_->FindPath(kMsiJsonPath); |
| } |
| |
| is_msi_installation_ = false; |
| if (is_msi && is_msi->is_bool() && is_msi->GetBool()) { |
| is_msi_installation_ = true; |
| } |
| } else { |
| // Honor the registry if it is found, otherwise fall back to MSI |
| // installation. |
| is_msi_installation_ = |
| GetUpdaterClientsAppPathFlagOrDefault(kMsiInstall, 1); |
| } |
| |
| HRESULT hr = |
| SetUpdaterClientsAppPathFlag(kMsiInstall, is_msi_installation_ ? 1 : 0); |
| if (FAILED(hr)) |
| LOGFN(ERROR) << "SetGlobalFlag failed" << putHR(hr); |
| } |
| |
| std::wstring StandaloneInstallerConfigurator::GetCurrentDate() { |
| static const wchar_t kDateFormat[] = L"yyyyMMdd"; |
| wchar_t date_str[std::size(kDateFormat)] = {0}; |
| int len = GetDateFormatW(LOCALE_INVARIANT, 0, nullptr, kDateFormat, date_str, |
| std::size(date_str)); |
| if (len) { |
| --len; // Subtract terminating \0. |
| } else { |
| LOGFN(ERROR) << "GetDateFormat failed"; |
| return L""; |
| } |
| |
| return std::wstring(date_str, len); |
| } |
| |
| bool StandaloneInstallerConfigurator::IsStandaloneInstallation() const { |
| return !is_msi_installation_; |
| } |
| |
| HRESULT StandaloneInstallerConfigurator::AddUninstallKey( |
| const base::FilePath& install_path) { |
| LOGFN(VERBOSE); |
| |
| if (is_msi_installation_) |
| return S_OK; |
| |
| std::wstring uninstall_reg = kRegUninstall; |
| uninstall_reg.append(L"\\"); |
| uninstall_reg.append(kRegUninstallProduct); |
| |
| base::win::RegKey key; |
| LONG status = |
| key.Create(HKEY_LOCAL_MACHINE, uninstall_reg.c_str(), KEY_SET_VALUE); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to create " << uninstall_reg << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| base::CommandLine uninstall_string( |
| install_path.Append(kCredentialProviderSetupExe)); |
| uninstall_string.AppendSwitch(switches::kUninstall); |
| |
| status = key.WriteValue(kRegUninstallString, |
| uninstall_string.GetCommandLineString().c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegUninstallString |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegUninstallDisplayName, |
| GetStringResource(IDS_PROJNAME_BASE).c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegUninstallDisplayName |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegInstallLocation, install_path.value().c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegInstallLocation |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue( |
| kRegDisplayIcon, |
| (install_path.Append(kCredentialProviderSetupExe).value() + L",0") |
| .c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegDisplayIcon |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegNoModify, 1); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegNoModify << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegNoRepair, 1); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegNoRepair << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegPublisherName, kRegPublisher); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegPublisherName |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegInstallDate, GetCurrentDate().c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegInstallDate |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| base::Version version(CHROME_VERSION_STRING); |
| |
| status = key.WriteValue(kRegVersion, |
| base::ASCIIToWide(version.GetString()).c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegVersion << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegDisplayVersion, |
| base::ASCIIToWide(version.GetString()).c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegDisplayVersion |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| const std::vector<uint32_t>& version_components = version.components(); |
| if (version_components.size() == 4) { |
| status = key.WriteValue(kRegVersionMajor, |
| static_cast<DWORD>(version_components[2])); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegVersionMajor |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.WriteValue(kRegVersionMinor, |
| static_cast<DWORD>(version_components[3])); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegVersionMinor |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT StandaloneInstallerConfigurator::RemoveUninstallKey() { |
| LOGFN(VERBOSE); |
| base::win::RegKey key; |
| |
| LONG status = key.Create(HKEY_LOCAL_MACHINE, kRegUninstall, KEY_SET_VALUE); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to create " << kRegUninstall << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| status = key.DeleteKey(kRegUninstallProduct); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to delete " << kRegUninstallProduct |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| return S_OK; |
| } |
| |
| bool StandaloneInstallerConfigurator::InitializeFromInstallerData( |
| base::FilePath prefs_path) { |
| std::string json_data; |
| if (base::PathExists(prefs_path) && |
| !base::ReadFileToString(prefs_path, &json_data)) { |
| LOGFN(ERROR) << "Failed to read preferences from " << prefs_path.value(); |
| return false; |
| } |
| |
| if (json_data.empty()) { |
| LOGFN(WARNING) << "Installer data is empty!"; |
| return false; |
| } |
| |
| installer_data_dictionary_.reset(ParseDistributionPreferences(json_data)); |
| |
| if (!installer_data_dictionary_) { |
| LOGFN(WARNING) << "Installer data is empty!"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace credential_provider |