| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/installer/util/google_update_util.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/path_service.h" |
| #include "base/process/kill.h" |
| #include "base/process/launch.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_split.h" |
| #include "base/time/time.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/win_util.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/installer/launcher_support/chrome_launcher_support.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "chrome/installer/util/google_update_constants.h" |
| #include "chrome/installer/util/google_update_settings.h" |
| #include "chrome/installer/util/install_util.h" |
| #include "chrome/installer/util/installation_state.h" |
| #include "chrome/installer/util/product.h" |
| |
| using base::win::RegKey; |
| |
| namespace google_update { |
| |
| namespace { |
| |
| const int kGoogleUpdateTimeoutMs = 20 * 1000; |
| |
| const char kEnvVariableUntrustedData[] = "GoogleUpdateUntrustedData"; |
| const int kUntrustedDataMaxLength = 4096; |
| |
| // Returns true if Google Update is present at the given level. |
| bool IsGoogleUpdatePresent(bool system_install) { |
| // Using the existence of version key in the registry to decide. |
| return GoogleUpdateSettings::GetGoogleUpdateVersion(system_install).IsValid(); |
| } |
| |
| // Returns GoogleUpdateSetup.exe's executable path at specified level. |
| // or an empty path if none is found. |
| base::FilePath GetGoogleUpdateSetupExe(bool system_install) { |
| const HKEY root_key = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
| RegKey update_key; |
| |
| if (update_key.Open(root_key, kRegPathGoogleUpdate, KEY_QUERY_VALUE) == |
| ERROR_SUCCESS) { |
| base::string16 path_str; |
| base::string16 version_str; |
| if ((update_key.ReadValue(kRegPathField, &path_str) == ERROR_SUCCESS) && |
| (update_key.ReadValue(kRegGoogleUpdateVersion, &version_str) == |
| ERROR_SUCCESS)) { |
| return base::FilePath(path_str).DirName().Append(version_str). |
| Append(kGoogleUpdateSetupExe); |
| } |
| } |
| return base::FilePath(); |
| } |
| |
| // If Google Update is present at system-level, sets |cmd_string| to the command |
| // line to install Google Update at user-level and returns true. |
| // Otherwise, clears |cmd_string| and returns false. |
| bool GetUserLevelGoogleUpdateInstallCommandLine(base::string16* cmd_string) { |
| cmd_string->clear(); |
| base::FilePath google_update_setup( |
| GetGoogleUpdateSetupExe(true)); // system-level. |
| if (!google_update_setup.empty()) { |
| CommandLine cmd(google_update_setup); |
| // Appends "/install runtime=true&needsadmin=false /silent /nomitag". |
| // NB: /nomitag needs to be at the end. |
| // Constants are found in code.google.com/p/omaha/common/const_cmd_line.h. |
| cmd.AppendArg("/install"); |
| // The "&" can be used in base::LaunchProcess() without quotation |
| // (this is problematic only if run from command prompt). |
| cmd.AppendArg("runtime=true&needsadmin=false"); |
| cmd.AppendArg("/silent"); |
| cmd.AppendArg("/nomitag"); |
| *cmd_string = cmd.GetCommandLineString(); |
| } |
| return !cmd_string->empty(); |
| } |
| |
| // Launches command |cmd_string|, and waits for |timeout| milliseconds before |
| // timing out. To wait indefinitely, one can set |
| // |timeout| to be base::TimeDelta::FromMilliseconds(INFINITE). |
| // Returns true if this executes successfully. |
| // Returns false if command execution fails to execute, or times out. |
| bool LaunchProcessAndWaitWithTimeout(const base::string16& cmd_string, |
| base::TimeDelta timeout) { |
| bool success = false; |
| base::win::ScopedHandle process; |
| int exit_code = 0; |
| VLOG(0) << "Launching: " << cmd_string; |
| if (!base::LaunchProcess(cmd_string, base::LaunchOptions(), |
| &process)) { |
| PLOG(ERROR) << "Failed to launch (" << cmd_string << ")"; |
| } else if (!base::WaitForExitCodeWithTimeout(process, &exit_code, timeout)) { |
| // The GetExitCodeProcess failed or timed-out. |
| LOG(ERROR) <<"Command (" << cmd_string << ") is taking more than " |
| << timeout.InMilliseconds() << " milliseconds to complete."; |
| } else if (exit_code != 0) { |
| LOG(ERROR) << "Command (" << cmd_string << ") exited with code " |
| << exit_code; |
| } else { |
| success = true; |
| } |
| return success; |
| } |
| |
| bool IsNotPrintable(unsigned char c) { |
| return c < 32 || c >= 127; |
| } |
| |
| // Returns whether or not |s| consists of printable characters. |
| bool IsStringPrintable(const std::string& s) { |
| return std::find_if(s.begin(), s.end(), IsNotPrintable) == s.end(); |
| } |
| |
| bool IsIllegalUntrustedDataKeyChar(unsigned char c) { |
| return !(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || |
| c >= '0' && c <= '9' || c == '-' || c == '_' || c == '$'); |
| } |
| |
| // Returns true if |key| from untrusted data is valid. |
| bool IsUntrustedDataKeyValid(const std::string& key) { |
| return std::find_if(key.begin(), key.end(), IsIllegalUntrustedDataKeyChar) |
| == key.end(); |
| } |
| |
| // Parses |data_string| as key-value pairs and overwrites |untrusted_data| with |
| // the result. Returns true if the data could be parsed. |
| bool ParseUntrustedData( |
| const std::string& data_string, |
| std::map<std::string, std::string>* untrusted_data) { |
| DCHECK(untrusted_data); |
| if (data_string.length() > kUntrustedDataMaxLength || |
| !IsStringPrintable(data_string)) { |
| LOG(ERROR) << "Invalid value in untrusted data string."; |
| return false; |
| } |
| |
| VLOG(1) << "Untrusted data string: " << data_string; |
| |
| std::vector<std::pair<std::string, std::string> > kv_pairs; |
| if (!base::SplitStringIntoKeyValuePairs(data_string, '=', '&', &kv_pairs)) { |
| LOG(ERROR) << "Failed to parse untrusted data: " << data_string; |
| return false; |
| } |
| |
| untrusted_data->clear(); |
| std::vector<std::pair<std::string, std::string> >::const_iterator it; |
| for (it = kv_pairs.begin(); it != kv_pairs.end(); ++it) { |
| const std::string& key(it->first); |
| // TODO(huangs): URL unescape |value|. |
| const std::string& value(it->second); |
| if (IsUntrustedDataKeyValid(key) && IsStringPrintable(value)) |
| (*untrusted_data)[key] = value; |
| else |
| LOG(ERROR) << "Illegal character found in untrusted data."; |
| } |
| return true; |
| } |
| |
| // Reads and parses untrusted data passed from Google Update as key-value |
| // pairs, then overwrites |untrusted_data_map| with the result. |
| // Returns true if data are successfully read. |
| bool GetGoogleUpdateUntrustedData( |
| std::map<std::string, std::string>* untrusted_data) { |
| scoped_ptr<base::Environment> env(base::Environment::Create()); |
| std::string data_string; |
| if (!env || !env->GetVar(kEnvVariableUntrustedData, &data_string)) |
| return false; |
| |
| return ParseUntrustedData(data_string, untrusted_data); |
| } |
| |
| } // namespace |
| |
| bool EnsureUserLevelGoogleUpdatePresent() { |
| VLOG(0) << "Ensuring Google Update is present at user-level."; |
| |
| bool success = false; |
| if (IsGoogleUpdatePresent(false)) { |
| success = true; |
| } else { |
| base::string16 cmd_string; |
| if (!GetUserLevelGoogleUpdateInstallCommandLine(&cmd_string)) { |
| LOG(ERROR) << "Cannot find Google Update at system-level."; |
| // Ideally we should return false. However, this case should not be |
| // encountered by regular users, and developers (who often installs |
| // Chrome without Google Update) may be unduly impeded by this case. |
| // Therefore we return true. |
| success = true; |
| } else { |
| success = LaunchProcessAndWaitWithTimeout(cmd_string, |
| base::TimeDelta::FromMilliseconds(INFINITE)); |
| } |
| } |
| return success; |
| } |
| |
| bool UninstallGoogleUpdate(bool system_install) { |
| bool success = false; |
| base::string16 cmd_string( |
| GoogleUpdateSettings::GetUninstallCommandLine(system_install)); |
| if (cmd_string.empty()) { |
| success = true; // Nothing to; vacuous success. |
| } else { |
| success = LaunchProcessAndWaitWithTimeout(cmd_string, |
| base::TimeDelta::FromMilliseconds(kGoogleUpdateTimeoutMs)); |
| } |
| return success; |
| } |
| |
| void ElevateIfNeededToReenableUpdates() { |
| base::FilePath chrome_exe; |
| if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { |
| NOTREACHED(); |
| return; |
| } |
| installer::ProductState product_state; |
| BrowserDistribution* dist = BrowserDistribution::GetDistribution(); |
| const bool system_install = !InstallUtil::IsPerUserInstall( |
| chrome_exe.value().c_str()); |
| if (!product_state.Initialize(system_install, dist)) |
| return; |
| base::FilePath exe_path(product_state.GetSetupPath()); |
| if (exe_path.empty() || !base::PathExists(exe_path)) { |
| LOG(ERROR) << "Could not find setup.exe to reenable updates."; |
| return; |
| } |
| |
| CommandLine cmd(exe_path); |
| cmd.AppendSwitch(installer::switches::kReenableAutoupdates); |
| installer::Product product(dist); |
| product.InitializeFromUninstallCommand(product_state.uninstall_command()); |
| product.AppendProductFlags(&cmd); |
| if (system_install) |
| cmd.AppendSwitch(installer::switches::kSystemLevel); |
| if (product_state.uninstall_command().HasSwitch( |
| installer::switches::kVerboseLogging)) { |
| cmd.AppendSwitch(installer::switches::kVerboseLogging); |
| } |
| |
| base::LaunchOptions launch_options; |
| launch_options.force_breakaway_from_job_ = true; |
| |
| if (base::win::GetVersion() >= base::win::VERSION_VISTA && |
| base::win::UserAccountControlIsEnabled()) { |
| base::LaunchElevatedProcess(cmd, launch_options, NULL); |
| } else { |
| base::LaunchProcess(cmd, launch_options, NULL); |
| } |
| } |
| |
| std::string GetUntrustedDataValue(const std::string& key) { |
| std::map<std::string, std::string> untrusted_data; |
| if (GetGoogleUpdateUntrustedData(&untrusted_data)) { |
| std::map<std::string, std::string>::const_iterator data_it( |
| untrusted_data.find(key)); |
| if (data_it != untrusted_data.end()) |
| return data_it->second; |
| } |
| |
| return std::string(); |
| } |
| |
| std::string GetUntrustedDataValueFromTag(const std::string& tag, |
| const std::string& key) { |
| std::map<std::string, std::string> untrusted_data; |
| if (ParseUntrustedData(tag, &untrusted_data)) |
| return untrusted_data[key]; |
| |
| return std::string(); |
| } |
| |
| } // namespace google_update |