blob: 14fcde2fb87e9c7372c523c6095924066dc47231 [file] [log] [blame]
// 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.
//
// This file defines functions that integrate Chrome in Windows shell. These
// functions can be used by Chrome as well as Chrome installer. All of the
// work is done by the local functions defined in anonymous namespace in
// this class.
#include "chrome/installer/util/shell_util.h"
#include <objbase.h>
#include <shlobj.h>
#include <shobjidl.h>
#include <wrl/client.h>
#include <algorithm>
#include <iterator>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/md5.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/cancellation_flag.h"
#include "base/values.h"
#include "base/win/registry.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/shortcut.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/install_static/install_constants.h"
#include "chrome/install_static/install_details.h"
#include "chrome/install_static/install_modes.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/beacons.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/installer_util_strings.h"
#include "chrome/installer/util/l10n_string_util.h"
#include "chrome/installer/util/master_preferences.h"
#include "chrome/installer/util/master_preferences_constants.h"
#include "chrome/installer/util/registry_entry.h"
#include "chrome/installer/util/scoped_user_protocol_entry.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/installer/util/work_item.h"
#include "components/base32/base32.h"
using base::win::RegKey;
namespace {
// An enum used to tell QuickIsChromeRegistered() which level of registration
// the caller wants to confirm.
enum RegistrationConfirmationLevel {
// Only look for Chrome's ProgIds.
// This is sufficient when we are trying to determine the suffix of the
// currently running Chrome as shell integration registrations might not be
// present.
CONFIRM_PROGID_REGISTRATION = 0,
// Confirm that Chrome is fully integrated with Windows (i.e. registered with
// Defaut Programs). These registrations can be in HKCU as of Windows 8.
// Note: Shell registration implies ProgId registration.
CONFIRM_SHELL_REGISTRATION,
// Same as CONFIRM_SHELL_REGISTRATION, but only look in HKLM (used when
// uninstalling to know whether elevation is required to clean up the
// registry).
CONFIRM_SHELL_REGISTRATION_IN_HKLM,
};
const wchar_t kReinstallCommand[] = L"ReinstallCommand";
// Returns the current (or installed) browser's ProgId (e.g.
// "ChromeHTML|suffix|").
// |suffix| can be the empty string.
base::string16 GetBrowserProgId(const base::string16& suffix) {
base::string16 chrome_html(install_static::GetProgIdPrefix());
chrome_html.append(suffix);
// ProgIds cannot be longer than 39 characters.
// Ref: http://msdn.microsoft.com/en-us/library/aa911706.aspx.
// Make all new registrations comply with this requirement (existing
// registrations must be preserved).
base::string16 new_style_suffix;
if (ShellUtil::GetUserSpecificRegistrySuffix(&new_style_suffix) &&
suffix == new_style_suffix && chrome_html.length() > 39) {
NOTREACHED();
chrome_html.erase(39);
}
return chrome_html;
}
// Returns the browser's application name. This application name will be
// suffixed as is appropriate for the current install. This is the name that is
// registered with Default Programs on Windows and that should thus be used to
// "make chrome default" and such.
base::string16 GetApplicationName(const base::FilePath& chrome_exe) {
return install_static::GetBaseAppName().append(
ShellUtil::GetCurrentInstallationSuffix(chrome_exe));
}
// This class is used to initialize and cache a base 32 encoding of the md5 hash
// of this user's sid preceded by a dot.
// This is guaranteed to be unique on the machine and 27 characters long
// (including the '.').
// This is then meant to be used as a suffix on all registrations that may
// conflict with another user-level Chrome install.
class UserSpecificRegistrySuffix {
public:
// All the initialization is done in the constructor to be able to build the
// suffix in a thread-safe manner when used in conjunction with a
// LazyInstance.
UserSpecificRegistrySuffix();
// Sets |suffix| to the pre-computed suffix cached in this object.
// Returns true unless the initialization originally failed.
bool GetSuffix(base::string16* suffix);
private:
base::string16 suffix_;
DISALLOW_COPY_AND_ASSIGN(UserSpecificRegistrySuffix);
}; // class UserSpecificRegistrySuffix
UserSpecificRegistrySuffix::UserSpecificRegistrySuffix() {
base::string16 user_sid;
if (!base::win::GetUserSidString(&user_sid)) {
NOTREACHED();
return;
}
static_assert(sizeof(base::MD5Digest) == 16, "size of MD5 not as expected");
base::MD5Digest md5_digest;
std::string user_sid_ascii(base::UTF16ToASCII(user_sid));
base::MD5Sum(user_sid_ascii.c_str(), user_sid_ascii.length(), &md5_digest);
std::string base32_md5 = base32::Base32Encode(
base::StringPiece(reinterpret_cast<char*>(md5_digest.a),
base::size(md5_digest.a)),
base32::Base32EncodePolicy::OMIT_PADDING);
// The value returned by the base32 algorithm above must never change.
DCHECK_EQ(base32_md5.length(), 26U);
suffix_.reserve(base32_md5.length() + 1);
suffix_.assign(1, L'.');
suffix_.append(base::ASCIIToUTF16(base32_md5));
}
bool UserSpecificRegistrySuffix::GetSuffix(base::string16* suffix) {
if (suffix_.empty()) {
NOTREACHED();
return false;
}
suffix->assign(suffix_);
return true;
}
// Details about a Windows application, to be entered into the registry for the
// purpose of file associations.
struct ApplicationInfo {
ApplicationInfo() : file_type_icon_index(0), application_icon_index(0) {}
// The ProgId used by Windows for file associations with this application.
// Must not be empty or start with a '.'.
base::string16 prog_id;
// The friendly name, and the path of the icon that will be used for files of
// these types when associated with this application by default. (They are NOT
// the name/icon that will represent the application under the Open With
// menu.)
base::string16 file_type_name;
base::FilePath file_type_icon_path;
int file_type_icon_index;
// The command to execute when opening a file via this association. It should
// contain "%1" (to tell Windows to pass the filename as an argument).
// TODO(mgiuca): |command_line| should be a base::CommandLine.
base::string16 command_line;
// The AppUserModelId used by Windows 8 for this application. Distinct from
// |prog_id|.
base::string16 app_id;
// User-visible details about this application. Any of these may be empty.
base::string16 application_name;
base::FilePath application_icon_path;
int application_icon_index;
base::string16 application_description;
base::string16 publisher_name;
// The CLSID for the application's DelegateExecute handler. May be empty.
base::string16 delegate_clsid;
};
// Returns the Windows browser client registration key for Chrome. For example:
// "Software\Clients\StartMenuInternet\Chromium[.user]". Strictly speaking, we
// should use the name of the executable (e.g., "chrome.exe"), but that ship has
// sailed. The cost of switching now is re-prompting users to make Chrome their
// default browser, which isn't polite. |suffix| is the user-specific
// registration suffix; see GetUserSpecificDefaultBrowserSuffix in shell_util.h
// for details.
base::string16 GetBrowserClientKey(const base::string16& suffix) {
DCHECK(suffix.empty() || suffix[0] == L'.');
return base::string16(ShellUtil::kRegStartMenuInternet)
.append(1, L'\\')
.append(install_static::GetBaseAppName())
.append(suffix);
}
// Returns the Windows Default Programs capabilities key for Chrome. For
// example:
// "Software\Clients\StartMenuInternet\Chromium[.user]\Capabilities".
base::string16 GetCapabilitiesKey(const base::string16& suffix) {
return GetBrowserClientKey(suffix).append(L"\\Capabilities");
}
// DelegateExecute ProgId. Needed for Chrome Metro in Windows 8. This is only
// needed for registring a web browser, not for general associations.
std::vector<std::unique_ptr<RegistryEntry>> GetChromeDelegateExecuteEntries(
const base::FilePath& chrome_exe,
const ApplicationInfo& app_info) {
std::vector<std::unique_ptr<RegistryEntry>> entries;
base::string16 app_id_shell_key(ShellUtil::kRegClasses);
app_id_shell_key.push_back(base::FilePath::kSeparators[0]);
app_id_shell_key.append(app_info.app_id);
app_id_shell_key.append(ShellUtil::kRegExePath);
app_id_shell_key.append(ShellUtil::kRegShellPath);
// <root hkey>\Software\Classes\<app_id>\.exe\shell @=open
entries.push_back(std::make_unique<RegistryEntry>(app_id_shell_key,
ShellUtil::kRegVerbOpen));
// The command to execute when opening this application via the Metro UI.
const base::string16 delegate_command(
ShellUtil::GetChromeDelegateCommand(chrome_exe));
// Each of Chrome's shortcuts has an appid; which, as of Windows 8, is
// registered to handle some verbs. This registration has the side-effect
// that these verbs now show up in the shortcut's context menu. We
// mitigate this side-effect by making the context menu entries
// user readable/localized strings. See relevant MSDN article:
// http://msdn.microsoft.com/en-US/library/windows/desktop/cc144171.aspx
static const struct {
const wchar_t* verb;
int name_id;
} verbs[] = {
{ShellUtil::kRegVerbOpen, -1},
{ShellUtil::kRegVerbOpenNewWindow, IDS_SHORTCUT_NEW_WINDOW_BASE},
};
for (const auto& verb_and_id : verbs) {
base::string16 sub_path(app_id_shell_key);
sub_path.push_back(base::FilePath::kSeparators[0]);
sub_path.append(verb_and_id.verb);
// <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>
if (verb_and_id.name_id != -1) {
// TODO(grt): http://crbug.com/75152 Write a reference to a localized
// resource.
const base::string16 verb_name(
installer::GetLocalizedString(verb_and_id.name_id));
entries.push_back(
std::make_unique<RegistryEntry>(sub_path, verb_name.c_str()));
}
entries.push_back(std::make_unique<RegistryEntry>(sub_path, L"CommandId",
L"Browser.Launch"));
sub_path.push_back(base::FilePath::kSeparators[0]);
sub_path.append(ShellUtil::kRegCommand);
// <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>\command
entries.push_back(
std::make_unique<RegistryEntry>(sub_path, delegate_command));
entries.push_back(std::make_unique<RegistryEntry>(
sub_path, ShellUtil::kRegDelegateExecute, app_info.delegate_clsid));
}
return entries;
}
// Gets the registry entries to register an application in the Windows registry.
// |app_info| provides all of the information needed.
void GetProgIdEntries(const ApplicationInfo& app_info,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
// Basic sanity checks.
DCHECK(!app_info.prog_id.empty());
DCHECK_NE(L'.', app_info.prog_id[0]);
// File association ProgId
base::string16 prog_id_path(ShellUtil::kRegClasses);
prog_id_path.push_back(base::FilePath::kSeparators[0]);
prog_id_path.append(app_info.prog_id);
entries->push_back(
std::make_unique<RegistryEntry>(prog_id_path, app_info.file_type_name));
entries->push_back(std::make_unique<RegistryEntry>(
prog_id_path + ShellUtil::kRegDefaultIcon,
ShellUtil::FormatIconLocation(app_info.file_type_icon_path,
app_info.file_type_icon_index)));
entries->push_back(std::make_unique<RegistryEntry>(
prog_id_path + ShellUtil::kRegShellOpen, app_info.command_line));
if (!app_info.delegate_clsid.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
prog_id_path + ShellUtil::kRegShellOpen, ShellUtil::kRegDelegateExecute,
app_info.delegate_clsid));
// TODO(scottmg): Simplify after Metro removal. https://crbug.com/558054.
entries->back()->set_removal_flag(RegistryEntry::RemovalFlag::VALUE);
}
// The following entries are required as of Windows 8, but do not
// depend on the DelegateExecute verb handler being set.
if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
if (!app_info.app_id.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
prog_id_path, ShellUtil::kRegAppUserModelId, app_info.app_id));
}
// Add \Software\Classes\<prog_id>\Application entries
base::string16 application_path(prog_id_path + ShellUtil::kRegApplication);
if (!app_info.app_id.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
application_path, ShellUtil::kRegAppUserModelId, app_info.app_id));
}
if (!app_info.application_icon_path.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
application_path, ShellUtil::kRegApplicationIcon,
ShellUtil::FormatIconLocation(app_info.application_icon_path,
app_info.application_icon_index)));
}
if (!app_info.application_name.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
application_path, ShellUtil::kRegApplicationName,
app_info.application_name));
}
if (!app_info.application_description.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
application_path, ShellUtil::kRegApplicationDescription,
app_info.application_description));
}
if (!app_info.publisher_name.empty()) {
entries->push_back(std::make_unique<RegistryEntry>(
application_path, ShellUtil::kRegApplicationCompany,
app_info.publisher_name));
}
}
}
// This method returns a list of all the registry entries that are needed to
// register this installation's ProgId and AppId. These entries need to be
// registered in HKLM prior to Win8.
void GetChromeProgIdEntries(
const base::FilePath& chrome_exe,
const base::string16& suffix,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
int chrome_icon_index = install_static::GetIconResourceIndex();
ApplicationInfo app_info;
app_info.prog_id = GetBrowserProgId(suffix);
app_info.file_type_name = install_static::GetProgIdDescription();
// File types associated with Chrome are just given the Chrome icon.
app_info.file_type_icon_path = chrome_exe;
app_info.file_type_icon_index = chrome_icon_index;
app_info.command_line = ShellUtil::GetChromeShellOpenCmd(chrome_exe);
// For user-level installs: entries for the app id will be in HKCU; thus we
// do not need a suffix on those entries.
app_info.app_id =
ShellUtil::GetBrowserModelId(InstallUtil::IsPerUserInstall());
// TODO(grt): http://crbug.com/75152 Write a reference to a localized
// resource for name, description, and company.
app_info.application_name = InstallUtil::GetDisplayName();
app_info.application_icon_path = chrome_exe;
app_info.application_icon_index = chrome_icon_index;
app_info.application_description = InstallUtil::GetAppDescription();
app_info.publisher_name = InstallUtil::GetPublisherName();
app_info.delegate_clsid = install_static::GetLegacyCommandExecuteImplClsid();
GetProgIdEntries(app_info, entries);
if (!app_info.delegate_clsid.empty()) {
auto delegate_execute_entries =
GetChromeDelegateExecuteEntries(chrome_exe, app_info);
// Remove the keys (not only their values) so that Windows will continue
// to launch Chrome without a pesky association error.
// TODO(scottmg): Simplify after Metro removal. https://crbug.com/558054.
for (const auto& entry : delegate_execute_entries)
entry->set_removal_flag(RegistryEntry::RemovalFlag::KEY);
// Move |delegate_execute_entries| to |entries|.
std::move(delegate_execute_entries.begin(), delegate_execute_entries.end(),
std::back_inserter(*entries));
}
}
// This method returns a list of the registry entries needed to declare a
// capability of handling a protocol on Windows.
void GetProtocolCapabilityEntries(
const base::string16& suffix,
const base::string16& protocol,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
entries->push_back(std::make_unique<RegistryEntry>(
GetCapabilitiesKey(suffix).append(L"\\URLAssociations"), protocol,
GetBrowserProgId(suffix)));
}
// This method returns a list of the registry entries required to register this
// installation in "RegisteredApplications" on Windows (to appear in Default
// Programs, StartMenuInternet, etc.). These entries need to be registered in
// HKLM prior to Win8. If |suffix| is not empty, these entries are guaranteed to
// be unique on this machine.
void GetShellIntegrationEntries(
const base::FilePath& chrome_exe,
const base::string16& suffix,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
const base::string16 icon_path(ShellUtil::FormatIconLocation(
chrome_exe, install_static::GetIconResourceIndex()));
const base::string16 quoted_exe_path(L"\"" + chrome_exe.value() + L"\"");
// Register for the Start Menu "Internet" link (pre-Win7).
const base::string16 start_menu_entry(GetBrowserClientKey(suffix));
// Register Chrome's display name.
// TODO(grt): http://crbug.com/75152 Also set LocalizedString; see
// http://msdn.microsoft.com/en-us/library/windows/desktop/cc144109(v=VS.85).aspx#registering_the_display_name
entries->push_back(std::make_unique<RegistryEntry>(
start_menu_entry, InstallUtil::GetDisplayName()));
// Register the "open" verb for launching Chrome via the "Internet" link.
entries->push_back(std::make_unique<RegistryEntry>(
start_menu_entry + ShellUtil::kRegShellOpen, quoted_exe_path));
// Register Chrome's icon for the Start Menu "Internet" link.
entries->push_back(std::make_unique<RegistryEntry>(
start_menu_entry + ShellUtil::kRegDefaultIcon, icon_path));
// Register installation information.
base::string16 install_info(start_menu_entry + L"\\InstallInfo");
// Note: not using CommandLine since it has ambiguous rules for quoting
// strings.
entries->push_back(std::make_unique<RegistryEntry>(
install_info, kReinstallCommand,
quoted_exe_path + L" --" +
base::ASCIIToUTF16(switches::kMakeDefaultBrowser)));
entries->push_back(std::make_unique<RegistryEntry>(
install_info, L"HideIconsCommand",
quoted_exe_path + L" --" + base::ASCIIToUTF16(switches::kHideIcons)));
entries->push_back(std::make_unique<RegistryEntry>(
install_info, L"ShowIconsCommand",
quoted_exe_path + L" --" + base::ASCIIToUTF16(switches::kShowIcons)));
entries->push_back(
std::make_unique<RegistryEntry>(install_info, L"IconsVisible", 1));
// Register with Default Programs.
const base::string16 reg_app_name(
install_static::GetBaseAppName().append(suffix));
// Tell Windows where to find Chrome's Default Programs info.
const base::string16 capabilities(GetCapabilitiesKey(suffix));
entries->push_back(std::make_unique<RegistryEntry>(
ShellUtil::kRegRegisteredApplications, reg_app_name, capabilities));
// Write out Chrome's Default Programs info.
// TODO(grt): http://crbug.com/75152 Write a reference to a localized
// resource rather than this.
entries->push_back(std::make_unique<RegistryEntry>(
capabilities, ShellUtil::kRegApplicationDescription,
InstallUtil::GetLongAppDescription()));
entries->push_back(std::make_unique<RegistryEntry>(
capabilities, ShellUtil::kRegApplicationIcon, icon_path));
entries->push_back(std::make_unique<RegistryEntry>(
capabilities, ShellUtil::kRegApplicationName,
InstallUtil::GetDisplayName()));
entries->push_back(std::make_unique<RegistryEntry>(
capabilities + L"\\Startmenu", L"StartMenuInternet", reg_app_name));
const base::string16 html_prog_id(GetBrowserProgId(suffix));
for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
entries->push_back(std::make_unique<RegistryEntry>(
capabilities + L"\\FileAssociations",
ShellUtil::kPotentialFileAssociations[i], html_prog_id));
}
for (int i = 0; ShellUtil::kPotentialProtocolAssociations[i] != NULL; i++) {
entries->push_back(std::make_unique<RegistryEntry>(
capabilities + L"\\URLAssociations",
ShellUtil::kPotentialProtocolAssociations[i], html_prog_id));
}
}
// Gets the registry entries to register an application as a handler for a
// particular file extension. |prog_id| is the ProgId used by Windows for the
// application. |ext| is the file extension, which must begin with a '.'.
void GetAppExtRegistrationEntries(
const base::string16& prog_id,
const base::string16& ext,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
// In HKEY_CURRENT_USER\Software\Classes\EXT\OpenWithProgids, create an
// empty value with this class's ProgId.
base::string16 key_name(ShellUtil::kRegClasses);
key_name.push_back(base::FilePath::kSeparators[0]);
key_name.append(ext);
key_name.push_back(base::FilePath::kSeparators[0]);
key_name.append(ShellUtil::kRegOpenWithProgids);
entries->push_back(
std::make_unique<RegistryEntry>(key_name, prog_id, base::string16()));
}
// This method returns a list of the registry entries required for this
// installation to be registered in the Windows shell.
// In particular:
// - App Paths
// http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
// - File Associations
// http://msdn.microsoft.com/en-us/library/bb166549
// These entries need to be registered in HKLM prior to Win8.
void GetChromeAppRegistrationEntries(
const base::FilePath& chrome_exe,
const base::string16& suffix,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
base::string16 app_path_key(ShellUtil::kAppPathsRegistryKey);
app_path_key.push_back(base::FilePath::kSeparators[0]);
app_path_key.append(chrome_exe.BaseName().value());
entries->push_back(
std::make_unique<RegistryEntry>(app_path_key, chrome_exe.value()));
entries->push_back(std::make_unique<RegistryEntry>(
app_path_key, ShellUtil::kAppPathsRegistryPathName,
chrome_exe.DirName().value()));
const base::string16 html_prog_id(GetBrowserProgId(suffix));
for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
GetAppExtRegistrationEntries(
html_prog_id, ShellUtil::kPotentialFileAssociations[i], entries);
}
}
// Gets the registry entries to register an application as the default handler
// for a particular file extension. |prog_id| is the ProgId used by Windows for
// the application. |ext| is the file extension, which must begin with a '.'. If
// |overwrite_existing|, always sets the default handler; otherwise only sets if
// there is no existing default.
//
// This has no effect on Windows 8. Windows 8 ignores the default and lets the
// user choose. If there is only one handler for a file, it will automatically
// become the default. Otherwise, the first time the user opens a file, they are
// presented with the dialog to set the default handler. (This is roughly
// equivalent to being called with |overwrite_existing| false.)
void GetAppDefaultRegistrationEntries(
const base::string16& prog_id,
const base::string16& ext,
bool overwrite_existing,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
// Set the default value of HKEY_CURRENT_USER\Software\Classes\EXT to this
// class's name.
base::string16 key_name(ShellUtil::kRegClasses);
key_name.push_back(base::FilePath::kSeparators[0]);
key_name.append(ext);
auto default_association = std::make_unique<RegistryEntry>(key_name, prog_id);
if (overwrite_existing ||
!default_association->KeyExistsInRegistry(RegistryEntry::LOOK_IN_HKCU)) {
entries->push_back(std::move(default_association));
}
}
// This method returns a list of all the user level registry entries that are
// needed to make Chromium the default handler for a protocol on XP.
void GetXPStyleUserProtocolEntries(
const base::string16& protocol,
const base::string16& chrome_icon,
const base::string16& chrome_open,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
// Protocols associations.
base::string16 url_key(ShellUtil::kRegClasses);
url_key.push_back(base::FilePath::kSeparators[0]);
url_key.append(protocol);
// This registry value tells Windows that this 'class' is a URL scheme
// so IE, explorer and other apps will route it to our handler.
// <root hkey>\Software\Classes\<protocol>\URL Protocol
entries->push_back(std::make_unique<RegistryEntry>(
url_key, ShellUtil::kRegUrlProtocol, base::string16()));
// <root hkey>\Software\Classes\<protocol>\DefaultIcon
base::string16 icon_key = url_key + ShellUtil::kRegDefaultIcon;
entries->push_back(std::make_unique<RegistryEntry>(icon_key, chrome_icon));
// <root hkey>\Software\Classes\<protocol>\shell\open\command
base::string16 shell_key = url_key + ShellUtil::kRegShellOpen;
entries->push_back(std::make_unique<RegistryEntry>(shell_key, chrome_open));
// <root hkey>\Software\Classes\<protocol>\shell\open\ddeexec
base::string16 dde_key = url_key + L"\\shell\\open\\ddeexec";
entries->push_back(
std::make_unique<RegistryEntry>(dde_key, base::string16()));
// <root hkey>\Software\Classes\<protocol>\shell\@
base::string16 protocol_shell_key = url_key + ShellUtil::kRegShellPath;
entries->push_back(
std::make_unique<RegistryEntry>(protocol_shell_key, L"open"));
}
// This method returns a list of all the user level registry entries that are
// needed to make Chromium default browser on XP. Some of these entries are
// irrelevant in recent versions of Windows, but we register them anyways as
// some legacy apps are hardcoded to lookup those values.
void GetXPStyleDefaultBrowserUserEntries(
const base::FilePath& chrome_exe,
const base::string16& suffix,
std::vector<std::unique_ptr<RegistryEntry>>* entries) {
// File extension associations.
base::string16 html_prog_id(GetBrowserProgId(suffix));
for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
GetAppDefaultRegistrationEntries(
html_prog_id, ShellUtil::kDefaultFileAssociations[i], true, entries);
}
// Protocols associations.
base::string16 chrome_open = ShellUtil::GetChromeShellOpenCmd(chrome_exe);
base::string16 chrome_icon = ShellUtil::FormatIconLocation(
chrome_exe, install_static::GetIconResourceIndex());
for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
GetXPStyleUserProtocolEntries(ShellUtil::kBrowserProtocolAssociations[i],
chrome_icon, chrome_open, entries);
}
// start->Internet shortcut.
base::string16 start_menu(ShellUtil::kRegStartMenuInternet);
base::string16 app_name = install_static::GetBaseAppName().append(suffix);
entries->push_back(std::make_unique<RegistryEntry>(start_menu, app_name));
}
// Checks that all |entries| are present on this computer (or absent if their
// |removal_flag_| is set). |look_for_in| is passed to
// RegistryEntry::ExistsInRegistry(). Documentation for it can be found there.
bool AreEntriesAsDesired(
const std::vector<std::unique_ptr<RegistryEntry>>& entries,
uint32_t look_for_in) {
for (const auto& entry : entries) {
if (entry->ExistsInRegistry(look_for_in) != !entry->IsFlaggedForRemoval())
return false;
}
return true;
}
// Checks that all required registry entries for Chrome are already present on
// this computer (or absent if their |removal_flag_| is set).
// See RegistryEntry::ExistsInRegistry for the behavior of |look_for_in|.
// Note: between r133333 and r154145 we were registering parts of Chrome in HKCU
// and parts in HKLM for user-level installs; we now always register everything
// under a single registry root. Not doing so caused http://crbug.com/144910 for
// users who first-installed Chrome in that revision range (those users are
// still impacted by http://crbug.com/144910). This method will keep returning
// true for affected users (i.e. who have all the registrations, but over both
// registry roots).
bool IsChromeRegistered(const base::FilePath& chrome_exe,
const base::string16& suffix,
uint32_t look_for_in) {
std::vector<std::unique_ptr<RegistryEntry>> entries;
GetChromeProgIdEntries(chrome_exe, suffix, &entries);
GetShellIntegrationEntries(chrome_exe, suffix, &entries);
GetChromeAppRegistrationEntries(chrome_exe, suffix, &entries);
return AreEntriesAsDesired(entries, look_for_in);
}
// This method checks if Chrome is already registered on the local machine
// for the requested protocol. It just checks the one value required for this.
// See RegistryEntry::ExistsInRegistry for the behavior of |look_for_in|.
bool IsChromeRegisteredForProtocol(const base::string16& suffix,
const base::string16& protocol,
uint32_t look_for_in) {
std::vector<std::unique_ptr<RegistryEntry>> entries;
GetProtocolCapabilityEntries(suffix, protocol, &entries);
return AreEntriesAsDesired(entries, look_for_in);
}
// This method registers Chrome by launching an elevated setup.exe. That will
// show the user the standard elevation prompt. If the user accepts it the new
// process will make the necessary changes and return SUCCESS that we capture
// and return. If protocol is non-empty we will also register Chrome as being
// capable of handling the protocol. This is used for general browser
// registration on Windows 7 for per-user installs where setup.exe did not have
// permission to register Chrome during install. It may also be used on Windows
// 7 for system-level installs to register Chrome for a specific protocol.
bool ElevateAndRegisterChrome(const base::FilePath& chrome_exe,
const base::string16& suffix,
const base::string16& protocol) {
// Check for setup.exe in the same directory as chrome.exe, as is the case
// when running out of a build output directory.
base::FilePath exe_path = chrome_exe.DirName().Append(installer::kSetupExe);
// Failing that, read the path to setup.exe from Chrome's ClientState key,
// which is the canonical location of the installer for all types of installs
// (see AddUninstallShortcutWorkItems).
const bool is_per_user = InstallUtil::IsPerUserInstall();
if (!base::PathExists(exe_path)) {
RegKey key(is_per_user ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE,
install_static::GetClientStateKeyPath().c_str(),
KEY_QUERY_VALUE | KEY_WOW64_32KEY);
base::string16 uninstall_string;
if (key.ReadValue(installer::kUninstallStringField, &uninstall_string) ==
ERROR_SUCCESS) {
exe_path = base::FilePath(uninstall_string);
}
}
if (base::PathExists(exe_path)) {
base::CommandLine cmd(exe_path);
InstallUtil::AppendModeSwitch(&cmd);
if (!is_per_user)
cmd.AppendSwitch(installer::switches::kSystemLevel);
cmd.AppendSwitchPath(installer::switches::kRegisterChromeBrowser,
chrome_exe);
if (!suffix.empty()) {
cmd.AppendSwitchNative(
installer::switches::kRegisterChromeBrowserSuffix, suffix);
}
if (!protocol.empty()) {
cmd.AppendSwitchNative(
installer::switches::kRegisterURLProtocol, protocol);
}
DWORD ret_val = 0;
InstallUtil::ExecuteExeAsAdmin(cmd, &ret_val);
if (ret_val == 0)
return true;
}
return false;
}
// Returns the target used as a activate parameter when opening the settings
// pointing to the page that is the most relevant to a user trying to change the
// default handler for |protocol|.
base::string16 GetTargetForDefaultAppsSettings(const wchar_t* protocol) {
static const wchar_t kSystemSettingsDefaultAppsFormat[] =
L"SystemSettings_DefaultApps_%ls";
if (base::EqualsCaseInsensitiveASCII(protocol, L"http"))
return base::StringPrintf(kSystemSettingsDefaultAppsFormat, L"Browser");
if (base::EqualsCaseInsensitiveASCII(protocol, L"mailto"))
return base::StringPrintf(kSystemSettingsDefaultAppsFormat, L"Email");
return L"SettingsPageAppsDefaultsProtocolView";
}
// Launches the Windows 'settings' modern app with the 'default apps' view
// focused. This only works for Windows 8 and Windows 10. The appModelId
// looks arbitrary but it is the same in Win8 and Win10. There is no easy way to
// retrieve the appModelId from the registry.
bool LaunchDefaultAppsSettingsModernDialog(const wchar_t* protocol) {
DCHECK(protocol);
static const wchar_t kControlPanelAppModelId[] =
L"windows.immersivecontrolpanel_cw5n1h2txyewy"
L"!microsoft.windows.immersivecontrolpanel";
static constexpr base::Feature kHighlightProtocolInWindowsSettings{
"HighlightProtocolInWindowsSettings", base::FEATURE_ENABLED_BY_DEFAULT};
Microsoft::WRL::ComPtr<IApplicationActivationManager> activator;
HRESULT hr = ::CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
CLSCTX_ALL, IID_PPV_ARGS(&activator));
if (SUCCEEDED(hr)) {
DWORD pid = 0;
CoAllowSetForegroundWindow(activator.Get(), nullptr);
hr = activator->ActivateApplication(kControlPanelAppModelId,
L"page=SettingsPageAppsDefaults",
AO_NONE, &pid);
if (SUCCEEDED(hr) &&
base::FeatureList::IsEnabled(kHighlightProtocolInWindowsSettings)) {
hr = activator->ActivateApplication(
kControlPanelAppModelId,
base::StringPrintf(L"page=SettingsPageAppsDefaults&target=%ls",
GetTargetForDefaultAppsSettings(protocol).c_str())
.c_str(),
AO_NONE, &pid);
}
if (SUCCEEDED(hr))
return true;
base::UmaHistogramSparse("DefaultBrowser.ActivateSettings.ErrorHresult",
hr);
}
return false;
}
// Launches the Windows 7 and Windows 8 dialog for picking the application to
// handle the given protocol. Most importantly, this is used to set the default
// handler for http (and, implicitly with it, https). In that case it is also
// known as the 'how do you want to open webpages' dialog.
// It is required that Chrome be already *registered* for the given protocol.
bool LaunchSelectDefaultProtocolHandlerDialog(const wchar_t* protocol) {
DCHECK(protocol);
OPENASINFO open_as_info = {};
open_as_info.pcszFile = protocol;
open_as_info.oaifInFlags =
OAIF_URL_PROTOCOL | OAIF_FORCE_REGISTRATION | OAIF_REGISTER_EXT;
HRESULT hr = SHOpenWithDialog(NULL, &open_as_info);
DLOG_IF(WARNING, FAILED(hr)) << "Failed to set as default " << protocol
<< " handler; hr=0x" << std::hex << hr;
if (FAILED(hr))
return false;
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
return true;
}
// Returns true if |chrome_exe| has been registered with |suffix| for |mode|.
// |confirmation_level| is the level of verification desired as described in
// the RegistrationConfirmationLevel enum above.
// |suffix| can be the empty string (this is used to support old installs
// where we used to not suffix user-level installs if they were the first to
// request the non-suffixed registry entries on the machine).
// NOTE: This a quick check that only validates that a single registry entry
// points to |chrome_exe|. This should only be used at run-time to determine
// how Chrome is registered, not to know whether the registration is complete
// at install-time (IsChromeRegistered() can be used for that).
bool QuickIsChromeRegisteredForMode(
const base::FilePath& chrome_exe,
const base::string16& suffix,
const install_static::InstallConstants& mode,
RegistrationConfirmationLevel confirmation_level) {
// Get the appropriate key to look for based on the level desired.
base::string16 reg_key;
switch (confirmation_level) {
case CONFIRM_PROGID_REGISTRATION:
// Software\Classes\ChromeHTML|suffix|
reg_key = ShellUtil::kRegClasses;
reg_key.push_back(base::FilePath::kSeparators[0]);
reg_key.append(mode.prog_id_prefix);
reg_key.append(suffix);
break;
case CONFIRM_SHELL_REGISTRATION:
case CONFIRM_SHELL_REGISTRATION_IN_HKLM:
// Software\Clients\StartMenuInternet\Google Chrome|suffix|
reg_key = GetBrowserClientKey(suffix);
break;
default:
NOTREACHED();
break;
}
reg_key.append(ShellUtil::kRegShellOpen);
// ProgId registrations are allowed to reside in HKCU for user-level installs
// (and values there have priority over values in HKLM). The same is true for
// shell integration entries as of Windows 8.
if (confirmation_level == CONFIRM_PROGID_REGISTRATION ||
(confirmation_level == CONFIRM_SHELL_REGISTRATION &&
base::win::GetVersion() >= base::win::VERSION_WIN8)) {
const RegKey key_hkcu(HKEY_CURRENT_USER, reg_key.c_str(), KEY_QUERY_VALUE);
base::string16 hkcu_value;
// If |reg_key| is present in HKCU, assert that it points to |chrome_exe|.
// Otherwise, fall back on an HKLM lookup below.
if (key_hkcu.ReadValue(L"", &hkcu_value) == ERROR_SUCCESS)
return InstallUtil::ProgramCompare(chrome_exe).Evaluate(hkcu_value);
}
// Assert that |reg_key| points to |chrome_exe| in HKLM.
const RegKey key_hklm(HKEY_LOCAL_MACHINE, reg_key.c_str(), KEY_QUERY_VALUE);
base::string16 hklm_value;
if (key_hklm.ReadValue(L"", &hklm_value) == ERROR_SUCCESS)
return InstallUtil::ProgramCompare(chrome_exe).Evaluate(hklm_value);
return false;
}
// Returns the installation suffix for |mode| at the system or user level based
// on |system_install|.
base::string16 GetInstallationSuffixForModeAtLevel(
const install_static::InstallConstants& mode,
bool system_install) {
// Search based on the default install location for the mode. If we ever
// support customizing the install location (https://crbug.com/113987,
// https://crbug.com/302491) this will have to change to something else, such
// as probing the Omaha keys in the registry to see where the mode is
// installed.
const base::FilePath chrome_exe =
installer::GetChromeInstallPath(system_install)
.Append(installer::kChromeExe);
// See the comment in ShellUtil::GetCurrentInstallationSuffix for details on
// what's going on here.
base::string16 tested_suffix;
if (!system_install &&
(!ShellUtil::GetUserSpecificRegistrySuffix(&tested_suffix) ||
!QuickIsChromeRegisteredForMode(chrome_exe, tested_suffix, mode,
CONFIRM_PROGID_REGISTRATION)) &&
(!ShellUtil::GetOldUserSpecificRegistrySuffix(&tested_suffix) ||
!QuickIsChromeRegisteredForMode(chrome_exe, tested_suffix, mode,
CONFIRM_PROGID_REGISTRATION)) &&
!QuickIsChromeRegisteredForMode(chrome_exe, tested_suffix.erase(), mode,
CONFIRM_PROGID_REGISTRATION)) {
// If Chrome is not registered under any of the possible suffixes (e.g.
// tests, Canary, etc.): use the new-style suffix at run-time.
if (!ShellUtil::GetUserSpecificRegistrySuffix(&tested_suffix))
NOTREACHED();
}
return tested_suffix;
}
// Returns |mode|'s application name at the system or user level based on
// |system_install|. This application name will be suffixed as is appropriate
// for the install. This is the name that is registered with Default Programs on
// Windows and that should thus be used to "make chrome default" and such.
base::string16 GetApplicationNameForModeAtLevel(
const install_static::InstallConstants& mode,
bool system_install) {
return base::string16(mode.base_app_name)
.append(GetInstallationSuffixForModeAtLevel(mode, system_install));
}
// Returns true if the current install's |chrome_exe| has been registered with
// |suffix|.
// |confirmation_level| is the level of verification desired as described in
// the RegistrationConfirmationLevel enum above.
// |suffix| can be the empty string (this is used to support old installs
// where we used to not suffix user-level installs if they were the first to
// request the non-suffixed registry entries on the machine).
// NOTE: This a quick check that only validates that a single registry entry
// points to |chrome_exe|. This should only be used at run-time to determine
// how Chrome is registered, not to know whether the registration is complete
// at install-time (IsChromeRegistered() can be used for that).
bool QuickIsChromeRegistered(const base::FilePath& chrome_exe,
const base::string16& suffix,
RegistrationConfirmationLevel confirmation_level) {
return QuickIsChromeRegisteredForMode(
chrome_exe, suffix, install_static::InstallDetails::Get().mode(),
confirmation_level);
}
// Sets |suffix| to a 27 character string that is specific to this user on this
// machine (on user-level installs only).
// To support old-style user-level installs however, |suffix| is cleared if the
// user currently owns the non-suffixed HKLM registrations.
// |suffix| can also be set to the user's username if the current install is
// suffixed as per the old-style registrations.
// |suffix| is cleared on system-level installs.
// |suffix| should then be appended to all Chrome properties that may conflict
// with other Chrome user-level installs.
// Returns true unless one of the underlying calls fails.
bool GetInstallationSpecificSuffix(const base::FilePath& chrome_exe,
base::string16* suffix) {
if (!InstallUtil::IsPerUserInstall() ||
QuickIsChromeRegistered(chrome_exe, base::string16(),
CONFIRM_SHELL_REGISTRATION)) {
// No suffix on system-level installs and user-level installs already
// registered with no suffix.
suffix->clear();
return true;
}
// Get the old suffix for the check below.
if (!ShellUtil::GetOldUserSpecificRegistrySuffix(suffix)) {
NOTREACHED();
return false;
}
if (QuickIsChromeRegistered(chrome_exe, *suffix,
CONFIRM_SHELL_REGISTRATION)) {
// Username suffix for installs that are suffixed as per the old-style.
return true;
}
return ShellUtil::GetUserSpecificRegistrySuffix(suffix);
}
// Returns the root registry key (HKLM or HKCU) under which registrations must
// be placed for this install. As of Windows 8 everything can go in HKCU for
// per-user installs.
HKEY DetermineRegistrationRoot(bool is_per_user) {
return is_per_user && base::win::GetVersion() >= base::win::VERSION_WIN8 ?
HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
}
// Associates Chrome with supported protocols and file associations. This should
// not be required on Vista+ but since some applications still read
// Software\Classes\http key directly, we have to do this on Vista+ as well.
bool RegisterChromeAsDefaultXPStyle(int shell_change,
const base::FilePath& chrome_exe) {
bool ret = true;
std::vector<std::unique_ptr<RegistryEntry>> entries;
GetXPStyleDefaultBrowserUserEntries(
chrome_exe, ShellUtil::GetCurrentInstallationSuffix(chrome_exe),
&entries);
// Change the default browser for current user.
if ((shell_change & ShellUtil::CURRENT_USER) &&
!ShellUtil::AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
ret = false;
LOG(ERROR) << "Could not make Chrome default browser (XP/current user).";
}
// Chrome as default browser at system level.
if ((shell_change & ShellUtil::SYSTEM_LEVEL) &&
!ShellUtil::AddRegistryEntries(HKEY_LOCAL_MACHINE, entries)) {
ret = false;
LOG(ERROR) << "Could not make Chrome default browser (XP/system level).";
}
return ret;
}
// Associates Chrome with |protocol| in the registry. This should not be
// required on Vista+ but since some applications still read these registry
// keys directly, we have to do this on Vista+ as well.
// See http://msdn.microsoft.com/library/aa767914.aspx for more details.
bool RegisterChromeAsDefaultProtocolClientXPStyle(
const base::FilePath& chrome_exe,
const base::string16& protocol) {
std::vector<std::unique_ptr<RegistryEntry>> entries;
const base::string16 chrome_open(
ShellUtil::GetChromeShellOpenCmd(chrome_exe));
const base::string16 chrome_icon(ShellUtil::FormatIconLocation(
chrome_exe, install_static::GetIconResourceIndex()));
GetXPStyleUserProtocolEntries(protocol, chrome_icon, chrome_open, &entries);
// Change the default protocol handler for current user.
if (!ShellUtil::AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
LOG(ERROR) << "Could not make Chrome default protocol client (XP).";
return false;
}
return true;
}
// Returns |properties.shortcut_name| if the property is set, otherwise it
// returns InstallUtil::GetShortcutName(). In any case, it makes sure the return
// value is suffixed with ".lnk".
base::string16 ExtractShortcutNameFromProperties(
const ShellUtil::ShortcutProperties& properties) {
base::string16 shortcut_name = properties.has_shortcut_name()
? properties.shortcut_name
: InstallUtil::GetShortcutName();
if (!base::EndsWith(shortcut_name, installer::kLnkExt,
base::CompareCase::INSENSITIVE_ASCII))
shortcut_name.append(installer::kLnkExt);
return shortcut_name;
}
// Converts ShellUtil::ShortcutOperation to the best-matching value in
// base::win::ShortcutOperation.
base::win::ShortcutOperation TranslateShortcutOperation(
ShellUtil::ShortcutOperation operation) {
switch (operation) {
case ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS: // Falls through.
case ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL:
return base::win::SHORTCUT_CREATE_ALWAYS;
case ShellUtil::SHELL_SHORTCUT_UPDATE_EXISTING:
return base::win::SHORTCUT_UPDATE_EXISTING;
case ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING:
return base::win::SHORTCUT_REPLACE_EXISTING;
default:
NOTREACHED();
return base::win::SHORTCUT_REPLACE_EXISTING;
}
}
// Returns a base::win::ShortcutProperties struct containing the properties
// to set on the shortcut based on the provided ShellUtil::ShortcutProperties.
base::win::ShortcutProperties TranslateShortcutProperties(
const ShellUtil::ShortcutProperties& properties) {
base::win::ShortcutProperties shortcut_properties;
if (properties.has_target()) {
shortcut_properties.set_target(properties.target);
DCHECK(!properties.target.DirName().empty());
shortcut_properties.set_working_dir(properties.target.DirName());
}
if (properties.has_arguments())
shortcut_properties.set_arguments(properties.arguments);
if (properties.has_description())
shortcut_properties.set_description(properties.description);
if (properties.has_icon())
shortcut_properties.set_icon(properties.icon, properties.icon_index);
if (properties.has_app_id())
shortcut_properties.set_app_id(properties.app_id);
if (properties.has_toast_activator_clsid()) {
shortcut_properties.set_toast_activator_clsid(
properties.toast_activator_clsid);
}
return shortcut_properties;
}
// Cleans up an old verb (run) we used to register in
// <root>\Software\Classes\Chrome<.suffix>\.exe\shell\run on Windows 8.
void RemoveRunVerbOnWindows8() {
if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
bool is_per_user_install = InstallUtil::IsPerUserInstall();
HKEY root_key = DetermineRegistrationRoot(is_per_user_install);
// There's no need to rollback, so forgo the usual work item lists and just
// remove the key from the registry.
base::string16 run_verb_key(ShellUtil::kRegClasses);
run_verb_key.push_back(base::FilePath::kSeparators[0]);
run_verb_key.append(ShellUtil::GetBrowserModelId(is_per_user_install));
run_verb_key.append(ShellUtil::kRegExePath);
run_verb_key.append(ShellUtil::kRegShellPath);
run_verb_key.push_back(base::FilePath::kSeparators[0]);
run_verb_key.append(ShellUtil::kRegVerbRun);
InstallUtil::DeleteRegistryKey(root_key, run_verb_key,
WorkItem::kWow64Default);
}
}
// Probe using IApplicationAssociationRegistration::QueryCurrentDefault
// (Windows 8); see ProbeProtocolHandlers. This mechanism is not suitable for
// use on previous versions of Windows despite the presence of
// QueryCurrentDefault on them since versions of Windows prior to Windows 8
// did not perform validation on the ProgID registered as the current default.
// As a result, stale ProgIDs could be returned, leading to false positives.
ShellUtil::DefaultState ProbeCurrentDefaultHandlers(
const base::FilePath& chrome_exe,
const wchar_t* const* protocols,
size_t num_protocols) {
Microsoft::WRL::ComPtr<IApplicationAssociationRegistration> registration;
HRESULT hr =
::CoCreateInstance(CLSID_ApplicationAssociationRegistration, NULL,
CLSCTX_INPROC, IID_PPV_ARGS(&registration));
if (FAILED(hr))
return ShellUtil::UNKNOWN_DEFAULT;
// Get the ProgID for the current install mode.
base::string16 prog_id(install_static::GetProgIdPrefix());
prog_id += ShellUtil::GetCurrentInstallationSuffix(chrome_exe);
const int current_install_mode_index =
install_static::InstallDetails::Get().install_mode_index();
bool other_mode_is_default = false;
for (size_t i = 0; i < num_protocols; ++i) {
base::win::ScopedCoMem<wchar_t> current_app;
hr = registration->QueryCurrentDefault(protocols[i], AT_URLPROTOCOL,
AL_EFFECTIVE, &current_app);
if (FAILED(hr))
return ShellUtil::NOT_DEFAULT;
if (prog_id.compare(current_app) == 0)
continue;
// See if another mode is the default handler for this protocol.
size_t current_app_len = std::char_traits<wchar_t>::length(current_app);
const auto* it = std::find_if(
&install_static::kInstallModes[0],
&install_static::kInstallModes[install_static::NUM_INSTALL_MODES],
[current_install_mode_index, &current_app,
current_app_len](const install_static::InstallConstants& mode) {
if (mode.index == current_install_mode_index)
return false;
const base::string16 mode_prog_id_prefix(mode.prog_id_prefix);
// Does the current app either match this mode's ProgID or contain
// this mode's ProgID as a prefix followed by the '.' separator for a
// per-user install's suffix?
return mode_prog_id_prefix.compare(current_app) == 0 ||
(InstallUtil::IsPerUserInstall() &&
current_app_len > mode_prog_id_prefix.length() &&
current_app[mode_prog_id_prefix.length()] == L'.' &&
std::char_traits<wchar_t>::compare(
mode_prog_id_prefix.c_str(), current_app,
mode_prog_id_prefix.length()) == 0);
});
if (it == &install_static::kInstallModes[install_static::NUM_INSTALL_MODES])
return ShellUtil::NOT_DEFAULT;
other_mode_is_default = true;
}
// This mode is default if it has all of the protocols.
return other_mode_is_default ? ShellUtil::OTHER_MODE_IS_DEFAULT
: ShellUtil::IS_DEFAULT;
}
// Probe using IApplicationAssociationRegistration::QueryAppIsDefault (Vista and
// Windows 7); see ProbeProtocolHandlers.
ShellUtil::DefaultState ProbeAppIsDefaultHandlers(
const base::FilePath& chrome_exe,
const wchar_t* const* protocols,
size_t num_protocols) {
Microsoft::WRL::ComPtr<IApplicationAssociationRegistration> registration;
HRESULT hr =
::CoCreateInstance(CLSID_ApplicationAssociationRegistration, NULL,
CLSCTX_INPROC, IID_PPV_ARGS(&registration));
if (FAILED(hr))
return ShellUtil::UNKNOWN_DEFAULT;
base::string16 app_name(GetApplicationName(chrome_exe));
// Generate the app names for this brand's other install modes at both user
// and system levels.
const int current_install_mode_index =
install_static::InstallDetails::Get().install_mode_index();
base::string16 other_app_names[install_static::NUM_INSTALL_MODES * 2];
for (int mode_index = 0; mode_index < install_static::NUM_INSTALL_MODES;
++mode_index) {
if (mode_index == current_install_mode_index)
continue; // Leave the entry for the current mode empty.
other_app_names[mode_index * 2] = GetApplicationNameForModeAtLevel(
install_static::kInstallModes[mode_index], false);
other_app_names[mode_index * 2 + 1] = GetApplicationNameForModeAtLevel(
install_static::kInstallModes[mode_index], true);
}
// Now check each protocol to see if this brand is default for all. This loop
// terminates when this brand is the default handler for the protocols.
bool other_mode_is_default = false;
for (size_t i = 0; i < num_protocols; ++i) {
const wchar_t* protocol = protocols[i];
BOOL result = TRUE;
// Check the current app name. This will fail (e.g., ERROR_FILE_NOT_FOUND)
// if |app_name| isn't registered.
hr = registration->QueryAppIsDefault(protocol, AT_URLPROTOCOL, AL_EFFECTIVE,
app_name.c_str(), &result);
if (SUCCEEDED(hr) && result)
continue;
// Search for a different install mode that is the default handler.
const auto* it =
std::find_if(std::cbegin(other_app_names), std::cend(other_app_names),
[&registration, protocol](const base::string16& app_name) {
if (app_name.empty())
return false;
BOOL result = TRUE;
HRESULT hr = registration->QueryAppIsDefault(
protocol, AT_URLPROTOCOL, AL_EFFECTIVE,
app_name.c_str(), &result);
return SUCCEEDED(hr) && result;
});
if (it == std::end(other_app_names))
return ShellUtil::NOT_DEFAULT;
other_mode_is_default = true;
}
return other_mode_is_default ? ShellUtil::OTHER_MODE_IS_DEFAULT
: ShellUtil::IS_DEFAULT;
}
// A helper function that probes default protocol handler registration (in a
// manner appropriate for the current version of Windows) to determine if
// Chrome is the default handler for |protocols|. Returns IS_DEFAULT
// only if Chrome is the default for all specified protocols.
ShellUtil::DefaultState ProbeProtocolHandlers(
const base::FilePath& chrome_exe,
const wchar_t* const* protocols,
size_t num_protocols) {
#if DCHECK_IS_ON()
DCHECK(!num_protocols || protocols);
for (size_t i = 0; i < num_protocols; ++i)
DCHECK(protocols[i] && *protocols[i]);
#endif
const base::win::Version windows_version = base::win::GetVersion();
if (windows_version >= base::win::VERSION_WIN8)
return ProbeCurrentDefaultHandlers(chrome_exe, protocols, num_protocols);
return ProbeAppIsDefaultHandlers(chrome_exe, protocols, num_protocols);
}
// (Windows 8+) Finds and stores an app shortcuts folder path in *|path|.
// Returns true on success.
bool GetAppShortcutsFolder(ShellUtil::ShellChange level, base::FilePath* path) {
DCHECK(path);
DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
base::FilePath folder;
if (!base::PathService::Get(base::DIR_APP_SHORTCUTS, &folder)) {
LOG(ERROR) << "Could not get application shortcuts location.";
return false;
}
folder = folder.Append(
ShellUtil::GetBrowserModelId(level == ShellUtil::CURRENT_USER));
if (!base::DirectoryExists(folder)) {
VLOG(1) << "No start screen shortcuts.";
return false;
}
*path = folder;
return true;
}
// Shortcut filters for BatchShortcutAction().
typedef base::Callback<bool(const base::FilePath& /*shortcut_path*/,
const base::string16& /*args*/)>
ShortcutFilterCallback;
// FilterTargetEq is a shortcut filter that matches only shortcuts that have a
// specific target, and optionally matches shortcuts that have non-empty
// arguments.
class FilterTargetEq {
public:
FilterTargetEq(const base::FilePath& desired_target_exe, bool require_args);
// Returns true if filter rules are satisfied, i.e.:
// - |target_path|'s target == |desired_target_compare_|, and
// - |args| is non-empty (if |require_args_| == true).
bool Match(const base::FilePath& target_path,
const base::string16& args) const;
// A convenience routine to create a callback to call Match().
// The callback is only valid during the lifetime of the FilterTargetEq
// instance.
ShortcutFilterCallback AsShortcutFilterCallback();
private:
InstallUtil::ProgramCompare desired_target_compare_;
bool require_args_;
};
FilterTargetEq::FilterTargetEq(const base::FilePath& desired_target_exe,
bool require_args)
: desired_target_compare_(desired_target_exe),
require_args_(require_args) {}
bool FilterTargetEq::Match(const base::FilePath& target_path,
const base::string16& args) const {
if (!desired_target_compare_.EvaluatePath(target_path))
return false;
if (require_args_ && args.empty())
return false;
return true;
}
ShortcutFilterCallback FilterTargetEq::AsShortcutFilterCallback() {
return base::Bind(&FilterTargetEq::Match, base::Unretained(this));
}
// Shortcut operations for BatchShortcutAction().
typedef base::Callback<bool(const base::FilePath& /*shortcut_path*/)>
ShortcutOperationCallback;
bool ShortcutOpUnpinFromTaskbar(const base::FilePath& shortcut_path) {
VLOG(1) << "Trying to unpin from taskbar " << shortcut_path.value();
if (!base::win::UnpinShortcutFromTaskbar(shortcut_path)) {
VLOG(1) << shortcut_path.value()
<< " wasn't pinned to taskbar (or the unpin failed).";
// No error, since shortcut might not be pinned.
}
return true;
}
bool ShortcutOpDelete(const base::FilePath& shortcut_path) {
bool ret = base::DeleteFile(shortcut_path, false);
LOG_IF(ERROR, !ret) << "Failed to remove " << shortcut_path.value();
return ret;
}
bool ShortcutOpRetarget(const base::FilePath& old_target,
const base::FilePath& new_target,
const base::FilePath& shortcut_path) {
base::win::ShortcutProperties new_prop;
new_prop.set_target(new_target);
// If the old icon matches old target, then update icon while keeping the old
// icon index. Non-fatal if we fail to get the old icon.
base::win::ShortcutProperties old_prop;
if (base::win::ResolveShortcutProperties(
shortcut_path,
base::win::ShortcutProperties::PROPERTIES_ICON,
&old_prop)) {
if (InstallUtil::ProgramCompare(old_target).EvaluatePath(old_prop.icon))
new_prop.set_icon(new_target, old_prop.icon_index);
} else {
LOG(ERROR) << "Failed to resolve " << shortcut_path.value();
}
bool result = base::win::CreateOrUpdateShortcutLink(
shortcut_path, new_prop, base::win::SHORTCUT_UPDATE_EXISTING);
LOG_IF(ERROR, !result) << "Failed to retarget " << shortcut_path.value();
return result;
}
bool ShortcutOpListOrRemoveUnknownArgs(
bool do_removal,
std::vector<std::pair<base::FilePath, base::string16> >* shortcuts,
const base::FilePath& shortcut_path) {
base::string16 args;
if (!base::win::ResolveShortcut(shortcut_path, NULL, &args))
return false;
base::CommandLine current_args(base::CommandLine::FromString(
base::StringPrintf(L"unused_program %ls", args.c_str())));
const char* const kept_switches[] = {
switches::kApp,
switches::kAppId,
switches::kShowAppList,
switches::kProfileDirectory,
};
base::CommandLine desired_args(base::CommandLine::NO_PROGRAM);
desired_args.CopySwitchesFrom(current_args, kept_switches,
base::size(kept_switches));
if (desired_args.argv().size() == current_args.argv().size())
return true;
if (shortcuts)
shortcuts->push_back(std::make_pair(shortcut_path, args));
if (!do_removal)
return true;
base::win::ShortcutProperties updated_properties;
updated_properties.set_arguments(desired_args.GetArgumentsString());
return base::win::CreateOrUpdateShortcutLink(
shortcut_path, updated_properties, base::win::SHORTCUT_UPDATE_EXISTING);
}
// {|location|, |level|} determine |shortcut_folder|.
// For each shortcut in |shortcut_folder| that match |shortcut_filter|, apply
// |shortcut_operation|. Returns true if all operations are successful.
// All intended operations are attempted, even if failures occur.
// This method will abort and return false if |cancel| is non-NULL and gets set
// at any point during this call.
bool BatchShortcutAction(
const ShortcutFilterCallback& shortcut_filter,
const ShortcutOperationCallback& shortcut_operation,
ShellUtil::ShortcutLocation location,
ShellUtil::ShellChange level,
const scoped_refptr<ShellUtil::SharedCancellationFlag>& cancel) {
DCHECK(!shortcut_operation.is_null());
// There is no system-level Quick Launch shortcut folder.
if (level == ShellUtil::SYSTEM_LEVEL &&
location == ShellUtil::SHORTCUT_LOCATION_QUICK_LAUNCH) {
return true;
}
base::FilePath shortcut_folder;
if (!ShellUtil::GetShortcutPath(location, level, &shortcut_folder)) {
LOG(WARNING) << "Cannot find path at location " << location;
return false;
}
bool success = true;
base::FileEnumerator enumerator(
shortcut_folder, false, base::FileEnumerator::FILES,
base::string16(L"*") + installer::kLnkExt);
base::FilePath target_path;
base::string16 args;
for (base::FilePath shortcut_path = enumerator.Next();
!shortcut_path.empty();
shortcut_path = enumerator.Next()) {
if (cancel.get() && cancel->data.IsSet())
return false;
if (base::win::ResolveShortcut(shortcut_path, &target_path, &args)) {
if (shortcut_filter.Run(target_path, args) &&
!shortcut_operation.Run(shortcut_path)) {
success = false;
}
} else {
LOG(ERROR) << "Cannot resolve shortcut at " << shortcut_path.value();
success = false;
}
}
return success;
}
// If the folder specified by {|location|, |level|} is empty, remove it.
// Otherwise do nothing. Returns true on success, including the vacuous case
// where no deletion occurred because directory is non-empty.
bool RemoveShortcutFolderIfEmpty(ShellUtil::ShortcutLocation location,
ShellUtil::ShellChange level) {
// Explicitly whitelist locations, since accidental calls can be very harmful.
if (location !=
ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_DIR_DEPRECATED &&
location != ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR &&
location != ShellUtil::SHORTCUT_LOCATION_APP_SHORTCUTS) {
NOTREACHED();
return false;
}
base::FilePath shortcut_folder;
if (!ShellUtil::GetShortcutPath(location, level, &shortcut_folder)) {
LOG(WARNING) << "Cannot find path at location " << location;
return false;
}
if (base::IsDirectoryEmpty(shortcut_folder) &&
!base::DeleteFile(shortcut_folder, true)) {
LOG(ERROR) << "Cannot remove folder " << shortcut_folder.value();
return false;
}
return true;
}
} // namespace
const wchar_t* ShellUtil::kRegDefaultIcon = L"\\DefaultIcon";
const wchar_t* ShellUtil::kRegShellPath = L"\\shell";
const wchar_t* ShellUtil::kRegShellOpen = L"\\shell\\open\\command";
const wchar_t* ShellUtil::kRegStartMenuInternet =
L"Software\\Clients\\StartMenuInternet";
const wchar_t* ShellUtil::kRegClasses = L"Software\\Classes";
const wchar_t* ShellUtil::kRegRegisteredApplications =
L"Software\\RegisteredApplications";
const wchar_t* ShellUtil::kRegVistaUrlPrefs =
L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
L"http\\UserChoice";
const wchar_t* ShellUtil::kAppPathsRegistryKey =
L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
const wchar_t* ShellUtil::kAppPathsRegistryPathName = L"Path";
const wchar_t* ShellUtil::kDefaultFileAssociations[] = {L".htm", L".html",
L".shtml", L".xht", L".xhtml", NULL};
const wchar_t* ShellUtil::kPotentialFileAssociations[] = {L".htm", L".html",
L".pdf", L".shtml", L".svg", L".xht", L".xhtml", L".webp", NULL};
const wchar_t* ShellUtil::kBrowserProtocolAssociations[] = {L"ftp", L"http",
L"https", NULL};
const wchar_t* ShellUtil::kPotentialProtocolAssociations[] = {L"ftp", L"http",
L"https", L"irc", L"mailto", L"mms", L"news", L"nntp", L"sms", L"smsto",
L"tel", L"urn", L"webcal", NULL};
const wchar_t* ShellUtil::kRegUrlProtocol = L"URL Protocol";
const wchar_t* ShellUtil::kRegApplication = L"\\Application";
const wchar_t* ShellUtil::kRegAppUserModelId = L"AppUserModelId";
const wchar_t* ShellUtil::kRegApplicationDescription =
L"ApplicationDescription";
const wchar_t* ShellUtil::kRegApplicationName = L"ApplicationName";
const wchar_t* ShellUtil::kRegApplicationIcon = L"ApplicationIcon";
const wchar_t* ShellUtil::kRegApplicationCompany = L"ApplicationCompany";
const wchar_t* ShellUtil::kRegExePath = L"\\.exe";
const wchar_t* ShellUtil::kRegVerbOpen = L"open";
const wchar_t* ShellUtil::kRegVerbOpenNewWindow = L"opennewwindow";
const wchar_t* ShellUtil::kRegVerbRun = L"run";
const wchar_t* ShellUtil::kRegCommand = L"command";
const wchar_t* ShellUtil::kRegDelegateExecute = L"DelegateExecute";
const wchar_t* ShellUtil::kRegOpenWithProgids = L"OpenWithProgids";
ShellUtil::ShortcutProperties::ShortcutProperties(ShellChange level_in)
: level(level_in),
icon_index(0),
pin_to_taskbar(false),
options(0U) {}
ShellUtil::ShortcutProperties::ShortcutProperties(
const ShortcutProperties& other) = default;
ShellUtil::ShortcutProperties::~ShortcutProperties() {
}
bool ShellUtil::QuickIsChromeRegisteredInHKLM(const base::FilePath& chrome_exe,
const base::string16& suffix) {
return QuickIsChromeRegistered(chrome_exe, suffix,
CONFIRM_SHELL_REGISTRATION_IN_HKLM);
}
bool ShellUtil::ShortcutLocationIsSupported(ShortcutLocation location) {
switch (location) {
case SHORTCUT_LOCATION_DESKTOP: // Falls through.
case SHORTCUT_LOCATION_QUICK_LAUNCH: // Falls through.
case SHORTCUT_LOCATION_START_MENU_ROOT: // Falls through.
case SHORTCUT_LOCATION_START_MENU_CHROME_DIR_DEPRECATED: // Falls through.
case SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR:
return true;
case SHORTCUT_LOCATION_TASKBAR_PINS:
return base::win::GetVersion() >= base::win::VERSION_WIN7;
case SHORTCUT_LOCATION_APP_SHORTCUTS:
return base::win::GetVersion() >= base::win::VERSION_WIN8;
default:
NOTREACHED();
return false;
}
}
bool ShellUtil::GetShortcutPath(ShortcutLocation location,
ShellChange level,
base::FilePath* path) {
DCHECK(path);
int dir_key = -1;
base::string16 folder_to_append;
switch (location) {
case SHORTCUT_LOCATION_DESKTOP:
dir_key = (level == CURRENT_USER) ? base::DIR_USER_DESKTOP :
base::DIR_COMMON_DESKTOP;
break;
case SHORTCUT_LOCATION_QUICK_LAUNCH:
// There is no support for a system-level Quick Launch shortcut.
DCHECK_EQ(level, CURRENT_USER);
dir_key = base::DIR_USER_QUICK_LAUNCH;
break;
case SHORTCUT_LOCATION_START_MENU_ROOT:
dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
base::DIR_COMMON_START_MENU;
break;
case SHORTCUT_LOCATION_START_MENU_CHROME_DIR_DEPRECATED:
dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
base::DIR_COMMON_START_MENU;
folder_to_append = InstallUtil::GetChromeShortcutDirNameDeprecated();
break;
case SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR:
dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
base::DIR_COMMON_START_MENU;
folder_to_append = InstallUtil::GetChromeAppsShortcutDirName();
break;
case SHORTCUT_LOCATION_TASKBAR_PINS:
dir_key = base::DIR_TASKBAR_PINS;
break;
case SHORTCUT_LOCATION_APP_SHORTCUTS:
// TODO(huangs): Move GetAppShortcutsFolder() logic into base_paths_win.
return GetAppShortcutsFolder(level, path);
default:
NOTREACHED();
return false;
}
if (!base::PathService::Get(dir_key, path) || path->empty()) {
NOTREACHED() << dir_key;
return false;
}
if (!folder_to_append.empty())
*path = path->Append(folder_to_append);
return true;
}
// Modifies a ShortcutProperties object by adding default values to
// uninitialized members. Tries to assign:
// - target: |target_exe|.
// - icon: from |target_exe|.
// - icon_index: the browser's icon index
// - app_id: the browser model id for the current install.
// - description: the browser's app description.
// static
void ShellUtil::AddDefaultShortcutProperties(const base::FilePath& target_exe,
ShortcutProperties* properties) {
if (!properties->has_target())
properties->set_target(target_exe);
if (!properties->has_icon())
properties->set_icon(target_exe, install_static::GetIconResourceIndex());
if (!properties->has_app_id()) {
properties->set_app_id(
GetBrowserModelId(!install_static::IsSystemInstall()));
}
if (!properties->has_description())
properties->set_description(InstallUtil::GetAppDescription());
}
bool ShellUtil::MoveExistingShortcut(ShortcutLocation old_location,
ShortcutLocation new_location,
const ShortcutProperties& properties) {
// Explicitly whitelist locations to which this is applicable.
if (old_location != SHORTCUT_LOCATION_START_MENU_CHROME_DIR_DEPRECATED ||
new_location != SHORTCUT_LOCATION_START_MENU_ROOT) {
NOTREACHED();
return false;
}
base::string16 shortcut_name(ExtractShortcutNameFromProperties(properties));
base::FilePath old_shortcut_path;
base::FilePath new_shortcut_path;
GetShortcutPath(old_location, properties.level, &old_shortcut_path);
GetShortcutPath(new_location, properties.level, &new_shortcut_path);
old_shortcut_path = old_shortcut_path.Append(shortcut_name);
new_shortcut_path = new_shortcut_path.Append(shortcut_name);
bool result = base::Move(old_shortcut_path, new_shortcut_path);
RemoveShortcutFolderIfEmpty(old_location, properties.level);
return result;
}
bool ShellUtil::CreateOrUpdateShortcut(
ShortcutLocation location,
const ShortcutProperties& properties,
ShortcutOperation operation) {
// Explicitly whitelist locations to which this is applicable.
if (location != SHORTCUT_LOCATION_DESKTOP &&
location != SHORTCUT_LOCATION_QUICK_LAUNCH &&
location != SHORTCUT_LOCATION_START_MENU_ROOT &&
location != SHORTCUT_LOCATION_START_MENU_CHROME_DIR_DEPRECATED &&
location != SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR) {
NOTREACHED();
return false;
}
// |pin_to_taskbar| is only acknowledged when first creating the shortcut.
DCHECK(!properties.pin_to_taskbar ||
operation == SHELL_SHORTCUT_CREATE_ALWAYS ||
operation == SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL);
base::FilePath user_shortcut_path;
base::FilePath system_shortcut_path;
if (location == SHORTCUT_LOCATION_QUICK_LAUNCH) {
// There is no system-level shortcut for Quick Launch.
DCHECK_EQ(properties.level, CURRENT_USER);
} else if (!GetShortcutPath(location, SYSTEM_LEVEL, &system_shortcut_path)) {
NOTREACHED();
return false;
}
base::string16 shortcut_name(ExtractShortcutNameFromProperties(properties));
system_shortcut_path = system_shortcut_path.Append(shortcut_name);
base::FilePath* chosen_path;
bool should_install_shortcut = true;
if (properties.level == SYSTEM_LEVEL) {
// Install the system-level shortcut if requested.
chosen_path = &system_shortcut_path;
} else if (operation != SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL ||
system_shortcut_path.empty() ||
!base::PathExists(system_shortcut_path)) {
// Otherwise install the user-level shortcut, unless the system-level
// variant of this shortcut is present on the machine and |operation| states
// not to create a user-level shortcut in that case.
if (!GetShortcutPath(location, CURRENT_USER, &user_shortcut_path)) {
NOTREACHED();
return false;
}
user_shortcut_path = user_shortcut_path.Append(shortcut_name);
chosen_path = &user_shortcut_path;
} else {
// Do not install any shortcut if we are told to install a user-level
// shortcut, but the system-level variant of that shortcut is present.
// Other actions (e.g., pinning) can still happen with respect to the
// existing system-level shortcut however.
chosen_path = &system_shortcut_path;
should_install_shortcut = false;
}
if (chosen_path == NULL || chosen_path->empty()) {
NOTREACHED();
return false;
}
base::win::ShortcutOperation shortcut_operation =
TranslateShortcutOperation(operation);
bool success = true;
if (should_install_shortcut) {
// Make sure the parent directories exist when creating the shortcut.
if (shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
!base::CreateDirectory(chosen_path->DirName())) {
NOTREACHED();
return false;
}
base::win::ShortcutProperties shortcut_properties(
TranslateShortcutProperties(properties));
success = base::win::CreateOrUpdateShortcutLink(
*chosen_path, shortcut_properties, shortcut_operation);
}
if (success && shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
properties.pin_to_taskbar && base::win::CanPinShortcutToTaskbar()) {
bool pinned = base::win::PinShortcutToTaskbar(*chosen_path);
LOG_IF(ERROR, !pinned) << "Failed to pin to taskbar "
<< chosen_path->value();
}
return success;
}
base::string16 ShellUtil::FormatIconLocation(const base::FilePath& icon_path,
int icon_index) {
base::string16 icon_string(icon_path.value());
icon_string.append(L",");
icon_string.append(base::IntToString16(icon_index));
return icon_string;
}
base::string16 ShellUtil::GetChromeShellOpenCmd(
const base::FilePath& chrome_exe) {
return L"\"" + chrome_exe.value() + L"\" -- \"%1\"";
}
base::string16 ShellUtil::GetChromeDelegateCommand(
const base::FilePath& chrome_exe) {
return L"\"" + chrome_exe.value() + L"\" -- %*";
}
void ShellUtil::GetRegisteredBrowsers(
std::map<base::string16, base::string16>* browsers) {
DCHECK(browsers);
const base::string16 base_key(kRegStartMenuInternet);
base::string16 client_path;
RegKey key;
base::string16 name;
base::string16 command;
// HKCU has precedence over HKLM for these registrations: http://goo.gl/xjczJ.
// Look in HKCU second to override any identical values found in HKLM.
const HKEY roots[] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
for (const HKEY root : roots) {
for (base::win::RegistryKeyIterator iter(root, base_key.c_str());
iter.Valid(); ++iter) {
client_path.assign(base_key).append(1, L'\\').append(iter.Name());
// Read the browser's name (localized according to install language).
if (key.Open(root, client_path.c_str(), KEY_QUERY_VALUE) !=
ERROR_SUCCESS ||
key.ReadValue(NULL, &name) != ERROR_SUCCESS || name.empty() ||
name.find(install_static::GetBaseAppName()) != base::string16::npos) {
continue;
}
// Read the browser's reinstall command.
if (key.Open(root, (client_path + L"\\InstallInfo").c_str(),
KEY_QUERY_VALUE) == ERROR_SUCCESS &&
key.ReadValue(kReinstallCommand, &command) == ERROR_SUCCESS &&
!command.empty()) {
(*browsers)[name] = command;
}
}
}
}
base::string16 ShellUtil::GetCurrentInstallationSuffix(
const base::FilePath& chrome_exe) {
// This method is somewhat the opposite of GetInstallationSpecificSuffix().
// In this case we are not trying to determine the current suffix for the
// upcoming installation (i.e. not trying to stick to a currently bad
// registration style if one is present).
// Here we want to determine which suffix we should use at run-time.
// In order of preference, we prefer (for user-level installs):
// 1) Base 32 encoding of the md5 hash of the user's sid (new-style).
// 2) Username (old-style).
// 3) Unsuffixed (even worse).
base::string16 tested_suffix;
if (InstallUtil::IsPerUserInstall() &&
(!GetUserSpecificRegistrySuffix(&tested_suffix) ||
!QuickIsChromeRegistered(chrome_exe, tested_suffix,
CONFIRM_PROGID_REGISTRATION)) &&
(!GetOldUserSpecificRegistrySuffix(&tested_suffix) ||
!QuickIsChromeRegistered(chrome_exe, tested_suffix,
CONFIRM_PROGID_REGISTRATION)) &&
!QuickIsChromeRegistered(chrome_exe, tested_suffix.erase(),
CONFIRM_PROGID_REGISTRATION)) {
// If Chrome is not registered under any of the possible suffixes (e.g.
// tests, Canary, etc.): use the new-style suffix at run-time.
if (!GetUserSpecificRegistrySuffix(&tested_suffix))
NOTREACHED();
}
return tested_suffix;
}
base::string16 ShellUtil::GetBrowserModelId(bool is_per_user_install) {
base::string16 app_id(install_static::GetBaseAppId());
base::string16 suffix;
// TODO(robertshield): Temporary hack to make the kRegisterChromeBrowserSuffix
// apply to all registry values computed down in these murky depths.
base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(
installer::switches::kRegisterChromeBrowserSuffix)) {
suffix = command_line.GetSwitchValueNative(
installer::switches::kRegisterChromeBrowserSuffix);
} else if (is_per_user_install && !GetUserSpecificRegistrySuffix(&suffix)) {
NOTREACHED();
}
// There is only one component (i.e. the suffixed appid) in this case, but it
// is still necessary to go through the appid constructor to make sure the
// returned appid is truncated if necessary.
std::vector<base::string16> components(1, app_id.append(suffix));
return BuildAppModelId(components);
}
base::string16 ShellUtil::BuildAppModelId(
const std::vector<base::string16>& components) {
DCHECK_GT(components.size(), 0U);
// Find the maximum numbers of characters allowed in each component
// (accounting for the dots added between each component).
const size_t available_chars =
installer::kMaxAppModelIdLength - (components.size() - 1);
const size_t max_component_length = available_chars / components.size();
// |max_component_length| should be at least 2; otherwise the truncation logic
// below breaks.
if (max_component_length < 2U) {
NOTREACHED();
return (*components.begin()).substr(0, installer::kMaxAppModelIdLength);
}
base::string16 app_id;
app_id.reserve(installer::kMaxAppModelIdLength);
for (std::vector<base::string16>::const_iterator it = components.begin();
it != components.end(); ++it) {
if (it != components.begin())
app_id.push_back(L'.');
const base::string16& component = *it;
DCHECK(!component.empty());
if (component.length() > max_component_length) {
// Append a shortened version of this component. Cut in the middle to try
// to avoid losing the unique parts of this component (which are usually
// at the beginning or end for things like usernames and paths).
app_id.append(component.c_str(), 0, max_component_length / 2);
app_id.append(component.c_str(),
component.length() - ((max_component_length + 1) / 2),
base::string16::npos);
} else {
app_id.append(component);
}
}
// No spaces are allowed in the AppUserModelId according to MSDN.
base::ReplaceChars(app_id, base::ASCIIToUTF16(" "), base::ASCIIToUTF16("_"),
&app_id);
return app_id;
}
ShellUtil::DefaultState ShellUtil::GetChromeDefaultState() {
base::FilePath app_path;
if (!base::PathService::Get(base::FILE_EXE, &app_path)) {
NOTREACHED();
return UNKNOWN_DEFAULT;
}
return GetChromeDefaultStateFromPath(app_path);
}
ShellUtil::DefaultState ShellUtil::GetChromeDefaultStateFromPath(
const base::FilePath& chrome_exe) {
// When we check for default browser we don't necessarily want to count file
// type handlers and icons as having changed the default browser status,
// since the user may have changed their shell settings to cause HTML files
// to open with a text editor for example. We also don't want to aggressively
// claim FTP, since the user may have a separate FTP client. It is an open
// question as to how to "heal" these settings. Perhaps the user should just
// re-run the installer or run with the --set-default-browser command line
// flag. There is doubtless some other key we can hook into to cause "Repair"
// to show up in Add/Remove programs for us.
static const wchar_t* const kChromeProtocols[] = { L"http", L"https" };
DefaultState default_state = ProbeProtocolHandlers(
chrome_exe, kChromeProtocols, base::size(kChromeProtocols));
UpdateDefaultBrowserBeaconWithState(default_state);
return default_state;
}
ShellUtil::DefaultState ShellUtil::GetChromeDefaultProtocolClientState(
const base::string16& protocol) {
if (protocol.empty())
return UNKNOWN_DEFAULT;
base::FilePath chrome_exe;
if (!base::PathService::Get(base::FILE_EXE, &chrome_exe)) {
NOTREACHED();
return UNKNOWN_DEFAULT;
}
const wchar_t* const protocols[] = { protocol.c_str() };
return ProbeProtocolHandlers(chrome_exe, protocols, base::size(protocols));
}
// static
bool ShellUtil::CanMakeChromeDefaultUnattended() {
return base::win::GetVersion() < base::win::VERSION_WIN8;
}
// static
ShellUtil::InteractiveSetDefaultMode ShellUtil::GetInteractiveSetDefaultMode() {
DCHECK(!CanMakeChromeDefaultUnattended());
if (base::win::GetVersion() >= base::win::VERSION_WIN10)
return InteractiveSetDefaultMode::SYSTEM_SETTINGS;
return InteractiveSetDefaultMode::INTENT_PICKER;
}
bool ShellUtil::MakeChromeDefault(int shell_change,
const base::FilePath& chrome_exe,
bool elevate_if_not_admin) {
DCHECK(!(shell_change & SYSTEM_LEVEL) || IsUserAnAdmin());
if (!install_static::SupportsSetAsDefaultBrowser())
return false;
// Windows 8 does not permit making a browser default just like that.
// This process needs to be routed through the system's UI. Use
// ShowMakeChromeDefaultSystemUI instead (below).
if (!CanMakeChromeDefaultUnattended()) {
return false;
}
if (!RegisterChromeBrowser(chrome_exe, base::string16(),
elevate_if_not_admin)) {
return false;
}
bool ret = true;
// First use the new "recommended" way on Vista to make Chrome default
// browser.
base::string16 app_name = GetApplicationName(chrome_exe);
// On Windows 7 we still can set ourselves via the the
// IApplicationAssociationRegistration interface.
VLOG(1) << "Registering Chrome as default browser on Windows 7.";
Microsoft::WRL::ComPtr<IApplicationAssociationRegistration> pAAR;
HRESULT hr = ::CoCreateInstance(CLSID_ApplicationAssociationRegistration,
NULL, CLSCTX_INPROC, IID_PPV_ARGS(&pAAR));
if (SUCCEEDED(hr)) {
for (int i = 0; kBrowserProtocolAssociations[i] != NULL; i++) {
hr = pAAR->SetAppAsDefault(
app_name.c_str(), kBrowserProtocolAssociations[i], AT_URLPROTOCOL);
if (!SUCCEEDED(hr)) {
ret = false;
LOG(ERROR) << "Failed to register as default for protocol "
<< kBrowserProtocolAssociations[i] << " (" << hr << ")";
}
}
for (int i = 0; kDefaultFileAssociations[i] != NULL; i++) {
hr = pAAR->SetAppAsDefault(app_name.c_str(), kDefaultFileAssociations[i],
AT_FILEEXTENSION);
if (!SUCCEEDED(hr)) {
ret = false;
LOG(ERROR) << "Failed to register as default for file extension "
<< kDefaultFileAssociations[i] << " (" << hr << ")";
}
}
}
if (!RegisterChromeAsDefaultXPStyle(shell_change, chrome_exe))
ret = false;
// Send Windows notification event so that it can update icons for
// file associations.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
return ret;
}
#if defined(GOOGLE_CHROME_BUILD)
// static
bool ShellUtil::LaunchUninstallAppsSettings() {
DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN10);
static constexpr wchar_t kControlPanelAppModelId[] =
L"windows.immersivecontrolpanel_cw5n1h2txyewy"
L"!microsoft.windows.immersivecontrolpanel";
Microsoft::WRL::ComPtr<IApplicationActivationManager> activator;
HRESULT hr = ::CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
CLSCTX_ALL, IID_PPV_ARGS(&activator));
if (FAILED(hr))
return false;
DWORD pid = 0;
CoAllowSetForegroundWindow(activator.Get(), nullptr);
hr = activator->ActivateApplication(
kControlPanelAppModelId, L"page=SettingsPageAppsSizes", AO_NONE, &pid);
return SUCCEEDED(hr);
}
#endif // defined(GOOGLE_CHROME_BUILD)
bool ShellUtil::ShowMakeChromeDefaultSystemUI(
const base::FilePath& chrome_exe) {
DCHECK(!CanMakeChromeDefaultUnattended());
if (!install_static::SupportsSetAsDefaultBrowser())
return false;
if (!RegisterChromeBrowser(chrome_exe, base::string16(), true))
return false;
bool succeeded = true;
bool is_default = (GetChromeDefaultState() == IS_DEFAULT);
if (!is_default) {
switch (GetInteractiveSetDefaultMode()) {
case INTENT_PICKER: {
// On Windows 8, you can't set yourself as the default handler
// programatically. In other words IApplicationAssociationRegistration
// has been rendered useless. What you can do is to launch
// "Set Program Associations" section of the "Default Programs"
// control panel, which is a mess, or pop the concise "How you want to
// open webpages?" dialog. We choose the latter.
ScopedUserProtocolEntry user_protocol_entry(L"http");
succeeded = LaunchSelectDefaultProtocolHandlerDialog(L"http");
} break;
case SYSTEM_SETTINGS:
// On Windows 10, you can't even launch the associations dialog.
// So we launch the settings dialog. Quoting from MSDN: "The Open With
// dialog box can no longer be used to change the default program used
// to open a file extension. You can only use SHOpenWithDialog to open
// a single file."
succeeded = LaunchDefaultAppsSettingsModernDialog(L"http");
break;
}
is_default = (succeeded && GetChromeDefaultState() == IS_DEFAULT);
}
if (succeeded && is_default)
RegisterChromeAsDefaultXPStyle(CURRENT_USER, chrome_exe);
return succeeded;
}
bool ShellUtil::MakeChromeDefaultProtocolClient(
const base::FilePath& chrome_exe,
const base::string16& protocol) {
if (!install_static::SupportsSetAsDefaultBrowser())
return false;
if (!RegisterChromeForProtocol(chrome_exe, base::string16(), protocol, true))
return false;
// Windows 8 does not permit making a browser default just like that.
// This process needs to be routed through the system's UI. Use
// ShowMakeChromeDefaultProtocolClientSystemUI instead (below).
if (!CanMakeChromeDefaultUnattended())
return false;
bool ret = true;
// First use the "recommended" way introduced in Vista to make Chrome default
// protocol handler.
VLOG(1) << "Registering Chrome as default handler for " << protocol
<< " on Windows 7.";
Microsoft::WRL::ComPtr<IApplicationAssociationRegistration> pAAR;
HRESULT hr = ::CoCreateInstance(CLSID_ApplicationAssociationRegistration,
NULL, CLSCTX_INPROC, IID_PPV_ARGS(&pAAR));
if (SUCCEEDED(hr)) {
base::string16 app_name = GetApplicationName(chrome_exe);
hr = pAAR->SetAppAsDefault(app_name.c_str(), protocol.c_str(),
AT_URLPROTOCOL);
}
if (!SUCCEEDED(hr)) {
ret = false;
LOG(ERROR) << "Could not make Chrome default protocol client (Windows 7):"
<< " HRESULT=" << hr << ".";
}
// Now use the old way to associate Chrome with the desired protocol. This
// should not be required on Vista+, but since some applications still read
// Software\Classes\<protocol> key directly, do this on Vista+ also.
if (!RegisterChromeAsDefaultProtocolClientXPStyle(chrome_exe, protocol))
ret = false;
return ret;
}
bool ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
const base::FilePath& chrome_exe,
const base::string16& protocol) {
DCHECK(!CanMakeChromeDefaultUnattended());
if (!install_static::SupportsSetAsDefaultBrowser())
return false;
if (!RegisterChromeForProtocol(chrome_exe, base::string16(), protocol, true))
return false;
bool succeeded = true;
bool is_default = (
GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
if (!is_default) {
switch (GetInteractiveSetDefaultMode()) {
case INTENT_PICKER: {
// On Windows 8, you can't set yourself as the default handler
// programatically. In other words IApplicationAssociationRegistration
// has been rendered useless. What you can do is to launch
// "Set Program Associations" section of the "Default Programs"
// control panel, which is a mess, or pop the concise "How you want to
// open
// links of this type (protocol)?" dialog. We choose the latter.
ScopedUserProtocolEntry user_protocol_entry(protocol.c_str());
succeeded = LaunchSelectDefaultProtocolHandlerDialog(protocol.c_str());
} break;
case SYSTEM_SETTINGS:
// On Windows 10, you can't even launch the associations dialog.
// So we launch the settings dialog.
succeeded = LaunchDefaultAppsSettingsModernDialog(protocol.c_str());
break;
}
is_default = (succeeded &&
GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
}
if (succeeded && is_default)
RegisterChromeAsDefaultProtocolClientXPStyle(chrome_exe, protocol);
return succeeded;
}
bool ShellUtil::RegisterChromeBrowser(const base::FilePath& chrome_exe,
const base::string16& unique_suffix,
bool elevate_if_not_admin) {
base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
base::string16 suffix;
if (!unique_suffix.empty()) {
suffix = unique_suffix;
} else if (command_line.HasSwitch(
installer::switches::kRegisterChromeBrowserSuffix)) {
suffix = command_line.GetSwitchValueNative(
installer::switches::kRegisterChromeBrowserSuffix);
} else if (!GetInstallationSpecificSuffix(chrome_exe, &suffix)) {
return false;
}
RemoveRunVerbOnWindows8();
bool user_level = InstallUtil::IsPerUserInstall();
HKEY root = DetermineRegistrationRoot(user_level);
// Look only in HKLM for system-level installs (otherwise, if a user-level
// install is also present, it will lead IsChromeRegistered() to think this
// system-level install isn't registered properly as it is shadowed by the
// user-level install's registrations).
uint32_t look_for_in = user_level ? RegistryEntry::LOOK_IN_HKCU_THEN_HKLM
: RegistryEntry::LOOK_IN_HKLM;
// Check if chrome is already registered with this suffix.
if (IsChromeRegistered(chrome_exe, suffix, look_for_in))
return true;
// Ensure that the shell is notified of the mutations below. Specific exit
// points may disable this if no mutations are made.
base::ScopedClosureRunner notify_on_exit(base::Bind([] {
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
}));
// Do the full registration at user-level or if the user is an admin.
if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
std::vector<std::unique_ptr<RegistryEntry>> progid_and_appreg_entries;
std::vector<std::unique_ptr<RegistryEntry>> shell_entries;
GetChromeProgIdEntries(chrome_exe, suffix, &progid_and_appreg_entries);
GetChromeAppRegistrationEntries(chrome_exe, suffix,
&progid_and_appreg_entries);
GetShellIntegrationEntries(chrome_exe, suffix, &shell_entries);
return AddRegistryEntries(root, progid_and_appreg_entries) &&
AddRegistryEntries(root, shell_entries);
}
// The installer is responsible for registration for system-level installs, so
// never try to do it here. Getting to this point for a system-level install
// likely means that IsChromeRegistered thinks registration is broken due to
// localization issues (see https://crbug.com/717913#c18). It likely is not,
// so return success to allow Chrome to be made default.
if (!user_level) {
notify_on_exit.Release().Reset();
return true;
}
// Try to elevate and register if requested for per-user installs if the user
// is not an admin.
if (elevate_if_not_admin &&
ElevateAndRegisterChrome(chrome_exe, suffix, base::string16())) {
return true;
}
// If we got to this point then all we can do is create ProgId and basic app
// registrations under HKCU.
std::vector<std::unique_ptr<RegistryEntry>> entries;
GetChromeProgIdEntries(chrome_exe, base::string16(), &entries);
// Prefer to use |suffix|; unless Chrome's ProgIds are already registered with
// no suffix (as per the old registration style): in which case some other
// registry entries could refer to them and since we were not able to set our
// HKLM entries above, we are better off not altering these here.
if (!AreEntriesAsDesired(entries, RegistryEntry::LOOK_IN_HKCU)) {
if (!suffix.empty()) {
entries.clear();
GetChromeProgIdEntries(chrome_exe, suffix, &entries);
GetChromeAppRegistrationEntries(chrome_exe, suffix, &entries);
}
return AddRegistryEntries(HKEY_CURRENT_USER, entries);
}
// The ProgId is registered unsuffixed in HKCU, also register the app with
// Windows in HKCU (this was not done in the old registration style and thus
// needs to be done after the above check for the unsuffixed registration).
entries.clear();
GetChromeAppRegistrationEntries(chrome_exe, base::string16(), &entries);
return AddRegistryEntries(HKEY_CURRENT_USER, entries);
}
bool ShellUtil::RegisterChromeForProtocol(const base::FilePath& chrome_exe,
const base::string16& unique_suffix,
const base::string16& protocol,
bool elevate_if_not_admin) {
base::string16 suffix;
if (!unique_suffix.empty()) {
suffix = unique_suffix;
} else if (!GetInstallationSpecificSuffix(chrome_exe, &suffix)) {
return false;
}
bool user_level = InstallUtil::IsPerUserInstall();
HKEY root = DetermineRegistrationRoot(user_level);
// Look only in HKLM for system-level installs (otherwise, if a user-level
// install is also present, it could lead IsChromeRegisteredForProtocol() to
// think this system-level install isn't registered properly as it may be
// shadowed by the user-level install's registrations).
uint32_t look_for_in = user_level ? RegistryEntry::LOOK_IN_HKCU_THEN_HKLM
: RegistryEntry::LOOK_IN_HKLM;
// Check if chrome is already registered with this suffix.
if (IsChromeRegisteredForProtocol(suffix, protocol, look_for_in))
return true;
if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
// We can do this operation directly.
// First, make sure Chrome is fully registered on this machine.
if (!RegisterChromeBrowser(chrome_exe, suffix, false))
return false;
// Write in the capabillity for the protocol.
std::vector<std::unique_ptr<RegistryEntry>> entries;
GetProtocolCapabilityEntries(suffix, protocol, &entries);
return AddRegistryEntries(root, entries);
} else if (elevate_if_not_admin) {
// Elevate to do the whole job
return ElevateAndRegisterChrome(chrome_exe, suffix, protocol);
} else {
// Admin rights are required to register capabilities before Windows 8.
return false;
}
}
// static
bool ShellUtil::RemoveShortcuts(ShortcutLocation location,
ShellChange level,
const base::FilePath& target_exe) {
if (!ShortcutLocationIsSupported(location))
return true; // Vacuous success.
FilterTargetEq shortcut_filter(target_exe, false);
// Main operation to apply to each shortcut in the directory specified.
ShortcutOperationCallback shortcut_operation(
location == SHORTCUT_LOCATION_TASKBAR_PINS
? base::Bind(&ShortcutOpUnpinFromTaskbar)
: base::Bind(&ShortcutOpDelete));
bool success =
BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
shortcut_operation, location, level, nullptr);
// Remove chrome-specific shortcut folders if they are now empty.
if (success &&
(location == SHORTCUT_LOCATION_START_MENU_CHROME_DIR_DEPRECATED ||
location == SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR ||
location == SHORTCUT_LOCATION_APP_SHORTCUTS)) {
success = RemoveShortcutFolderIfEmpty(location, level);
}
return success;
}
// static
bool ShellUtil::RetargetShortcutsWithArgs(
ShortcutLocation location,
ShellChange level,
const base::FilePath& old_target_exe,
const base::FilePath& new_target_exe) {
if (!ShortcutLocationIsSupported(location))
return true; // Vacuous success.
FilterTargetEq shortcut_filter(old_target_exe, true);
ShortcutOperationCallback shortcut_operation(
base::Bind(&ShortcutOpRetarget, old_target_exe, new_target_exe));
return BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
shortcut_operation, location, level, nullptr);
}
// static
bool ShellUtil::ShortcutListMaybeRemoveUnknownArgs(
ShortcutLocation location,
ShellChange level,
const base::FilePath& chrome_exe,
bool do_removal,
const scoped_refptr<SharedCancellationFlag>& cancel,
std::vector<std::pair<base::FilePath, base::string16> >* shortcuts) {
if (!ShortcutLocationIsSupported(location))
return false;
FilterTargetEq shortcut_filter(chrome_exe, true);
ShortcutOperationCallback shortcut_operation(
base::Bind(&ShortcutOpListOrRemoveUnknownArgs, do_removal, shortcuts));
return BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
shortcut_operation, location, level, cancel);
}
bool ShellUtil::GetUserSpecificRegistrySuffix(base::string16* suffix) {
// Use a thread-safe cache for the user's suffix.
static base::LazyInstance<UserSpecificRegistrySuffix>::Leaky suffix_instance =
LAZY_INSTANCE_INITIALIZER;
return suffix_instance.Get().GetSuffix(suffix);
}
bool ShellUtil::GetOldUserSpecificRegistrySuffix(base::string16* suffix) {
wchar_t user_name[256];
DWORD size = base::size(user_name);
if (::GetUserName(user_name, &size) == 0 || size < 1) {
NOTREACHED();
return false;
}
suffix->reserve(size);
suffix->assign(1, L'.');
suffix->append(user_name, size - 1);
return true;
}
// static
bool ShellUtil::AddFileAssociations(
const base::string16& prog_id,
const base::CommandLine& command_line,
const base::string16& file_type_name,
const base::FilePath& icon_path,
const std::set<base::string16>& file_extensions) {
std::vector<std::unique_ptr<RegistryEntry>> entries;
// Create a class for this app.
ApplicationInfo app_info;
app_info.prog_id = prog_id;
app_info.file_type_name = file_type_name;
app_info.file_type_icon_path = icon_path;
app_info.file_type_icon_index = 0;
app_info.command_line = command_line.GetCommandLineStringWithPlaceholders();
GetProgIdEntries(app_info, &entries);
// Associate each extension that the app can handle with the class. Set this
// app as the default handler if and only if there is no existing default.
for (std::set<base::string16>::const_iterator it = file_extensions.begin();
it != file_extensions.end();
++it) {
// Do not allow empty file extensions, or extensions beginning with a '.'.
DCHECK(!it->empty());
DCHECK_NE(L'.', (*it)[0]);
base::string16 ext(1, L'.');
ext.append(*it);
GetAppExtRegistrationEntries(prog_id, ext, &entries);
// Regstering as the default will have no effect on Windows 8 (see
// documentation for GetAppDefaultRegistrationEntries). However, if our app
// is the only handler, it will automatically become the default, so the
// same effect is achieved.
GetAppDefaultRegistrationEntries(prog_id, ext, false, &entries);
}
return AddRegistryEntries(HKEY_CURRENT_USER, entries);
}
// static
bool ShellUtil::DeleteFileAssociations(const base::string16& prog_id) {
// Delete the key HKEY_CURRENT_USER\Software\Classes\PROGID.
base::string16 key_path(kRegClasses);
key_path.push_back(base::FilePath::kSeparators[0]);
key_path.append(prog_id);
return InstallUtil::DeleteRegistryKey(
HKEY_CURRENT_USER, key_path, WorkItem::kWow64Default);
// TODO(mgiuca): Remove the extension association entries. This requires that
// the extensions associated with a particular prog_id are stored in that
// prog_id's key.
}
// static
bool ShellUtil::AddRegistryEntries(
HKEY root,
const std::vector<std::unique_ptr<RegistryEntry>>& entries) {
std::unique_ptr<WorkItemList> items(WorkItem::CreateWorkItemList());
for (const auto& entry : entries)
entry->AddToWorkItemList(root, items.get());
// Apply all the registry changes and if there is a problem, rollback
if (!items->Do()) {
items->Rollback();
return false;
}
return true;
}