[Android] Added UMA for search by image context menu.
[chromium-blink-merge.git] / chrome / installer / util / shell_util.cc
blob9ef0876b7bc3faa512191b8715584e2bc774ec8a
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>
15 #include <limits>
16 #include <string>
18 #include "base/bind.h"
19 #include "base/command_line.h"
20 #include "base/file_util.h"
21 #include "base/files/file_enumerator.h"
22 #include "base/files/file_path.h"
23 #include "base/lazy_instance.h"
24 #include "base/logging.h"
25 #include "base/md5.h"
26 #include "base/memory/scoped_ptr.h"
27 #include "base/memory/scoped_vector.h"
28 #include "base/path_service.h"
29 #include "base/strings/string16.h"
30 #include "base/strings/string_number_conversions.h"
31 #include "base/strings/string_split.h"
32 #include "base/strings/string_util.h"
33 #include "base/strings/utf_string_conversions.h"
34 #include "base/values.h"
35 #include "base/win/registry.h"
36 #include "base/win/scoped_co_mem.h"
37 #include "base/win/scoped_comptr.h"
38 #include "base/win/shortcut.h"
39 #include "base/win/win_util.h"
40 #include "base/win/windows_version.h"
41 #include "chrome/common/chrome_constants.h"
42 #include "chrome/common/chrome_switches.h"
43 #include "chrome/installer/util/browser_distribution.h"
44 #include "chrome/installer/util/install_util.h"
45 #include "chrome/installer/util/l10n_string_util.h"
46 #include "chrome/installer/util/master_preferences.h"
47 #include "chrome/installer/util/master_preferences_constants.h"
48 #include "chrome/installer/util/util_constants.h"
50 #include "installer_util_strings.h" // NOLINT
52 using base::win::RegKey;
54 namespace {
56 // An enum used to tell QuickIsChromeRegistered() which level of registration
57 // the caller wants to confirm.
58 enum RegistrationConfirmationLevel {
59 // Only look for Chrome's ProgIds.
60 // This is sufficient when we are trying to determine the suffix of the
61 // currently running Chrome as shell integration registrations might not be
62 // present.
63 CONFIRM_PROGID_REGISTRATION = 0,
64 // Confirm that Chrome is fully integrated with Windows (i.e. registered with
65 // Defaut Programs). These registrations can be in HKCU as of Windows 8.
66 // Note: Shell registration implies ProgId registration.
67 CONFIRM_SHELL_REGISTRATION,
68 // Same as CONFIRM_SHELL_REGISTRATION, but only look in HKLM (used when
69 // uninstalling to know whether elevation is required to clean up the
70 // registry).
71 CONFIRM_SHELL_REGISTRATION_IN_HKLM,
74 const wchar_t kReinstallCommand[] = L"ReinstallCommand";
76 // Returns true if Chrome Metro is supported on this OS (Win 8 8370 or greater).
77 // TODO(gab): Change this to a simple check for Win 8 once old Win8 builds
78 // become irrelevant.
79 bool IsChromeMetroSupported() {
80 OSVERSIONINFOEX min_version_info = {};
81 min_version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
82 min_version_info.dwMajorVersion = 6;
83 min_version_info.dwMinorVersion = 2;
84 min_version_info.dwBuildNumber = 8370;
85 min_version_info.wServicePackMajor = 0;
86 min_version_info.wServicePackMinor = 0;
88 DWORDLONG condition_mask = 0;
89 VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
90 VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
91 VER_SET_CONDITION(condition_mask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
92 VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
93 VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
95 DWORD type_mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER |
96 VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR;
98 return VerifyVersionInfo(&min_version_info, type_mask, condition_mask) != 0;
101 // Returns the current (or installed) browser's ProgId (e.g.
102 // "ChromeHTML|suffix|").
103 // |suffix| can be the empty string.
104 string16 GetBrowserProgId(const string16& suffix) {
105 string16 chrome_html(ShellUtil::kChromeHTMLProgId);
106 chrome_html.append(suffix);
108 // ProgIds cannot be longer than 39 characters.
109 // Ref: http://msdn.microsoft.com/en-us/library/aa911706.aspx.
110 // Make all new registrations comply with this requirement (existing
111 // registrations must be preserved).
112 string16 new_style_suffix;
113 if (ShellUtil::GetUserSpecificRegistrySuffix(&new_style_suffix) &&
114 suffix == new_style_suffix && chrome_html.length() > 39) {
115 NOTREACHED();
116 chrome_html.erase(39);
118 return chrome_html;
121 // This class is used to initialize and cache a base 32 encoding of the md5 hash
122 // of this user's sid preceded by a dot.
123 // This is guaranteed to be unique on the machine and 27 characters long
124 // (including the '.').
125 // This is then meant to be used as a suffix on all registrations that may
126 // conflict with another user-level Chrome install.
127 class UserSpecificRegistrySuffix {
128 public:
129 // All the initialization is done in the constructor to be able to build the
130 // suffix in a thread-safe manner when used in conjunction with a
131 // LazyInstance.
132 UserSpecificRegistrySuffix();
134 // Sets |suffix| to the pre-computed suffix cached in this object.
135 // Returns true unless the initialization originally failed.
136 bool GetSuffix(string16* suffix);
138 private:
139 string16 suffix_;
141 DISALLOW_COPY_AND_ASSIGN(UserSpecificRegistrySuffix);
142 }; // class UserSpecificRegistrySuffix
144 UserSpecificRegistrySuffix::UserSpecificRegistrySuffix() {
145 string16 user_sid;
146 if (!base::win::GetUserSidString(&user_sid)) {
147 NOTREACHED();
148 return;
150 COMPILE_ASSERT(sizeof(base::MD5Digest) == 16, size_of_MD5_not_as_expected_);
151 base::MD5Digest md5_digest;
152 std::string user_sid_ascii(UTF16ToASCII(user_sid));
153 base::MD5Sum(user_sid_ascii.c_str(), user_sid_ascii.length(), &md5_digest);
154 const string16 base32_md5(
155 ShellUtil::ByteArrayToBase32(md5_digest.a, arraysize(md5_digest.a)));
156 // The value returned by the base32 algorithm above must never change and
157 // must always be 26 characters long (i.e. if someone ever moves this to
158 // base and implements the full base32 algorithm (i.e. with appended '='
159 // signs in the output), they must provide a flag to allow this method to
160 // still request the output with no appended '=' signs).
161 DCHECK_EQ(base32_md5.length(), 26U);
162 suffix_.reserve(base32_md5.length() + 1);
163 suffix_.assign(1, L'.');
164 suffix_.append(base32_md5);
167 bool UserSpecificRegistrySuffix::GetSuffix(string16* suffix) {
168 if (suffix_.empty()) {
169 NOTREACHED();
170 return false;
172 suffix->assign(suffix_);
173 return true;
176 // This class represents a single registry entry. The objective is to
177 // encapsulate all the registry entries required for registering Chrome at one
178 // place. This class can not be instantiated outside the class and the objects
179 // of this class type can be obtained only by calling a static method of this
180 // class.
181 class RegistryEntry {
182 public:
183 // A bit-field enum of places to look for this key in the Windows registry.
184 enum LookForIn {
185 LOOK_IN_HKCU = 1 << 0,
186 LOOK_IN_HKLM = 1 << 1,
187 LOOK_IN_HKCU_THEN_HKLM = LOOK_IN_HKCU | LOOK_IN_HKLM,
190 // Returns the Windows browser client registration key for Chrome. For
191 // example: "Software\Clients\StartMenuInternet\Chromium[.user]". Strictly
192 // speaking, we should use the name of the executable (e.g., "chrome.exe"),
193 // but that ship has sailed. The cost of switching now is re-prompting users
194 // to make Chrome their default browser, which isn't polite. |suffix| is the
195 // user-specific registration suffix; see GetUserSpecificDefaultBrowserSuffix
196 // in shell_util.h for details.
197 static string16 GetBrowserClientKey(BrowserDistribution* dist,
198 const string16& suffix) {
199 DCHECK(suffix.empty() || suffix[0] == L'.');
200 return string16(ShellUtil::kRegStartMenuInternet)
201 .append(1, L'\\')
202 .append(dist->GetBaseAppName())
203 .append(suffix);
206 // Returns the Windows Default Programs capabilities key for Chrome. For
207 // example:
208 // "Software\Clients\StartMenuInternet\Chromium[.user]\Capabilities".
209 static string16 GetCapabilitiesKey(BrowserDistribution* dist,
210 const string16& suffix) {
211 return GetBrowserClientKey(dist, suffix).append(L"\\Capabilities");
214 // This method returns a list of all the registry entries that
215 // are needed to register this installation's ProgId and AppId.
216 // These entries need to be registered in HKLM prior to Win8.
217 static void GetProgIdEntries(BrowserDistribution* dist,
218 const string16& chrome_exe,
219 const string16& suffix,
220 ScopedVector<RegistryEntry>* entries) {
221 string16 icon_path(
222 ShellUtil::FormatIconLocation(chrome_exe, dist->GetIconIndex()));
223 string16 open_cmd(ShellUtil::GetChromeShellOpenCmd(chrome_exe));
224 string16 delegate_command(ShellUtil::GetChromeDelegateCommand(chrome_exe));
225 // For user-level installs: entries for the app id and DelegateExecute verb
226 // handler will be in HKCU; thus we do not need a suffix on those entries.
227 string16 app_id(
228 ShellUtil::GetBrowserModelId(
229 dist, InstallUtil::IsPerUserInstall(chrome_exe.c_str())));
230 string16 delegate_guid;
231 bool set_delegate_execute =
232 IsChromeMetroSupported() &&
233 dist->GetCommandExecuteImplClsid(&delegate_guid);
235 // DelegateExecute ProgId. Needed for Chrome Metro in Windows 8.
236 if (set_delegate_execute) {
237 string16 model_id_shell(ShellUtil::kRegClasses);
238 model_id_shell.push_back(base::FilePath::kSeparators[0]);
239 model_id_shell.append(app_id);
240 model_id_shell.append(ShellUtil::kRegExePath);
241 model_id_shell.append(ShellUtil::kRegShellPath);
243 // <root hkey>\Software\Classes\<app_id>\.exe\shell @=open
244 entries->push_back(new RegistryEntry(model_id_shell,
245 ShellUtil::kRegVerbOpen));
247 // Each of Chrome's shortcuts has an appid; which, as of Windows 8, is
248 // registered to handle some verbs. This registration has the side-effect
249 // that these verbs now show up in the shortcut's context menu. We
250 // mitigate this side-effect by making the context menu entries
251 // user readable/localized strings. See relevant MSDN article:
252 // http://msdn.microsoft.com/en-US/library/windows/desktop/cc144171.aspx
253 const struct {
254 const wchar_t* verb;
255 int name_id;
256 } verbs[] = {
257 { ShellUtil::kRegVerbOpen, -1 },
258 { ShellUtil::kRegVerbOpenNewWindow, IDS_SHORTCUT_NEW_WINDOW_BASE },
260 for (size_t i = 0; i < arraysize(verbs); ++i) {
261 string16 sub_path(model_id_shell);
262 sub_path.push_back(base::FilePath::kSeparators[0]);
263 sub_path.append(verbs[i].verb);
265 // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>
266 if (verbs[i].name_id != -1) {
267 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
268 // resource.
269 string16 verb_name(installer::GetLocalizedString(verbs[i].name_id));
270 entries->push_back(new RegistryEntry(sub_path, verb_name.c_str()));
272 entries->push_back(new RegistryEntry(
273 sub_path, L"CommandId", L"Browser.Launch"));
275 sub_path.push_back(base::FilePath::kSeparators[0]);
276 sub_path.append(ShellUtil::kRegCommand);
278 // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>\command
279 entries->push_back(new RegistryEntry(sub_path, delegate_command));
280 entries->push_back(new RegistryEntry(
281 sub_path, ShellUtil::kRegDelegateExecute, delegate_guid));
285 // File association ProgId
286 string16 chrome_html_prog_id(ShellUtil::kRegClasses);
287 chrome_html_prog_id.push_back(base::FilePath::kSeparators[0]);
288 chrome_html_prog_id.append(GetBrowserProgId(suffix));
289 entries->push_back(new RegistryEntry(
290 chrome_html_prog_id, ShellUtil::kChromeHTMLProgIdDesc));
291 entries->push_back(new RegistryEntry(
292 chrome_html_prog_id, ShellUtil::kRegUrlProtocol, L""));
293 entries->push_back(new RegistryEntry(
294 chrome_html_prog_id + ShellUtil::kRegDefaultIcon, icon_path));
295 entries->push_back(new RegistryEntry(
296 chrome_html_prog_id + ShellUtil::kRegShellOpen, open_cmd));
297 if (set_delegate_execute) {
298 entries->push_back(new RegistryEntry(
299 chrome_html_prog_id + ShellUtil::kRegShellOpen,
300 ShellUtil::kRegDelegateExecute, delegate_guid));
303 // The following entries are required as of Windows 8, but do not
304 // depend on the DelegateExecute verb handler being set.
305 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
306 entries->push_back(new RegistryEntry(
307 chrome_html_prog_id, ShellUtil::kRegAppUserModelId, app_id));
309 // Add \Software\Classes\ChromeHTML\Application entries
310 string16 chrome_application(chrome_html_prog_id +
311 ShellUtil::kRegApplication);
312 entries->push_back(new RegistryEntry(
313 chrome_application, ShellUtil::kRegAppUserModelId, app_id));
314 entries->push_back(new RegistryEntry(
315 chrome_application, ShellUtil::kRegApplicationIcon, icon_path));
316 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
317 // resource for name, description, and company.
318 entries->push_back(new RegistryEntry(
319 chrome_application, ShellUtil::kRegApplicationName,
320 dist->GetAppShortCutName()));
321 entries->push_back(new RegistryEntry(
322 chrome_application, ShellUtil::kRegApplicationDescription,
323 dist->GetAppDescription()));
324 entries->push_back(new RegistryEntry(
325 chrome_application, ShellUtil::kRegApplicationCompany,
326 dist->GetPublisherName()));
330 // This method returns a list of the registry entries needed to declare a
331 // capability of handling a protocol on Windows.
332 static void GetProtocolCapabilityEntries(
333 BrowserDistribution* dist,
334 const string16& suffix,
335 const string16& protocol,
336 ScopedVector<RegistryEntry>* entries) {
337 entries->push_back(new RegistryEntry(
338 GetCapabilitiesKey(dist, suffix).append(L"\\URLAssociations"),
339 protocol, GetBrowserProgId(suffix)));
342 // This method returns a list of the registry entries required to register
343 // this installation in "RegisteredApplications" on Windows (to appear in
344 // Default Programs, StartMenuInternet, etc.).
345 // These entries need to be registered in HKLM prior to Win8.
346 // If |suffix| is not empty, these entries are guaranteed to be unique on this
347 // machine.
348 static void GetShellIntegrationEntries(BrowserDistribution* dist,
349 const string16& chrome_exe,
350 const string16& suffix,
351 ScopedVector<RegistryEntry>* entries) {
352 const string16 icon_path(
353 ShellUtil::FormatIconLocation(chrome_exe, dist->GetIconIndex()));
354 const string16 quoted_exe_path(L"\"" + chrome_exe + L"\"");
356 // Register for the Start Menu "Internet" link (pre-Win7).
357 const string16 start_menu_entry(GetBrowserClientKey(dist, suffix));
358 // Register Chrome's display name.
359 // TODO(grt): http://crbug.com/75152 Also set LocalizedString; see
360 // http://msdn.microsoft.com/en-us/library/windows/desktop/cc144109(v=VS.85).aspx#registering_the_display_name
361 entries->push_back(new RegistryEntry(
362 start_menu_entry, dist->GetAppShortCutName()));
363 // Register the "open" verb for launching Chrome via the "Internet" link.
364 entries->push_back(new RegistryEntry(
365 start_menu_entry + ShellUtil::kRegShellOpen, quoted_exe_path));
366 // Register Chrome's icon for the Start Menu "Internet" link.
367 entries->push_back(new RegistryEntry(
368 start_menu_entry + ShellUtil::kRegDefaultIcon, icon_path));
370 // Register installation information.
371 string16 install_info(start_menu_entry + L"\\InstallInfo");
372 // Note: not using CommandLine since it has ambiguous rules for quoting
373 // strings.
374 entries->push_back(new RegistryEntry(install_info, kReinstallCommand,
375 quoted_exe_path + L" --" + ASCIIToWide(switches::kMakeDefaultBrowser)));
376 entries->push_back(new RegistryEntry(install_info, L"HideIconsCommand",
377 quoted_exe_path + L" --" + ASCIIToWide(switches::kHideIcons)));
378 entries->push_back(new RegistryEntry(install_info, L"ShowIconsCommand",
379 quoted_exe_path + L" --" + ASCIIToWide(switches::kShowIcons)));
380 entries->push_back(new RegistryEntry(install_info, L"IconsVisible", 1));
382 // Register with Default Programs.
383 const string16 reg_app_name(dist->GetBaseAppName().append(suffix));
384 // Tell Windows where to find Chrome's Default Programs info.
385 const string16 capabilities(GetCapabilitiesKey(dist, suffix));
386 entries->push_back(new RegistryEntry(ShellUtil::kRegRegisteredApplications,
387 reg_app_name, capabilities));
388 // Write out Chrome's Default Programs info.
389 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
390 // resource rather than this.
391 entries->push_back(new RegistryEntry(
392 capabilities, ShellUtil::kRegApplicationDescription,
393 dist->GetLongAppDescription()));
394 entries->push_back(new RegistryEntry(
395 capabilities, ShellUtil::kRegApplicationIcon, icon_path));
396 entries->push_back(new RegistryEntry(
397 capabilities, ShellUtil::kRegApplicationName,
398 dist->GetAppShortCutName()));
400 entries->push_back(new RegistryEntry(capabilities + L"\\Startmenu",
401 L"StartMenuInternet", reg_app_name));
403 const string16 html_prog_id(GetBrowserProgId(suffix));
404 for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
405 entries->push_back(new RegistryEntry(
406 capabilities + L"\\FileAssociations",
407 ShellUtil::kPotentialFileAssociations[i], html_prog_id));
409 for (int i = 0; ShellUtil::kPotentialProtocolAssociations[i] != NULL;
410 i++) {
411 entries->push_back(new RegistryEntry(
412 capabilities + L"\\URLAssociations",
413 ShellUtil::kPotentialProtocolAssociations[i], html_prog_id));
417 // This method returns a list of the registry entries required for this
418 // installation to be registered in the Windows shell.
419 // In particular:
420 // - App Paths
421 // http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
422 // - File Associations
423 // http://msdn.microsoft.com/en-us/library/bb166549
424 // These entries need to be registered in HKLM prior to Win8.
425 static void GetAppRegistrationEntries(const string16& chrome_exe,
426 const string16& suffix,
427 ScopedVector<RegistryEntry>* entries) {
428 const base::FilePath chrome_path(chrome_exe);
429 string16 app_path_key(ShellUtil::kAppPathsRegistryKey);
430 app_path_key.push_back(base::FilePath::kSeparators[0]);
431 app_path_key.append(chrome_path.BaseName().value());
432 entries->push_back(new RegistryEntry(app_path_key, chrome_exe));
433 entries->push_back(new RegistryEntry(app_path_key,
434 ShellUtil::kAppPathsRegistryPathName, chrome_path.DirName().value()));
436 const string16 html_prog_id(GetBrowserProgId(suffix));
437 for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
438 string16 key(ShellUtil::kRegClasses);
439 key.push_back(base::FilePath::kSeparators[0]);
440 key.append(ShellUtil::kPotentialFileAssociations[i]);
441 key.push_back(base::FilePath::kSeparators[0]);
442 key.append(ShellUtil::kRegOpenWithProgids);
443 entries->push_back(new RegistryEntry(key, html_prog_id, string16()));
447 // This method returns a list of all the user level registry entries that
448 // are needed to make Chromium the default handler for a protocol on XP.
449 static void GetXPStyleUserProtocolEntries(
450 const string16& protocol,
451 const string16& chrome_icon,
452 const string16& chrome_open,
453 ScopedVector<RegistryEntry>* entries) {
454 // Protocols associations.
455 string16 url_key(ShellUtil::kRegClasses);
456 url_key.push_back(base::FilePath::kSeparators[0]);
457 url_key.append(protocol);
459 // This registry value tells Windows that this 'class' is a URL scheme
460 // so IE, explorer and other apps will route it to our handler.
461 // <root hkey>\Software\Classes\<protocol>\URL Protocol
462 entries->push_back(new RegistryEntry(url_key,
463 ShellUtil::kRegUrlProtocol, L""));
465 // <root hkey>\Software\Classes\<protocol>\DefaultIcon
466 string16 icon_key = url_key + ShellUtil::kRegDefaultIcon;
467 entries->push_back(new RegistryEntry(icon_key, chrome_icon));
469 // <root hkey>\Software\Classes\<protocol>\shell\open\command
470 string16 shell_key = url_key + ShellUtil::kRegShellOpen;
471 entries->push_back(new RegistryEntry(shell_key, chrome_open));
473 // <root hkey>\Software\Classes\<protocol>\shell\open\ddeexec
474 string16 dde_key = url_key + L"\\shell\\open\\ddeexec";
475 entries->push_back(new RegistryEntry(dde_key, L""));
477 // <root hkey>\Software\Classes\<protocol>\shell\@
478 string16 protocol_shell_key = url_key + ShellUtil::kRegShellPath;
479 entries->push_back(new RegistryEntry(protocol_shell_key, L"open"));
482 // This method returns a list of all the user level registry entries that
483 // are needed to make Chromium default browser on XP.
484 // Some of these entries are irrelevant in recent versions of Windows, but
485 // we register them anyways as some legacy apps are hardcoded to lookup those
486 // values.
487 static void GetXPStyleDefaultBrowserUserEntries(
488 BrowserDistribution* dist,
489 const string16& chrome_exe,
490 const string16& suffix,
491 ScopedVector<RegistryEntry>* entries) {
492 // File extension associations.
493 string16 html_prog_id(GetBrowserProgId(suffix));
494 for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
495 string16 ext_key(ShellUtil::kRegClasses);
496 ext_key.push_back(base::FilePath::kSeparators[0]);
497 ext_key.append(ShellUtil::kDefaultFileAssociations[i]);
498 entries->push_back(new RegistryEntry(ext_key, html_prog_id));
501 // Protocols associations.
502 string16 chrome_open = ShellUtil::GetChromeShellOpenCmd(chrome_exe);
503 string16 chrome_icon =
504 ShellUtil::FormatIconLocation(chrome_exe, dist->GetIconIndex());
505 for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
506 GetXPStyleUserProtocolEntries(ShellUtil::kBrowserProtocolAssociations[i],
507 chrome_icon, chrome_open, entries);
510 // start->Internet shortcut.
511 string16 start_menu(ShellUtil::kRegStartMenuInternet);
512 string16 app_name = dist->GetBaseAppName() + suffix;
513 entries->push_back(new RegistryEntry(start_menu, app_name));
516 // Generate work_item tasks required to create current registry entry and
517 // add them to the given work item list.
518 void AddToWorkItemList(HKEY root, WorkItemList *items) const {
519 items->AddCreateRegKeyWorkItem(root, _key_path);
520 if (_is_string) {
521 items->AddSetRegValueWorkItem(root, _key_path, _name, _value, true);
522 } else {
523 items->AddSetRegValueWorkItem(root, _key_path, _name, _int_value, true);
527 // Checks if the current registry entry exists in HKCU\|_key_path|\|_name|
528 // and value is |_value|. If the key does NOT exist in HKCU, checks for
529 // the correct name and value in HKLM.
530 // |look_for_in| specifies roots (HKCU and/or HKLM) in which to look for the
531 // key, unspecified roots are not looked into (i.e. the the key is assumed not
532 // to exist in them).
533 // |look_for_in| must at least specify one root to look into.
534 // If |look_for_in| is LOOK_IN_HKCU_THEN_HKLM, this method mimics Windows'
535 // behavior when searching in HKCR (HKCU takes precedence over HKLM). For
536 // registrations outside of HKCR on versions of Windows prior to Win8,
537 // Chrome's values go in HKLM. This function will make unnecessary (but
538 // harmless) queries into HKCU in that case.
539 bool ExistsInRegistry(uint32 look_for_in) const {
540 DCHECK(look_for_in);
542 RegistryStatus status = DOES_NOT_EXIST;
543 if (look_for_in & LOOK_IN_HKCU)
544 status = StatusInRegistryUnderRoot(HKEY_CURRENT_USER);
545 if (status == DOES_NOT_EXIST && (look_for_in & LOOK_IN_HKLM))
546 status = StatusInRegistryUnderRoot(HKEY_LOCAL_MACHINE);
547 return status == SAME_VALUE;
550 private:
551 // States this RegistryKey can be in compared to the registry.
552 enum RegistryStatus {
553 // |_name| does not exist in the registry
554 DOES_NOT_EXIST,
555 // |_name| exists, but its value != |_value|
556 DIFFERENT_VALUE,
557 // |_name| exists and its value is |_value|
558 SAME_VALUE,
561 // Create a object that represent default value of a key
562 RegistryEntry(const string16& key_path, const string16& value)
563 : _key_path(key_path), _name(),
564 _is_string(true), _value(value), _int_value(0) {
567 // Create a object that represent a key of type REG_SZ
568 RegistryEntry(const string16& key_path, const string16& name,
569 const string16& value)
570 : _key_path(key_path), _name(name),
571 _is_string(true), _value(value), _int_value(0) {
574 // Create a object that represent a key of integer type
575 RegistryEntry(const string16& key_path, const string16& name,
576 DWORD value)
577 : _key_path(key_path), _name(name),
578 _is_string(false), _value(), _int_value(value) {
581 string16 _key_path; // key path for the registry entry
582 string16 _name; // name of the registry entry
583 bool _is_string; // true if current registry entry is of type REG_SZ
584 string16 _value; // string value (useful if _is_string = true)
585 DWORD _int_value; // integer value (useful if _is_string = false)
587 // Helper function for ExistsInRegistry().
588 // Returns the RegistryStatus of the current registry entry in
589 // |root|\|_key_path|\|_name|.
590 RegistryStatus StatusInRegistryUnderRoot(HKEY root) const {
591 RegKey key(root, _key_path.c_str(), KEY_QUERY_VALUE);
592 bool found = false;
593 bool correct_value = false;
594 if (_is_string) {
595 string16 read_value;
596 found = key.ReadValue(_name.c_str(), &read_value) == ERROR_SUCCESS;
597 correct_value = read_value.size() == _value.size() &&
598 std::equal(_value.begin(), _value.end(), read_value.begin(),
599 base::CaseInsensitiveCompare<wchar_t>());
600 } else {
601 DWORD read_value;
602 found = key.ReadValueDW(_name.c_str(), &read_value) == ERROR_SUCCESS;
603 correct_value = read_value == _int_value;
605 return found ?
606 (correct_value ? SAME_VALUE : DIFFERENT_VALUE) : DOES_NOT_EXIST;
609 DISALLOW_COPY_AND_ASSIGN(RegistryEntry);
610 }; // class RegistryEntry
613 // This method converts all the RegistryEntries from the given list to
614 // Set/CreateRegWorkItems and runs them using WorkItemList.
615 bool AddRegistryEntries(HKEY root, const ScopedVector<RegistryEntry>& entries) {
616 scoped_ptr<WorkItemList> items(WorkItem::CreateWorkItemList());
618 for (ScopedVector<RegistryEntry>::const_iterator itr = entries.begin();
619 itr != entries.end(); ++itr)
620 (*itr)->AddToWorkItemList(root, items.get());
622 // Apply all the registry changes and if there is a problem, rollback
623 if (!items->Do()) {
624 items->Rollback();
625 return false;
627 return true;
630 // Checks that all |entries| are present on this computer.
631 // |look_for_in| is passed to RegistryEntry::ExistsInRegistry(). Documentation
632 // for it can be found there.
633 bool AreEntriesRegistered(const ScopedVector<RegistryEntry>& entries,
634 uint32 look_for_in) {
635 bool registered = true;
636 for (ScopedVector<RegistryEntry>::const_iterator itr = entries.begin();
637 registered && itr != entries.end(); ++itr) {
638 // We do not need registered = registered && ... since the loop condition
639 // is set to exit early.
640 registered = (*itr)->ExistsInRegistry(look_for_in);
642 return registered;
645 // Checks that all required registry entries for Chrome are already present
646 // on this computer. See RegistryEntry::ExistsInRegistry for the behavior of
647 // |look_for_in|.
648 // Note: between r133333 and r154145 we were registering parts of Chrome in HKCU
649 // and parts in HKLM for user-level installs; we now always register everything
650 // under a single registry root. Not doing so caused http://crbug.com/144910 for
651 // users who first-installed Chrome in that revision range (those users are
652 // still impacted by http://crbug.com/144910). This method will keep returning
653 // true for affected users (i.e. who have all the registrations, but over both
654 // registry roots).
655 bool IsChromeRegistered(BrowserDistribution* dist,
656 const string16& chrome_exe,
657 const string16& suffix,
658 uint32 look_for_in) {
659 ScopedVector<RegistryEntry> entries;
660 RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries);
661 RegistryEntry::GetShellIntegrationEntries(dist, chrome_exe, suffix, &entries);
662 RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, &entries);
663 return AreEntriesRegistered(entries, look_for_in);
666 // This method checks if Chrome is already registered on the local machine
667 // for the requested protocol. It just checks the one value required for this.
668 // See RegistryEntry::ExistsInRegistry for the behavior of |look_for_in|.
669 bool IsChromeRegisteredForProtocol(BrowserDistribution* dist,
670 const string16& suffix,
671 const string16& protocol,
672 uint32 look_for_in) {
673 ScopedVector<RegistryEntry> entries;
674 RegistryEntry::GetProtocolCapabilityEntries(dist, suffix, protocol, &entries);
675 return AreEntriesRegistered(entries, look_for_in);
678 // This method registers Chrome on Vista by launching an elevated setup.exe.
679 // That will show the user the standard Vista elevation prompt. If the user
680 // accepts it the new process will make the necessary changes and return SUCCESS
681 // that we capture and return.
682 // If protocol is non-empty we will also register Chrome as being capable of
683 // handling the protocol.
684 bool ElevateAndRegisterChrome(BrowserDistribution* dist,
685 const string16& chrome_exe,
686 const string16& suffix,
687 const string16& protocol) {
688 // Only user-level installs prior to Windows 8 should need to elevate to
689 // register.
690 DCHECK(InstallUtil::IsPerUserInstall(chrome_exe.c_str()));
691 DCHECK_LT(base::win::GetVersion(), base::win::VERSION_WIN8);
692 base::FilePath exe_path =
693 base::FilePath::FromWStringHack(chrome_exe).DirName()
694 .Append(installer::kSetupExe);
695 if (!base::PathExists(exe_path)) {
696 HKEY reg_root = InstallUtil::IsPerUserInstall(chrome_exe.c_str()) ?
697 HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
698 RegKey key(reg_root, dist->GetUninstallRegPath().c_str(), KEY_READ);
699 string16 uninstall_string;
700 key.ReadValue(installer::kUninstallStringField, &uninstall_string);
701 CommandLine command_line = CommandLine::FromString(uninstall_string);
702 exe_path = command_line.GetProgram();
705 if (base::PathExists(exe_path)) {
706 CommandLine cmd(exe_path);
707 cmd.AppendSwitchNative(installer::switches::kRegisterChromeBrowser,
708 chrome_exe);
709 if (!suffix.empty()) {
710 cmd.AppendSwitchNative(
711 installer::switches::kRegisterChromeBrowserSuffix, suffix);
714 CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
715 if (browser_command_line.HasSwitch(switches::kChromeFrame)) {
716 cmd.AppendSwitch(installer::switches::kChromeFrame);
719 if (!protocol.empty()) {
720 cmd.AppendSwitchNative(
721 installer::switches::kRegisterURLProtocol, protocol);
724 DWORD ret_val = 0;
725 InstallUtil::ExecuteExeAsAdmin(cmd, &ret_val);
726 if (ret_val == 0)
727 return true;
729 return false;
732 // Launches the Windows 7 and Windows 8 dialog for picking the application to
733 // handle the given protocol. Most importantly, this is used to set the default
734 // handler for http (and, implicitly with it, https). In that case it is also
735 // known as the 'how do you want to open webpages' dialog.
736 // It is required that Chrome be already *registered* for the given protocol.
737 bool LaunchSelectDefaultProtocolHandlerDialog(const wchar_t* protocol) {
738 DCHECK(protocol);
739 OPENASINFO open_as_info = {};
740 open_as_info.pcszFile = protocol;
741 open_as_info.oaifInFlags =
742 OAIF_URL_PROTOCOL | OAIF_FORCE_REGISTRATION | OAIF_REGISTER_EXT;
743 HRESULT hr = SHOpenWithDialog(NULL, &open_as_info);
744 DLOG_IF(WARNING, FAILED(hr)) << "Failed to set as default " << protocol
745 << " handler; hr=0x" << std::hex << hr;
746 if (FAILED(hr))
747 return false;
748 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
749 return true;
752 // Launches the Windows 7 and Windows 8 application association dialog, which
753 // is the only documented way to make a browser the default browser on
754 // Windows 8.
755 bool LaunchApplicationAssociationDialog(const string16& app_id) {
756 base::win::ScopedComPtr<IApplicationAssociationRegistrationUI> aarui;
757 HRESULT hr = aarui.CreateInstance(CLSID_ApplicationAssociationRegistrationUI);
758 if (FAILED(hr))
759 return false;
760 hr = aarui->LaunchAdvancedAssociationUI(app_id.c_str());
761 return SUCCEEDED(hr);
764 // Returns true if the current install's |chrome_exe| has been registered with
765 // |suffix|.
766 // |confirmation_level| is the level of verification desired as described in
767 // the RegistrationConfirmationLevel enum above.
768 // |suffix| can be the empty string (this is used to support old installs
769 // where we used to not suffix user-level installs if they were the first to
770 // request the non-suffixed registry entries on the machine).
771 // NOTE: This a quick check that only validates that a single registry entry
772 // points to |chrome_exe|. This should only be used at run-time to determine
773 // how Chrome is registered, not to know whether the registration is complete
774 // at install-time (IsChromeRegistered() can be used for that).
775 bool QuickIsChromeRegistered(BrowserDistribution* dist,
776 const string16& chrome_exe,
777 const string16& suffix,
778 RegistrationConfirmationLevel confirmation_level) {
779 // Get the appropriate key to look for based on the level desired.
780 string16 reg_key;
781 switch (confirmation_level) {
782 case CONFIRM_PROGID_REGISTRATION:
783 // Software\Classes\ChromeHTML|suffix|
784 reg_key = ShellUtil::kRegClasses;
785 reg_key.push_back(base::FilePath::kSeparators[0]);
786 reg_key.append(ShellUtil::kChromeHTMLProgId);
787 reg_key.append(suffix);
788 break;
789 case CONFIRM_SHELL_REGISTRATION:
790 case CONFIRM_SHELL_REGISTRATION_IN_HKLM:
791 // Software\Clients\StartMenuInternet\Google Chrome|suffix|
792 reg_key = RegistryEntry::GetBrowserClientKey(dist, suffix);
793 break;
794 default:
795 NOTREACHED();
796 break;
798 reg_key.append(ShellUtil::kRegShellOpen);
800 // ProgId registrations are allowed to reside in HKCU for user-level installs
801 // (and values there have priority over values in HKLM). The same is true for
802 // shell integration entries as of Windows 8.
803 if (confirmation_level == CONFIRM_PROGID_REGISTRATION ||
804 (confirmation_level == CONFIRM_SHELL_REGISTRATION &&
805 base::win::GetVersion() >= base::win::VERSION_WIN8)) {
806 const RegKey key_hkcu(HKEY_CURRENT_USER, reg_key.c_str(), KEY_QUERY_VALUE);
807 string16 hkcu_value;
808 // If |reg_key| is present in HKCU, assert that it points to |chrome_exe|.
809 // Otherwise, fall back on an HKLM lookup below.
810 if (key_hkcu.ReadValue(L"", &hkcu_value) == ERROR_SUCCESS) {
811 return InstallUtil::ProgramCompare(
812 base::FilePath(chrome_exe)).Evaluate(hkcu_value);
816 // Assert that |reg_key| points to |chrome_exe| in HKLM.
817 const RegKey key_hklm(HKEY_LOCAL_MACHINE, reg_key.c_str(), KEY_QUERY_VALUE);
818 string16 hklm_value;
819 if (key_hklm.ReadValue(L"", &hklm_value) == ERROR_SUCCESS) {
820 return InstallUtil::ProgramCompare(
821 base::FilePath(chrome_exe)).Evaluate(hklm_value);
823 return false;
826 // Sets |suffix| to a 27 character string that is specific to this user on this
827 // machine (on user-level installs only).
828 // To support old-style user-level installs however, |suffix| is cleared if the
829 // user currently owns the non-suffixed HKLM registrations.
830 // |suffix| can also be set to the user's username if the current install is
831 // suffixed as per the old-style registrations.
832 // |suffix| is cleared on system-level installs.
833 // |suffix| should then be appended to all Chrome properties that may conflict
834 // with other Chrome user-level installs.
835 // Returns true unless one of the underlying calls fails.
836 bool GetInstallationSpecificSuffix(BrowserDistribution* dist,
837 const string16& chrome_exe,
838 string16* suffix) {
839 if (!InstallUtil::IsPerUserInstall(chrome_exe.c_str()) ||
840 QuickIsChromeRegistered(dist, chrome_exe, string16(),
841 CONFIRM_SHELL_REGISTRATION)) {
842 // No suffix on system-level installs and user-level installs already
843 // registered with no suffix.
844 suffix->clear();
845 return true;
848 // Get the old suffix for the check below.
849 if (!ShellUtil::GetOldUserSpecificRegistrySuffix(suffix)) {
850 NOTREACHED();
851 return false;
853 if (QuickIsChromeRegistered(dist, chrome_exe, *suffix,
854 CONFIRM_SHELL_REGISTRATION)) {
855 // Username suffix for installs that are suffixed as per the old-style.
856 return true;
859 return ShellUtil::GetUserSpecificRegistrySuffix(suffix);
862 // Returns the root registry key (HKLM or HKCU) under which registrations must
863 // be placed for this install. As of Windows 8 everything can go in HKCU for
864 // per-user installs.
865 HKEY DetermineRegistrationRoot(bool is_per_user) {
866 return is_per_user && base::win::GetVersion() >= base::win::VERSION_WIN8 ?
867 HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
870 // Associates Chrome with supported protocols and file associations. This should
871 // not be required on Vista+ but since some applications still read
872 // Software\Classes\http key directly, we have to do this on Vista+ as well.
873 bool RegisterChromeAsDefaultXPStyle(BrowserDistribution* dist,
874 int shell_change,
875 const string16& chrome_exe) {
876 bool ret = true;
877 ScopedVector<RegistryEntry> entries;
878 RegistryEntry::GetXPStyleDefaultBrowserUserEntries(
879 dist, chrome_exe,
880 ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe), &entries);
882 // Change the default browser for current user.
883 if ((shell_change & ShellUtil::CURRENT_USER) &&
884 !AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
885 ret = false;
886 LOG(ERROR) << "Could not make Chrome default browser (XP/current user).";
889 // Chrome as default browser at system level.
890 if ((shell_change & ShellUtil::SYSTEM_LEVEL) &&
891 !AddRegistryEntries(HKEY_LOCAL_MACHINE, entries)) {
892 ret = false;
893 LOG(ERROR) << "Could not make Chrome default browser (XP/system level).";
896 return ret;
899 // Associates Chrome with |protocol| in the registry. This should not be
900 // required on Vista+ but since some applications still read these registry
901 // keys directly, we have to do this on Vista+ as well.
902 // See http://msdn.microsoft.com/library/aa767914.aspx for more details.
903 bool RegisterChromeAsDefaultProtocolClientXPStyle(BrowserDistribution* dist,
904 const string16& chrome_exe,
905 const string16& protocol) {
906 ScopedVector<RegistryEntry> entries;
907 const string16 chrome_open(ShellUtil::GetChromeShellOpenCmd(chrome_exe));
908 const string16 chrome_icon(
909 ShellUtil::FormatIconLocation(chrome_exe, dist->GetIconIndex()));
910 RegistryEntry::GetXPStyleUserProtocolEntries(protocol, chrome_icon,
911 chrome_open, &entries);
912 // Change the default protocol handler for current user.
913 if (!AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
914 LOG(ERROR) << "Could not make Chrome default protocol client (XP).";
915 return false;
918 return true;
921 // Returns |properties.shortcut_name| if the property is set, otherwise it
922 // returns dist->GetAppShortcutName(). In any case, it makes sure the
923 // return value is suffixed with ".lnk".
924 string16 ExtractShortcutNameFromProperties(
925 BrowserDistribution* dist,
926 const ShellUtil::ShortcutProperties& properties) {
927 DCHECK(dist);
928 string16 shortcut_name;
929 if (properties.has_shortcut_name())
930 shortcut_name = properties.shortcut_name;
931 else
932 shortcut_name = dist->GetAppShortCutName();
934 if (!EndsWith(shortcut_name, installer::kLnkExt, false))
935 shortcut_name.append(installer::kLnkExt);
937 return shortcut_name;
940 // Converts ShellUtil::ShortcutOperation to the best-matching value in
941 // base::win::ShortcutOperation.
942 base::win::ShortcutOperation TranslateShortcutOperation(
943 ShellUtil::ShortcutOperation operation) {
944 switch (operation) {
945 case ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS: // Falls through.
946 case ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL:
947 return base::win::SHORTCUT_CREATE_ALWAYS;
949 case ShellUtil::SHELL_SHORTCUT_UPDATE_EXISTING:
950 return base::win::SHORTCUT_UPDATE_EXISTING;
952 case ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING:
953 return base::win::SHORTCUT_REPLACE_EXISTING;
955 default:
956 NOTREACHED();
957 return base::win::SHORTCUT_REPLACE_EXISTING;
961 // Returns a base::win::ShortcutProperties struct containing the properties
962 // to set on the shortcut based on the provided ShellUtil::ShortcutProperties.
963 base::win::ShortcutProperties TranslateShortcutProperties(
964 const ShellUtil::ShortcutProperties& properties) {
965 base::win::ShortcutProperties shortcut_properties;
967 if (properties.has_target()) {
968 shortcut_properties.set_target(properties.target);
969 DCHECK(!properties.target.DirName().empty());
970 shortcut_properties.set_working_dir(properties.target.DirName());
973 if (properties.has_arguments())
974 shortcut_properties.set_arguments(properties.arguments);
976 if (properties.has_description())
977 shortcut_properties.set_description(properties.description);
979 if (properties.has_icon())
980 shortcut_properties.set_icon(properties.icon, properties.icon_index);
982 if (properties.has_app_id())
983 shortcut_properties.set_app_id(properties.app_id);
985 if (properties.has_dual_mode())
986 shortcut_properties.set_dual_mode(properties.dual_mode);
988 return shortcut_properties;
991 // Cleans up an old verb (run) we used to register in
992 // <root>\Software\Classes\Chrome<.suffix>\.exe\shell\run on Windows 8.
993 void RemoveRunVerbOnWindows8(
994 BrowserDistribution* dist,
995 const string16& chrome_exe) {
996 if (IsChromeMetroSupported()) {
997 bool is_per_user_install =InstallUtil::IsPerUserInstall(chrome_exe.c_str());
998 HKEY root_key = DetermineRegistrationRoot(is_per_user_install);
999 // There's no need to rollback, so forgo the usual work item lists and just
1000 // remove the key from the registry.
1001 string16 run_verb_key(ShellUtil::kRegClasses);
1002 run_verb_key.push_back(base::FilePath::kSeparators[0]);
1003 run_verb_key.append(ShellUtil::GetBrowserModelId(
1004 dist, is_per_user_install));
1005 run_verb_key.append(ShellUtil::kRegExePath);
1006 run_verb_key.append(ShellUtil::kRegShellPath);
1007 run_verb_key.push_back(base::FilePath::kSeparators[0]);
1008 run_verb_key.append(ShellUtil::kRegVerbRun);
1009 InstallUtil::DeleteRegistryKey(root_key, run_verb_key);
1013 // Gets the short (8.3) form of |path|, putting the result in |short_path| and
1014 // returning true on success. |short_path| is not modified on failure.
1015 bool ShortNameFromPath(const base::FilePath& path, string16* short_path) {
1016 DCHECK(short_path);
1017 string16 result(MAX_PATH, L'\0');
1018 DWORD short_length = GetShortPathName(path.value().c_str(), &result[0],
1019 result.size());
1020 if (short_length == 0 || short_length > result.size()) {
1021 PLOG(ERROR) << "Error getting short (8.3) path";
1022 return false;
1025 result.resize(short_length);
1026 short_path->swap(result);
1027 return true;
1030 // Probe using IApplicationAssociationRegistration::QueryCurrentDefault
1031 // (Windows 8); see ProbeProtocolHandlers. This mechanism is not suitable for
1032 // use on previous versions of Windows despite the presence of
1033 // QueryCurrentDefault on them since versions of Windows prior to Windows 8
1034 // did not perform validation on the ProgID registered as the current default.
1035 // As a result, stale ProgIDs could be returned, leading to false positives.
1036 ShellUtil::DefaultState ProbeCurrentDefaultHandlers(
1037 const wchar_t* const* protocols,
1038 size_t num_protocols) {
1039 base::win::ScopedComPtr<IApplicationAssociationRegistration> registration;
1040 HRESULT hr = registration.CreateInstance(
1041 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC);
1042 if (FAILED(hr))
1043 return ShellUtil::UNKNOWN_DEFAULT;
1045 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
1046 base::FilePath chrome_exe;
1047 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
1048 NOTREACHED();
1049 return ShellUtil::UNKNOWN_DEFAULT;
1051 string16 prog_id(ShellUtil::kChromeHTMLProgId);
1052 prog_id += ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe.value());
1054 for (size_t i = 0; i < num_protocols; ++i) {
1055 base::win::ScopedCoMem<wchar_t> current_app;
1056 hr = registration->QueryCurrentDefault(protocols[i], AT_URLPROTOCOL,
1057 AL_EFFECTIVE, &current_app);
1058 if (FAILED(hr) || prog_id.compare(current_app) != 0)
1059 return ShellUtil::NOT_DEFAULT;
1062 return ShellUtil::IS_DEFAULT;
1065 // Probe using IApplicationAssociationRegistration::QueryAppIsDefault (Vista and
1066 // Windows 7); see ProbeProtocolHandlers.
1067 ShellUtil::DefaultState ProbeAppIsDefaultHandlers(
1068 const wchar_t* const* protocols,
1069 size_t num_protocols) {
1070 base::win::ScopedComPtr<IApplicationAssociationRegistration> registration;
1071 HRESULT hr = registration.CreateInstance(
1072 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC);
1073 if (FAILED(hr))
1074 return ShellUtil::UNKNOWN_DEFAULT;
1076 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
1077 base::FilePath chrome_exe;
1078 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
1079 NOTREACHED();
1080 return ShellUtil::UNKNOWN_DEFAULT;
1082 string16 app_name(ShellUtil::GetApplicationName(dist, chrome_exe.value()));
1084 BOOL result;
1085 for (size_t i = 0; i < num_protocols; ++i) {
1086 result = TRUE;
1087 hr = registration->QueryAppIsDefault(protocols[i], AT_URLPROTOCOL,
1088 AL_EFFECTIVE, app_name.c_str(), &result);
1089 if (FAILED(hr) || result == FALSE)
1090 return ShellUtil::NOT_DEFAULT;
1093 return ShellUtil::IS_DEFAULT;
1096 // Probe the current commands registered to handle the shell "open" verb for
1097 // |protocols| (Windows XP); see ProbeProtocolHandlers.
1098 ShellUtil::DefaultState ProbeOpenCommandHandlers(
1099 const wchar_t* const* protocols,
1100 size_t num_protocols) {
1101 // Get the path to the current exe (Chrome).
1102 base::FilePath app_path;
1103 if (!PathService::Get(base::FILE_EXE, &app_path)) {
1104 LOG(ERROR) << "Error getting app exe path";
1105 return ShellUtil::UNKNOWN_DEFAULT;
1108 // Get its short (8.3) form.
1109 string16 short_app_path;
1110 if (!ShortNameFromPath(app_path, &short_app_path))
1111 return ShellUtil::UNKNOWN_DEFAULT;
1113 const HKEY root_key = HKEY_CLASSES_ROOT;
1114 string16 key_path;
1115 base::win::RegKey key;
1116 string16 value;
1117 CommandLine command_line(CommandLine::NO_PROGRAM);
1118 string16 short_path;
1120 for (size_t i = 0; i < num_protocols; ++i) {
1121 // Get the command line from HKCU\<protocol>\shell\open\command.
1122 key_path.assign(protocols[i]).append(ShellUtil::kRegShellOpen);
1123 if ((key.Open(root_key, key_path.c_str(),
1124 KEY_QUERY_VALUE) != ERROR_SUCCESS) ||
1125 (key.ReadValue(L"", &value) != ERROR_SUCCESS)) {
1126 return ShellUtil::NOT_DEFAULT;
1129 // Need to normalize path in case it's been munged.
1130 command_line = CommandLine::FromString(value);
1131 if (!ShortNameFromPath(command_line.GetProgram(), &short_path))
1132 return ShellUtil::UNKNOWN_DEFAULT;
1134 if (!base::FilePath::CompareEqualIgnoreCase(short_path, short_app_path))
1135 return ShellUtil::NOT_DEFAULT;
1138 return ShellUtil::IS_DEFAULT;
1141 // A helper function that probes default protocol handler registration (in a
1142 // manner appropriate for the current version of Windows) to determine if
1143 // Chrome is the default handler for |protocols|. Returns IS_DEFAULT
1144 // only if Chrome is the default for all specified protocols.
1145 ShellUtil::DefaultState ProbeProtocolHandlers(
1146 const wchar_t* const* protocols,
1147 size_t num_protocols) {
1148 DCHECK(!num_protocols || protocols);
1149 if (DCHECK_IS_ON()) {
1150 for (size_t i = 0; i < num_protocols; ++i)
1151 DCHECK(protocols[i] && *protocols[i]);
1154 const base::win::Version windows_version = base::win::GetVersion();
1156 if (windows_version >= base::win::VERSION_WIN8)
1157 return ProbeCurrentDefaultHandlers(protocols, num_protocols);
1158 else if (windows_version >= base::win::VERSION_VISTA)
1159 return ProbeAppIsDefaultHandlers(protocols, num_protocols);
1161 return ProbeOpenCommandHandlers(protocols, num_protocols);
1164 // (Windows 8+) Finds and stores an app shortcuts folder path in *|path|.
1165 // Returns true on success.
1166 bool GetAppShortcutsFolder(BrowserDistribution* dist,
1167 ShellUtil::ShellChange level,
1168 base::FilePath *path) {
1169 DCHECK(path);
1170 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1172 base::FilePath folder;
1173 if (!PathService::Get(base::DIR_APP_SHORTCUTS, &folder)) {
1174 LOG(ERROR) << "Could not get application shortcuts location.";
1175 return false;
1178 folder = folder.Append(
1179 ShellUtil::GetBrowserModelId(dist, level == ShellUtil::CURRENT_USER));
1180 if (!base::DirectoryExists(folder)) {
1181 VLOG(1) << "No start screen shortcuts.";
1182 return false;
1185 *path = folder;
1186 return true;
1189 typedef base::Callback<bool(const base::FilePath&)> FileOperationCallback;
1191 // Shortcut operations for BatchShortcutAction().
1193 bool ShortcutOpUnpin(const base::FilePath& shortcut_path) {
1194 VLOG(1) << "Trying to unpin " << shortcut_path.value();
1195 if (!base::win::TaskbarUnpinShortcutLink(shortcut_path.value().c_str())) {
1196 VLOG(1) << shortcut_path.value() << " wasn't pinned (or the unpin failed).";
1197 // No error, since shortcut might not be pinned.
1199 return true;
1202 bool ShortcutOpDelete(const base::FilePath& shortcut_path) {
1203 bool ret = base::DeleteFile(shortcut_path, false);
1204 LOG_IF(ERROR, !ret) << "Failed to remove " << shortcut_path.value();
1205 return ret;
1208 bool ShortcutOpUpdate(const base::win::ShortcutProperties& shortcut_properties,
1209 const base::FilePath& shortcut_path) {
1210 bool ret = base::win::CreateOrUpdateShortcutLink(
1211 shortcut_path, shortcut_properties, base::win::SHORTCUT_UPDATE_EXISTING);
1212 LOG_IF(ERROR, !ret) << "Failed to update " << shortcut_path.value();
1213 return ret;
1216 // {|location|, |dist|, |level|} determine |shortcut_folder|.
1217 // Applies |shortcut_operation| to each shortcut in |shortcut_folder| that
1218 // targets |target_exe|.
1219 // Returns true if all operations are successful. All intended operations are
1220 // attempted even if failures occur.
1221 bool BatchShortcutAction(const FileOperationCallback& shortcut_operation,
1222 ShellUtil::ShortcutLocation location,
1223 BrowserDistribution* dist,
1224 ShellUtil::ShellChange level,
1225 const base::FilePath& target_exe) {
1226 DCHECK(!shortcut_operation.is_null());
1227 base::FilePath shortcut_folder;
1228 if (!ShellUtil::GetShortcutPath(location, dist, level, &shortcut_folder)) {
1229 LOG(WARNING) << "Cannot find path at location " << location;
1230 return false;
1233 bool success = true;
1234 InstallUtil::ProgramCompare target_compare(target_exe);
1235 base::FileEnumerator enumerator(
1236 shortcut_folder, false, base::FileEnumerator::FILES,
1237 string16(L"*") + installer::kLnkExt);
1238 base::FilePath target_path;
1239 for (base::FilePath shortcut_path = enumerator.Next();
1240 !shortcut_path.empty();
1241 shortcut_path = enumerator.Next()) {
1242 if (base::win::ResolveShortcut(shortcut_path, &target_path, NULL)) {
1243 if (target_compare.EvaluatePath(target_path) &&
1244 !shortcut_operation.Run(shortcut_path)) {
1245 success = false;
1247 } else {
1248 LOG(ERROR) << "Cannot resolve shortcut at " << shortcut_path.value();
1249 success = false;
1252 return success;
1255 // Removes folder spsecified by {|location|, |dist|, |level|}.
1256 bool RemoveShortcutFolder(ShellUtil::ShortcutLocation location,
1257 BrowserDistribution* dist,
1258 ShellUtil::ShellChange level) {
1260 // Explicitly whitelist locations, since accidental calls can be very harmful.
1261 if (location != ShellUtil::SHORTCUT_LOCATION_START_MENU &&
1262 location != ShellUtil::SHORTCUT_LOCATION_APP_SHORTCUTS) {
1263 NOTREACHED();
1264 return false;
1267 base::FilePath shortcut_folder;
1268 if (!ShellUtil::GetShortcutPath(location, dist, level, &shortcut_folder)) {
1269 LOG(WARNING) << "Cannot find path at location " << location;
1270 return false;
1272 if (!base::DeleteFile(shortcut_folder, true)) {
1273 LOG(ERROR) << "Cannot remove folder " << shortcut_folder.value();
1274 return false;
1276 return true;
1279 } // namespace
1281 const wchar_t* ShellUtil::kRegDefaultIcon = L"\\DefaultIcon";
1282 const wchar_t* ShellUtil::kRegShellPath = L"\\shell";
1283 const wchar_t* ShellUtil::kRegShellOpen = L"\\shell\\open\\command";
1284 const wchar_t* ShellUtil::kRegStartMenuInternet =
1285 L"Software\\Clients\\StartMenuInternet";
1286 const wchar_t* ShellUtil::kRegClasses = L"Software\\Classes";
1287 const wchar_t* ShellUtil::kRegRegisteredApplications =
1288 L"Software\\RegisteredApplications";
1289 const wchar_t* ShellUtil::kRegVistaUrlPrefs =
1290 L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
1291 L"http\\UserChoice";
1292 const wchar_t* ShellUtil::kAppPathsRegistryKey =
1293 L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
1294 const wchar_t* ShellUtil::kAppPathsRegistryPathName = L"Path";
1296 #if defined(GOOGLE_CHROME_BUILD)
1297 const wchar_t* ShellUtil::kChromeHTMLProgId = L"ChromeHTML";
1298 const wchar_t* ShellUtil::kChromeHTMLProgIdDesc = L"Chrome HTML Document";
1299 #else
1300 // This used to be "ChromiumHTML", but was forced to become "ChromiumHTM"
1301 // because of http://crbug.com/153349 as with the '.' and 26 characters suffix
1302 // added on user-level installs, the generated progid for Chromium was 39
1303 // characters long which, according to MSDN (
1304 // http://msdn.microsoft.com/library/aa911706.aspx), is the maximum length
1305 // for a progid. It was however determined through experimentation that the 39
1306 // character limit mentioned on MSDN includes the NULL character...
1307 const wchar_t* ShellUtil::kChromeHTMLProgId = L"ChromiumHTM";
1308 const wchar_t* ShellUtil::kChromeHTMLProgIdDesc = L"Chromium HTML Document";
1309 #endif
1311 const wchar_t* ShellUtil::kDefaultFileAssociations[] = {L".htm", L".html",
1312 L".shtml", L".xht", L".xhtml", NULL};
1313 const wchar_t* ShellUtil::kPotentialFileAssociations[] = {L".htm", L".html",
1314 L".shtml", L".xht", L".xhtml", L".webp", NULL};
1315 const wchar_t* ShellUtil::kBrowserProtocolAssociations[] = {L"ftp", L"http",
1316 L"https", NULL};
1317 const wchar_t* ShellUtil::kPotentialProtocolAssociations[] = {L"ftp", L"http",
1318 L"https", L"irc", L"mailto", L"mms", L"news", L"nntp", L"sms", L"smsto",
1319 L"tel", L"urn", L"webcal", NULL};
1320 const wchar_t* ShellUtil::kRegUrlProtocol = L"URL Protocol";
1321 const wchar_t* ShellUtil::kRegApplication = L"\\Application";
1322 const wchar_t* ShellUtil::kRegAppUserModelId = L"AppUserModelId";
1323 const wchar_t* ShellUtil::kRegApplicationDescription =
1324 L"ApplicationDescription";
1325 const wchar_t* ShellUtil::kRegApplicationName = L"ApplicationName";
1326 const wchar_t* ShellUtil::kRegApplicationIcon = L"ApplicationIcon";
1327 const wchar_t* ShellUtil::kRegApplicationCompany = L"ApplicationCompany";
1328 const wchar_t* ShellUtil::kRegExePath = L"\\.exe";
1329 const wchar_t* ShellUtil::kRegVerbOpen = L"open";
1330 const wchar_t* ShellUtil::kRegVerbOpenNewWindow = L"opennewwindow";
1331 const wchar_t* ShellUtil::kRegVerbRun = L"run";
1332 const wchar_t* ShellUtil::kRegCommand = L"command";
1333 const wchar_t* ShellUtil::kRegDelegateExecute = L"DelegateExecute";
1334 const wchar_t* ShellUtil::kRegOpenWithProgids = L"OpenWithProgids";
1336 bool ShellUtil::QuickIsChromeRegisteredInHKLM(BrowserDistribution* dist,
1337 const string16& chrome_exe,
1338 const string16& suffix) {
1339 return QuickIsChromeRegistered(dist, chrome_exe, suffix,
1340 CONFIRM_SHELL_REGISTRATION_IN_HKLM);
1343 bool ShellUtil::ShortcutLocationIsSupported(
1344 ShellUtil::ShortcutLocation location) {
1345 switch (location) {
1346 case SHORTCUT_LOCATION_DESKTOP:
1347 return true;
1348 case SHORTCUT_LOCATION_QUICK_LAUNCH:
1349 return true;
1350 case SHORTCUT_LOCATION_START_MENU:
1351 return true;
1352 case SHORTCUT_LOCATION_TASKBAR_PINS:
1353 return base::win::GetVersion() >= base::win::VERSION_WIN7;
1354 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1355 return base::win::GetVersion() >= base::win::VERSION_WIN8;
1356 default:
1357 NOTREACHED();
1358 return false;
1362 bool ShellUtil::GetShortcutPath(ShellUtil::ShortcutLocation location,
1363 BrowserDistribution* dist,
1364 ShellChange level,
1365 base::FilePath* path) {
1366 DCHECK(path);
1367 int dir_key = -1;
1368 bool add_folder_for_dist = false;
1369 switch (location) {
1370 case SHORTCUT_LOCATION_DESKTOP:
1371 dir_key = (level == CURRENT_USER) ? base::DIR_USER_DESKTOP :
1372 base::DIR_COMMON_DESKTOP;
1373 break;
1374 case SHORTCUT_LOCATION_QUICK_LAUNCH:
1375 dir_key = (level == CURRENT_USER) ? base::DIR_USER_QUICK_LAUNCH :
1376 base::DIR_DEFAULT_USER_QUICK_LAUNCH;
1377 break;
1378 case SHORTCUT_LOCATION_START_MENU:
1379 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1380 base::DIR_COMMON_START_MENU;
1381 add_folder_for_dist = true;
1382 break;
1383 case SHORTCUT_LOCATION_TASKBAR_PINS:
1384 dir_key = base::DIR_TASKBAR_PINS;
1385 break;
1386 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1387 // TODO(huangs): Move GetAppShortcutsFolder() logic into base_paths_win.
1388 return GetAppShortcutsFolder(dist, level, path);
1390 default:
1391 NOTREACHED();
1392 return false;
1395 if (!PathService::Get(dir_key, path) || path->empty()) {
1396 NOTREACHED() << dir_key;
1397 return false;
1400 if (add_folder_for_dist)
1401 *path = path->Append(dist->GetAppShortCutName());
1403 return true;
1406 bool ShellUtil::CreateOrUpdateShortcut(
1407 ShellUtil::ShortcutLocation location,
1408 BrowserDistribution* dist,
1409 const ShellUtil::ShortcutProperties& properties,
1410 ShellUtil::ShortcutOperation operation) {
1411 // Explicitly whitelist locations to which this is applicable.
1412 if (location != SHORTCUT_LOCATION_DESKTOP &&
1413 location != SHORTCUT_LOCATION_QUICK_LAUNCH &&
1414 location != SHORTCUT_LOCATION_START_MENU) {
1415 NOTREACHED();
1416 return false;
1419 DCHECK(dist);
1420 // |pin_to_taskbar| is only acknowledged when first creating the shortcut.
1421 DCHECK(!properties.pin_to_taskbar ||
1422 operation == SHELL_SHORTCUT_CREATE_ALWAYS ||
1423 operation == SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL);
1425 base::FilePath user_shortcut_path;
1426 base::FilePath system_shortcut_path;
1427 if (!GetShortcutPath(location, dist, SYSTEM_LEVEL, &system_shortcut_path)) {
1428 NOTREACHED();
1429 return false;
1432 string16 shortcut_name(ExtractShortcutNameFromProperties(dist, properties));
1433 system_shortcut_path = system_shortcut_path.Append(shortcut_name);
1435 base::FilePath* chosen_path;
1436 bool should_install_shortcut = true;
1437 if (properties.level == SYSTEM_LEVEL) {
1438 // Install the system-level shortcut if requested.
1439 chosen_path = &system_shortcut_path;
1440 } else if (operation != SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL ||
1441 !base::PathExists(system_shortcut_path)) {
1442 // Otherwise install the user-level shortcut, unless the system-level
1443 // variant of this shortcut is present on the machine and |operation| states
1444 // not to create a user-level shortcut in that case.
1445 if (!GetShortcutPath(location, dist, CURRENT_USER, &user_shortcut_path)) {
1446 NOTREACHED();
1447 return false;
1449 user_shortcut_path = user_shortcut_path.Append(shortcut_name);
1450 chosen_path = &user_shortcut_path;
1451 } else {
1452 // Do not install any shortcut if we are told to install a user-level
1453 // shortcut, but the system-level variant of that shortcut is present.
1454 // Other actions (e.g., pinning) can still happen with respect to the
1455 // existing system-level shortcut however.
1456 chosen_path = &system_shortcut_path;
1457 should_install_shortcut = false;
1460 if (chosen_path == NULL || chosen_path->empty()) {
1461 NOTREACHED();
1462 return false;
1465 base::win::ShortcutOperation shortcut_operation =
1466 TranslateShortcutOperation(operation);
1467 bool ret = true;
1468 if (should_install_shortcut) {
1469 // Make sure the parent directories exist when creating the shortcut.
1470 if (shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
1471 !file_util::CreateDirectory(chosen_path->DirName())) {
1472 NOTREACHED();
1473 return false;
1476 base::win::ShortcutProperties shortcut_properties(
1477 TranslateShortcutProperties(properties));
1478 ret = base::win::CreateOrUpdateShortcutLink(
1479 *chosen_path, shortcut_properties, shortcut_operation);
1482 if (ret && shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
1483 properties.pin_to_taskbar &&
1484 base::win::GetVersion() >= base::win::VERSION_WIN7) {
1485 ret = base::win::TaskbarPinShortcutLink(chosen_path->value().c_str());
1486 if (!ret) {
1487 LOG(ERROR) << "Failed to pin " << chosen_path->value();
1491 return ret;
1494 string16 ShellUtil::FormatIconLocation(const string16& icon_path,
1495 int icon_index) {
1496 string16 icon_string(icon_path);
1497 icon_string.append(L",");
1498 icon_string.append(base::IntToString16(icon_index));
1499 return icon_string;
1502 string16 ShellUtil::GetChromeShellOpenCmd(const string16& chrome_exe) {
1503 return L"\"" + chrome_exe + L"\" -- \"%1\"";
1506 string16 ShellUtil::GetChromeDelegateCommand(const string16& chrome_exe) {
1507 return L"\"" + chrome_exe + L"\" -- %*";
1510 void ShellUtil::GetRegisteredBrowsers(
1511 BrowserDistribution* dist,
1512 std::map<string16, string16>* browsers) {
1513 DCHECK(dist);
1514 DCHECK(browsers);
1516 const string16 base_key(ShellUtil::kRegStartMenuInternet);
1517 string16 client_path;
1518 RegKey key;
1519 string16 name;
1520 string16 command;
1522 // HKCU has precedence over HKLM for these registrations: http://goo.gl/xjczJ.
1523 // Look in HKCU second to override any identical values found in HKLM.
1524 const HKEY roots[] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
1525 for (int i = 0; i < arraysize(roots); ++i) {
1526 const HKEY root = roots[i];
1527 for (base::win::RegistryKeyIterator iter(root, base_key.c_str());
1528 iter.Valid(); ++iter) {
1529 client_path.assign(base_key).append(1, L'\\').append(iter.Name());
1530 // Read the browser's name (localized according to install language).
1531 if (key.Open(root, client_path.c_str(),
1532 KEY_QUERY_VALUE) != ERROR_SUCCESS ||
1533 key.ReadValue(NULL, &name) != ERROR_SUCCESS ||
1534 name.empty() ||
1535 name.find(dist->GetBaseAppName()) != string16::npos) {
1536 continue;
1538 // Read the browser's reinstall command.
1539 if (key.Open(root, (client_path + L"\\InstallInfo").c_str(),
1540 KEY_QUERY_VALUE) == ERROR_SUCCESS &&
1541 key.ReadValue(kReinstallCommand, &command) == ERROR_SUCCESS &&
1542 !command.empty()) {
1543 (*browsers)[name] = command;
1549 string16 ShellUtil::GetCurrentInstallationSuffix(BrowserDistribution* dist,
1550 const string16& chrome_exe) {
1551 // This method is somewhat the opposite of GetInstallationSpecificSuffix().
1552 // In this case we are not trying to determine the current suffix for the
1553 // upcoming installation (i.e. not trying to stick to a currently bad
1554 // registration style if one is present).
1555 // Here we want to determine which suffix we should use at run-time.
1556 // In order of preference, we prefer (for user-level installs):
1557 // 1) Base 32 encoding of the md5 hash of the user's sid (new-style).
1558 // 2) Username (old-style).
1559 // 3) Unsuffixed (even worse).
1560 string16 tested_suffix;
1561 if (InstallUtil::IsPerUserInstall(chrome_exe.c_str()) &&
1562 (!GetUserSpecificRegistrySuffix(&tested_suffix) ||
1563 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix,
1564 CONFIRM_PROGID_REGISTRATION)) &&
1565 (!GetOldUserSpecificRegistrySuffix(&tested_suffix) ||
1566 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix,
1567 CONFIRM_PROGID_REGISTRATION)) &&
1568 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix.erase(),
1569 CONFIRM_PROGID_REGISTRATION)) {
1570 // If Chrome is not registered under any of the possible suffixes (e.g.
1571 // tests, Canary, etc.): use the new-style suffix at run-time.
1572 if (!GetUserSpecificRegistrySuffix(&tested_suffix))
1573 NOTREACHED();
1575 return tested_suffix;
1578 string16 ShellUtil::GetApplicationName(BrowserDistribution* dist,
1579 const string16& chrome_exe) {
1580 string16 app_name = dist->GetBaseAppName();
1581 app_name += GetCurrentInstallationSuffix(dist, chrome_exe);
1582 return app_name;
1585 string16 ShellUtil::GetBrowserModelId(BrowserDistribution* dist,
1586 bool is_per_user_install) {
1587 string16 app_id(dist->GetBaseAppId());
1588 string16 suffix;
1590 // TODO(robertshield): Temporary hack to make the kRegisterChromeBrowserSuffix
1591 // apply to all registry values computed down in these murky depths.
1592 CommandLine& command_line = *CommandLine::ForCurrentProcess();
1593 if (command_line.HasSwitch(
1594 installer::switches::kRegisterChromeBrowserSuffix)) {
1595 suffix = command_line.GetSwitchValueNative(
1596 installer::switches::kRegisterChromeBrowserSuffix);
1597 } else if (is_per_user_install && !GetUserSpecificRegistrySuffix(&suffix)) {
1598 NOTREACHED();
1600 // There is only one component (i.e. the suffixed appid) in this case, but it
1601 // is still necessary to go through the appid constructor to make sure the
1602 // returned appid is truncated if necessary.
1603 std::vector<string16> components(1, app_id.append(suffix));
1604 return BuildAppModelId(components);
1607 string16 ShellUtil::BuildAppModelId(
1608 const std::vector<string16>& components) {
1609 DCHECK_GT(components.size(), 0U);
1611 // Find the maximum numbers of characters allowed in each component
1612 // (accounting for the dots added between each component).
1613 const size_t available_chars =
1614 installer::kMaxAppModelIdLength - (components.size() - 1);
1615 const size_t max_component_length = available_chars / components.size();
1617 // |max_component_length| should be at least 2; otherwise the truncation logic
1618 // below breaks.
1619 if (max_component_length < 2U) {
1620 NOTREACHED();
1621 return (*components.begin()).substr(0, installer::kMaxAppModelIdLength);
1624 string16 app_id;
1625 app_id.reserve(installer::kMaxAppModelIdLength);
1626 for (std::vector<string16>::const_iterator it = components.begin();
1627 it != components.end(); ++it) {
1628 if (it != components.begin())
1629 app_id.push_back(L'.');
1631 const string16& component = *it;
1632 DCHECK(!component.empty());
1633 if (component.length() > max_component_length) {
1634 // Append a shortened version of this component. Cut in the middle to try
1635 // to avoid losing the unique parts of this component (which are usually
1636 // at the beginning or end for things like usernames and paths).
1637 app_id.append(component.c_str(), 0, max_component_length / 2);
1638 app_id.append(component.c_str(),
1639 component.length() - ((max_component_length + 1) / 2),
1640 string16::npos);
1641 } else {
1642 app_id.append(component);
1645 // No spaces are allowed in the AppUserModelId according to MSDN.
1646 ReplaceChars(app_id, L" ", L"_", &app_id);
1647 return app_id;
1650 ShellUtil::DefaultState ShellUtil::GetChromeDefaultState() {
1651 // When we check for default browser we don't necessarily want to count file
1652 // type handlers and icons as having changed the default browser status,
1653 // since the user may have changed their shell settings to cause HTML files
1654 // to open with a text editor for example. We also don't want to aggressively
1655 // claim FTP, since the user may have a separate FTP client. It is an open
1656 // question as to how to "heal" these settings. Perhaps the user should just
1657 // re-run the installer or run with the --set-default-browser command line
1658 // flag. There is doubtless some other key we can hook into to cause "Repair"
1659 // to show up in Add/Remove programs for us.
1660 static const wchar_t* const kChromeProtocols[] = { L"http", L"https" };
1661 return ProbeProtocolHandlers(kChromeProtocols, arraysize(kChromeProtocols));
1664 ShellUtil::DefaultState ShellUtil::GetChromeDefaultProtocolClientState(
1665 const string16& protocol) {
1666 if (protocol.empty())
1667 return UNKNOWN_DEFAULT;
1669 const wchar_t* const protocols[] = { protocol.c_str() };
1670 return ProbeProtocolHandlers(protocols, arraysize(protocols));
1673 // static
1674 bool ShellUtil::CanMakeChromeDefaultUnattended() {
1675 return base::win::GetVersion() < base::win::VERSION_WIN8;
1678 bool ShellUtil::MakeChromeDefault(BrowserDistribution* dist,
1679 int shell_change,
1680 const string16& chrome_exe,
1681 bool elevate_if_not_admin) {
1682 DCHECK(!(shell_change & ShellUtil::SYSTEM_LEVEL) || IsUserAnAdmin());
1684 if (!dist->CanSetAsDefault())
1685 return false;
1687 // Windows 8 does not permit making a browser default just like that.
1688 // This process needs to be routed through the system's UI. Use
1689 // ShowMakeChromeDefaultSystemUI instead (below).
1690 if (!CanMakeChromeDefaultUnattended()) {
1691 return false;
1694 if (!ShellUtil::RegisterChromeBrowser(
1695 dist, chrome_exe, string16(), elevate_if_not_admin)) {
1696 return false;
1699 bool ret = true;
1700 // First use the new "recommended" way on Vista to make Chrome default
1701 // browser.
1702 string16 app_name = GetApplicationName(dist, chrome_exe);
1704 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
1705 // On Windows Vista and Win7 we still can set ourselves via the
1706 // the IApplicationAssociationRegistration interface.
1707 VLOG(1) << "Registering Chrome as default browser on Vista.";
1708 base::win::ScopedComPtr<IApplicationAssociationRegistration> pAAR;
1709 HRESULT hr = pAAR.CreateInstance(CLSID_ApplicationAssociationRegistration,
1710 NULL, CLSCTX_INPROC);
1711 if (SUCCEEDED(hr)) {
1712 for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
1713 hr = pAAR->SetAppAsDefault(app_name.c_str(),
1714 ShellUtil::kBrowserProtocolAssociations[i], AT_URLPROTOCOL);
1715 if (!SUCCEEDED(hr)) {
1716 ret = false;
1717 LOG(ERROR) << "Failed to register as default for protocol "
1718 << ShellUtil::kBrowserProtocolAssociations[i]
1719 << " (" << hr << ")";
1723 for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
1724 hr = pAAR->SetAppAsDefault(app_name.c_str(),
1725 ShellUtil::kDefaultFileAssociations[i], AT_FILEEXTENSION);
1726 if (!SUCCEEDED(hr)) {
1727 ret = false;
1728 LOG(ERROR) << "Failed to register as default for file extension "
1729 << ShellUtil::kDefaultFileAssociations[i]
1730 << " (" << hr << ")";
1736 if (!RegisterChromeAsDefaultXPStyle(dist, shell_change, chrome_exe))
1737 ret = false;
1739 // Send Windows notification event so that it can update icons for
1740 // file associations.
1741 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
1742 return ret;
1745 bool ShellUtil::ShowMakeChromeDefaultSystemUI(BrowserDistribution* dist,
1746 const string16& chrome_exe) {
1747 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1748 if (!dist->CanSetAsDefault())
1749 return false;
1751 if (!RegisterChromeBrowser(dist, chrome_exe, string16(), true))
1752 return false;
1754 bool succeeded = true;
1755 bool is_default = (GetChromeDefaultState() == IS_DEFAULT);
1756 if (!is_default) {
1757 // On Windows 8, you can't set yourself as the default handler
1758 // programatically. In other words IApplicationAssociationRegistration
1759 // has been rendered useless. What you can do is to launch
1760 // "Set Program Associations" section of the "Default Programs"
1761 // control panel, which is a mess, or pop the concise "How you want to open
1762 // webpages?" dialog. We choose the latter.
1763 succeeded = LaunchSelectDefaultProtocolHandlerDialog(L"http");
1764 is_default = (succeeded && GetChromeDefaultState() == IS_DEFAULT);
1766 if (succeeded && is_default)
1767 RegisterChromeAsDefaultXPStyle(dist, CURRENT_USER, chrome_exe);
1768 return succeeded;
1771 bool ShellUtil::MakeChromeDefaultProtocolClient(BrowserDistribution* dist,
1772 const string16& chrome_exe,
1773 const string16& protocol) {
1774 if (!dist->CanSetAsDefault())
1775 return false;
1777 if (!RegisterChromeForProtocol(dist, chrome_exe, string16(), protocol, true))
1778 return false;
1780 // Windows 8 does not permit making a browser default just like that.
1781 // This process needs to be routed through the system's UI. Use
1782 // ShowMakeChromeDefaultProocolClientSystemUI instead (below).
1783 if (!CanMakeChromeDefaultUnattended())
1784 return false;
1786 bool ret = true;
1787 // First use the new "recommended" way on Vista to make Chrome default
1788 // protocol handler.
1789 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
1790 VLOG(1) << "Registering Chrome as default handler for " << protocol
1791 << " on Vista.";
1792 base::win::ScopedComPtr<IApplicationAssociationRegistration> pAAR;
1793 HRESULT hr = pAAR.CreateInstance(CLSID_ApplicationAssociationRegistration,
1794 NULL, CLSCTX_INPROC);
1795 if (SUCCEEDED(hr)) {
1796 string16 app_name = GetApplicationName(dist, chrome_exe);
1797 hr = pAAR->SetAppAsDefault(app_name.c_str(), protocol.c_str(),
1798 AT_URLPROTOCOL);
1800 if (!SUCCEEDED(hr)) {
1801 ret = false;
1802 LOG(ERROR) << "Could not make Chrome default protocol client (Vista):"
1803 << " HRESULT=" << hr << ".";
1807 // Now use the old way to associate Chrome with the desired protocol. This
1808 // should not be required on Vista+, but since some applications still read
1809 // Software\Classes\<protocol> key directly, do this on Vista+ also.
1810 if (!RegisterChromeAsDefaultProtocolClientXPStyle(dist, chrome_exe, protocol))
1811 ret = false;
1813 return ret;
1816 bool ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
1817 BrowserDistribution* dist,
1818 const string16& chrome_exe,
1819 const string16& protocol) {
1820 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1821 if (!dist->CanSetAsDefault())
1822 return false;
1824 if (!RegisterChromeForProtocol(dist, chrome_exe, string16(), protocol, true))
1825 return false;
1827 bool succeeded = true;
1828 bool is_default = (
1829 GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
1830 if (!is_default) {
1831 // On Windows 8, you can't set yourself as the default handler
1832 // programatically. In other words IApplicationAssociationRegistration
1833 // has been rendered useless. What you can do is to launch
1834 // "Set Program Associations" section of the "Default Programs"
1835 // control panel, which is a mess, or pop the concise "How you want to open
1836 // links of this type (protocol)?" dialog. We choose the latter.
1837 succeeded = LaunchSelectDefaultProtocolHandlerDialog(protocol.c_str());
1838 is_default = (succeeded &&
1839 GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
1841 if (succeeded && is_default)
1842 RegisterChromeAsDefaultProtocolClientXPStyle(dist, chrome_exe, protocol);
1843 return succeeded;
1846 bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist,
1847 const string16& chrome_exe,
1848 const string16& unique_suffix,
1849 bool elevate_if_not_admin) {
1850 if (!dist->CanSetAsDefault())
1851 return false;
1853 CommandLine& command_line = *CommandLine::ForCurrentProcess();
1855 string16 suffix;
1856 if (!unique_suffix.empty()) {
1857 suffix = unique_suffix;
1858 } else if (command_line.HasSwitch(
1859 installer::switches::kRegisterChromeBrowserSuffix)) {
1860 suffix = command_line.GetSwitchValueNative(
1861 installer::switches::kRegisterChromeBrowserSuffix);
1862 } else if (!GetInstallationSpecificSuffix(dist, chrome_exe, &suffix)) {
1863 return false;
1866 RemoveRunVerbOnWindows8(dist, chrome_exe);
1868 bool user_level = InstallUtil::IsPerUserInstall(chrome_exe.c_str());
1869 HKEY root = DetermineRegistrationRoot(user_level);
1871 // Look only in HKLM for system-level installs (otherwise, if a user-level
1872 // install is also present, it will lead IsChromeRegistered() to think this
1873 // system-level install isn't registered properly as it is shadowed by the
1874 // user-level install's registrations).
1875 uint32 look_for_in = user_level ?
1876 RegistryEntry::LOOK_IN_HKCU_THEN_HKLM : RegistryEntry::LOOK_IN_HKLM;
1878 // Check if chrome is already registered with this suffix.
1879 if (IsChromeRegistered(dist, chrome_exe, suffix, look_for_in))
1880 return true;
1882 bool result = true;
1883 if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
1884 // Do the full registration if we can do it at user-level or if the user is
1885 // an admin.
1886 ScopedVector<RegistryEntry> progid_and_appreg_entries;
1887 ScopedVector<RegistryEntry> shell_entries;
1888 RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix,
1889 &progid_and_appreg_entries);
1890 RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix,
1891 &progid_and_appreg_entries);
1892 RegistryEntry::GetShellIntegrationEntries(
1893 dist, chrome_exe, suffix, &shell_entries);
1894 result = (AddRegistryEntries(root, progid_and_appreg_entries) &&
1895 AddRegistryEntries(root, shell_entries));
1896 } else if (elevate_if_not_admin &&
1897 base::win::GetVersion() >= base::win::VERSION_VISTA &&
1898 ElevateAndRegisterChrome(dist, chrome_exe, suffix, L"")) {
1899 // If the user is not an admin and OS is between Vista and Windows 7
1900 // inclusively, try to elevate and register. This is only intended for
1901 // user-level installs as system-level installs should always be run with
1902 // admin rights.
1903 result = true;
1904 } else {
1905 // If we got to this point then all we can do is create ProgId and basic app
1906 // registrations under HKCU.
1907 ScopedVector<RegistryEntry> entries;
1908 RegistryEntry::GetProgIdEntries(dist, chrome_exe, string16(), &entries);
1909 // Prefer to use |suffix|; unless Chrome's ProgIds are already registered
1910 // with no suffix (as per the old registration style): in which case some
1911 // other registry entries could refer to them and since we were not able to
1912 // set our HKLM entries above, we are better off not altering these here.
1913 if (!AreEntriesRegistered(entries, RegistryEntry::LOOK_IN_HKCU)) {
1914 if (!suffix.empty()) {
1915 entries.clear();
1916 RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries);
1917 RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, &entries);
1919 result = AddRegistryEntries(HKEY_CURRENT_USER, entries);
1920 } else {
1921 // The ProgId is registered unsuffixed in HKCU, also register the app with
1922 // Windows in HKCU (this was not done in the old registration style and
1923 // thus needs to be done after the above check for the unsuffixed
1924 // registration).
1925 entries.clear();
1926 RegistryEntry::GetAppRegistrationEntries(chrome_exe, string16(),
1927 &entries);
1928 result = AddRegistryEntries(HKEY_CURRENT_USER, entries);
1931 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
1932 return result;
1935 bool ShellUtil::RegisterChromeForProtocol(BrowserDistribution* dist,
1936 const string16& chrome_exe,
1937 const string16& unique_suffix,
1938 const string16& protocol,
1939 bool elevate_if_not_admin) {
1940 if (!dist->CanSetAsDefault())
1941 return false;
1943 string16 suffix;
1944 if (!unique_suffix.empty()) {
1945 suffix = unique_suffix;
1946 } else if (!GetInstallationSpecificSuffix(dist, chrome_exe, &suffix)) {
1947 return false;
1950 bool user_level = InstallUtil::IsPerUserInstall(chrome_exe.c_str());
1951 HKEY root = DetermineRegistrationRoot(user_level);
1953 // Look only in HKLM for system-level installs (otherwise, if a user-level
1954 // install is also present, it could lead IsChromeRegisteredForProtocol() to
1955 // think this system-level install isn't registered properly as it may be
1956 // shadowed by the user-level install's registrations).
1957 uint32 look_for_in = user_level ?
1958 RegistryEntry::LOOK_IN_HKCU_THEN_HKLM : RegistryEntry::LOOK_IN_HKLM;
1960 // Check if chrome is already registered with this suffix.
1961 if (IsChromeRegisteredForProtocol(dist, suffix, protocol, look_for_in))
1962 return true;
1964 if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
1965 // We can do this operation directly.
1966 // First, make sure Chrome is fully registered on this machine.
1967 if (!RegisterChromeBrowser(dist, chrome_exe, suffix, false))
1968 return false;
1970 // Write in the capabillity for the protocol.
1971 ScopedVector<RegistryEntry> entries;
1972 RegistryEntry::GetProtocolCapabilityEntries(dist, suffix, protocol,
1973 &entries);
1974 return AddRegistryEntries(root, entries);
1975 } else if (elevate_if_not_admin &&
1976 base::win::GetVersion() >= base::win::VERSION_VISTA) {
1977 // Elevate to do the whole job
1978 return ElevateAndRegisterChrome(dist, chrome_exe, suffix, protocol);
1979 } else {
1980 // Admin rights are required to register capabilities before Windows 8.
1981 return false;
1985 // static
1986 bool ShellUtil::RemoveShortcuts(ShellUtil::ShortcutLocation location,
1987 BrowserDistribution* dist,
1988 ShellChange level,
1989 const base::FilePath& target_exe) {
1990 if (!ShellUtil::ShortcutLocationIsSupported(location))
1991 return true; // Vacuous success.
1993 switch (location) {
1994 case SHORTCUT_LOCATION_START_MENU: // Falls through.
1995 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1996 return RemoveShortcutFolder(location, dist, level);
1998 case SHORTCUT_LOCATION_TASKBAR_PINS:
1999 return BatchShortcutAction(base::Bind(&ShortcutOpUnpin), location, dist,
2000 level, target_exe);
2002 default:
2003 return BatchShortcutAction(base::Bind(&ShortcutOpDelete), location, dist,
2004 level, target_exe);
2008 // static
2009 bool ShellUtil::UpdateShortcuts(
2010 ShellUtil::ShortcutLocation location,
2011 BrowserDistribution* dist,
2012 ShellChange level,
2013 const base::FilePath& target_exe,
2014 const ShellUtil::ShortcutProperties& properties) {
2015 if (!ShellUtil::ShortcutLocationIsSupported(location))
2016 return true; // Vacuous success.
2018 base::win::ShortcutProperties shortcut_properties(
2019 TranslateShortcutProperties(properties));
2020 return BatchShortcutAction(base::Bind(&ShortcutOpUpdate, shortcut_properties),
2021 location, dist, level, target_exe);
2024 bool ShellUtil::GetUserSpecificRegistrySuffix(string16* suffix) {
2025 // Use a thread-safe cache for the user's suffix.
2026 static base::LazyInstance<UserSpecificRegistrySuffix>::Leaky suffix_instance =
2027 LAZY_INSTANCE_INITIALIZER;
2028 return suffix_instance.Get().GetSuffix(suffix);
2031 bool ShellUtil::GetOldUserSpecificRegistrySuffix(string16* suffix) {
2032 wchar_t user_name[256];
2033 DWORD size = arraysize(user_name);
2034 if (::GetUserName(user_name, &size) == 0 || size < 1) {
2035 NOTREACHED();
2036 return false;
2038 suffix->reserve(size);
2039 suffix->assign(1, L'.');
2040 suffix->append(user_name, size - 1);
2041 return true;
2044 string16 ShellUtil::ByteArrayToBase32(const uint8* bytes, size_t size) {
2045 static const char kEncoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
2047 // Eliminate special cases first.
2048 if (size == 0) {
2049 return string16();
2050 } else if (size == 1) {
2051 string16 ret;
2052 ret.push_back(kEncoding[(bytes[0] & 0xf8) >> 3]);
2053 ret.push_back(kEncoding[(bytes[0] & 0x07) << 2]);
2054 return ret;
2055 } else if (size >= std::numeric_limits<size_t>::max() / 8) {
2056 // If |size| is too big, the calculation of |encoded_length| below will
2057 // overflow.
2058 NOTREACHED();
2059 return string16();
2062 // Overestimate the number of bits in the string by 4 so that dividing by 5
2063 // is the equivalent of rounding up the actual number of bits divided by 5.
2064 const size_t encoded_length = (size * 8 + 4) / 5;
2066 string16 ret;
2067 ret.reserve(encoded_length);
2069 // A bit stream which will be read from the left and appended to from the
2070 // right as it's emptied.
2071 uint16 bit_stream = (bytes[0] << 8) + bytes[1];
2072 size_t next_byte_index = 2;
2073 int free_bits = 0;
2074 while (free_bits < 16) {
2075 // Extract the 5 leftmost bits in the stream
2076 ret.push_back(kEncoding[(bit_stream & 0xf800) >> 11]);
2077 bit_stream <<= 5;
2078 free_bits += 5;
2080 // If there is enough room in the bit stream, inject another byte (if there
2081 // are any left...).
2082 if (free_bits >= 8 && next_byte_index < size) {
2083 free_bits -= 8;
2084 bit_stream += bytes[next_byte_index++] << free_bits;
2088 DCHECK_EQ(ret.length(), encoded_length);
2089 return ret;