Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / installer / util / shell_util.cc
bloba753f802a160ddf145e294d92f9ec2bf4aed136b
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // This file defines functions that integrate Chrome in Windows shell. These
6 // functions can be used by Chrome as well as Chrome installer. All of the
7 // work is done by the local functions defined in anonymous namespace in
8 // this class.
10 #include "chrome/installer/util/shell_util.h"
12 #include <windows.h>
13 #include <shlobj.h>
14 #include <shobjidl.h>
16 #include <limits>
17 #include <string>
19 #include "base/bind.h"
20 #include "base/command_line.h"
21 #include "base/files/file_enumerator.h"
22 #include "base/files/file_path.h"
23 #include "base/files/file_util.h"
24 #include "base/lazy_instance.h"
25 #include "base/logging.h"
26 #include "base/md5.h"
27 #include "base/memory/scoped_ptr.h"
28 #include "base/memory/scoped_vector.h"
29 #include "base/path_service.h"
30 #include "base/strings/string16.h"
31 #include "base/strings/string_number_conversions.h"
32 #include "base/strings/string_split.h"
33 #include "base/strings/string_util.h"
34 #include "base/strings/stringprintf.h"
35 #include "base/strings/utf_string_conversions.h"
36 #include "base/synchronization/cancellation_flag.h"
37 #include "base/values.h"
38 #include "base/win/registry.h"
39 #include "base/win/scoped_co_mem.h"
40 #include "base/win/scoped_comptr.h"
41 #include "base/win/shortcut.h"
42 #include "base/win/win_util.h"
43 #include "base/win/windows_version.h"
44 #include "chrome/common/chrome_constants.h"
45 #include "chrome/common/chrome_switches.h"
46 #include "chrome/installer/util/beacons.h"
47 #include "chrome/installer/util/browser_distribution.h"
48 #include "chrome/installer/util/install_util.h"
49 #include "chrome/installer/util/installer_util_strings.h"
50 #include "chrome/installer/util/l10n_string_util.h"
51 #include "chrome/installer/util/master_preferences.h"
52 #include "chrome/installer/util/master_preferences_constants.h"
53 #include "chrome/installer/util/util_constants.h"
54 #include "chrome/installer/util/work_item.h"
56 using base::win::RegKey;
58 namespace {
60 // An enum used to tell QuickIsChromeRegistered() which level of registration
61 // the caller wants to confirm.
62 enum RegistrationConfirmationLevel {
63 // Only look for Chrome's ProgIds.
64 // This is sufficient when we are trying to determine the suffix of the
65 // currently running Chrome as shell integration registrations might not be
66 // present.
67 CONFIRM_PROGID_REGISTRATION = 0,
68 // Confirm that Chrome is fully integrated with Windows (i.e. registered with
69 // Defaut Programs). These registrations can be in HKCU as of Windows 8.
70 // Note: Shell registration implies ProgId registration.
71 CONFIRM_SHELL_REGISTRATION,
72 // Same as CONFIRM_SHELL_REGISTRATION, but only look in HKLM (used when
73 // uninstalling to know whether elevation is required to clean up the
74 // registry).
75 CONFIRM_SHELL_REGISTRATION_IN_HKLM,
78 const wchar_t kReinstallCommand[] = L"ReinstallCommand";
80 // Returns true if Chrome Metro is supported on this version of Windows
81 // (supported as of Win8; deprecated as of Win10).
82 bool IsChromeMetroSupported() {
83 const base::win::Version win_version = base::win::GetVersion();
84 return win_version >= base::win::VERSION_WIN8 &&
85 win_version < base::win::VERSION_WIN10;
88 // Returns the current (or installed) browser's ProgId (e.g.
89 // "ChromeHTML|suffix|").
90 // |suffix| can be the empty string.
91 base::string16 GetBrowserProgId(const base::string16& suffix) {
92 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
93 base::string16 chrome_html(dist->GetBrowserProgIdPrefix());
94 chrome_html.append(suffix);
96 // ProgIds cannot be longer than 39 characters.
97 // Ref: http://msdn.microsoft.com/en-us/library/aa911706.aspx.
98 // Make all new registrations comply with this requirement (existing
99 // registrations must be preserved).
100 base::string16 new_style_suffix;
101 if (ShellUtil::GetUserSpecificRegistrySuffix(&new_style_suffix) &&
102 suffix == new_style_suffix && chrome_html.length() > 39) {
103 NOTREACHED();
104 chrome_html.erase(39);
106 return chrome_html;
109 // This class is used to initialize and cache a base 32 encoding of the md5 hash
110 // of this user's sid preceded by a dot.
111 // This is guaranteed to be unique on the machine and 27 characters long
112 // (including the '.').
113 // This is then meant to be used as a suffix on all registrations that may
114 // conflict with another user-level Chrome install.
115 class UserSpecificRegistrySuffix {
116 public:
117 // All the initialization is done in the constructor to be able to build the
118 // suffix in a thread-safe manner when used in conjunction with a
119 // LazyInstance.
120 UserSpecificRegistrySuffix();
122 // Sets |suffix| to the pre-computed suffix cached in this object.
123 // Returns true unless the initialization originally failed.
124 bool GetSuffix(base::string16* suffix);
126 private:
127 base::string16 suffix_;
129 DISALLOW_COPY_AND_ASSIGN(UserSpecificRegistrySuffix);
130 }; // class UserSpecificRegistrySuffix
132 UserSpecificRegistrySuffix::UserSpecificRegistrySuffix() {
133 base::string16 user_sid;
134 if (!base::win::GetUserSidString(&user_sid)) {
135 NOTREACHED();
136 return;
138 COMPILE_ASSERT(sizeof(base::MD5Digest) == 16, size_of_MD5_not_as_expected_);
139 base::MD5Digest md5_digest;
140 std::string user_sid_ascii(base::UTF16ToASCII(user_sid));
141 base::MD5Sum(user_sid_ascii.c_str(), user_sid_ascii.length(), &md5_digest);
142 const base::string16 base32_md5(
143 ShellUtil::ByteArrayToBase32(md5_digest.a, arraysize(md5_digest.a)));
144 // The value returned by the base32 algorithm above must never change and
145 // must always be 26 characters long (i.e. if someone ever moves this to
146 // base and implements the full base32 algorithm (i.e. with appended '='
147 // signs in the output), they must provide a flag to allow this method to
148 // still request the output with no appended '=' signs).
149 DCHECK_EQ(base32_md5.length(), 26U);
150 suffix_.reserve(base32_md5.length() + 1);
151 suffix_.assign(1, L'.');
152 suffix_.append(base32_md5);
155 bool UserSpecificRegistrySuffix::GetSuffix(base::string16* suffix) {
156 if (suffix_.empty()) {
157 NOTREACHED();
158 return false;
160 suffix->assign(suffix_);
161 return true;
164 // This class represents a single registry entry. The objective is to
165 // encapsulate all the registry entries required for registering Chrome at one
166 // place. This class can not be instantiated outside the class and the objects
167 // of this class type can be obtained only by calling a static method of this
168 // class.
169 class RegistryEntry {
170 public:
171 // A bit-field enum of places to look for this key in the Windows registry.
172 enum LookForIn {
173 LOOK_IN_HKCU = 1 << 0,
174 LOOK_IN_HKLM = 1 << 1,
175 LOOK_IN_HKCU_THEN_HKLM = LOOK_IN_HKCU | LOOK_IN_HKLM,
178 // Identifies the type of removal this RegistryEntry is flagged for, if any.
179 enum class RemovalFlag {
180 // Default case: install the key/value.
181 NONE,
182 // Registry value under |key_path_|\|name_| is flagged for deletion.
183 VALUE,
184 // Registry key under |key_path_| is flag for deletion.
185 KEY,
188 // Details about a Windows application, to be entered into the registry for
189 // the purpose of file associations.
190 struct ApplicationInfo {
191 ApplicationInfo() : file_type_icon_index(0), application_icon_index(0) {}
193 // The ProgId used by Windows for file associations with this application.
194 // Must not be empty or start with a '.'.
195 base::string16 prog_id;
196 // The friendly name, and the path of the icon that will be used for files
197 // of these types when associated with this application by default. (They
198 // are NOT the name/icon that will represent the application under the Open
199 // With menu.)
200 base::string16 file_type_name;
201 base::FilePath file_type_icon_path;
202 int file_type_icon_index;
203 // The command to execute when opening a file via this association. It
204 // should contain "%1" (to tell Windows to pass the filename as an
205 // argument).
206 // TODO(mgiuca): |command_line| should be a base::CommandLine.
207 base::string16 command_line;
208 // The AppUserModelId used by Windows 8 for this application. Distinct from
209 // |prog_id|.
210 base::string16 app_id;
212 // User-visible details about this application. Any of these may be empty.
213 base::string16 application_name;
214 base::FilePath application_icon_path;
215 int application_icon_index;
216 base::string16 application_description;
217 base::string16 publisher_name;
219 // The CLSID for the application's DelegateExecute handler. May be empty.
220 base::string16 delegate_clsid;
223 // Returns the Windows browser client registration key for Chrome. For
224 // example: "Software\Clients\StartMenuInternet\Chromium[.user]". Strictly
225 // speaking, we should use the name of the executable (e.g., "chrome.exe"),
226 // but that ship has sailed. The cost of switching now is re-prompting users
227 // to make Chrome their default browser, which isn't polite. |suffix| is the
228 // user-specific registration suffix; see GetUserSpecificDefaultBrowserSuffix
229 // in shell_util.h for details.
230 static base::string16 GetBrowserClientKey(BrowserDistribution* dist,
231 const base::string16& suffix) {
232 DCHECK(suffix.empty() || suffix[0] == L'.');
233 return base::string16(ShellUtil::kRegStartMenuInternet)
234 .append(1, L'\\')
235 .append(dist->GetBaseAppName())
236 .append(suffix);
239 // Returns the Windows Default Programs capabilities key for Chrome. For
240 // example:
241 // "Software\Clients\StartMenuInternet\Chromium[.user]\Capabilities".
242 static base::string16 GetCapabilitiesKey(BrowserDistribution* dist,
243 const base::string16& suffix) {
244 return GetBrowserClientKey(dist, suffix).append(L"\\Capabilities");
247 // DelegateExecute ProgId. Needed for Chrome Metro in Windows 8. This is
248 // only needed for registring a web browser, not for general associations.
249 static ScopedVector<RegistryEntry> GetChromeDelegateExecuteEntries(
250 const base::FilePath& chrome_exe,
251 const ApplicationInfo& app_info) {
252 ScopedVector<RegistryEntry> entries;
254 base::string16 app_id_shell_key(ShellUtil::kRegClasses);
255 app_id_shell_key.push_back(base::FilePath::kSeparators[0]);
256 app_id_shell_key.append(app_info.app_id);
257 app_id_shell_key.append(ShellUtil::kRegExePath);
258 app_id_shell_key.append(ShellUtil::kRegShellPath);
260 // <root hkey>\Software\Classes\<app_id>\.exe\shell @=open
261 entries.push_back(
262 new RegistryEntry(app_id_shell_key, ShellUtil::kRegVerbOpen));
264 // The command to execute when opening this application via the Metro UI.
265 const base::string16 delegate_command(
266 ShellUtil::GetChromeDelegateCommand(chrome_exe));
268 // Each of Chrome's shortcuts has an appid; which, as of Windows 8, is
269 // registered to handle some verbs. This registration has the side-effect
270 // that these verbs now show up in the shortcut's context menu. We
271 // mitigate this side-effect by making the context menu entries
272 // user readable/localized strings. See relevant MSDN article:
273 // http://msdn.microsoft.com/en-US/library/windows/desktop/cc144171.aspx
274 static const struct {
275 const wchar_t* verb;
276 int name_id;
277 } verbs[] = {
278 {ShellUtil::kRegVerbOpen, -1},
279 {ShellUtil::kRegVerbOpenNewWindow, IDS_SHORTCUT_NEW_WINDOW_BASE},
281 for (const auto& verb_and_id : verbs) {
282 base::string16 sub_path(app_id_shell_key);
283 sub_path.push_back(base::FilePath::kSeparators[0]);
284 sub_path.append(verb_and_id.verb);
286 // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>
287 if (verb_and_id.name_id != -1) {
288 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
289 // resource.
290 const base::string16 verb_name(
291 installer::GetLocalizedString(verb_and_id.name_id));
292 entries.push_back(new RegistryEntry(sub_path, verb_name.c_str()));
294 entries.push_back(
295 new RegistryEntry(sub_path, L"CommandId", L"Browser.Launch"));
297 sub_path.push_back(base::FilePath::kSeparators[0]);
298 sub_path.append(ShellUtil::kRegCommand);
300 // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>\command
301 entries.push_back(new RegistryEntry(sub_path, delegate_command));
302 entries.push_back(new RegistryEntry(
303 sub_path, ShellUtil::kRegDelegateExecute, app_info.delegate_clsid));
306 return entries.Pass();
309 // This method returns a list of all the registry entries that
310 // are needed to register this installation's ProgId and AppId.
311 // These entries need to be registered in HKLM prior to Win8.
312 static void GetChromeProgIdEntries(BrowserDistribution* dist,
313 const base::FilePath& chrome_exe,
314 const base::string16& suffix,
315 ScopedVector<RegistryEntry>* entries) {
316 int chrome_icon_index =
317 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME);
319 ApplicationInfo app_info;
320 app_info.prog_id = GetBrowserProgId(suffix);
321 app_info.file_type_name = dist->GetBrowserProgIdDesc();
322 // File types associated with Chrome are just given the Chrome icon.
323 app_info.file_type_icon_path = chrome_exe;
324 app_info.file_type_icon_index = chrome_icon_index;
325 app_info.command_line = ShellUtil::GetChromeShellOpenCmd(chrome_exe);
326 // For user-level installs: entries for the app id will be in HKCU; thus we
327 // do not need a suffix on those entries.
328 app_info.app_id = ShellUtil::GetBrowserModelId(
329 dist, InstallUtil::IsPerUserInstall(chrome_exe));
331 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
332 // resource for name, description, and company.
333 app_info.application_name = dist->GetDisplayName();
334 app_info.application_icon_path = chrome_exe;
335 app_info.application_icon_index = chrome_icon_index;
336 app_info.application_description = dist->GetAppDescription();
337 app_info.publisher_name = dist->GetPublisherName();
339 app_info.delegate_clsid = dist->GetCommandExecuteImplClsid();
341 GetProgIdEntries(app_info, entries);
343 if (!app_info.delegate_clsid.empty()) {
344 ScopedVector<RegistryEntry> delegate_execute_entries =
345 GetChromeDelegateExecuteEntries(chrome_exe, app_info);
346 if (!IsChromeMetroSupported()) {
347 // Remove the keys (not only their values) so that Windows will continue
348 // to launch Chrome without a pesky association error.
349 for (RegistryEntry* entry : delegate_execute_entries)
350 entry->set_removal_flag(RemovalFlag::KEY);
352 // Move |delegate_execute_entries| to |entries|.
353 entries->insert(entries->end(), delegate_execute_entries.begin(),
354 delegate_execute_entries.end());
355 delegate_execute_entries.weak_clear();
359 // Gets the registry entries to register an application in the Windows
360 // registry. |app_info| provides all of the information needed.
361 static void GetProgIdEntries(const ApplicationInfo& app_info,
362 ScopedVector<RegistryEntry>* entries) {
363 // Basic sanity checks.
364 DCHECK(!app_info.prog_id.empty());
365 DCHECK_NE(L'.', app_info.prog_id[0]);
367 // File association ProgId
368 base::string16 prog_id_path(ShellUtil::kRegClasses);
369 prog_id_path.push_back(base::FilePath::kSeparators[0]);
370 prog_id_path.append(app_info.prog_id);
371 entries->push_back(
372 new RegistryEntry(prog_id_path, app_info.file_type_name));
373 entries->push_back(new RegistryEntry(
374 prog_id_path + ShellUtil::kRegDefaultIcon,
375 ShellUtil::FormatIconLocation(app_info.file_type_icon_path,
376 app_info.file_type_icon_index)));
377 entries->push_back(new RegistryEntry(
378 prog_id_path + ShellUtil::kRegShellOpen, app_info.command_line));
379 if (!app_info.delegate_clsid.empty()) {
380 entries->push_back(
381 new RegistryEntry(prog_id_path + ShellUtil::kRegShellOpen,
382 ShellUtil::kRegDelegateExecute,
383 app_info.delegate_clsid));
384 // If Metro is not supported, remove the DelegateExecute entry instead of
385 // adding it.
386 if (!IsChromeMetroSupported())
387 entries->back()->set_removal_flag(RemovalFlag::VALUE);
390 // The following entries are required as of Windows 8, but do not
391 // depend on the DelegateExecute verb handler being set.
392 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
393 if (!app_info.app_id.empty()) {
394 entries->push_back(new RegistryEntry(
395 prog_id_path, ShellUtil::kRegAppUserModelId, app_info.app_id));
398 // Add \Software\Classes\<prog_id>\Application entries
399 base::string16 application_path(prog_id_path +
400 ShellUtil::kRegApplication);
401 if (!app_info.app_id.empty()) {
402 entries->push_back(new RegistryEntry(
403 application_path, ShellUtil::kRegAppUserModelId, app_info.app_id));
405 if (!app_info.application_icon_path.empty()) {
406 entries->push_back(new RegistryEntry(
407 application_path,
408 ShellUtil::kRegApplicationIcon,
409 ShellUtil::FormatIconLocation(app_info.application_icon_path,
410 app_info.application_icon_index)));
412 if (!app_info.application_name.empty()) {
413 entries->push_back(new RegistryEntry(application_path,
414 ShellUtil::kRegApplicationName,
415 app_info.application_name));
417 if (!app_info.application_description.empty()) {
418 entries->push_back(
419 new RegistryEntry(application_path,
420 ShellUtil::kRegApplicationDescription,
421 app_info.application_description));
423 if (!app_info.publisher_name.empty()) {
424 entries->push_back(new RegistryEntry(application_path,
425 ShellUtil::kRegApplicationCompany,
426 app_info.publisher_name));
431 // This method returns a list of the registry entries needed to declare a
432 // capability of handling a protocol on Windows.
433 static void GetProtocolCapabilityEntries(
434 BrowserDistribution* dist,
435 const base::string16& suffix,
436 const base::string16& protocol,
437 ScopedVector<RegistryEntry>* entries) {
438 entries->push_back(new RegistryEntry(
439 GetCapabilitiesKey(dist, suffix).append(L"\\URLAssociations"),
440 protocol, GetBrowserProgId(suffix)));
443 // This method returns a list of the registry entries required to register
444 // this installation in "RegisteredApplications" on Windows (to appear in
445 // Default Programs, StartMenuInternet, etc.).
446 // These entries need to be registered in HKLM prior to Win8.
447 // If |suffix| is not empty, these entries are guaranteed to be unique on this
448 // machine.
449 static void GetShellIntegrationEntries(BrowserDistribution* dist,
450 const base::FilePath& chrome_exe,
451 const base::string16& suffix,
452 ScopedVector<RegistryEntry>* entries) {
453 const base::string16 icon_path(
454 ShellUtil::FormatIconLocation(
455 chrome_exe,
456 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME)));
457 const base::string16 quoted_exe_path(L"\"" + chrome_exe.value() + L"\"");
459 // Register for the Start Menu "Internet" link (pre-Win7).
460 const base::string16 start_menu_entry(GetBrowserClientKey(dist, suffix));
461 // Register Chrome's display name.
462 // TODO(grt): http://crbug.com/75152 Also set LocalizedString; see
463 // http://msdn.microsoft.com/en-us/library/windows/desktop/cc144109(v=VS.85).aspx#registering_the_display_name
464 entries->push_back(new RegistryEntry(
465 start_menu_entry, dist->GetDisplayName()));
466 // Register the "open" verb for launching Chrome via the "Internet" link.
467 entries->push_back(new RegistryEntry(
468 start_menu_entry + ShellUtil::kRegShellOpen, quoted_exe_path));
469 // Register Chrome's icon for the Start Menu "Internet" link.
470 entries->push_back(new RegistryEntry(
471 start_menu_entry + ShellUtil::kRegDefaultIcon, icon_path));
473 // Register installation information.
474 base::string16 install_info(start_menu_entry + L"\\InstallInfo");
475 // Note: not using CommandLine since it has ambiguous rules for quoting
476 // strings.
477 entries->push_back(new RegistryEntry(install_info, kReinstallCommand,
478 quoted_exe_path + L" --" +
479 base::ASCIIToUTF16(switches::kMakeDefaultBrowser)));
480 entries->push_back(new RegistryEntry(install_info, L"HideIconsCommand",
481 quoted_exe_path + L" --" +
482 base::ASCIIToUTF16(switches::kHideIcons)));
483 entries->push_back(new RegistryEntry(install_info, L"ShowIconsCommand",
484 quoted_exe_path + L" --" +
485 base::ASCIIToUTF16(switches::kShowIcons)));
486 entries->push_back(new RegistryEntry(install_info, L"IconsVisible", 1));
488 // Register with Default Programs.
489 const base::string16 reg_app_name(dist->GetBaseAppName().append(suffix));
490 // Tell Windows where to find Chrome's Default Programs info.
491 const base::string16 capabilities(GetCapabilitiesKey(dist, suffix));
492 entries->push_back(new RegistryEntry(ShellUtil::kRegRegisteredApplications,
493 reg_app_name, capabilities));
494 // Write out Chrome's Default Programs info.
495 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
496 // resource rather than this.
497 entries->push_back(new RegistryEntry(
498 capabilities, ShellUtil::kRegApplicationDescription,
499 dist->GetLongAppDescription()));
500 entries->push_back(new RegistryEntry(
501 capabilities, ShellUtil::kRegApplicationIcon, icon_path));
502 entries->push_back(new RegistryEntry(
503 capabilities, ShellUtil::kRegApplicationName,
504 dist->GetDisplayName()));
506 entries->push_back(new RegistryEntry(capabilities + L"\\Startmenu",
507 L"StartMenuInternet", reg_app_name));
509 const base::string16 html_prog_id(GetBrowserProgId(suffix));
510 for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
511 entries->push_back(new RegistryEntry(
512 capabilities + L"\\FileAssociations",
513 ShellUtil::kPotentialFileAssociations[i], html_prog_id));
515 for (int i = 0; ShellUtil::kPotentialProtocolAssociations[i] != NULL;
516 i++) {
517 entries->push_back(new RegistryEntry(
518 capabilities + L"\\URLAssociations",
519 ShellUtil::kPotentialProtocolAssociations[i], html_prog_id));
523 // This method returns a list of the registry entries required for this
524 // installation to be registered in the Windows shell.
525 // In particular:
526 // - App Paths
527 // http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
528 // - File Associations
529 // http://msdn.microsoft.com/en-us/library/bb166549
530 // These entries need to be registered in HKLM prior to Win8.
531 static void GetChromeAppRegistrationEntries(
532 const base::FilePath& chrome_exe,
533 const base::string16& suffix,
534 ScopedVector<RegistryEntry>* entries) {
535 base::string16 app_path_key(ShellUtil::kAppPathsRegistryKey);
536 app_path_key.push_back(base::FilePath::kSeparators[0]);
537 app_path_key.append(chrome_exe.BaseName().value());
538 entries->push_back(new RegistryEntry(app_path_key, chrome_exe.value()));
539 entries->push_back(new RegistryEntry(app_path_key,
540 ShellUtil::kAppPathsRegistryPathName,
541 chrome_exe.DirName().value()));
543 const base::string16 html_prog_id(GetBrowserProgId(suffix));
544 for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
545 GetAppExtRegistrationEntries(
546 html_prog_id, ShellUtil::kPotentialFileAssociations[i], entries);
550 // Gets the registry entries to register an application as a handler for a
551 // particular file extension. |prog_id| is the ProgId used by Windows for the
552 // application. |ext| is the file extension, which must begin with a '.'.
553 static void GetAppExtRegistrationEntries(
554 const base::string16& prog_id,
555 const base::string16& ext,
556 ScopedVector<RegistryEntry>* entries) {
557 // In HKEY_CURRENT_USER\Software\Classes\EXT\OpenWithProgids, create an
558 // empty value with this class's ProgId.
559 base::string16 key_name(ShellUtil::kRegClasses);
560 key_name.push_back(base::FilePath::kSeparators[0]);
561 key_name.append(ext);
562 key_name.push_back(base::FilePath::kSeparators[0]);
563 key_name.append(ShellUtil::kRegOpenWithProgids);
564 entries->push_back(new RegistryEntry(key_name, prog_id, base::string16()));
567 // Gets the registry entries to register an application as the default handler
568 // for a particular file extension. |prog_id| is the ProgId used by Windows
569 // for the application. |ext| is the file extension, which must begin with a
570 // '.'. If |overwrite_existing|, always sets the default handler; otherwise
571 // only sets if there is no existing default.
573 // This has no effect on Windows 8. Windows 8 ignores the default and lets the
574 // user choose. If there is only one handler for a file, it will automatically
575 // become the default. Otherwise, the first time the user opens a file, they
576 // are presented with the dialog to set the default handler. (This is roughly
577 // equivalent to being called with |overwrite_existing| false.)
578 static void GetAppDefaultRegistrationEntries(
579 const base::string16& prog_id,
580 const base::string16& ext,
581 bool overwrite_existing,
582 ScopedVector<RegistryEntry>* entries) {
583 // Set the default value of HKEY_CURRENT_USER\Software\Classes\EXT to this
584 // class's name.
585 base::string16 key_name(ShellUtil::kRegClasses);
586 key_name.push_back(base::FilePath::kSeparators[0]);
587 key_name.append(ext);
588 scoped_ptr<RegistryEntry> default_association(
589 new RegistryEntry(key_name, prog_id));
590 if (overwrite_existing ||
591 !default_association->KeyExistsInRegistry(
592 RegistryEntry::LOOK_IN_HKCU)) {
593 entries->push_back(default_association.release());
597 // This method returns a list of all the user level registry entries that
598 // are needed to make Chromium the default handler for a protocol on XP.
599 static void GetXPStyleUserProtocolEntries(
600 const base::string16& protocol,
601 const base::string16& chrome_icon,
602 const base::string16& chrome_open,
603 ScopedVector<RegistryEntry>* entries) {
604 // Protocols associations.
605 base::string16 url_key(ShellUtil::kRegClasses);
606 url_key.push_back(base::FilePath::kSeparators[0]);
607 url_key.append(protocol);
609 // This registry value tells Windows that this 'class' is a URL scheme
610 // so IE, explorer and other apps will route it to our handler.
611 // <root hkey>\Software\Classes\<protocol>\URL Protocol
612 entries->push_back(new RegistryEntry(url_key,
613 ShellUtil::kRegUrlProtocol, base::string16()));
615 // <root hkey>\Software\Classes\<protocol>\DefaultIcon
616 base::string16 icon_key = url_key + ShellUtil::kRegDefaultIcon;
617 entries->push_back(new RegistryEntry(icon_key, chrome_icon));
619 // <root hkey>\Software\Classes\<protocol>\shell\open\command
620 base::string16 shell_key = url_key + ShellUtil::kRegShellOpen;
621 entries->push_back(new RegistryEntry(shell_key, chrome_open));
623 // <root hkey>\Software\Classes\<protocol>\shell\open\ddeexec
624 base::string16 dde_key = url_key + L"\\shell\\open\\ddeexec";
625 entries->push_back(new RegistryEntry(dde_key, base::string16()));
627 // <root hkey>\Software\Classes\<protocol>\shell\@
628 base::string16 protocol_shell_key = url_key + ShellUtil::kRegShellPath;
629 entries->push_back(new RegistryEntry(protocol_shell_key, L"open"));
632 // This method returns a list of all the user level registry entries that
633 // are needed to make Chromium default browser on XP.
634 // Some of these entries are irrelevant in recent versions of Windows, but
635 // we register them anyways as some legacy apps are hardcoded to lookup those
636 // values.
637 static void GetXPStyleDefaultBrowserUserEntries(
638 BrowserDistribution* dist,
639 const base::FilePath& chrome_exe,
640 const base::string16& suffix,
641 ScopedVector<RegistryEntry>* entries) {
642 // File extension associations.
643 base::string16 html_prog_id(GetBrowserProgId(suffix));
644 for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
645 GetAppDefaultRegistrationEntries(
646 html_prog_id, ShellUtil::kDefaultFileAssociations[i], true, entries);
649 // Protocols associations.
650 base::string16 chrome_open = ShellUtil::GetChromeShellOpenCmd(chrome_exe);
651 base::string16 chrome_icon =
652 ShellUtil::FormatIconLocation(
653 chrome_exe,
654 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
655 for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
656 GetXPStyleUserProtocolEntries(ShellUtil::kBrowserProtocolAssociations[i],
657 chrome_icon, chrome_open, entries);
660 // start->Internet shortcut.
661 base::string16 start_menu(ShellUtil::kRegStartMenuInternet);
662 base::string16 app_name = dist->GetBaseAppName() + suffix;
663 entries->push_back(new RegistryEntry(start_menu, app_name));
666 // Flags this RegistryKey with |removal_flag|, indicating that it should be
667 // removed rather than created. Note that this will not result in cleaning up
668 // the entire registry hierarchy below RegistryEntry even if it is left empty
669 // by this operation (this should thus not be used for uninstall, but only to
670 // unregister keys that should explicitly no longer be active in the current
671 // configuration).
672 void set_removal_flag(RemovalFlag removal_flag) {
673 removal_flag_ = removal_flag;
676 // Generates work_item tasks required to create (or potentially delete based
677 // on |removal_flag_|) the current RegistryEntry and add them to the given
678 // work item list.
679 void AddToWorkItemList(HKEY root, WorkItemList *items) const {
680 if (removal_flag_ == RemovalFlag::VALUE) {
681 items->AddDeleteRegValueWorkItem(root, key_path_, WorkItem::kWow64Default,
682 name_);
683 } else if (removal_flag_ == RemovalFlag::KEY) {
684 items->AddDeleteRegKeyWorkItem(root, key_path_, WorkItem::kWow64Default);
685 } else {
686 DCHECK(removal_flag_ == RemovalFlag::NONE);
687 items->AddCreateRegKeyWorkItem(root, key_path_, WorkItem::kWow64Default);
688 if (is_string_) {
689 items->AddSetRegValueWorkItem(root, key_path_, WorkItem::kWow64Default,
690 name_, value_, true);
691 } else {
692 items->AddSetRegValueWorkItem(root, key_path_, WorkItem::kWow64Default,
693 name_, int_value_, true);
698 // Returns true if this key is flagged for removal.
699 bool IsFlaggedForRemoval() const {
700 return removal_flag_ != RemovalFlag::NONE;
703 // Checks if the current registry entry exists in HKCU\|key_path_|\|name_|
704 // and value is |value_|. If the key does NOT exist in HKCU, checks for
705 // the correct name and value in HKLM.
706 // |look_for_in| specifies roots (HKCU and/or HKLM) in which to look for the
707 // key, unspecified roots are not looked into (i.e. the the key is assumed not
708 // to exist in them).
709 // |look_for_in| must at least specify one root to look into.
710 // If |look_for_in| is LOOK_IN_HKCU_THEN_HKLM, this method mimics Windows'
711 // behavior when searching in HKCR (HKCU takes precedence over HKLM). For
712 // registrations outside of HKCR on versions of Windows prior to Win8,
713 // Chrome's values go in HKLM. This function will make unnecessary (but
714 // harmless) queries into HKCU in that case.
715 bool ExistsInRegistry(uint32 look_for_in) const {
716 DCHECK(look_for_in);
718 RegistryStatus status = DOES_NOT_EXIST;
719 if (look_for_in & LOOK_IN_HKCU)
720 status = StatusInRegistryUnderRoot(HKEY_CURRENT_USER);
721 if (status == DOES_NOT_EXIST && (look_for_in & LOOK_IN_HKLM))
722 status = StatusInRegistryUnderRoot(HKEY_LOCAL_MACHINE);
723 return status == SAME_VALUE;
726 // Checks if the current registry entry exists in \|key_path_|\|name_|,
727 // regardless of value. Same lookup rules as ExistsInRegistry.
728 // Unlike ExistsInRegistry, this returns true if some other value is present
729 // with the same key.
730 bool KeyExistsInRegistry(uint32 look_for_in) const {
731 DCHECK(look_for_in);
733 RegistryStatus status = DOES_NOT_EXIST;
734 if (look_for_in & LOOK_IN_HKCU)
735 status = StatusInRegistryUnderRoot(HKEY_CURRENT_USER);
736 if (status == DOES_NOT_EXIST && (look_for_in & LOOK_IN_HKLM))
737 status = StatusInRegistryUnderRoot(HKEY_LOCAL_MACHINE);
738 return status != DOES_NOT_EXIST;
741 private:
742 // States this RegistryKey can be in compared to the registry.
743 enum RegistryStatus {
744 // |name_| does not exist in the registry
745 DOES_NOT_EXIST,
746 // |name_| exists, but its value != |value_|
747 DIFFERENT_VALUE,
748 // |name_| exists and its value is |value_|
749 SAME_VALUE,
752 // Create a object that represent default value of a key
753 RegistryEntry(const base::string16& key_path, const base::string16& value)
754 : key_path_(key_path),
755 name_(),
756 is_string_(true),
757 value_(value),
758 int_value_(0),
759 removal_flag_(RemovalFlag::NONE) {}
761 // Create a object that represent a key of type REG_SZ
762 RegistryEntry(const base::string16& key_path,
763 const base::string16& name,
764 const base::string16& value)
765 : key_path_(key_path),
766 name_(name),
767 is_string_(true),
768 value_(value),
769 int_value_(0),
770 removal_flag_(RemovalFlag::NONE) {}
772 // Create a object that represent a key of integer type
773 RegistryEntry(const base::string16& key_path,
774 const base::string16& name,
775 DWORD value)
776 : key_path_(key_path),
777 name_(name),
778 is_string_(false),
779 value_(),
780 int_value_(value),
781 removal_flag_(RemovalFlag::NONE) {}
783 base::string16 key_path_; // key path for the registry entry
784 base::string16 name_; // name of the registry entry
785 bool is_string_; // true if current registry entry is of type REG_SZ
786 base::string16 value_; // string value (useful if is_string_ = true)
787 DWORD int_value_; // integer value (useful if is_string_ = false)
789 // Identifies whether this RegistryEntry is flagged for removal (i.e. no
790 // longer relevant on the configuration it was created under).
791 RemovalFlag removal_flag_;
793 // Helper function for ExistsInRegistry().
794 // Returns the RegistryStatus of the current registry entry in
795 // |root|\|key_path_|\|name_|.
796 RegistryStatus StatusInRegistryUnderRoot(HKEY root) const {
797 RegKey key(root, key_path_.c_str(), KEY_QUERY_VALUE);
798 bool found = false;
799 bool correct_value = false;
800 if (is_string_) {
801 base::string16 read_value;
802 found = key.ReadValue(name_.c_str(), &read_value) == ERROR_SUCCESS;
803 if (found) {
804 correct_value = read_value.size() == value_.size() &&
805 ::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
806 read_value.data(),
807 base::saturated_cast<int>(read_value.size()),
808 value_.data(),
809 base::saturated_cast<int>(value_.size()))
810 == CSTR_EQUAL;
812 } else {
813 DWORD read_value;
814 found = key.ReadValueDW(name_.c_str(), &read_value) == ERROR_SUCCESS;
815 if (found)
816 correct_value = read_value == int_value_;
818 return found ?
819 (correct_value ? SAME_VALUE : DIFFERENT_VALUE) : DOES_NOT_EXIST;
822 DISALLOW_COPY_AND_ASSIGN(RegistryEntry);
823 }; // class RegistryEntry
826 // This method converts all the RegistryEntries from the given list to
827 // Set/CreateRegWorkItems and runs them using WorkItemList.
828 bool AddRegistryEntries(HKEY root, const ScopedVector<RegistryEntry>& entries) {
829 scoped_ptr<WorkItemList> items(WorkItem::CreateWorkItemList());
831 for (ScopedVector<RegistryEntry>::const_iterator itr = entries.begin();
832 itr != entries.end(); ++itr)
833 (*itr)->AddToWorkItemList(root, items.get());
835 // Apply all the registry changes and if there is a problem, rollback
836 if (!items->Do()) {
837 items->Rollback();
838 return false;
840 return true;
843 // Checks that all |entries| are present on this computer (or absent if their
844 // |removal_flag_| is set). |look_for_in| is passed to
845 // RegistryEntry::ExistsInRegistry(). Documentation for it can be found there.
846 bool AreEntriesAsDesired(const ScopedVector<RegistryEntry>& entries,
847 uint32 look_for_in) {
848 for (const auto* entry : entries) {
849 if (entry->ExistsInRegistry(look_for_in) != !entry->IsFlaggedForRemoval())
850 return false;
852 return true;
855 // Checks that all required registry entries for Chrome are already present on
856 // this computer (or absent if their |removal_flag_| is set).
857 // See RegistryEntry::ExistsInRegistry for the behavior of |look_for_in|.
858 // Note: between r133333 and r154145 we were registering parts of Chrome in HKCU
859 // and parts in HKLM for user-level installs; we now always register everything
860 // under a single registry root. Not doing so caused http://crbug.com/144910 for
861 // users who first-installed Chrome in that revision range (those users are
862 // still impacted by http://crbug.com/144910). This method will keep returning
863 // true for affected users (i.e. who have all the registrations, but over both
864 // registry roots).
865 bool IsChromeRegistered(BrowserDistribution* dist,
866 const base::FilePath& chrome_exe,
867 const base::string16& suffix,
868 uint32 look_for_in) {
869 ScopedVector<RegistryEntry> entries;
870 RegistryEntry::GetChromeProgIdEntries(dist, chrome_exe, suffix, &entries);
871 RegistryEntry::GetShellIntegrationEntries(dist, chrome_exe, suffix, &entries);
872 RegistryEntry::GetChromeAppRegistrationEntries(chrome_exe, suffix, &entries);
873 return AreEntriesAsDesired(entries, look_for_in);
876 // This method checks if Chrome is already registered on the local machine
877 // for the requested protocol. It just checks the one value required for this.
878 // See RegistryEntry::ExistsInRegistry for the behavior of |look_for_in|.
879 bool IsChromeRegisteredForProtocol(BrowserDistribution* dist,
880 const base::string16& suffix,
881 const base::string16& protocol,
882 uint32 look_for_in) {
883 ScopedVector<RegistryEntry> entries;
884 RegistryEntry::GetProtocolCapabilityEntries(dist, suffix, protocol, &entries);
885 return AreEntriesAsDesired(entries, look_for_in);
888 // This method registers Chrome on Vista by launching an elevated setup.exe.
889 // That will show the user the standard Vista elevation prompt. If the user
890 // accepts it the new process will make the necessary changes and return SUCCESS
891 // that we capture and return.
892 // If protocol is non-empty we will also register Chrome as being capable of
893 // handling the protocol.
894 bool ElevateAndRegisterChrome(BrowserDistribution* dist,
895 const base::FilePath& chrome_exe,
896 const base::string16& suffix,
897 const base::string16& protocol) {
898 // Only user-level installs prior to Windows 8 should need to elevate to
899 // register.
900 DCHECK(InstallUtil::IsPerUserInstall(chrome_exe));
901 DCHECK_LT(base::win::GetVersion(), base::win::VERSION_WIN8);
902 base::FilePath exe_path = chrome_exe.DirName().Append(installer::kSetupExe);
903 if (!base::PathExists(exe_path)) {
904 HKEY reg_root = InstallUtil::IsPerUserInstall(chrome_exe) ?
905 HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
906 RegKey key(reg_root,
907 dist->GetUninstallRegPath().c_str(),
908 KEY_READ | KEY_WOW64_32KEY);
909 base::string16 uninstall_string;
910 key.ReadValue(installer::kUninstallStringField, &uninstall_string);
911 base::CommandLine command_line =
912 base::CommandLine::FromString(uninstall_string);
913 exe_path = command_line.GetProgram();
916 if (base::PathExists(exe_path)) {
917 base::CommandLine cmd(exe_path);
918 cmd.AppendSwitchPath(installer::switches::kRegisterChromeBrowser,
919 chrome_exe);
920 if (!suffix.empty()) {
921 cmd.AppendSwitchNative(
922 installer::switches::kRegisterChromeBrowserSuffix, suffix);
925 if (!protocol.empty()) {
926 cmd.AppendSwitchNative(
927 installer::switches::kRegisterURLProtocol, protocol);
930 DWORD ret_val = 0;
931 InstallUtil::ExecuteExeAsAdmin(cmd, &ret_val);
932 if (ret_val == 0)
933 return true;
935 return false;
938 // Launches the Windows 'settings' modern app with the 'default apps' view
939 // focused. This only works for Windows 8 and Windows 10. The appModelId
940 // looks arbitrary but it is the same in Win8 and Win10 previews. There
941 // is no easy way to retrieve the appModelId from the registry.
942 bool LaunchDefaultAppsSettingsModernDialog() {
943 base::win::ScopedComPtr<IApplicationActivationManager> activator;
944 HRESULT hr = activator.CreateInstance(CLSID_ApplicationActivationManager);
945 if (SUCCEEDED(hr)) {
946 DWORD pid = 0;
947 CoAllowSetForegroundWindow(activator.get(), nullptr);
948 hr = activator->ActivateApplication(
949 L"windows.immersivecontrolpanel_cw5n1h2txyewy"
950 L"!microsoft.windows.immersivecontrolpanel",
951 L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
952 return SUCCEEDED(hr);
954 return false;
957 // Launches the Windows 7 and Windows 8 dialog for picking the application to
958 // handle the given protocol. Most importantly, this is used to set the default
959 // handler for http (and, implicitly with it, https). In that case it is also
960 // known as the 'how do you want to open webpages' dialog.
961 // It is required that Chrome be already *registered* for the given protocol.
962 bool LaunchSelectDefaultProtocolHandlerDialog(const wchar_t* protocol) {
963 DCHECK(protocol);
964 OPENASINFO open_as_info = {};
965 open_as_info.pcszFile = protocol;
966 open_as_info.oaifInFlags =
967 OAIF_URL_PROTOCOL | OAIF_FORCE_REGISTRATION | OAIF_REGISTER_EXT;
968 HRESULT hr = SHOpenWithDialog(NULL, &open_as_info);
969 DLOG_IF(WARNING, FAILED(hr)) << "Failed to set as default " << protocol
970 << " handler; hr=0x" << std::hex << hr;
971 if (FAILED(hr))
972 return false;
973 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
974 return true;
977 // Returns true if the current install's |chrome_exe| has been registered with
978 // |suffix|.
979 // |confirmation_level| is the level of verification desired as described in
980 // the RegistrationConfirmationLevel enum above.
981 // |suffix| can be the empty string (this is used to support old installs
982 // where we used to not suffix user-level installs if they were the first to
983 // request the non-suffixed registry entries on the machine).
984 // NOTE: This a quick check that only validates that a single registry entry
985 // points to |chrome_exe|. This should only be used at run-time to determine
986 // how Chrome is registered, not to know whether the registration is complete
987 // at install-time (IsChromeRegistered() can be used for that).
988 bool QuickIsChromeRegistered(BrowserDistribution* dist,
989 const base::FilePath& chrome_exe,
990 const base::string16& suffix,
991 RegistrationConfirmationLevel confirmation_level) {
992 // Get the appropriate key to look for based on the level desired.
993 base::string16 reg_key;
994 switch (confirmation_level) {
995 case CONFIRM_PROGID_REGISTRATION:
996 // Software\Classes\ChromeHTML|suffix|
997 reg_key = ShellUtil::kRegClasses;
998 reg_key.push_back(base::FilePath::kSeparators[0]);
999 reg_key.append(dist->GetBrowserProgIdPrefix());
1000 reg_key.append(suffix);
1001 break;
1002 case CONFIRM_SHELL_REGISTRATION:
1003 case CONFIRM_SHELL_REGISTRATION_IN_HKLM:
1004 // Software\Clients\StartMenuInternet\Google Chrome|suffix|
1005 reg_key = RegistryEntry::GetBrowserClientKey(dist, suffix);
1006 break;
1007 default:
1008 NOTREACHED();
1009 break;
1011 reg_key.append(ShellUtil::kRegShellOpen);
1013 // ProgId registrations are allowed to reside in HKCU for user-level installs
1014 // (and values there have priority over values in HKLM). The same is true for
1015 // shell integration entries as of Windows 8.
1016 if (confirmation_level == CONFIRM_PROGID_REGISTRATION ||
1017 (confirmation_level == CONFIRM_SHELL_REGISTRATION &&
1018 base::win::GetVersion() >= base::win::VERSION_WIN8)) {
1019 const RegKey key_hkcu(HKEY_CURRENT_USER, reg_key.c_str(), KEY_QUERY_VALUE);
1020 base::string16 hkcu_value;
1021 // If |reg_key| is present in HKCU, assert that it points to |chrome_exe|.
1022 // Otherwise, fall back on an HKLM lookup below.
1023 if (key_hkcu.ReadValue(L"", &hkcu_value) == ERROR_SUCCESS)
1024 return InstallUtil::ProgramCompare(chrome_exe).Evaluate(hkcu_value);
1027 // Assert that |reg_key| points to |chrome_exe| in HKLM.
1028 const RegKey key_hklm(HKEY_LOCAL_MACHINE, reg_key.c_str(), KEY_QUERY_VALUE);
1029 base::string16 hklm_value;
1030 if (key_hklm.ReadValue(L"", &hklm_value) == ERROR_SUCCESS)
1031 return InstallUtil::ProgramCompare(chrome_exe).Evaluate(hklm_value);
1032 return false;
1035 // Sets |suffix| to a 27 character string that is specific to this user on this
1036 // machine (on user-level installs only).
1037 // To support old-style user-level installs however, |suffix| is cleared if the
1038 // user currently owns the non-suffixed HKLM registrations.
1039 // |suffix| can also be set to the user's username if the current install is
1040 // suffixed as per the old-style registrations.
1041 // |suffix| is cleared on system-level installs.
1042 // |suffix| should then be appended to all Chrome properties that may conflict
1043 // with other Chrome user-level installs.
1044 // Returns true unless one of the underlying calls fails.
1045 bool GetInstallationSpecificSuffix(BrowserDistribution* dist,
1046 const base::FilePath& chrome_exe,
1047 base::string16* suffix) {
1048 if (!InstallUtil::IsPerUserInstall(chrome_exe) ||
1049 QuickIsChromeRegistered(dist, chrome_exe, base::string16(),
1050 CONFIRM_SHELL_REGISTRATION)) {
1051 // No suffix on system-level installs and user-level installs already
1052 // registered with no suffix.
1053 suffix->clear();
1054 return true;
1057 // Get the old suffix for the check below.
1058 if (!ShellUtil::GetOldUserSpecificRegistrySuffix(suffix)) {
1059 NOTREACHED();
1060 return false;
1062 if (QuickIsChromeRegistered(dist, chrome_exe, *suffix,
1063 CONFIRM_SHELL_REGISTRATION)) {
1064 // Username suffix for installs that are suffixed as per the old-style.
1065 return true;
1068 return ShellUtil::GetUserSpecificRegistrySuffix(suffix);
1071 // Returns the root registry key (HKLM or HKCU) under which registrations must
1072 // be placed for this install. As of Windows 8 everything can go in HKCU for
1073 // per-user installs.
1074 HKEY DetermineRegistrationRoot(bool is_per_user) {
1075 return is_per_user && base::win::GetVersion() >= base::win::VERSION_WIN8 ?
1076 HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
1079 // Associates Chrome with supported protocols and file associations. This should
1080 // not be required on Vista+ but since some applications still read
1081 // Software\Classes\http key directly, we have to do this on Vista+ as well.
1082 bool RegisterChromeAsDefaultXPStyle(BrowserDistribution* dist,
1083 int shell_change,
1084 const base::FilePath& chrome_exe) {
1085 bool ret = true;
1086 ScopedVector<RegistryEntry> entries;
1087 RegistryEntry::GetXPStyleDefaultBrowserUserEntries(
1088 dist, chrome_exe,
1089 ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe), &entries);
1091 // Change the default browser for current user.
1092 if ((shell_change & ShellUtil::CURRENT_USER) &&
1093 !AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
1094 ret = false;
1095 LOG(ERROR) << "Could not make Chrome default browser (XP/current user).";
1098 // Chrome as default browser at system level.
1099 if ((shell_change & ShellUtil::SYSTEM_LEVEL) &&
1100 !AddRegistryEntries(HKEY_LOCAL_MACHINE, entries)) {
1101 ret = false;
1102 LOG(ERROR) << "Could not make Chrome default browser (XP/system level).";
1105 return ret;
1108 // Associates Chrome with |protocol| in the registry. This should not be
1109 // required on Vista+ but since some applications still read these registry
1110 // keys directly, we have to do this on Vista+ as well.
1111 // See http://msdn.microsoft.com/library/aa767914.aspx for more details.
1112 bool RegisterChromeAsDefaultProtocolClientXPStyle(
1113 BrowserDistribution* dist,
1114 const base::FilePath& chrome_exe,
1115 const base::string16& protocol) {
1116 ScopedVector<RegistryEntry> entries;
1117 const base::string16 chrome_open(
1118 ShellUtil::GetChromeShellOpenCmd(chrome_exe));
1119 const base::string16 chrome_icon(
1120 ShellUtil::FormatIconLocation(
1121 chrome_exe,
1122 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME)));
1123 RegistryEntry::GetXPStyleUserProtocolEntries(protocol, chrome_icon,
1124 chrome_open, &entries);
1125 // Change the default protocol handler for current user.
1126 if (!AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
1127 LOG(ERROR) << "Could not make Chrome default protocol client (XP).";
1128 return false;
1131 return true;
1134 // Returns |properties.shortcut_name| if the property is set, otherwise it
1135 // returns dist->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME). In any
1136 // case, it makes sure the return value is suffixed with ".lnk".
1137 base::string16 ExtractShortcutNameFromProperties(
1138 BrowserDistribution* dist,
1139 const ShellUtil::ShortcutProperties& properties) {
1140 DCHECK(dist);
1141 base::string16 shortcut_name;
1142 if (properties.has_shortcut_name()) {
1143 shortcut_name = properties.shortcut_name;
1144 } else {
1145 shortcut_name =
1146 dist->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME);
1149 if (!base::EndsWith(shortcut_name, installer::kLnkExt,
1150 base::CompareCase::INSENSITIVE_ASCII))
1151 shortcut_name.append(installer::kLnkExt);
1153 return shortcut_name;
1156 // Converts ShellUtil::ShortcutOperation to the best-matching value in
1157 // base::win::ShortcutOperation.
1158 base::win::ShortcutOperation TranslateShortcutOperation(
1159 ShellUtil::ShortcutOperation operation) {
1160 switch (operation) {
1161 case ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS: // Falls through.
1162 case ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL:
1163 return base::win::SHORTCUT_CREATE_ALWAYS;
1165 case ShellUtil::SHELL_SHORTCUT_UPDATE_EXISTING:
1166 return base::win::SHORTCUT_UPDATE_EXISTING;
1168 case ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING:
1169 return base::win::SHORTCUT_REPLACE_EXISTING;
1171 default:
1172 NOTREACHED();
1173 return base::win::SHORTCUT_REPLACE_EXISTING;
1177 // Returns a base::win::ShortcutProperties struct containing the properties
1178 // to set on the shortcut based on the provided ShellUtil::ShortcutProperties.
1179 base::win::ShortcutProperties TranslateShortcutProperties(
1180 const ShellUtil::ShortcutProperties& properties) {
1181 base::win::ShortcutProperties shortcut_properties;
1183 if (properties.has_target()) {
1184 shortcut_properties.set_target(properties.target);
1185 DCHECK(!properties.target.DirName().empty());
1186 shortcut_properties.set_working_dir(properties.target.DirName());
1189 if (properties.has_arguments())
1190 shortcut_properties.set_arguments(properties.arguments);
1192 if (properties.has_description())
1193 shortcut_properties.set_description(properties.description);
1195 if (properties.has_icon())
1196 shortcut_properties.set_icon(properties.icon, properties.icon_index);
1198 if (properties.has_app_id())
1199 shortcut_properties.set_app_id(properties.app_id);
1201 if (properties.has_dual_mode())
1202 shortcut_properties.set_dual_mode(properties.dual_mode);
1204 return shortcut_properties;
1207 // Cleans up an old verb (run) we used to register in
1208 // <root>\Software\Classes\Chrome<.suffix>\.exe\shell\run on Windows 8.
1209 void RemoveRunVerbOnWindows8(BrowserDistribution* dist,
1210 const base::FilePath& chrome_exe) {
1211 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
1212 bool is_per_user_install = InstallUtil::IsPerUserInstall(chrome_exe);
1213 HKEY root_key = DetermineRegistrationRoot(is_per_user_install);
1214 // There's no need to rollback, so forgo the usual work item lists and just
1215 // remove the key from the registry.
1216 base::string16 run_verb_key(ShellUtil::kRegClasses);
1217 run_verb_key.push_back(base::FilePath::kSeparators[0]);
1218 run_verb_key.append(ShellUtil::GetBrowserModelId(
1219 dist, is_per_user_install));
1220 run_verb_key.append(ShellUtil::kRegExePath);
1221 run_verb_key.append(ShellUtil::kRegShellPath);
1222 run_verb_key.push_back(base::FilePath::kSeparators[0]);
1223 run_verb_key.append(ShellUtil::kRegVerbRun);
1224 InstallUtil::DeleteRegistryKey(root_key, run_verb_key,
1225 WorkItem::kWow64Default);
1229 // Gets the short (8.3) form of |path|, putting the result in |short_path| and
1230 // returning true on success. |short_path| is not modified on failure.
1231 bool ShortNameFromPath(const base::FilePath& path, base::string16* short_path) {
1232 DCHECK(short_path);
1233 base::string16 result(MAX_PATH, L'\0');
1234 DWORD short_length = GetShortPathName(path.value().c_str(), &result[0],
1235 result.size());
1236 if (short_length == 0 || short_length > result.size()) {
1237 PLOG(ERROR) << "Error getting short (8.3) path for " << path.value();
1238 return false;
1241 result.resize(short_length);
1242 short_path->swap(result);
1243 return true;
1246 // Probe using IApplicationAssociationRegistration::QueryCurrentDefault
1247 // (Windows 8); see ProbeProtocolHandlers. This mechanism is not suitable for
1248 // use on previous versions of Windows despite the presence of
1249 // QueryCurrentDefault on them since versions of Windows prior to Windows 8
1250 // did not perform validation on the ProgID registered as the current default.
1251 // As a result, stale ProgIDs could be returned, leading to false positives.
1252 ShellUtil::DefaultState ProbeCurrentDefaultHandlers(
1253 const base::FilePath& chrome_exe,
1254 const wchar_t* const* protocols,
1255 size_t num_protocols) {
1256 base::win::ScopedComPtr<IApplicationAssociationRegistration> registration;
1257 HRESULT hr = registration.CreateInstance(
1258 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC);
1259 if (FAILED(hr))
1260 return ShellUtil::UNKNOWN_DEFAULT;
1262 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
1263 base::string16 prog_id(dist->GetBrowserProgIdPrefix());
1264 prog_id += ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe);
1266 for (size_t i = 0; i < num_protocols; ++i) {
1267 base::win::ScopedCoMem<wchar_t> current_app;
1268 hr = registration->QueryCurrentDefault(protocols[i], AT_URLPROTOCOL,
1269 AL_EFFECTIVE, &current_app);
1270 if (FAILED(hr) || prog_id.compare(current_app) != 0)
1271 return ShellUtil::NOT_DEFAULT;
1274 return ShellUtil::IS_DEFAULT;
1277 // Probe using IApplicationAssociationRegistration::QueryAppIsDefault (Vista and
1278 // Windows 7); see ProbeProtocolHandlers.
1279 ShellUtil::DefaultState ProbeAppIsDefaultHandlers(
1280 const base::FilePath& chrome_exe,
1281 const wchar_t* const* protocols,
1282 size_t num_protocols) {
1283 base::win::ScopedComPtr<IApplicationAssociationRegistration> registration;
1284 HRESULT hr = registration.CreateInstance(
1285 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC);
1286 if (FAILED(hr))
1287 return ShellUtil::UNKNOWN_DEFAULT;
1289 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
1290 base::string16 app_name(ShellUtil::GetApplicationName(dist, chrome_exe));
1292 BOOL result;
1293 for (size_t i = 0; i < num_protocols; ++i) {
1294 result = TRUE;
1295 hr = registration->QueryAppIsDefault(protocols[i], AT_URLPROTOCOL,
1296 AL_EFFECTIVE, app_name.c_str(), &result);
1297 if (FAILED(hr) || result == FALSE)
1298 return ShellUtil::NOT_DEFAULT;
1301 return ShellUtil::IS_DEFAULT;
1304 // Probe the current commands registered to handle the shell "open" verb for
1305 // |protocols| (Windows XP); see ProbeProtocolHandlers.
1306 ShellUtil::DefaultState ProbeOpenCommandHandlers(
1307 const base::FilePath& chrome_exe,
1308 const wchar_t* const* protocols,
1309 size_t num_protocols) {
1310 // Get its short (8.3) form.
1311 base::string16 short_app_path;
1312 if (!ShortNameFromPath(chrome_exe, &short_app_path))
1313 return ShellUtil::UNKNOWN_DEFAULT;
1315 const HKEY root_key = HKEY_CLASSES_ROOT;
1316 base::string16 key_path;
1317 base::win::RegKey key;
1318 base::string16 value;
1319 base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
1320 base::string16 short_path;
1322 for (size_t i = 0; i < num_protocols; ++i) {
1323 // Get the command line from HKCU\<protocol>\shell\open\command.
1324 key_path.assign(protocols[i]).append(ShellUtil::kRegShellOpen);
1325 if ((key.Open(root_key, key_path.c_str(),
1326 KEY_QUERY_VALUE) != ERROR_SUCCESS) ||
1327 (key.ReadValue(L"", &value) != ERROR_SUCCESS)) {
1328 return ShellUtil::NOT_DEFAULT;
1331 // Need to normalize path in case it's been munged.
1332 command_line = base::CommandLine::FromString(value);
1333 if (!ShortNameFromPath(command_line.GetProgram(), &short_path))
1334 return ShellUtil::UNKNOWN_DEFAULT;
1336 if (!base::FilePath::CompareEqualIgnoreCase(short_path, short_app_path))
1337 return ShellUtil::NOT_DEFAULT;
1340 return ShellUtil::IS_DEFAULT;
1343 // A helper function that probes default protocol handler registration (in a
1344 // manner appropriate for the current version of Windows) to determine if
1345 // Chrome is the default handler for |protocols|. Returns IS_DEFAULT
1346 // only if Chrome is the default for all specified protocols.
1347 ShellUtil::DefaultState ProbeProtocolHandlers(
1348 const base::FilePath& chrome_exe,
1349 const wchar_t* const* protocols,
1350 size_t num_protocols) {
1351 #if DCHECK_IS_ON()
1352 DCHECK(!num_protocols || protocols);
1353 for (size_t i = 0; i < num_protocols; ++i)
1354 DCHECK(protocols[i] && *protocols[i]);
1355 #endif
1357 const base::win::Version windows_version = base::win::GetVersion();
1359 if (windows_version >= base::win::VERSION_WIN8)
1360 return ProbeCurrentDefaultHandlers(chrome_exe, protocols, num_protocols);
1361 else if (windows_version >= base::win::VERSION_VISTA)
1362 return ProbeAppIsDefaultHandlers(chrome_exe, protocols, num_protocols);
1364 return ProbeOpenCommandHandlers(chrome_exe, protocols, num_protocols);
1367 // (Windows 8+) Finds and stores an app shortcuts folder path in *|path|.
1368 // Returns true on success.
1369 bool GetAppShortcutsFolder(BrowserDistribution* dist,
1370 ShellUtil::ShellChange level,
1371 base::FilePath *path) {
1372 DCHECK(path);
1373 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1375 base::FilePath folder;
1376 if (!PathService::Get(base::DIR_APP_SHORTCUTS, &folder)) {
1377 LOG(ERROR) << "Could not get application shortcuts location.";
1378 return false;
1381 folder = folder.Append(
1382 ShellUtil::GetBrowserModelId(dist, level == ShellUtil::CURRENT_USER));
1383 if (!base::DirectoryExists(folder)) {
1384 VLOG(1) << "No start screen shortcuts.";
1385 return false;
1388 *path = folder;
1389 return true;
1392 // Shortcut filters for BatchShortcutAction().
1394 typedef base::Callback<bool(const base::FilePath& /*shortcut_path*/,
1395 const base::string16& /*args*/)>
1396 ShortcutFilterCallback;
1398 // FilterTargetEq is a shortcut filter that matches only shortcuts that have a
1399 // specific target, and optionally matches shortcuts that have non-empty
1400 // arguments.
1401 class FilterTargetEq {
1402 public:
1403 FilterTargetEq(const base::FilePath& desired_target_exe, bool require_args);
1405 // Returns true if filter rules are satisfied, i.e.:
1406 // - |target_path|'s target == |desired_target_compare_|, and
1407 // - |args| is non-empty (if |require_args_| == true).
1408 bool Match(const base::FilePath& target_path,
1409 const base::string16& args) const;
1411 // A convenience routine to create a callback to call Match().
1412 // The callback is only valid during the lifetime of the FilterTargetEq
1413 // instance.
1414 ShortcutFilterCallback AsShortcutFilterCallback();
1416 private:
1417 InstallUtil::ProgramCompare desired_target_compare_;
1419 bool require_args_;
1422 FilterTargetEq::FilterTargetEq(const base::FilePath& desired_target_exe,
1423 bool require_args)
1424 : desired_target_compare_(desired_target_exe),
1425 require_args_(require_args) {}
1427 bool FilterTargetEq::Match(const base::FilePath& target_path,
1428 const base::string16& args) const {
1429 if (!desired_target_compare_.EvaluatePath(target_path))
1430 return false;
1431 if (require_args_ && args.empty())
1432 return false;
1433 return true;
1436 ShortcutFilterCallback FilterTargetEq::AsShortcutFilterCallback() {
1437 return base::Bind(&FilterTargetEq::Match, base::Unretained(this));
1440 // Shortcut operations for BatchShortcutAction().
1442 typedef base::Callback<bool(const base::FilePath& /*shortcut_path*/)>
1443 ShortcutOperationCallback;
1445 bool ShortcutOpUnpinFromTaskbar(const base::FilePath& shortcut_path) {
1446 VLOG(1) << "Trying to unpin from taskbar " << shortcut_path.value();
1447 if (!base::win::UnpinShortcutFromTaskbar(shortcut_path)) {
1448 VLOG(1) << shortcut_path.value()
1449 << " wasn't pinned to taskbar (or the unpin failed).";
1450 // No error, since shortcut might not be pinned.
1452 return true;
1455 bool ShortcutOpDelete(const base::FilePath& shortcut_path) {
1456 bool ret = base::DeleteFile(shortcut_path, false);
1457 LOG_IF(ERROR, !ret) << "Failed to remove " << shortcut_path.value();
1458 return ret;
1461 bool ShortcutOpRetarget(const base::FilePath& old_target,
1462 const base::FilePath& new_target,
1463 const base::FilePath& shortcut_path) {
1464 base::win::ShortcutProperties new_prop;
1465 new_prop.set_target(new_target);
1467 // If the old icon matches old target, then update icon while keeping the old
1468 // icon index. Non-fatal if we fail to get the old icon.
1469 base::win::ShortcutProperties old_prop;
1470 if (base::win::ResolveShortcutProperties(
1471 shortcut_path,
1472 base::win::ShortcutProperties::PROPERTIES_ICON,
1473 &old_prop)) {
1474 if (InstallUtil::ProgramCompare(old_target).EvaluatePath(old_prop.icon))
1475 new_prop.set_icon(new_target, old_prop.icon_index);
1476 } else {
1477 LOG(ERROR) << "Failed to resolve " << shortcut_path.value();
1480 bool result = base::win::CreateOrUpdateShortcutLink(
1481 shortcut_path, new_prop, base::win::SHORTCUT_UPDATE_EXISTING);
1482 LOG_IF(ERROR, !result) << "Failed to retarget " << shortcut_path.value();
1483 return result;
1486 bool ShortcutOpListOrRemoveUnknownArgs(
1487 bool do_removal,
1488 std::vector<std::pair<base::FilePath, base::string16> >* shortcuts,
1489 const base::FilePath& shortcut_path) {
1490 base::string16 args;
1491 if (!base::win::ResolveShortcut(shortcut_path, NULL, &args))
1492 return false;
1494 base::CommandLine current_args(base::CommandLine::FromString(
1495 base::StringPrintf(L"unused_program %ls", args.c_str())));
1496 const char* const kept_switches[] = {
1497 switches::kApp,
1498 switches::kAppId,
1499 switches::kShowAppList,
1500 switches::kProfileDirectory,
1502 base::CommandLine desired_args(base::CommandLine::NO_PROGRAM);
1503 desired_args.CopySwitchesFrom(current_args, kept_switches,
1504 arraysize(kept_switches));
1505 if (desired_args.argv().size() == current_args.argv().size())
1506 return true;
1507 if (shortcuts)
1508 shortcuts->push_back(std::make_pair(shortcut_path, args));
1509 if (!do_removal)
1510 return true;
1511 base::win::ShortcutProperties updated_properties;
1512 updated_properties.set_arguments(desired_args.GetArgumentsString());
1513 return base::win::CreateOrUpdateShortcutLink(
1514 shortcut_path, updated_properties, base::win::SHORTCUT_UPDATE_EXISTING);
1517 // {|location|, |dist|, |level|} determine |shortcut_folder|.
1518 // For each shortcut in |shortcut_folder| that match |shortcut_filter|, apply
1519 // |shortcut_operation|. Returns true if all operations are successful.
1520 // All intended operations are attempted, even if failures occur.
1521 // This method will abort and return false if |cancel| is non-NULL and gets set
1522 // at any point during this call.
1523 bool BatchShortcutAction(
1524 const ShortcutFilterCallback& shortcut_filter,
1525 const ShortcutOperationCallback& shortcut_operation,
1526 ShellUtil::ShortcutLocation location,
1527 BrowserDistribution* dist,
1528 ShellUtil::ShellChange level,
1529 const scoped_refptr<ShellUtil::SharedCancellationFlag>& cancel) {
1530 DCHECK(!shortcut_operation.is_null());
1532 // There is no system-level Quick Launch shortcut folder.
1533 if (level == ShellUtil::SYSTEM_LEVEL &&
1534 location == ShellUtil::SHORTCUT_LOCATION_QUICK_LAUNCH) {
1535 return true;
1538 base::FilePath shortcut_folder;
1539 if (!ShellUtil::GetShortcutPath(location, dist, level, &shortcut_folder)) {
1540 LOG(WARNING) << "Cannot find path at location " << location;
1541 return false;
1544 bool success = true;
1545 base::FileEnumerator enumerator(
1546 shortcut_folder, false, base::FileEnumerator::FILES,
1547 base::string16(L"*") + installer::kLnkExt);
1548 base::FilePath target_path;
1549 base::string16 args;
1550 for (base::FilePath shortcut_path = enumerator.Next();
1551 !shortcut_path.empty();
1552 shortcut_path = enumerator.Next()) {
1553 if (cancel.get() && cancel->data.IsSet())
1554 return false;
1555 if (base::win::ResolveShortcut(shortcut_path, &target_path, &args)) {
1556 if (shortcut_filter.Run(target_path, args) &&
1557 !shortcut_operation.Run(shortcut_path)) {
1558 success = false;
1560 } else {
1561 LOG(ERROR) << "Cannot resolve shortcut at " << shortcut_path.value();
1562 success = false;
1565 return success;
1569 // If the folder specified by {|location|, |dist|, |level|} is empty, remove it.
1570 // Otherwise do nothing. Returns true on success, including the vacuous case
1571 // where no deletion occurred because directory is non-empty.
1572 bool RemoveShortcutFolderIfEmpty(ShellUtil::ShortcutLocation location,
1573 BrowserDistribution* dist,
1574 ShellUtil::ShellChange level) {
1575 // Explicitly whitelist locations, since accidental calls can be very harmful.
1576 if (location != ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_DIR &&
1577 location != ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR &&
1578 location != ShellUtil::SHORTCUT_LOCATION_APP_SHORTCUTS) {
1579 NOTREACHED();
1580 return false;
1583 base::FilePath shortcut_folder;
1584 if (!ShellUtil::GetShortcutPath(location, dist, level, &shortcut_folder)) {
1585 LOG(WARNING) << "Cannot find path at location " << location;
1586 return false;
1588 if (base::IsDirectoryEmpty(shortcut_folder) &&
1589 !base::DeleteFile(shortcut_folder, true)) {
1590 LOG(ERROR) << "Cannot remove folder " << shortcut_folder.value();
1591 return false;
1593 return true;
1596 } // namespace
1598 const wchar_t* ShellUtil::kRegDefaultIcon = L"\\DefaultIcon";
1599 const wchar_t* ShellUtil::kRegShellPath = L"\\shell";
1600 const wchar_t* ShellUtil::kRegShellOpen = L"\\shell\\open\\command";
1601 const wchar_t* ShellUtil::kRegStartMenuInternet =
1602 L"Software\\Clients\\StartMenuInternet";
1603 const wchar_t* ShellUtil::kRegClasses = L"Software\\Classes";
1604 const wchar_t* ShellUtil::kRegRegisteredApplications =
1605 L"Software\\RegisteredApplications";
1606 const wchar_t* ShellUtil::kRegVistaUrlPrefs =
1607 L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
1608 L"http\\UserChoice";
1609 const wchar_t* ShellUtil::kAppPathsRegistryKey =
1610 L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
1611 const wchar_t* ShellUtil::kAppPathsRegistryPathName = L"Path";
1613 const wchar_t* ShellUtil::kDefaultFileAssociations[] = {L".htm", L".html",
1614 L".shtml", L".xht", L".xhtml", NULL};
1615 const wchar_t* ShellUtil::kPotentialFileAssociations[] = {L".htm", L".html",
1616 L".shtml", L".xht", L".xhtml", L".webp", NULL};
1617 const wchar_t* ShellUtil::kBrowserProtocolAssociations[] = {L"ftp", L"http",
1618 L"https", NULL};
1619 const wchar_t* ShellUtil::kPotentialProtocolAssociations[] = {L"ftp", L"http",
1620 L"https", L"irc", L"mailto", L"mms", L"news", L"nntp", L"sms", L"smsto",
1621 L"tel", L"urn", L"webcal", NULL};
1622 const wchar_t* ShellUtil::kRegUrlProtocol = L"URL Protocol";
1623 const wchar_t* ShellUtil::kRegApplication = L"\\Application";
1624 const wchar_t* ShellUtil::kRegAppUserModelId = L"AppUserModelId";
1625 const wchar_t* ShellUtil::kRegApplicationDescription =
1626 L"ApplicationDescription";
1627 const wchar_t* ShellUtil::kRegApplicationName = L"ApplicationName";
1628 const wchar_t* ShellUtil::kRegApplicationIcon = L"ApplicationIcon";
1629 const wchar_t* ShellUtil::kRegApplicationCompany = L"ApplicationCompany";
1630 const wchar_t* ShellUtil::kRegExePath = L"\\.exe";
1631 const wchar_t* ShellUtil::kRegVerbOpen = L"open";
1632 const wchar_t* ShellUtil::kRegVerbOpenNewWindow = L"opennewwindow";
1633 const wchar_t* ShellUtil::kRegVerbRun = L"run";
1634 const wchar_t* ShellUtil::kRegCommand = L"command";
1635 const wchar_t* ShellUtil::kRegDelegateExecute = L"DelegateExecute";
1636 const wchar_t* ShellUtil::kRegOpenWithProgids = L"OpenWithProgids";
1638 ShellUtil::ShortcutProperties::ShortcutProperties(ShellChange level_in)
1639 : level(level_in),
1640 icon_index(0),
1641 dual_mode(false),
1642 pin_to_taskbar(false),
1643 pin_to_start(false),
1644 options(0U) {}
1646 ShellUtil::ShortcutProperties::~ShortcutProperties() {
1649 bool ShellUtil::QuickIsChromeRegisteredInHKLM(BrowserDistribution* dist,
1650 const base::FilePath& chrome_exe,
1651 const base::string16& suffix) {
1652 return QuickIsChromeRegistered(dist, chrome_exe, suffix,
1653 CONFIRM_SHELL_REGISTRATION_IN_HKLM);
1656 bool ShellUtil::ShortcutLocationIsSupported(
1657 ShellUtil::ShortcutLocation location) {
1658 switch (location) {
1659 case SHORTCUT_LOCATION_DESKTOP: // Falls through.
1660 case SHORTCUT_LOCATION_QUICK_LAUNCH: // Falls through.
1661 case SHORTCUT_LOCATION_START_MENU_ROOT: // Falls through.
1662 case SHORTCUT_LOCATION_START_MENU_CHROME_DIR: // Falls through.
1663 case SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR:
1664 return true;
1665 case SHORTCUT_LOCATION_TASKBAR_PINS:
1666 return base::win::GetVersion() >= base::win::VERSION_WIN7;
1667 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1668 return base::win::GetVersion() >= base::win::VERSION_WIN8;
1669 default:
1670 NOTREACHED();
1671 return false;
1675 bool ShellUtil::GetShortcutPath(ShellUtil::ShortcutLocation location,
1676 BrowserDistribution* dist,
1677 ShellChange level,
1678 base::FilePath* path) {
1679 DCHECK(path);
1680 int dir_key = -1;
1681 base::string16 folder_to_append;
1682 switch (location) {
1683 case SHORTCUT_LOCATION_DESKTOP:
1684 dir_key = (level == CURRENT_USER) ? base::DIR_USER_DESKTOP :
1685 base::DIR_COMMON_DESKTOP;
1686 break;
1687 case SHORTCUT_LOCATION_QUICK_LAUNCH:
1688 // There is no support for a system-level Quick Launch shortcut.
1689 DCHECK_EQ(level, CURRENT_USER);
1690 dir_key = base::DIR_USER_QUICK_LAUNCH;
1691 break;
1692 case SHORTCUT_LOCATION_START_MENU_ROOT:
1693 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1694 base::DIR_COMMON_START_MENU;
1695 break;
1696 case SHORTCUT_LOCATION_START_MENU_CHROME_DIR:
1697 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1698 base::DIR_COMMON_START_MENU;
1699 folder_to_append = dist->GetStartMenuShortcutSubfolder(
1700 BrowserDistribution::SUBFOLDER_CHROME);
1701 break;
1702 case SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR:
1703 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1704 base::DIR_COMMON_START_MENU;
1705 folder_to_append = dist->GetStartMenuShortcutSubfolder(
1706 BrowserDistribution::SUBFOLDER_APPS);
1707 break;
1708 case SHORTCUT_LOCATION_TASKBAR_PINS:
1709 dir_key = base::DIR_TASKBAR_PINS;
1710 break;
1711 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1712 // TODO(huangs): Move GetAppShortcutsFolder() logic into base_paths_win.
1713 return GetAppShortcutsFolder(dist, level, path);
1715 default:
1716 NOTREACHED();
1717 return false;
1720 if (!PathService::Get(dir_key, path) || path->empty()) {
1721 NOTREACHED() << dir_key;
1722 return false;
1725 if (!folder_to_append.empty())
1726 *path = path->Append(folder_to_append);
1728 return true;
1731 bool ShellUtil::CreateOrUpdateShortcut(
1732 ShellUtil::ShortcutLocation location,
1733 BrowserDistribution* dist,
1734 const ShellUtil::ShortcutProperties& properties,
1735 ShellUtil::ShortcutOperation operation) {
1736 // Explicitly whitelist locations to which this is applicable.
1737 if (location != SHORTCUT_LOCATION_DESKTOP &&
1738 location != SHORTCUT_LOCATION_QUICK_LAUNCH &&
1739 location != SHORTCUT_LOCATION_START_MENU_ROOT &&
1740 location != SHORTCUT_LOCATION_START_MENU_CHROME_DIR &&
1741 location != SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR) {
1742 NOTREACHED();
1743 return false;
1746 DCHECK(dist);
1747 // |pin_to_taskbar| is only acknowledged when first creating the shortcut.
1748 DCHECK(!properties.pin_to_taskbar ||
1749 operation == SHELL_SHORTCUT_CREATE_ALWAYS ||
1750 operation == SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL);
1752 base::FilePath user_shortcut_path;
1753 base::FilePath system_shortcut_path;
1754 if (location == SHORTCUT_LOCATION_QUICK_LAUNCH) {
1755 // There is no system-level shortcut for Quick Launch.
1756 DCHECK_EQ(properties.level, CURRENT_USER);
1757 } else if (!GetShortcutPath(
1758 location, dist, SYSTEM_LEVEL, &system_shortcut_path)) {
1759 NOTREACHED();
1760 return false;
1763 base::string16 shortcut_name(
1764 ExtractShortcutNameFromProperties(dist, properties));
1765 system_shortcut_path = system_shortcut_path.Append(shortcut_name);
1767 base::FilePath* chosen_path;
1768 bool should_install_shortcut = true;
1769 if (properties.level == SYSTEM_LEVEL) {
1770 // Install the system-level shortcut if requested.
1771 chosen_path = &system_shortcut_path;
1772 } else if (operation != SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL ||
1773 system_shortcut_path.empty() ||
1774 !base::PathExists(system_shortcut_path)) {
1775 // Otherwise install the user-level shortcut, unless the system-level
1776 // variant of this shortcut is present on the machine and |operation| states
1777 // not to create a user-level shortcut in that case.
1778 if (!GetShortcutPath(location, dist, CURRENT_USER, &user_shortcut_path)) {
1779 NOTREACHED();
1780 return false;
1782 user_shortcut_path = user_shortcut_path.Append(shortcut_name);
1783 chosen_path = &user_shortcut_path;
1784 } else {
1785 // Do not install any shortcut if we are told to install a user-level
1786 // shortcut, but the system-level variant of that shortcut is present.
1787 // Other actions (e.g., pinning) can still happen with respect to the
1788 // existing system-level shortcut however.
1789 chosen_path = &system_shortcut_path;
1790 should_install_shortcut = false;
1793 if (chosen_path == NULL || chosen_path->empty()) {
1794 NOTREACHED();
1795 return false;
1798 base::win::ShortcutOperation shortcut_operation =
1799 TranslateShortcutOperation(operation);
1800 bool success = true;
1801 if (should_install_shortcut) {
1802 // Make sure the parent directories exist when creating the shortcut.
1803 if (shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
1804 !base::CreateDirectory(chosen_path->DirName())) {
1805 NOTREACHED();
1806 return false;
1809 base::win::ShortcutProperties shortcut_properties(
1810 TranslateShortcutProperties(properties));
1811 success = base::win::CreateOrUpdateShortcutLink(
1812 *chosen_path, shortcut_properties, shortcut_operation);
1815 if (success && shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS) {
1816 if (properties.pin_to_taskbar &&
1817 base::win::GetVersion() >= base::win::VERSION_WIN7) {
1818 bool pinned = base::win::PinShortcutToTaskbar(*chosen_path);
1819 LOG_IF(ERROR, !pinned) << "Failed to pin to taskbar "
1820 << chosen_path->value();
1822 if (properties.pin_to_start &&
1823 base::win::GetVersion() >= base::win::VERSION_WIN10) {
1824 bool pinned = base::win::PinShortcutToStart(*chosen_path);
1825 LOG_IF(ERROR, !pinned) << "Failed to pin to start "
1826 << chosen_path->value();
1830 return success;
1833 base::string16 ShellUtil::FormatIconLocation(const base::FilePath& icon_path,
1834 int icon_index) {
1835 base::string16 icon_string(icon_path.value());
1836 icon_string.append(L",");
1837 icon_string.append(base::IntToString16(icon_index));
1838 return icon_string;
1841 base::string16 ShellUtil::GetChromeShellOpenCmd(
1842 const base::FilePath& chrome_exe) {
1843 return L"\"" + chrome_exe.value() + L"\" -- \"%1\"";
1846 base::string16 ShellUtil::GetChromeDelegateCommand(
1847 const base::FilePath& chrome_exe) {
1848 return L"\"" + chrome_exe.value() + L"\" -- %*";
1851 void ShellUtil::GetRegisteredBrowsers(
1852 BrowserDistribution* dist,
1853 std::map<base::string16, base::string16>* browsers) {
1854 DCHECK(dist);
1855 DCHECK(browsers);
1857 const base::string16 base_key(ShellUtil::kRegStartMenuInternet);
1858 base::string16 client_path;
1859 RegKey key;
1860 base::string16 name;
1861 base::string16 command;
1863 // HKCU has precedence over HKLM for these registrations: http://goo.gl/xjczJ.
1864 // Look in HKCU second to override any identical values found in HKLM.
1865 const HKEY roots[] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
1866 for (int i = 0; i < arraysize(roots); ++i) {
1867 const HKEY root = roots[i];
1868 for (base::win::RegistryKeyIterator iter(root, base_key.c_str());
1869 iter.Valid(); ++iter) {
1870 client_path.assign(base_key).append(1, L'\\').append(iter.Name());
1871 // Read the browser's name (localized according to install language).
1872 if (key.Open(root, client_path.c_str(),
1873 KEY_QUERY_VALUE) != ERROR_SUCCESS ||
1874 key.ReadValue(NULL, &name) != ERROR_SUCCESS ||
1875 name.empty() ||
1876 name.find(dist->GetBaseAppName()) != base::string16::npos) {
1877 continue;
1879 // Read the browser's reinstall command.
1880 if (key.Open(root, (client_path + L"\\InstallInfo").c_str(),
1881 KEY_QUERY_VALUE) == ERROR_SUCCESS &&
1882 key.ReadValue(kReinstallCommand, &command) == ERROR_SUCCESS &&
1883 !command.empty()) {
1884 (*browsers)[name] = command;
1890 base::string16 ShellUtil::GetCurrentInstallationSuffix(
1891 BrowserDistribution* dist,
1892 const base::FilePath& chrome_exe) {
1893 // This method is somewhat the opposite of GetInstallationSpecificSuffix().
1894 // In this case we are not trying to determine the current suffix for the
1895 // upcoming installation (i.e. not trying to stick to a currently bad
1896 // registration style if one is present).
1897 // Here we want to determine which suffix we should use at run-time.
1898 // In order of preference, we prefer (for user-level installs):
1899 // 1) Base 32 encoding of the md5 hash of the user's sid (new-style).
1900 // 2) Username (old-style).
1901 // 3) Unsuffixed (even worse).
1902 base::string16 tested_suffix;
1903 if (InstallUtil::IsPerUserInstall(chrome_exe) &&
1904 (!GetUserSpecificRegistrySuffix(&tested_suffix) ||
1905 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix,
1906 CONFIRM_PROGID_REGISTRATION)) &&
1907 (!GetOldUserSpecificRegistrySuffix(&tested_suffix) ||
1908 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix,
1909 CONFIRM_PROGID_REGISTRATION)) &&
1910 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix.erase(),
1911 CONFIRM_PROGID_REGISTRATION)) {
1912 // If Chrome is not registered under any of the possible suffixes (e.g.
1913 // tests, Canary, etc.): use the new-style suffix at run-time.
1914 if (!GetUserSpecificRegistrySuffix(&tested_suffix))
1915 NOTREACHED();
1917 return tested_suffix;
1920 base::string16 ShellUtil::GetApplicationName(BrowserDistribution* dist,
1921 const base::FilePath& chrome_exe) {
1922 base::string16 app_name = dist->GetBaseAppName();
1923 app_name += GetCurrentInstallationSuffix(dist, chrome_exe);
1924 return app_name;
1927 base::string16 ShellUtil::GetBrowserModelId(BrowserDistribution* dist,
1928 bool is_per_user_install) {
1929 base::string16 app_id(dist->GetBaseAppId());
1930 base::string16 suffix;
1932 // TODO(robertshield): Temporary hack to make the kRegisterChromeBrowserSuffix
1933 // apply to all registry values computed down in these murky depths.
1934 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
1935 if (command_line.HasSwitch(
1936 installer::switches::kRegisterChromeBrowserSuffix)) {
1937 suffix = command_line.GetSwitchValueNative(
1938 installer::switches::kRegisterChromeBrowserSuffix);
1939 } else if (is_per_user_install && !GetUserSpecificRegistrySuffix(&suffix)) {
1940 NOTREACHED();
1942 // There is only one component (i.e. the suffixed appid) in this case, but it
1943 // is still necessary to go through the appid constructor to make sure the
1944 // returned appid is truncated if necessary.
1945 std::vector<base::string16> components(1, app_id.append(suffix));
1946 return BuildAppModelId(components);
1949 base::string16 ShellUtil::BuildAppModelId(
1950 const std::vector<base::string16>& components) {
1951 DCHECK_GT(components.size(), 0U);
1953 // Find the maximum numbers of characters allowed in each component
1954 // (accounting for the dots added between each component).
1955 const size_t available_chars =
1956 installer::kMaxAppModelIdLength - (components.size() - 1);
1957 const size_t max_component_length = available_chars / components.size();
1959 // |max_component_length| should be at least 2; otherwise the truncation logic
1960 // below breaks.
1961 if (max_component_length < 2U) {
1962 NOTREACHED();
1963 return (*components.begin()).substr(0, installer::kMaxAppModelIdLength);
1966 base::string16 app_id;
1967 app_id.reserve(installer::kMaxAppModelIdLength);
1968 for (std::vector<base::string16>::const_iterator it = components.begin();
1969 it != components.end(); ++it) {
1970 if (it != components.begin())
1971 app_id.push_back(L'.');
1973 const base::string16& component = *it;
1974 DCHECK(!component.empty());
1975 if (component.length() > max_component_length) {
1976 // Append a shortened version of this component. Cut in the middle to try
1977 // to avoid losing the unique parts of this component (which are usually
1978 // at the beginning or end for things like usernames and paths).
1979 app_id.append(component.c_str(), 0, max_component_length / 2);
1980 app_id.append(component.c_str(),
1981 component.length() - ((max_component_length + 1) / 2),
1982 base::string16::npos);
1983 } else {
1984 app_id.append(component);
1987 // No spaces are allowed in the AppUserModelId according to MSDN.
1988 base::ReplaceChars(app_id, base::ASCIIToUTF16(" "), base::ASCIIToUTF16("_"),
1989 &app_id);
1990 return app_id;
1993 ShellUtil::DefaultState ShellUtil::GetChromeDefaultState() {
1994 base::FilePath app_path;
1995 if (!PathService::Get(base::FILE_EXE, &app_path)) {
1996 NOTREACHED();
1997 return ShellUtil::UNKNOWN_DEFAULT;
2000 return GetChromeDefaultStateFromPath(app_path);
2003 ShellUtil::DefaultState ShellUtil::GetChromeDefaultStateFromPath(
2004 const base::FilePath& chrome_exe) {
2005 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
2006 if (distribution->GetDefaultBrowserControlPolicy() ==
2007 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
2008 return NOT_DEFAULT;
2010 // When we check for default browser we don't necessarily want to count file
2011 // type handlers and icons as having changed the default browser status,
2012 // since the user may have changed their shell settings to cause HTML files
2013 // to open with a text editor for example. We also don't want to aggressively
2014 // claim FTP, since the user may have a separate FTP client. It is an open
2015 // question as to how to "heal" these settings. Perhaps the user should just
2016 // re-run the installer or run with the --set-default-browser command line
2017 // flag. There is doubtless some other key we can hook into to cause "Repair"
2018 // to show up in Add/Remove programs for us.
2019 static const wchar_t* const kChromeProtocols[] = { L"http", L"https" };
2020 DefaultState default_state = ProbeProtocolHandlers(
2021 chrome_exe, kChromeProtocols, arraysize(kChromeProtocols));
2022 UpdateDefaultBrowserBeaconWithState(chrome_exe, distribution, default_state);
2023 return default_state;
2026 ShellUtil::DefaultState ShellUtil::GetChromeDefaultProtocolClientState(
2027 const base::string16& protocol) {
2028 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
2029 if (distribution->GetDefaultBrowserControlPolicy() ==
2030 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
2031 return NOT_DEFAULT;
2034 if (protocol.empty())
2035 return UNKNOWN_DEFAULT;
2037 base::FilePath chrome_exe;
2038 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
2039 NOTREACHED();
2040 return ShellUtil::UNKNOWN_DEFAULT;
2043 const wchar_t* const protocols[] = { protocol.c_str() };
2044 return ProbeProtocolHandlers(chrome_exe,
2045 protocols,
2046 arraysize(protocols));
2049 // static
2050 bool ShellUtil::CanMakeChromeDefaultUnattended() {
2051 return base::win::GetVersion() < base::win::VERSION_WIN8;
2054 bool ShellUtil::MakeChromeDefault(BrowserDistribution* dist,
2055 int shell_change,
2056 const base::FilePath& chrome_exe,
2057 bool elevate_if_not_admin) {
2058 DCHECK(!(shell_change & ShellUtil::SYSTEM_LEVEL) || IsUserAnAdmin());
2060 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
2061 if (distribution->GetDefaultBrowserControlPolicy() !=
2062 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
2063 return false;
2066 // Windows 8 does not permit making a browser default just like that.
2067 // This process needs to be routed through the system's UI. Use
2068 // ShowMakeChromeDefaultSystemUI instead (below).
2069 if (!CanMakeChromeDefaultUnattended()) {
2070 return false;
2073 if (!ShellUtil::RegisterChromeBrowser(
2074 dist, chrome_exe, base::string16(), elevate_if_not_admin)) {
2075 return false;
2078 bool ret = true;
2079 // First use the new "recommended" way on Vista to make Chrome default
2080 // browser.
2081 base::string16 app_name = GetApplicationName(dist, chrome_exe);
2083 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
2084 // On Windows Vista and Win7 we still can set ourselves via the
2085 // the IApplicationAssociationRegistration interface.
2086 VLOG(1) << "Registering Chrome as default browser on Vista.";
2087 base::win::ScopedComPtr<IApplicationAssociationRegistration> pAAR;
2088 HRESULT hr = pAAR.CreateInstance(CLSID_ApplicationAssociationRegistration,
2089 NULL, CLSCTX_INPROC);
2090 if (SUCCEEDED(hr)) {
2091 for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
2092 hr = pAAR->SetAppAsDefault(app_name.c_str(),
2093 ShellUtil::kBrowserProtocolAssociations[i], AT_URLPROTOCOL);
2094 if (!SUCCEEDED(hr)) {
2095 ret = false;
2096 LOG(ERROR) << "Failed to register as default for protocol "
2097 << ShellUtil::kBrowserProtocolAssociations[i]
2098 << " (" << hr << ")";
2102 for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
2103 hr = pAAR->SetAppAsDefault(app_name.c_str(),
2104 ShellUtil::kDefaultFileAssociations[i], AT_FILEEXTENSION);
2105 if (!SUCCEEDED(hr)) {
2106 ret = false;
2107 LOG(ERROR) << "Failed to register as default for file extension "
2108 << ShellUtil::kDefaultFileAssociations[i]
2109 << " (" << hr << ")";
2115 if (!RegisterChromeAsDefaultXPStyle(dist, shell_change, chrome_exe))
2116 ret = false;
2118 // Send Windows notification event so that it can update icons for
2119 // file associations.
2120 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
2121 return ret;
2124 bool ShellUtil::ShowMakeChromeDefaultSystemUI(
2125 BrowserDistribution* dist,
2126 const base::FilePath& chrome_exe) {
2127 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
2128 if (dist->GetDefaultBrowserControlPolicy() !=
2129 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
2130 return false;
2133 if (!RegisterChromeBrowser(dist, chrome_exe, base::string16(), true))
2134 return false;
2136 bool succeeded = true;
2137 bool is_default = (GetChromeDefaultState() == IS_DEFAULT);
2138 if (!is_default) {
2139 if (base::win::GetVersion() < base::win::VERSION_WIN10) {
2140 // On Windows 8, you can't set yourself as the default handler
2141 // programatically. In other words IApplicationAssociationRegistration
2142 // has been rendered useless. What you can do is to launch
2143 // "Set Program Associations" section of the "Default Programs"
2144 // control panel, which is a mess, or pop the concise "How you want to
2145 // open webpages?" dialog. We choose the latter.
2146 succeeded = LaunchSelectDefaultProtocolHandlerDialog(L"http");
2147 } else {
2148 // On Windows 10, you can't even launch the associations dialog.
2149 // So we launch the settings dialog. Quoting from MSDN: "The Open With
2150 // dialog box can no longer be used to change the default program used to
2151 // open a file extension. You can only use SHOpenWithDialog to open
2152 // a single file."
2153 succeeded = LaunchDefaultAppsSettingsModernDialog();
2155 is_default = (succeeded && GetChromeDefaultState() == IS_DEFAULT);
2157 if (succeeded && is_default)
2158 RegisterChromeAsDefaultXPStyle(dist, CURRENT_USER, chrome_exe);
2159 return succeeded;
2162 bool ShellUtil::MakeChromeDefaultProtocolClient(
2163 BrowserDistribution* dist,
2164 const base::FilePath& chrome_exe,
2165 const base::string16& protocol) {
2166 if (dist->GetDefaultBrowserControlPolicy() !=
2167 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
2168 return false;
2171 if (!RegisterChromeForProtocol(
2172 dist, chrome_exe, base::string16(), protocol, true))
2173 return false;
2175 // Windows 8 does not permit making a browser default just like that.
2176 // This process needs to be routed through the system's UI. Use
2177 // ShowMakeChromeDefaultProocolClientSystemUI instead (below).
2178 if (!CanMakeChromeDefaultUnattended())
2179 return false;
2181 bool ret = true;
2182 // First use the new "recommended" way on Vista to make Chrome default
2183 // protocol handler.
2184 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
2185 VLOG(1) << "Registering Chrome as default handler for " << protocol
2186 << " on Vista.";
2187 base::win::ScopedComPtr<IApplicationAssociationRegistration> pAAR;
2188 HRESULT hr = pAAR.CreateInstance(CLSID_ApplicationAssociationRegistration,
2189 NULL, CLSCTX_INPROC);
2190 if (SUCCEEDED(hr)) {
2191 base::string16 app_name = GetApplicationName(dist, chrome_exe);
2192 hr = pAAR->SetAppAsDefault(app_name.c_str(), protocol.c_str(),
2193 AT_URLPROTOCOL);
2195 if (!SUCCEEDED(hr)) {
2196 ret = false;
2197 LOG(ERROR) << "Could not make Chrome default protocol client (Vista):"
2198 << " HRESULT=" << hr << ".";
2202 // Now use the old way to associate Chrome with the desired protocol. This
2203 // should not be required on Vista+, but since some applications still read
2204 // Software\Classes\<protocol> key directly, do this on Vista+ also.
2205 if (!RegisterChromeAsDefaultProtocolClientXPStyle(dist, chrome_exe, protocol))
2206 ret = false;
2208 return ret;
2211 bool ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
2212 BrowserDistribution* dist,
2213 const base::FilePath& chrome_exe,
2214 const base::string16& protocol) {
2215 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
2216 if (dist->GetDefaultBrowserControlPolicy() !=
2217 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
2218 return false;
2221 if (!RegisterChromeForProtocol(
2222 dist, chrome_exe, base::string16(), protocol, true))
2223 return false;
2225 bool succeeded = true;
2226 bool is_default = (
2227 GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
2228 if (!is_default) {
2229 // On Windows 8, you can't set yourself as the default handler
2230 // programatically. In other words IApplicationAssociationRegistration
2231 // has been rendered useless. What you can do is to launch
2232 // "Set Program Associations" section of the "Default Programs"
2233 // control panel, which is a mess, or pop the concise "How you want to open
2234 // links of this type (protocol)?" dialog. We choose the latter.
2235 succeeded = LaunchSelectDefaultProtocolHandlerDialog(protocol.c_str());
2236 is_default = (succeeded &&
2237 GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
2239 if (succeeded && is_default)
2240 RegisterChromeAsDefaultProtocolClientXPStyle(dist, chrome_exe, protocol);
2241 return succeeded;
2244 bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist,
2245 const base::FilePath& chrome_exe,
2246 const base::string16& unique_suffix,
2247 bool elevate_if_not_admin) {
2248 if (dist->GetDefaultBrowserControlPolicy() ==
2249 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
2250 return false;
2253 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
2255 base::string16 suffix;
2256 if (!unique_suffix.empty()) {
2257 suffix = unique_suffix;
2258 } else if (command_line.HasSwitch(
2259 installer::switches::kRegisterChromeBrowserSuffix)) {
2260 suffix = command_line.GetSwitchValueNative(
2261 installer::switches::kRegisterChromeBrowserSuffix);
2262 } else if (!GetInstallationSpecificSuffix(dist, chrome_exe, &suffix)) {
2263 return false;
2266 RemoveRunVerbOnWindows8(dist, chrome_exe);
2268 bool user_level = InstallUtil::IsPerUserInstall(chrome_exe);
2269 HKEY root = DetermineRegistrationRoot(user_level);
2271 // Look only in HKLM for system-level installs (otherwise, if a user-level
2272 // install is also present, it will lead IsChromeRegistered() to think this
2273 // system-level install isn't registered properly as it is shadowed by the
2274 // user-level install's registrations).
2275 uint32 look_for_in = user_level ?
2276 RegistryEntry::LOOK_IN_HKCU_THEN_HKLM : RegistryEntry::LOOK_IN_HKLM;
2278 // Check if chrome is already registered with this suffix.
2279 if (IsChromeRegistered(dist, chrome_exe, suffix, look_for_in))
2280 return true;
2282 bool result = true;
2283 if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
2284 // Do the full registration if we can do it at user-level or if the user is
2285 // an admin.
2286 ScopedVector<RegistryEntry> progid_and_appreg_entries;
2287 ScopedVector<RegistryEntry> shell_entries;
2288 RegistryEntry::GetChromeProgIdEntries(
2289 dist, chrome_exe, suffix, &progid_and_appreg_entries);
2290 RegistryEntry::GetChromeAppRegistrationEntries(
2291 chrome_exe, suffix, &progid_and_appreg_entries);
2292 RegistryEntry::GetShellIntegrationEntries(
2293 dist, chrome_exe, suffix, &shell_entries);
2294 result = (AddRegistryEntries(root, progid_and_appreg_entries) &&
2295 AddRegistryEntries(root, shell_entries));
2296 } else if (elevate_if_not_admin &&
2297 base::win::GetVersion() >= base::win::VERSION_VISTA &&
2298 ElevateAndRegisterChrome(dist, chrome_exe, suffix, base::string16())) {
2299 // If the user is not an admin and OS is between Vista and Windows 7
2300 // inclusively, try to elevate and register. This is only intended for
2301 // user-level installs as system-level installs should always be run with
2302 // admin rights.
2303 result = true;
2304 } else {
2305 // If we got to this point then all we can do is create ProgId and basic app
2306 // registrations under HKCU.
2307 ScopedVector<RegistryEntry> entries;
2308 RegistryEntry::GetChromeProgIdEntries(
2309 dist, chrome_exe, base::string16(), &entries);
2310 // Prefer to use |suffix|; unless Chrome's ProgIds are already registered
2311 // with no suffix (as per the old registration style): in which case some
2312 // other registry entries could refer to them and since we were not able to
2313 // set our HKLM entries above, we are better off not altering these here.
2314 if (!AreEntriesAsDesired(entries, RegistryEntry::LOOK_IN_HKCU)) {
2315 if (!suffix.empty()) {
2316 entries.clear();
2317 RegistryEntry::GetChromeProgIdEntries(
2318 dist, chrome_exe, suffix, &entries);
2319 RegistryEntry::GetChromeAppRegistrationEntries(
2320 chrome_exe, suffix, &entries);
2322 result = AddRegistryEntries(HKEY_CURRENT_USER, entries);
2323 } else {
2324 // The ProgId is registered unsuffixed in HKCU, also register the app with
2325 // Windows in HKCU (this was not done in the old registration style and
2326 // thus needs to be done after the above check for the unsuffixed
2327 // registration).
2328 entries.clear();
2329 RegistryEntry::GetChromeAppRegistrationEntries(
2330 chrome_exe, base::string16(), &entries);
2331 result = AddRegistryEntries(HKEY_CURRENT_USER, entries);
2334 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
2335 return result;
2338 bool ShellUtil::RegisterChromeForProtocol(BrowserDistribution* dist,
2339 const base::FilePath& chrome_exe,
2340 const base::string16& unique_suffix,
2341 const base::string16& protocol,
2342 bool elevate_if_not_admin) {
2343 if (dist->GetDefaultBrowserControlPolicy() ==
2344 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
2345 return false;
2348 base::string16 suffix;
2349 if (!unique_suffix.empty()) {
2350 suffix = unique_suffix;
2351 } else if (!GetInstallationSpecificSuffix(dist, chrome_exe, &suffix)) {
2352 return false;
2355 bool user_level = InstallUtil::IsPerUserInstall(chrome_exe);
2356 HKEY root = DetermineRegistrationRoot(user_level);
2358 // Look only in HKLM for system-level installs (otherwise, if a user-level
2359 // install is also present, it could lead IsChromeRegisteredForProtocol() to
2360 // think this system-level install isn't registered properly as it may be
2361 // shadowed by the user-level install's registrations).
2362 uint32 look_for_in = user_level ?
2363 RegistryEntry::LOOK_IN_HKCU_THEN_HKLM : RegistryEntry::LOOK_IN_HKLM;
2365 // Check if chrome is already registered with this suffix.
2366 if (IsChromeRegisteredForProtocol(dist, suffix, protocol, look_for_in))
2367 return true;
2369 if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
2370 // We can do this operation directly.
2371 // First, make sure Chrome is fully registered on this machine.
2372 if (!RegisterChromeBrowser(dist, chrome_exe, suffix, false))
2373 return false;
2375 // Write in the capabillity for the protocol.
2376 ScopedVector<RegistryEntry> entries;
2377 RegistryEntry::GetProtocolCapabilityEntries(dist, suffix, protocol,
2378 &entries);
2379 return AddRegistryEntries(root, entries);
2380 } else if (elevate_if_not_admin &&
2381 base::win::GetVersion() >= base::win::VERSION_VISTA) {
2382 // Elevate to do the whole job
2383 return ElevateAndRegisterChrome(dist, chrome_exe, suffix, protocol);
2384 } else {
2385 // Admin rights are required to register capabilities before Windows 8.
2386 return false;
2390 // static
2391 bool ShellUtil::RemoveShortcuts(ShellUtil::ShortcutLocation location,
2392 BrowserDistribution* dist,
2393 ShellChange level,
2394 const base::FilePath& target_exe) {
2395 if (!ShellUtil::ShortcutLocationIsSupported(location))
2396 return true; // Vacuous success.
2398 FilterTargetEq shortcut_filter(target_exe, false);
2399 // Main operation to apply to each shortcut in the directory specified.
2400 ShortcutOperationCallback shortcut_operation(
2401 location == SHORTCUT_LOCATION_TASKBAR_PINS
2402 ? base::Bind(&ShortcutOpUnpinFromTaskbar)
2403 : base::Bind(&ShortcutOpDelete));
2404 bool success = BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
2405 shortcut_operation, location, dist, level,
2406 NULL);
2407 // Remove chrome-specific shortcut folders if they are now empty.
2408 if (success &&
2409 (location == SHORTCUT_LOCATION_START_MENU_CHROME_DIR ||
2410 location == SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR ||
2411 location == SHORTCUT_LOCATION_APP_SHORTCUTS)) {
2412 success = RemoveShortcutFolderIfEmpty(location, dist, level);
2414 return success;
2417 // static
2418 bool ShellUtil::RetargetShortcutsWithArgs(
2419 ShellUtil::ShortcutLocation location,
2420 BrowserDistribution* dist,
2421 ShellChange level,
2422 const base::FilePath& old_target_exe,
2423 const base::FilePath& new_target_exe) {
2424 if (!ShellUtil::ShortcutLocationIsSupported(location))
2425 return true; // Vacuous success.
2427 FilterTargetEq shortcut_filter(old_target_exe, true);
2428 ShortcutOperationCallback shortcut_operation(
2429 base::Bind(&ShortcutOpRetarget, old_target_exe, new_target_exe));
2430 return BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
2431 shortcut_operation, location, dist, level, NULL);
2434 // static
2435 bool ShellUtil::ShortcutListMaybeRemoveUnknownArgs(
2436 ShellUtil::ShortcutLocation location,
2437 BrowserDistribution* dist,
2438 ShellChange level,
2439 const base::FilePath& chrome_exe,
2440 bool do_removal,
2441 const scoped_refptr<SharedCancellationFlag>& cancel,
2442 std::vector<std::pair<base::FilePath, base::string16> >* shortcuts) {
2443 if (!ShellUtil::ShortcutLocationIsSupported(location))
2444 return false;
2445 DCHECK(dist);
2446 FilterTargetEq shortcut_filter(chrome_exe, true);
2447 ShortcutOperationCallback shortcut_operation(
2448 base::Bind(&ShortcutOpListOrRemoveUnknownArgs, do_removal, shortcuts));
2449 return BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
2450 shortcut_operation, location, dist, level, cancel);
2453 bool ShellUtil::GetUserSpecificRegistrySuffix(base::string16* suffix) {
2454 // Use a thread-safe cache for the user's suffix.
2455 static base::LazyInstance<UserSpecificRegistrySuffix>::Leaky suffix_instance =
2456 LAZY_INSTANCE_INITIALIZER;
2457 return suffix_instance.Get().GetSuffix(suffix);
2460 bool ShellUtil::GetOldUserSpecificRegistrySuffix(base::string16* suffix) {
2461 wchar_t user_name[256];
2462 DWORD size = arraysize(user_name);
2463 if (::GetUserName(user_name, &size) == 0 || size < 1) {
2464 NOTREACHED();
2465 return false;
2467 suffix->reserve(size);
2468 suffix->assign(1, L'.');
2469 suffix->append(user_name, size - 1);
2470 return true;
2473 base::string16 ShellUtil::ByteArrayToBase32(const uint8* bytes, size_t size) {
2474 static const char kEncoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
2476 // Eliminate special cases first.
2477 if (size == 0) {
2478 return base::string16();
2479 } else if (size == 1) {
2480 base::string16 ret;
2481 ret.push_back(kEncoding[(bytes[0] & 0xf8) >> 3]);
2482 ret.push_back(kEncoding[(bytes[0] & 0x07) << 2]);
2483 return ret;
2484 } else if (size >= std::numeric_limits<size_t>::max() / 8) {
2485 // If |size| is too big, the calculation of |encoded_length| below will
2486 // overflow.
2487 NOTREACHED();
2488 return base::string16();
2491 // Overestimate the number of bits in the string by 4 so that dividing by 5
2492 // is the equivalent of rounding up the actual number of bits divided by 5.
2493 const size_t encoded_length = (size * 8 + 4) / 5;
2495 base::string16 ret;
2496 ret.reserve(encoded_length);
2498 // A bit stream which will be read from the left and appended to from the
2499 // right as it's emptied.
2500 uint16 bit_stream = (bytes[0] << 8) + bytes[1];
2501 size_t next_byte_index = 2;
2502 int free_bits = 0;
2503 while (free_bits < 16) {
2504 // Extract the 5 leftmost bits in the stream
2505 ret.push_back(kEncoding[(bit_stream & 0xf800) >> 11]);
2506 bit_stream <<= 5;
2507 free_bits += 5;
2509 // If there is enough room in the bit stream, inject another byte (if there
2510 // are any left...).
2511 if (free_bits >= 8 && next_byte_index < size) {
2512 free_bits -= 8;
2513 bit_stream += bytes[next_byte_index++] << free_bits;
2517 DCHECK_EQ(ret.length(), encoded_length);
2518 return ret;
2521 // static
2522 bool ShellUtil::AddFileAssociations(
2523 const base::string16& prog_id,
2524 const base::CommandLine& command_line,
2525 const base::string16& file_type_name,
2526 const base::FilePath& icon_path,
2527 const std::set<base::string16>& file_extensions) {
2528 ScopedVector<RegistryEntry> entries;
2530 // Create a class for this app.
2531 RegistryEntry::ApplicationInfo app_info;
2532 app_info.prog_id = prog_id;
2533 app_info.file_type_name = file_type_name;
2534 app_info.file_type_icon_path = icon_path;
2535 app_info.file_type_icon_index = 0;
2536 app_info.command_line = command_line.GetCommandLineStringWithPlaceholders();
2537 RegistryEntry::GetProgIdEntries(app_info, &entries);
2539 // Associate each extension that the app can handle with the class. Set this
2540 // app as the default handler if and only if there is no existing default.
2541 for (std::set<base::string16>::const_iterator it = file_extensions.begin();
2542 it != file_extensions.end();
2543 ++it) {
2544 // Do not allow empty file extensions, or extensions beginning with a '.'.
2545 DCHECK(!it->empty());
2546 DCHECK_NE(L'.', (*it)[0]);
2547 base::string16 ext(1, L'.');
2548 ext.append(*it);
2549 RegistryEntry::GetAppExtRegistrationEntries(prog_id, ext, &entries);
2551 // Regstering as the default will have no effect on Windows 8 (see
2552 // documentation for GetAppDefaultRegistrationEntries). However, if our app
2553 // is the only handler, it will automatically become the default, so the
2554 // same effect is achieved.
2555 RegistryEntry::GetAppDefaultRegistrationEntries(
2556 prog_id, ext, false, &entries);
2559 return AddRegistryEntries(HKEY_CURRENT_USER, entries);
2562 // static
2563 bool ShellUtil::DeleteFileAssociations(const base::string16& prog_id) {
2564 // Delete the key HKEY_CURRENT_USER\Software\Classes\PROGID.
2565 base::string16 key_path(ShellUtil::kRegClasses);
2566 key_path.push_back(base::FilePath::kSeparators[0]);
2567 key_path.append(prog_id);
2568 return InstallUtil::DeleteRegistryKey(
2569 HKEY_CURRENT_USER, key_path, WorkItem::kWow64Default);
2571 // TODO(mgiuca): Remove the extension association entries. This requires that
2572 // the extensions associated with a particular prog_id are stored in that
2573 // prog_id's key.