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.
9
10 #include "chrome/installer/util/shell_util.h"
11
12 #include <windows.h>
13 #include <shlobj.h>
14
15 #include <limits>
16 #include <string>
17
18 #include "base/bind.h"
19 #include "base/command_line.h"
20 #include "base/files/file_enumerator.h"
21 #include "base/files/file_path.h"
22 #include "base/files/file_util.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/stringprintf.h"
34 #include "base/strings/utf_string_conversions.h"
35 #include "base/synchronization/cancellation_flag.h"
36 #include "base/values.h"
37 #include "base/win/registry.h"
38 #include "base/win/scoped_co_mem.h"
39 #include "base/win/scoped_comptr.h"
40 #include "base/win/shortcut.h"
41 #include "base/win/win_util.h"
42 #include "base/win/windows_version.h"
43 #include "chrome/common/chrome_constants.h"
44 #include "chrome/common/chrome_switches.h"
45 #include "chrome/installer/util/browser_distribution.h"
46 #include "chrome/installer/util/install_util.h"
47 #include "chrome/installer/util/l10n_string_util.h"
48 #include "chrome/installer/util/master_preferences.h"
49 #include "chrome/installer/util/master_preferences_constants.h"
50 #include "chrome/installer/util/util_constants.h"
51 #include "chrome/installer/util/work_item.h"
52
53 #include "installer_util_strings.h" // NOLINT
54
55 using base::win::RegKey;
56
57 namespace {
58
59 // An enum used to tell QuickIsChromeRegistered() which level of registration
60 // the caller wants to confirm.
61 enum RegistrationConfirmationLevel {
62 // Only look for Chrome's ProgIds.
63 // This is sufficient when we are trying to determine the suffix of the
64 // currently running Chrome as shell integration registrations might not be
65 // present.
66 CONFIRM_PROGID_REGISTRATION = 0,
67 // Confirm that Chrome is fully integrated with Windows (i.e. registered with
68 // Defaut Programs). These registrations can be in HKCU as of Windows 8.
69 // Note: Shell registration implies ProgId registration.
70 CONFIRM_SHELL_REGISTRATION,
71 // Same as CONFIRM_SHELL_REGISTRATION, but only look in HKLM (used when
72 // uninstalling to know whether elevation is required to clean up the
73 // registry).
74 CONFIRM_SHELL_REGISTRATION_IN_HKLM,
75 };
76
77 const wchar_t kReinstallCommand[] = L"ReinstallCommand";
78
79 // Returns true if Chrome Metro is supported on this OS (Win 8 8370 or greater).
80 // TODO(gab): Change this to a simple check for Win 8 once old Win8 builds
81 // become irrelevant.
IsChromeMetroSupported()82 bool IsChromeMetroSupported() {
83 OSVERSIONINFOEX min_version_info = {};
84 min_version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
85 min_version_info.dwMajorVersion = 6;
86 min_version_info.dwMinorVersion = 2;
87 min_version_info.dwBuildNumber = 8370;
88 min_version_info.wServicePackMajor = 0;
89 min_version_info.wServicePackMinor = 0;
90
91 DWORDLONG condition_mask = 0;
92 VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
93 VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
94 VER_SET_CONDITION(condition_mask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
95 VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
96 VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
97
98 DWORD type_mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER |
99 VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR;
100
101 return VerifyVersionInfo(&min_version_info, type_mask, condition_mask) != 0;
102 }
103
104 // Returns the current (or installed) browser's ProgId (e.g.
105 // "ChromeHTML|suffix|").
106 // |suffix| can be the empty string.
GetBrowserProgId(const base::string16 & suffix)107 base::string16 GetBrowserProgId(const base::string16& suffix) {
108 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
109 base::string16 chrome_html(dist->GetBrowserProgIdPrefix());
110 chrome_html.append(suffix);
111
112 // ProgIds cannot be longer than 39 characters.
113 // Ref: http://msdn.microsoft.com/en-us/library/aa911706.aspx.
114 // Make all new registrations comply with this requirement (existing
115 // registrations must be preserved).
116 base::string16 new_style_suffix;
117 if (ShellUtil::GetUserSpecificRegistrySuffix(&new_style_suffix) &&
118 suffix == new_style_suffix && chrome_html.length() > 39) {
119 NOTREACHED();
120 chrome_html.erase(39);
121 }
122 return chrome_html;
123 }
124
125 // This class is used to initialize and cache a base 32 encoding of the md5 hash
126 // of this user's sid preceded by a dot.
127 // This is guaranteed to be unique on the machine and 27 characters long
128 // (including the '.').
129 // This is then meant to be used as a suffix on all registrations that may
130 // conflict with another user-level Chrome install.
131 class UserSpecificRegistrySuffix {
132 public:
133 // All the initialization is done in the constructor to be able to build the
134 // suffix in a thread-safe manner when used in conjunction with a
135 // LazyInstance.
136 UserSpecificRegistrySuffix();
137
138 // Sets |suffix| to the pre-computed suffix cached in this object.
139 // Returns true unless the initialization originally failed.
140 bool GetSuffix(base::string16* suffix);
141
142 private:
143 base::string16 suffix_;
144
145 DISALLOW_COPY_AND_ASSIGN(UserSpecificRegistrySuffix);
146 }; // class UserSpecificRegistrySuffix
147
UserSpecificRegistrySuffix()148 UserSpecificRegistrySuffix::UserSpecificRegistrySuffix() {
149 base::string16 user_sid;
150 if (!base::win::GetUserSidString(&user_sid)) {
151 NOTREACHED();
152 return;
153 }
154 COMPILE_ASSERT(sizeof(base::MD5Digest) == 16, size_of_MD5_not_as_expected_);
155 base::MD5Digest md5_digest;
156 std::string user_sid_ascii(base::UTF16ToASCII(user_sid));
157 base::MD5Sum(user_sid_ascii.c_str(), user_sid_ascii.length(), &md5_digest);
158 const base::string16 base32_md5(
159 ShellUtil::ByteArrayToBase32(md5_digest.a, arraysize(md5_digest.a)));
160 // The value returned by the base32 algorithm above must never change and
161 // must always be 26 characters long (i.e. if someone ever moves this to
162 // base and implements the full base32 algorithm (i.e. with appended '='
163 // signs in the output), they must provide a flag to allow this method to
164 // still request the output with no appended '=' signs).
165 DCHECK_EQ(base32_md5.length(), 26U);
166 suffix_.reserve(base32_md5.length() + 1);
167 suffix_.assign(1, L'.');
168 suffix_.append(base32_md5);
169 }
170
GetSuffix(base::string16 * suffix)171 bool UserSpecificRegistrySuffix::GetSuffix(base::string16* suffix) {
172 if (suffix_.empty()) {
173 NOTREACHED();
174 return false;
175 }
176 suffix->assign(suffix_);
177 return true;
178 }
179
180 // This class represents a single registry entry. The objective is to
181 // encapsulate all the registry entries required for registering Chrome at one
182 // place. This class can not be instantiated outside the class and the objects
183 // of this class type can be obtained only by calling a static method of this
184 // class.
185 class RegistryEntry {
186 public:
187 // A bit-field enum of places to look for this key in the Windows registry.
188 enum LookForIn {
189 LOOK_IN_HKCU = 1 << 0,
190 LOOK_IN_HKLM = 1 << 1,
191 LOOK_IN_HKCU_THEN_HKLM = LOOK_IN_HKCU | LOOK_IN_HKLM,
192 };
193
194 // Returns the Windows browser client registration key for Chrome. For
195 // example: "Software\Clients\StartMenuInternet\Chromium[.user]". Strictly
196 // speaking, we should use the name of the executable (e.g., "chrome.exe"),
197 // but that ship has sailed. The cost of switching now is re-prompting users
198 // to make Chrome their default browser, which isn't polite. |suffix| is the
199 // user-specific registration suffix; see GetUserSpecificDefaultBrowserSuffix
200 // in shell_util.h for details.
GetBrowserClientKey(BrowserDistribution * dist,const base::string16 & suffix)201 static base::string16 GetBrowserClientKey(BrowserDistribution* dist,
202 const base::string16& suffix) {
203 DCHECK(suffix.empty() || suffix[0] == L'.');
204 return base::string16(ShellUtil::kRegStartMenuInternet)
205 .append(1, L'\\')
206 .append(dist->GetBaseAppName())
207 .append(suffix);
208 }
209
210 // Returns the Windows Default Programs capabilities key for Chrome. For
211 // example:
212 // "Software\Clients\StartMenuInternet\Chromium[.user]\Capabilities".
GetCapabilitiesKey(BrowserDistribution * dist,const base::string16 & suffix)213 static base::string16 GetCapabilitiesKey(BrowserDistribution* dist,
214 const base::string16& suffix) {
215 return GetBrowserClientKey(dist, suffix).append(L"\\Capabilities");
216 }
217
218 // This method returns a list of all the registry entries that
219 // are needed to register this installation's ProgId and AppId.
220 // These entries need to be registered in HKLM prior to Win8.
GetProgIdEntries(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix,ScopedVector<RegistryEntry> * entries)221 static void GetProgIdEntries(BrowserDistribution* dist,
222 const base::string16& chrome_exe,
223 const base::string16& suffix,
224 ScopedVector<RegistryEntry>* entries) {
225 base::string16 icon_path(
226 ShellUtil::FormatIconLocation(
227 chrome_exe,
228 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME)));
229 base::string16 open_cmd(ShellUtil::GetChromeShellOpenCmd(chrome_exe));
230 base::string16 delegate_command(
231 ShellUtil::GetChromeDelegateCommand(chrome_exe));
232 // For user-level installs: entries for the app id and DelegateExecute verb
233 // handler will be in HKCU; thus we do not need a suffix on those entries.
234 base::string16 app_id(
235 ShellUtil::GetBrowserModelId(
236 dist, InstallUtil::IsPerUserInstall(chrome_exe.c_str())));
237 base::string16 delegate_guid;
238 bool set_delegate_execute =
239 IsChromeMetroSupported() &&
240 dist->GetCommandExecuteImplClsid(&delegate_guid);
241
242 // DelegateExecute ProgId. Needed for Chrome Metro in Windows 8.
243 if (set_delegate_execute) {
244 base::string16 model_id_shell(ShellUtil::kRegClasses);
245 model_id_shell.push_back(base::FilePath::kSeparators[0]);
246 model_id_shell.append(app_id);
247 model_id_shell.append(ShellUtil::kRegExePath);
248 model_id_shell.append(ShellUtil::kRegShellPath);
249
250 // <root hkey>\Software\Classes\<app_id>\.exe\shell @=open
251 entries->push_back(new RegistryEntry(model_id_shell,
252 ShellUtil::kRegVerbOpen));
253
254 // Each of Chrome's shortcuts has an appid; which, as of Windows 8, is
255 // registered to handle some verbs. This registration has the side-effect
256 // that these verbs now show up in the shortcut's context menu. We
257 // mitigate this side-effect by making the context menu entries
258 // user readable/localized strings. See relevant MSDN article:
259 // http://msdn.microsoft.com/en-US/library/windows/desktop/cc144171.aspx
260 const struct {
261 const wchar_t* verb;
262 int name_id;
263 } verbs[] = {
264 { ShellUtil::kRegVerbOpen, -1 },
265 { ShellUtil::kRegVerbOpenNewWindow, IDS_SHORTCUT_NEW_WINDOW_BASE },
266 };
267 for (size_t i = 0; i < arraysize(verbs); ++i) {
268 base::string16 sub_path(model_id_shell);
269 sub_path.push_back(base::FilePath::kSeparators[0]);
270 sub_path.append(verbs[i].verb);
271
272 // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>
273 if (verbs[i].name_id != -1) {
274 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
275 // resource.
276 base::string16 verb_name(
277 installer::GetLocalizedString(verbs[i].name_id));
278 entries->push_back(new RegistryEntry(sub_path, verb_name.c_str()));
279 }
280 entries->push_back(new RegistryEntry(
281 sub_path, L"CommandId", L"Browser.Launch"));
282
283 sub_path.push_back(base::FilePath::kSeparators[0]);
284 sub_path.append(ShellUtil::kRegCommand);
285
286 // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>\command
287 entries->push_back(new RegistryEntry(sub_path, delegate_command));
288 entries->push_back(new RegistryEntry(
289 sub_path, ShellUtil::kRegDelegateExecute, delegate_guid));
290 }
291 }
292
293 // File association ProgId
294 base::string16 chrome_html_prog_id(ShellUtil::kRegClasses);
295 chrome_html_prog_id.push_back(base::FilePath::kSeparators[0]);
296 chrome_html_prog_id.append(GetBrowserProgId(suffix));
297 entries->push_back(new RegistryEntry(
298 chrome_html_prog_id, dist->GetBrowserProgIdDesc()));
299 entries->push_back(new RegistryEntry(
300 chrome_html_prog_id + ShellUtil::kRegDefaultIcon, icon_path));
301 entries->push_back(new RegistryEntry(
302 chrome_html_prog_id + ShellUtil::kRegShellOpen, open_cmd));
303 if (set_delegate_execute) {
304 entries->push_back(new RegistryEntry(
305 chrome_html_prog_id + ShellUtil::kRegShellOpen,
306 ShellUtil::kRegDelegateExecute, delegate_guid));
307 }
308
309 // The following entries are required as of Windows 8, but do not
310 // depend on the DelegateExecute verb handler being set.
311 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
312 entries->push_back(new RegistryEntry(
313 chrome_html_prog_id, ShellUtil::kRegAppUserModelId, app_id));
314
315 // Add \Software\Classes\ChromeHTML\Application entries
316 base::string16 chrome_application(chrome_html_prog_id +
317 ShellUtil::kRegApplication);
318 entries->push_back(new RegistryEntry(
319 chrome_application, ShellUtil::kRegAppUserModelId, app_id));
320 entries->push_back(new RegistryEntry(
321 chrome_application, ShellUtil::kRegApplicationIcon, icon_path));
322 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
323 // resource for name, description, and company.
324 entries->push_back(new RegistryEntry(
325 chrome_application, ShellUtil::kRegApplicationName,
326 dist->GetDisplayName()));
327 entries->push_back(new RegistryEntry(
328 chrome_application, ShellUtil::kRegApplicationDescription,
329 dist->GetAppDescription()));
330 entries->push_back(new RegistryEntry(
331 chrome_application, ShellUtil::kRegApplicationCompany,
332 dist->GetPublisherName()));
333 }
334 }
335
336 // This method returns a list of the registry entries needed to declare a
337 // capability of handling a protocol on Windows.
GetProtocolCapabilityEntries(BrowserDistribution * dist,const base::string16 & suffix,const base::string16 & protocol,ScopedVector<RegistryEntry> * entries)338 static void GetProtocolCapabilityEntries(
339 BrowserDistribution* dist,
340 const base::string16& suffix,
341 const base::string16& protocol,
342 ScopedVector<RegistryEntry>* entries) {
343 entries->push_back(new RegistryEntry(
344 GetCapabilitiesKey(dist, suffix).append(L"\\URLAssociations"),
345 protocol, GetBrowserProgId(suffix)));
346 }
347
348 // This method returns a list of the registry entries required to register
349 // this installation in "RegisteredApplications" on Windows (to appear in
350 // Default Programs, StartMenuInternet, etc.).
351 // These entries need to be registered in HKLM prior to Win8.
352 // If |suffix| is not empty, these entries are guaranteed to be unique on this
353 // machine.
GetShellIntegrationEntries(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix,ScopedVector<RegistryEntry> * entries)354 static void GetShellIntegrationEntries(BrowserDistribution* dist,
355 const base::string16& chrome_exe,
356 const base::string16& suffix,
357 ScopedVector<RegistryEntry>* entries) {
358 const base::string16 icon_path(
359 ShellUtil::FormatIconLocation(
360 chrome_exe,
361 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME)));
362 const base::string16 quoted_exe_path(L"\"" + chrome_exe + L"\"");
363
364 // Register for the Start Menu "Internet" link (pre-Win7).
365 const base::string16 start_menu_entry(GetBrowserClientKey(dist, suffix));
366 // Register Chrome's display name.
367 // TODO(grt): http://crbug.com/75152 Also set LocalizedString; see
368 // http://msdn.microsoft.com/en-us/library/windows/desktop/cc144109(v=VS.85).aspx#registering_the_display_name
369 entries->push_back(new RegistryEntry(
370 start_menu_entry, dist->GetDisplayName()));
371 // Register the "open" verb for launching Chrome via the "Internet" link.
372 entries->push_back(new RegistryEntry(
373 start_menu_entry + ShellUtil::kRegShellOpen, quoted_exe_path));
374 // Register Chrome's icon for the Start Menu "Internet" link.
375 entries->push_back(new RegistryEntry(
376 start_menu_entry + ShellUtil::kRegDefaultIcon, icon_path));
377
378 // Register installation information.
379 base::string16 install_info(start_menu_entry + L"\\InstallInfo");
380 // Note: not using CommandLine since it has ambiguous rules for quoting
381 // strings.
382 entries->push_back(new RegistryEntry(install_info, kReinstallCommand,
383 quoted_exe_path + L" --" +
384 base::ASCIIToWide(switches::kMakeDefaultBrowser)));
385 entries->push_back(new RegistryEntry(install_info, L"HideIconsCommand",
386 quoted_exe_path + L" --" +
387 base::ASCIIToWide(switches::kHideIcons)));
388 entries->push_back(new RegistryEntry(install_info, L"ShowIconsCommand",
389 quoted_exe_path + L" --" +
390 base::ASCIIToWide(switches::kShowIcons)));
391 entries->push_back(new RegistryEntry(install_info, L"IconsVisible", 1));
392
393 // Register with Default Programs.
394 const base::string16 reg_app_name(dist->GetBaseAppName().append(suffix));
395 // Tell Windows where to find Chrome's Default Programs info.
396 const base::string16 capabilities(GetCapabilitiesKey(dist, suffix));
397 entries->push_back(new RegistryEntry(ShellUtil::kRegRegisteredApplications,
398 reg_app_name, capabilities));
399 // Write out Chrome's Default Programs info.
400 // TODO(grt): http://crbug.com/75152 Write a reference to a localized
401 // resource rather than this.
402 entries->push_back(new RegistryEntry(
403 capabilities, ShellUtil::kRegApplicationDescription,
404 dist->GetLongAppDescription()));
405 entries->push_back(new RegistryEntry(
406 capabilities, ShellUtil::kRegApplicationIcon, icon_path));
407 entries->push_back(new RegistryEntry(
408 capabilities, ShellUtil::kRegApplicationName,
409 dist->GetDisplayName()));
410
411 entries->push_back(new RegistryEntry(capabilities + L"\\Startmenu",
412 L"StartMenuInternet", reg_app_name));
413
414 const base::string16 html_prog_id(GetBrowserProgId(suffix));
415 for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
416 entries->push_back(new RegistryEntry(
417 capabilities + L"\\FileAssociations",
418 ShellUtil::kPotentialFileAssociations[i], html_prog_id));
419 }
420 for (int i = 0; ShellUtil::kPotentialProtocolAssociations[i] != NULL;
421 i++) {
422 entries->push_back(new RegistryEntry(
423 capabilities + L"\\URLAssociations",
424 ShellUtil::kPotentialProtocolAssociations[i], html_prog_id));
425 }
426 }
427
428 // This method returns a list of the registry entries required for this
429 // installation to be registered in the Windows shell.
430 // In particular:
431 // - App Paths
432 // http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
433 // - File Associations
434 // http://msdn.microsoft.com/en-us/library/bb166549
435 // These entries need to be registered in HKLM prior to Win8.
GetAppRegistrationEntries(const base::string16 & chrome_exe,const base::string16 & suffix,ScopedVector<RegistryEntry> * entries)436 static void GetAppRegistrationEntries(const base::string16& chrome_exe,
437 const base::string16& suffix,
438 ScopedVector<RegistryEntry>* entries) {
439 const base::FilePath chrome_path(chrome_exe);
440 base::string16 app_path_key(ShellUtil::kAppPathsRegistryKey);
441 app_path_key.push_back(base::FilePath::kSeparators[0]);
442 app_path_key.append(chrome_path.BaseName().value());
443 entries->push_back(new RegistryEntry(app_path_key, chrome_exe));
444 entries->push_back(new RegistryEntry(app_path_key,
445 ShellUtil::kAppPathsRegistryPathName, chrome_path.DirName().value()));
446
447 const base::string16 html_prog_id(GetBrowserProgId(suffix));
448 for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) {
449 base::string16 key(ShellUtil::kRegClasses);
450 key.push_back(base::FilePath::kSeparators[0]);
451 key.append(ShellUtil::kPotentialFileAssociations[i]);
452 key.push_back(base::FilePath::kSeparators[0]);
453 key.append(ShellUtil::kRegOpenWithProgids);
454 entries->push_back(
455 new RegistryEntry(key, html_prog_id, base::string16()));
456 }
457 }
458
459 // This method returns a list of all the user level registry entries that
460 // are needed to make Chromium the default handler for a protocol on XP.
GetXPStyleUserProtocolEntries(const base::string16 & protocol,const base::string16 & chrome_icon,const base::string16 & chrome_open,ScopedVector<RegistryEntry> * entries)461 static void GetXPStyleUserProtocolEntries(
462 const base::string16& protocol,
463 const base::string16& chrome_icon,
464 const base::string16& chrome_open,
465 ScopedVector<RegistryEntry>* entries) {
466 // Protocols associations.
467 base::string16 url_key(ShellUtil::kRegClasses);
468 url_key.push_back(base::FilePath::kSeparators[0]);
469 url_key.append(protocol);
470
471 // This registry value tells Windows that this 'class' is a URL scheme
472 // so IE, explorer and other apps will route it to our handler.
473 // <root hkey>\Software\Classes\<protocol>\URL Protocol
474 entries->push_back(new RegistryEntry(url_key,
475 ShellUtil::kRegUrlProtocol, base::string16()));
476
477 // <root hkey>\Software\Classes\<protocol>\DefaultIcon
478 base::string16 icon_key = url_key + ShellUtil::kRegDefaultIcon;
479 entries->push_back(new RegistryEntry(icon_key, chrome_icon));
480
481 // <root hkey>\Software\Classes\<protocol>\shell\open\command
482 base::string16 shell_key = url_key + ShellUtil::kRegShellOpen;
483 entries->push_back(new RegistryEntry(shell_key, chrome_open));
484
485 // <root hkey>\Software\Classes\<protocol>\shell\open\ddeexec
486 base::string16 dde_key = url_key + L"\\shell\\open\\ddeexec";
487 entries->push_back(new RegistryEntry(dde_key, base::string16()));
488
489 // <root hkey>\Software\Classes\<protocol>\shell\@
490 base::string16 protocol_shell_key = url_key + ShellUtil::kRegShellPath;
491 entries->push_back(new RegistryEntry(protocol_shell_key, L"open"));
492 }
493
494 // This method returns a list of all the user level registry entries that
495 // are needed to make Chromium default browser on XP.
496 // Some of these entries are irrelevant in recent versions of Windows, but
497 // we register them anyways as some legacy apps are hardcoded to lookup those
498 // values.
GetXPStyleDefaultBrowserUserEntries(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix,ScopedVector<RegistryEntry> * entries)499 static void GetXPStyleDefaultBrowserUserEntries(
500 BrowserDistribution* dist,
501 const base::string16& chrome_exe,
502 const base::string16& suffix,
503 ScopedVector<RegistryEntry>* entries) {
504 // File extension associations.
505 base::string16 html_prog_id(GetBrowserProgId(suffix));
506 for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
507 base::string16 ext_key(ShellUtil::kRegClasses);
508 ext_key.push_back(base::FilePath::kSeparators[0]);
509 ext_key.append(ShellUtil::kDefaultFileAssociations[i]);
510 entries->push_back(new RegistryEntry(ext_key, html_prog_id));
511 }
512
513 // Protocols associations.
514 base::string16 chrome_open = ShellUtil::GetChromeShellOpenCmd(chrome_exe);
515 base::string16 chrome_icon =
516 ShellUtil::FormatIconLocation(
517 chrome_exe,
518 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
519 for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
520 GetXPStyleUserProtocolEntries(ShellUtil::kBrowserProtocolAssociations[i],
521 chrome_icon, chrome_open, entries);
522 }
523
524 // start->Internet shortcut.
525 base::string16 start_menu(ShellUtil::kRegStartMenuInternet);
526 base::string16 app_name = dist->GetBaseAppName() + suffix;
527 entries->push_back(new RegistryEntry(start_menu, app_name));
528 }
529
530 // Generate work_item tasks required to create current registry entry and
531 // add them to the given work item list.
AddToWorkItemList(HKEY root,WorkItemList * items) const532 void AddToWorkItemList(HKEY root, WorkItemList *items) const {
533 items->AddCreateRegKeyWorkItem(root, key_path_, WorkItem::kWow64Default);
534 if (is_string_) {
535 items->AddSetRegValueWorkItem(
536 root, key_path_, WorkItem::kWow64Default, name_, value_, true);
537 } else {
538 items->AddSetRegValueWorkItem(
539 root, key_path_, WorkItem::kWow64Default, name_, int_value_, true);
540 }
541 }
542
543 // Checks if the current registry entry exists in HKCU\|key_path_|\|name_|
544 // and value is |value_|. If the key does NOT exist in HKCU, checks for
545 // the correct name and value in HKLM.
546 // |look_for_in| specifies roots (HKCU and/or HKLM) in which to look for the
547 // key, unspecified roots are not looked into (i.e. the the key is assumed not
548 // to exist in them).
549 // |look_for_in| must at least specify one root to look into.
550 // If |look_for_in| is LOOK_IN_HKCU_THEN_HKLM, this method mimics Windows'
551 // behavior when searching in HKCR (HKCU takes precedence over HKLM). For
552 // registrations outside of HKCR on versions of Windows prior to Win8,
553 // Chrome's values go in HKLM. This function will make unnecessary (but
554 // harmless) queries into HKCU in that case.
ExistsInRegistry(uint32 look_for_in) const555 bool ExistsInRegistry(uint32 look_for_in) const {
556 DCHECK(look_for_in);
557
558 RegistryStatus status = DOES_NOT_EXIST;
559 if (look_for_in & LOOK_IN_HKCU)
560 status = StatusInRegistryUnderRoot(HKEY_CURRENT_USER);
561 if (status == DOES_NOT_EXIST && (look_for_in & LOOK_IN_HKLM))
562 status = StatusInRegistryUnderRoot(HKEY_LOCAL_MACHINE);
563 return status == SAME_VALUE;
564 }
565
566 private:
567 // States this RegistryKey can be in compared to the registry.
568 enum RegistryStatus {
569 // |name_| does not exist in the registry
570 DOES_NOT_EXIST,
571 // |name_| exists, but its value != |value_|
572 DIFFERENT_VALUE,
573 // |name_| exists and its value is |value_|
574 SAME_VALUE,
575 };
576
577 // Create a object that represent default value of a key
RegistryEntry(const base::string16 & key_path,const base::string16 & value)578 RegistryEntry(const base::string16& key_path, const base::string16& value)
579 : key_path_(key_path), name_(),
580 is_string_(true), value_(value), int_value_(0) {
581 }
582
583 // Create a object that represent a key of type REG_SZ
RegistryEntry(const base::string16 & key_path,const base::string16 & name,const base::string16 & value)584 RegistryEntry(const base::string16& key_path, const base::string16& name,
585 const base::string16& value)
586 : key_path_(key_path), name_(name),
587 is_string_(true), value_(value), int_value_(0) {
588 }
589
590 // Create a object that represent a key of integer type
RegistryEntry(const base::string16 & key_path,const base::string16 & name,DWORD value)591 RegistryEntry(const base::string16& key_path, const base::string16& name,
592 DWORD value)
593 : key_path_(key_path), name_(name),
594 is_string_(false), value_(), int_value_(value) {
595 }
596
597 base::string16 key_path_; // key path for the registry entry
598 base::string16 name_; // name of the registry entry
599 bool is_string_; // true if current registry entry is of type REG_SZ
600 base::string16 value_; // string value (useful if is_string_ = true)
601 DWORD int_value_; // integer value (useful if is_string_ = false)
602
603 // Helper function for ExistsInRegistry().
604 // Returns the RegistryStatus of the current registry entry in
605 // |root|\|key_path_|\|name_|.
StatusInRegistryUnderRoot(HKEY root) const606 RegistryStatus StatusInRegistryUnderRoot(HKEY root) const {
607 RegKey key(root, key_path_.c_str(), KEY_QUERY_VALUE);
608 bool found = false;
609 bool correct_value = false;
610 if (is_string_) {
611 base::string16 read_value;
612 found = key.ReadValue(name_.c_str(), &read_value) == ERROR_SUCCESS;
613 if (found) {
614 correct_value = read_value.size() == value_.size() &&
615 std::equal(value_.begin(), value_.end(), read_value.begin(),
616 base::CaseInsensitiveCompare<wchar_t>());
617 }
618 } else {
619 DWORD read_value;
620 found = key.ReadValueDW(name_.c_str(), &read_value) == ERROR_SUCCESS;
621 if (found)
622 correct_value = read_value == int_value_;
623 }
624 return found ?
625 (correct_value ? SAME_VALUE : DIFFERENT_VALUE) : DOES_NOT_EXIST;
626 }
627
628 DISALLOW_COPY_AND_ASSIGN(RegistryEntry);
629 }; // class RegistryEntry
630
631
632 // This method converts all the RegistryEntries from the given list to
633 // Set/CreateRegWorkItems and runs them using WorkItemList.
AddRegistryEntries(HKEY root,const ScopedVector<RegistryEntry> & entries)634 bool AddRegistryEntries(HKEY root, const ScopedVector<RegistryEntry>& entries) {
635 scoped_ptr<WorkItemList> items(WorkItem::CreateWorkItemList());
636
637 for (ScopedVector<RegistryEntry>::const_iterator itr = entries.begin();
638 itr != entries.end(); ++itr)
639 (*itr)->AddToWorkItemList(root, items.get());
640
641 // Apply all the registry changes and if there is a problem, rollback
642 if (!items->Do()) {
643 items->Rollback();
644 return false;
645 }
646 return true;
647 }
648
649 // Checks that all |entries| are present on this computer.
650 // |look_for_in| is passed to RegistryEntry::ExistsInRegistry(). Documentation
651 // for it can be found there.
AreEntriesRegistered(const ScopedVector<RegistryEntry> & entries,uint32 look_for_in)652 bool AreEntriesRegistered(const ScopedVector<RegistryEntry>& entries,
653 uint32 look_for_in) {
654 bool registered = true;
655 for (ScopedVector<RegistryEntry>::const_iterator itr = entries.begin();
656 registered && itr != entries.end(); ++itr) {
657 // We do not need registered = registered && ... since the loop condition
658 // is set to exit early.
659 registered = (*itr)->ExistsInRegistry(look_for_in);
660 }
661 return registered;
662 }
663
664 // Checks that all required registry entries for Chrome are already present
665 // on this computer. See RegistryEntry::ExistsInRegistry for the behavior of
666 // |look_for_in|.
667 // Note: between r133333 and r154145 we were registering parts of Chrome in HKCU
668 // and parts in HKLM for user-level installs; we now always register everything
669 // under a single registry root. Not doing so caused http://crbug.com/144910 for
670 // users who first-installed Chrome in that revision range (those users are
671 // still impacted by http://crbug.com/144910). This method will keep returning
672 // true for affected users (i.e. who have all the registrations, but over both
673 // registry roots).
IsChromeRegistered(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix,uint32 look_for_in)674 bool IsChromeRegistered(BrowserDistribution* dist,
675 const base::string16& chrome_exe,
676 const base::string16& suffix,
677 uint32 look_for_in) {
678 ScopedVector<RegistryEntry> entries;
679 RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries);
680 RegistryEntry::GetShellIntegrationEntries(dist, chrome_exe, suffix, &entries);
681 RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, &entries);
682 return AreEntriesRegistered(entries, look_for_in);
683 }
684
685 // This method checks if Chrome is already registered on the local machine
686 // for the requested protocol. It just checks the one value required for this.
687 // See RegistryEntry::ExistsInRegistry for the behavior of |look_for_in|.
IsChromeRegisteredForProtocol(BrowserDistribution * dist,const base::string16 & suffix,const base::string16 & protocol,uint32 look_for_in)688 bool IsChromeRegisteredForProtocol(BrowserDistribution* dist,
689 const base::string16& suffix,
690 const base::string16& protocol,
691 uint32 look_for_in) {
692 ScopedVector<RegistryEntry> entries;
693 RegistryEntry::GetProtocolCapabilityEntries(dist, suffix, protocol, &entries);
694 return AreEntriesRegistered(entries, look_for_in);
695 }
696
697 // This method registers Chrome on Vista by launching an elevated setup.exe.
698 // That will show the user the standard Vista elevation prompt. If the user
699 // accepts it the new process will make the necessary changes and return SUCCESS
700 // that we capture and return.
701 // If protocol is non-empty we will also register Chrome as being capable of
702 // handling the protocol.
ElevateAndRegisterChrome(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix,const base::string16 & protocol)703 bool ElevateAndRegisterChrome(BrowserDistribution* dist,
704 const base::string16& chrome_exe,
705 const base::string16& suffix,
706 const base::string16& protocol) {
707 // Only user-level installs prior to Windows 8 should need to elevate to
708 // register.
709 DCHECK(InstallUtil::IsPerUserInstall(chrome_exe.c_str()));
710 DCHECK_LT(base::win::GetVersion(), base::win::VERSION_WIN8);
711 base::FilePath exe_path =
712 base::FilePath(chrome_exe).DirName().Append(installer::kSetupExe);
713 if (!base::PathExists(exe_path)) {
714 HKEY reg_root = InstallUtil::IsPerUserInstall(chrome_exe.c_str()) ?
715 HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
716 RegKey key(reg_root,
717 dist->GetUninstallRegPath().c_str(),
718 KEY_READ | KEY_WOW64_32KEY);
719 base::string16 uninstall_string;
720 key.ReadValue(installer::kUninstallStringField, &uninstall_string);
721 CommandLine command_line = CommandLine::FromString(uninstall_string);
722 exe_path = command_line.GetProgram();
723 }
724
725 if (base::PathExists(exe_path)) {
726 CommandLine cmd(exe_path);
727 cmd.AppendSwitchNative(installer::switches::kRegisterChromeBrowser,
728 chrome_exe);
729 if (!suffix.empty()) {
730 cmd.AppendSwitchNative(
731 installer::switches::kRegisterChromeBrowserSuffix, suffix);
732 }
733
734 if (!protocol.empty()) {
735 cmd.AppendSwitchNative(
736 installer::switches::kRegisterURLProtocol, protocol);
737 }
738
739 DWORD ret_val = 0;
740 InstallUtil::ExecuteExeAsAdmin(cmd, &ret_val);
741 if (ret_val == 0)
742 return true;
743 }
744 return false;
745 }
746
747 // Launches the Windows 7 and Windows 8 dialog for picking the application to
748 // handle the given protocol. Most importantly, this is used to set the default
749 // handler for http (and, implicitly with it, https). In that case it is also
750 // known as the 'how do you want to open webpages' dialog.
751 // It is required that Chrome be already *registered* for the given protocol.
LaunchSelectDefaultProtocolHandlerDialog(const wchar_t * protocol)752 bool LaunchSelectDefaultProtocolHandlerDialog(const wchar_t* protocol) {
753 DCHECK(protocol);
754 OPENASINFO open_as_info = {};
755 open_as_info.pcszFile = protocol;
756 open_as_info.oaifInFlags =
757 OAIF_URL_PROTOCOL | OAIF_FORCE_REGISTRATION | OAIF_REGISTER_EXT;
758 HRESULT hr = SHOpenWithDialog(NULL, &open_as_info);
759 DLOG_IF(WARNING, FAILED(hr)) << "Failed to set as default " << protocol
760 << " handler; hr=0x" << std::hex << hr;
761 if (FAILED(hr))
762 return false;
763 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
764 return true;
765 }
766
767 // Launches the Windows 7 and Windows 8 application association dialog, which
768 // is the only documented way to make a browser the default browser on
769 // Windows 8.
LaunchApplicationAssociationDialog(const base::string16 & app_id)770 bool LaunchApplicationAssociationDialog(const base::string16& app_id) {
771 base::win::ScopedComPtr<IApplicationAssociationRegistrationUI> aarui;
772 HRESULT hr = aarui.CreateInstance(CLSID_ApplicationAssociationRegistrationUI);
773 if (FAILED(hr))
774 return false;
775 hr = aarui->LaunchAdvancedAssociationUI(app_id.c_str());
776 return SUCCEEDED(hr);
777 }
778
779 // Returns true if the current install's |chrome_exe| has been registered with
780 // |suffix|.
781 // |confirmation_level| is the level of verification desired as described in
782 // the RegistrationConfirmationLevel enum above.
783 // |suffix| can be the empty string (this is used to support old installs
784 // where we used to not suffix user-level installs if they were the first to
785 // request the non-suffixed registry entries on the machine).
786 // NOTE: This a quick check that only validates that a single registry entry
787 // points to |chrome_exe|. This should only be used at run-time to determine
788 // how Chrome is registered, not to know whether the registration is complete
789 // at install-time (IsChromeRegistered() can be used for that).
QuickIsChromeRegistered(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix,RegistrationConfirmationLevel confirmation_level)790 bool QuickIsChromeRegistered(BrowserDistribution* dist,
791 const base::string16& chrome_exe,
792 const base::string16& suffix,
793 RegistrationConfirmationLevel confirmation_level) {
794 // Get the appropriate key to look for based on the level desired.
795 base::string16 reg_key;
796 switch (confirmation_level) {
797 case CONFIRM_PROGID_REGISTRATION:
798 // Software\Classes\ChromeHTML|suffix|
799 reg_key = ShellUtil::kRegClasses;
800 reg_key.push_back(base::FilePath::kSeparators[0]);
801 reg_key.append(dist->GetBrowserProgIdPrefix());
802 reg_key.append(suffix);
803 break;
804 case CONFIRM_SHELL_REGISTRATION:
805 case CONFIRM_SHELL_REGISTRATION_IN_HKLM:
806 // Software\Clients\StartMenuInternet\Google Chrome|suffix|
807 reg_key = RegistryEntry::GetBrowserClientKey(dist, suffix);
808 break;
809 default:
810 NOTREACHED();
811 break;
812 }
813 reg_key.append(ShellUtil::kRegShellOpen);
814
815 // ProgId registrations are allowed to reside in HKCU for user-level installs
816 // (and values there have priority over values in HKLM). The same is true for
817 // shell integration entries as of Windows 8.
818 if (confirmation_level == CONFIRM_PROGID_REGISTRATION ||
819 (confirmation_level == CONFIRM_SHELL_REGISTRATION &&
820 base::win::GetVersion() >= base::win::VERSION_WIN8)) {
821 const RegKey key_hkcu(HKEY_CURRENT_USER, reg_key.c_str(), KEY_QUERY_VALUE);
822 base::string16 hkcu_value;
823 // If |reg_key| is present in HKCU, assert that it points to |chrome_exe|.
824 // Otherwise, fall back on an HKLM lookup below.
825 if (key_hkcu.ReadValue(L"", &hkcu_value) == ERROR_SUCCESS) {
826 return InstallUtil::ProgramCompare(
827 base::FilePath(chrome_exe)).Evaluate(hkcu_value);
828 }
829 }
830
831 // Assert that |reg_key| points to |chrome_exe| in HKLM.
832 const RegKey key_hklm(HKEY_LOCAL_MACHINE, reg_key.c_str(), KEY_QUERY_VALUE);
833 base::string16 hklm_value;
834 if (key_hklm.ReadValue(L"", &hklm_value) == ERROR_SUCCESS) {
835 return InstallUtil::ProgramCompare(
836 base::FilePath(chrome_exe)).Evaluate(hklm_value);
837 }
838 return false;
839 }
840
841 // Sets |suffix| to a 27 character string that is specific to this user on this
842 // machine (on user-level installs only).
843 // To support old-style user-level installs however, |suffix| is cleared if the
844 // user currently owns the non-suffixed HKLM registrations.
845 // |suffix| can also be set to the user's username if the current install is
846 // suffixed as per the old-style registrations.
847 // |suffix| is cleared on system-level installs.
848 // |suffix| should then be appended to all Chrome properties that may conflict
849 // with other Chrome user-level installs.
850 // Returns true unless one of the underlying calls fails.
GetInstallationSpecificSuffix(BrowserDistribution * dist,const base::string16 & chrome_exe,base::string16 * suffix)851 bool GetInstallationSpecificSuffix(BrowserDistribution* dist,
852 const base::string16& chrome_exe,
853 base::string16* suffix) {
854 if (!InstallUtil::IsPerUserInstall(chrome_exe.c_str()) ||
855 QuickIsChromeRegistered(dist, chrome_exe, base::string16(),
856 CONFIRM_SHELL_REGISTRATION)) {
857 // No suffix on system-level installs and user-level installs already
858 // registered with no suffix.
859 suffix->clear();
860 return true;
861 }
862
863 // Get the old suffix for the check below.
864 if (!ShellUtil::GetOldUserSpecificRegistrySuffix(suffix)) {
865 NOTREACHED();
866 return false;
867 }
868 if (QuickIsChromeRegistered(dist, chrome_exe, *suffix,
869 CONFIRM_SHELL_REGISTRATION)) {
870 // Username suffix for installs that are suffixed as per the old-style.
871 return true;
872 }
873
874 return ShellUtil::GetUserSpecificRegistrySuffix(suffix);
875 }
876
877 // Returns the root registry key (HKLM or HKCU) under which registrations must
878 // be placed for this install. As of Windows 8 everything can go in HKCU for
879 // per-user installs.
DetermineRegistrationRoot(bool is_per_user)880 HKEY DetermineRegistrationRoot(bool is_per_user) {
881 return is_per_user && base::win::GetVersion() >= base::win::VERSION_WIN8 ?
882 HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
883 }
884
885 // Associates Chrome with supported protocols and file associations. This should
886 // not be required on Vista+ but since some applications still read
887 // Software\Classes\http key directly, we have to do this on Vista+ as well.
RegisterChromeAsDefaultXPStyle(BrowserDistribution * dist,int shell_change,const base::string16 & chrome_exe)888 bool RegisterChromeAsDefaultXPStyle(BrowserDistribution* dist,
889 int shell_change,
890 const base::string16& chrome_exe) {
891 bool ret = true;
892 ScopedVector<RegistryEntry> entries;
893 RegistryEntry::GetXPStyleDefaultBrowserUserEntries(
894 dist, chrome_exe,
895 ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe), &entries);
896
897 // Change the default browser for current user.
898 if ((shell_change & ShellUtil::CURRENT_USER) &&
899 !AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
900 ret = false;
901 LOG(ERROR) << "Could not make Chrome default browser (XP/current user).";
902 }
903
904 // Chrome as default browser at system level.
905 if ((shell_change & ShellUtil::SYSTEM_LEVEL) &&
906 !AddRegistryEntries(HKEY_LOCAL_MACHINE, entries)) {
907 ret = false;
908 LOG(ERROR) << "Could not make Chrome default browser (XP/system level).";
909 }
910
911 return ret;
912 }
913
914 // Associates Chrome with |protocol| in the registry. This should not be
915 // required on Vista+ but since some applications still read these registry
916 // keys directly, we have to do this on Vista+ as well.
917 // See http://msdn.microsoft.com/library/aa767914.aspx for more details.
RegisterChromeAsDefaultProtocolClientXPStyle(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & protocol)918 bool RegisterChromeAsDefaultProtocolClientXPStyle(
919 BrowserDistribution* dist,
920 const base::string16& chrome_exe,
921 const base::string16& protocol) {
922 ScopedVector<RegistryEntry> entries;
923 const base::string16 chrome_open(
924 ShellUtil::GetChromeShellOpenCmd(chrome_exe));
925 const base::string16 chrome_icon(
926 ShellUtil::FormatIconLocation(
927 chrome_exe,
928 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME)));
929 RegistryEntry::GetXPStyleUserProtocolEntries(protocol, chrome_icon,
930 chrome_open, &entries);
931 // Change the default protocol handler for current user.
932 if (!AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
933 LOG(ERROR) << "Could not make Chrome default protocol client (XP).";
934 return false;
935 }
936
937 return true;
938 }
939
940 // Returns |properties.shortcut_name| if the property is set, otherwise it
941 // returns dist->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME). In any
942 // case, it makes sure the return value is suffixed with ".lnk".
ExtractShortcutNameFromProperties(BrowserDistribution * dist,const ShellUtil::ShortcutProperties & properties)943 base::string16 ExtractShortcutNameFromProperties(
944 BrowserDistribution* dist,
945 const ShellUtil::ShortcutProperties& properties) {
946 DCHECK(dist);
947 base::string16 shortcut_name;
948 if (properties.has_shortcut_name()) {
949 shortcut_name = properties.shortcut_name;
950 } else {
951 shortcut_name =
952 dist->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME);
953 }
954
955 if (!EndsWith(shortcut_name, installer::kLnkExt, false))
956 shortcut_name.append(installer::kLnkExt);
957
958 return shortcut_name;
959 }
960
961 // Converts ShellUtil::ShortcutOperation to the best-matching value in
962 // base::win::ShortcutOperation.
TranslateShortcutOperation(ShellUtil::ShortcutOperation operation)963 base::win::ShortcutOperation TranslateShortcutOperation(
964 ShellUtil::ShortcutOperation operation) {
965 switch (operation) {
966 case ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS: // Falls through.
967 case ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL:
968 return base::win::SHORTCUT_CREATE_ALWAYS;
969
970 case ShellUtil::SHELL_SHORTCUT_UPDATE_EXISTING:
971 return base::win::SHORTCUT_UPDATE_EXISTING;
972
973 case ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING:
974 return base::win::SHORTCUT_REPLACE_EXISTING;
975
976 default:
977 NOTREACHED();
978 return base::win::SHORTCUT_REPLACE_EXISTING;
979 }
980 }
981
982 // Returns a base::win::ShortcutProperties struct containing the properties
983 // to set on the shortcut based on the provided ShellUtil::ShortcutProperties.
TranslateShortcutProperties(const ShellUtil::ShortcutProperties & properties)984 base::win::ShortcutProperties TranslateShortcutProperties(
985 const ShellUtil::ShortcutProperties& properties) {
986 base::win::ShortcutProperties shortcut_properties;
987
988 if (properties.has_target()) {
989 shortcut_properties.set_target(properties.target);
990 DCHECK(!properties.target.DirName().empty());
991 shortcut_properties.set_working_dir(properties.target.DirName());
992 }
993
994 if (properties.has_arguments())
995 shortcut_properties.set_arguments(properties.arguments);
996
997 if (properties.has_description())
998 shortcut_properties.set_description(properties.description);
999
1000 if (properties.has_icon())
1001 shortcut_properties.set_icon(properties.icon, properties.icon_index);
1002
1003 if (properties.has_app_id())
1004 shortcut_properties.set_app_id(properties.app_id);
1005
1006 if (properties.has_dual_mode())
1007 shortcut_properties.set_dual_mode(properties.dual_mode);
1008
1009 return shortcut_properties;
1010 }
1011
1012 // Cleans up an old verb (run) we used to register in
1013 // <root>\Software\Classes\Chrome<.suffix>\.exe\shell\run on Windows 8.
RemoveRunVerbOnWindows8(BrowserDistribution * dist,const base::string16 & chrome_exe)1014 void RemoveRunVerbOnWindows8(BrowserDistribution* dist,
1015 const base::string16& chrome_exe) {
1016 if (IsChromeMetroSupported()) {
1017 bool is_per_user_install =InstallUtil::IsPerUserInstall(chrome_exe.c_str());
1018 HKEY root_key = DetermineRegistrationRoot(is_per_user_install);
1019 // There's no need to rollback, so forgo the usual work item lists and just
1020 // remove the key from the registry.
1021 base::string16 run_verb_key(ShellUtil::kRegClasses);
1022 run_verb_key.push_back(base::FilePath::kSeparators[0]);
1023 run_verb_key.append(ShellUtil::GetBrowserModelId(
1024 dist, is_per_user_install));
1025 run_verb_key.append(ShellUtil::kRegExePath);
1026 run_verb_key.append(ShellUtil::kRegShellPath);
1027 run_verb_key.push_back(base::FilePath::kSeparators[0]);
1028 run_verb_key.append(ShellUtil::kRegVerbRun);
1029 InstallUtil::DeleteRegistryKey(root_key, run_verb_key,
1030 WorkItem::kWow64Default);
1031 }
1032 }
1033
1034 // Gets the short (8.3) form of |path|, putting the result in |short_path| and
1035 // returning true on success. |short_path| is not modified on failure.
ShortNameFromPath(const base::FilePath & path,base::string16 * short_path)1036 bool ShortNameFromPath(const base::FilePath& path, base::string16* short_path) {
1037 DCHECK(short_path);
1038 base::string16 result(MAX_PATH, L'\0');
1039 DWORD short_length = GetShortPathName(path.value().c_str(), &result[0],
1040 result.size());
1041 if (short_length == 0 || short_length > result.size()) {
1042 PLOG(ERROR) << "Error getting short (8.3) path";
1043 return false;
1044 }
1045
1046 result.resize(short_length);
1047 short_path->swap(result);
1048 return true;
1049 }
1050
1051 // Probe using IApplicationAssociationRegistration::QueryCurrentDefault
1052 // (Windows 8); see ProbeProtocolHandlers. This mechanism is not suitable for
1053 // use on previous versions of Windows despite the presence of
1054 // QueryCurrentDefault on them since versions of Windows prior to Windows 8
1055 // did not perform validation on the ProgID registered as the current default.
1056 // As a result, stale ProgIDs could be returned, leading to false positives.
ProbeCurrentDefaultHandlers(const base::FilePath & chrome_exe,const wchar_t * const * protocols,size_t num_protocols)1057 ShellUtil::DefaultState ProbeCurrentDefaultHandlers(
1058 const base::FilePath& chrome_exe,
1059 const wchar_t* const* protocols,
1060 size_t num_protocols) {
1061 base::win::ScopedComPtr<IApplicationAssociationRegistration> registration;
1062 HRESULT hr = registration.CreateInstance(
1063 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC);
1064 if (FAILED(hr))
1065 return ShellUtil::UNKNOWN_DEFAULT;
1066
1067 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
1068 base::string16 prog_id(dist->GetBrowserProgIdPrefix());
1069 prog_id += ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe.value());
1070
1071 for (size_t i = 0; i < num_protocols; ++i) {
1072 base::win::ScopedCoMem<wchar_t> current_app;
1073 hr = registration->QueryCurrentDefault(protocols[i], AT_URLPROTOCOL,
1074 AL_EFFECTIVE, ¤t_app);
1075 if (FAILED(hr) || prog_id.compare(current_app) != 0)
1076 return ShellUtil::NOT_DEFAULT;
1077 }
1078
1079 return ShellUtil::IS_DEFAULT;
1080 }
1081
1082 // Probe using IApplicationAssociationRegistration::QueryAppIsDefault (Vista and
1083 // Windows 7); see ProbeProtocolHandlers.
ProbeAppIsDefaultHandlers(const base::FilePath & chrome_exe,const wchar_t * const * protocols,size_t num_protocols)1084 ShellUtil::DefaultState ProbeAppIsDefaultHandlers(
1085 const base::FilePath& chrome_exe,
1086 const wchar_t* const* protocols,
1087 size_t num_protocols) {
1088 base::win::ScopedComPtr<IApplicationAssociationRegistration> registration;
1089 HRESULT hr = registration.CreateInstance(
1090 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC);
1091 if (FAILED(hr))
1092 return ShellUtil::UNKNOWN_DEFAULT;
1093
1094 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
1095 base::string16 app_name(
1096 ShellUtil::GetApplicationName(dist, chrome_exe.value()));
1097
1098 BOOL result;
1099 for (size_t i = 0; i < num_protocols; ++i) {
1100 result = TRUE;
1101 hr = registration->QueryAppIsDefault(protocols[i], AT_URLPROTOCOL,
1102 AL_EFFECTIVE, app_name.c_str(), &result);
1103 if (FAILED(hr) || result == FALSE)
1104 return ShellUtil::NOT_DEFAULT;
1105 }
1106
1107 return ShellUtil::IS_DEFAULT;
1108 }
1109
1110 // Probe the current commands registered to handle the shell "open" verb for
1111 // |protocols| (Windows XP); see ProbeProtocolHandlers.
ProbeOpenCommandHandlers(const base::FilePath & chrome_exe,const wchar_t * const * protocols,size_t num_protocols)1112 ShellUtil::DefaultState ProbeOpenCommandHandlers(
1113 const base::FilePath& chrome_exe,
1114 const wchar_t* const* protocols,
1115 size_t num_protocols) {
1116 // Get its short (8.3) form.
1117 base::string16 short_app_path;
1118 if (!ShortNameFromPath(chrome_exe, &short_app_path))
1119 return ShellUtil::UNKNOWN_DEFAULT;
1120
1121 const HKEY root_key = HKEY_CLASSES_ROOT;
1122 base::string16 key_path;
1123 base::win::RegKey key;
1124 base::string16 value;
1125 CommandLine command_line(CommandLine::NO_PROGRAM);
1126 base::string16 short_path;
1127
1128 for (size_t i = 0; i < num_protocols; ++i) {
1129 // Get the command line from HKCU\<protocol>\shell\open\command.
1130 key_path.assign(protocols[i]).append(ShellUtil::kRegShellOpen);
1131 if ((key.Open(root_key, key_path.c_str(),
1132 KEY_QUERY_VALUE) != ERROR_SUCCESS) ||
1133 (key.ReadValue(L"", &value) != ERROR_SUCCESS)) {
1134 return ShellUtil::NOT_DEFAULT;
1135 }
1136
1137 // Need to normalize path in case it's been munged.
1138 command_line = CommandLine::FromString(value);
1139 if (!ShortNameFromPath(command_line.GetProgram(), &short_path))
1140 return ShellUtil::UNKNOWN_DEFAULT;
1141
1142 if (!base::FilePath::CompareEqualIgnoreCase(short_path, short_app_path))
1143 return ShellUtil::NOT_DEFAULT;
1144 }
1145
1146 return ShellUtil::IS_DEFAULT;
1147 }
1148
1149 // A helper function that probes default protocol handler registration (in a
1150 // manner appropriate for the current version of Windows) to determine if
1151 // Chrome is the default handler for |protocols|. Returns IS_DEFAULT
1152 // only if Chrome is the default for all specified protocols.
ProbeProtocolHandlers(const base::FilePath & chrome_exe,const wchar_t * const * protocols,size_t num_protocols)1153 ShellUtil::DefaultState ProbeProtocolHandlers(
1154 const base::FilePath& chrome_exe,
1155 const wchar_t* const* protocols,
1156 size_t num_protocols) {
1157 #if DCHECK_IS_ON
1158 DCHECK(!num_protocols || protocols);
1159 for (size_t i = 0; i < num_protocols; ++i)
1160 DCHECK(protocols[i] && *protocols[i]);
1161 #endif
1162
1163 const base::win::Version windows_version = base::win::GetVersion();
1164
1165 if (windows_version >= base::win::VERSION_WIN8)
1166 return ProbeCurrentDefaultHandlers(chrome_exe, protocols, num_protocols);
1167 else if (windows_version >= base::win::VERSION_VISTA)
1168 return ProbeAppIsDefaultHandlers(chrome_exe, protocols, num_protocols);
1169
1170 return ProbeOpenCommandHandlers(chrome_exe, protocols, num_protocols);
1171 }
1172
1173 // (Windows 8+) Finds and stores an app shortcuts folder path in *|path|.
1174 // Returns true on success.
GetAppShortcutsFolder(BrowserDistribution * dist,ShellUtil::ShellChange level,base::FilePath * path)1175 bool GetAppShortcutsFolder(BrowserDistribution* dist,
1176 ShellUtil::ShellChange level,
1177 base::FilePath *path) {
1178 DCHECK(path);
1179 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1180
1181 base::FilePath folder;
1182 if (!PathService::Get(base::DIR_APP_SHORTCUTS, &folder)) {
1183 LOG(ERROR) << "Could not get application shortcuts location.";
1184 return false;
1185 }
1186
1187 folder = folder.Append(
1188 ShellUtil::GetBrowserModelId(dist, level == ShellUtil::CURRENT_USER));
1189 if (!base::DirectoryExists(folder)) {
1190 VLOG(1) << "No start screen shortcuts.";
1191 return false;
1192 }
1193
1194 *path = folder;
1195 return true;
1196 }
1197
1198 // Shortcut filters for BatchShortcutAction().
1199
1200 typedef base::Callback<bool(const base::FilePath& /*shortcut_path*/,
1201 const base::string16& /*args*/)>
1202 ShortcutFilterCallback;
1203
1204 // FilterTargetEq is a shortcut filter that matches only shortcuts that have a
1205 // specific target, and optionally matches shortcuts that have non-empty
1206 // arguments.
1207 class FilterTargetEq {
1208 public:
1209 FilterTargetEq(const base::FilePath& desired_target_exe, bool require_args);
1210
1211 // Returns true if filter rules are satisfied, i.e.:
1212 // - |target_path|'s target == |desired_target_compare_|, and
1213 // - |args| is non-empty (if |require_args_| == true).
1214 bool Match(const base::FilePath& target_path,
1215 const base::string16& args) const;
1216
1217 // A convenience routine to create a callback to call Match().
1218 // The callback is only valid during the lifetime of the FilterTargetEq
1219 // instance.
1220 ShortcutFilterCallback AsShortcutFilterCallback();
1221
1222 private:
1223 InstallUtil::ProgramCompare desired_target_compare_;
1224
1225 bool require_args_;
1226 };
1227
FilterTargetEq(const base::FilePath & desired_target_exe,bool require_args)1228 FilterTargetEq::FilterTargetEq(const base::FilePath& desired_target_exe,
1229 bool require_args)
1230 : desired_target_compare_(desired_target_exe),
1231 require_args_(require_args) {}
1232
Match(const base::FilePath & target_path,const base::string16 & args) const1233 bool FilterTargetEq::Match(const base::FilePath& target_path,
1234 const base::string16& args) const {
1235 if (!desired_target_compare_.EvaluatePath(target_path))
1236 return false;
1237 if (require_args_ && args.empty())
1238 return false;
1239 return true;
1240 }
1241
AsShortcutFilterCallback()1242 ShortcutFilterCallback FilterTargetEq::AsShortcutFilterCallback() {
1243 return base::Bind(&FilterTargetEq::Match, base::Unretained(this));
1244 }
1245
1246 // Shortcut operations for BatchShortcutAction().
1247
1248 typedef base::Callback<bool(const base::FilePath& /*shortcut_path*/)>
1249 ShortcutOperationCallback;
1250
ShortcutOpUnpin(const base::FilePath & shortcut_path)1251 bool ShortcutOpUnpin(const base::FilePath& shortcut_path) {
1252 VLOG(1) << "Trying to unpin " << shortcut_path.value();
1253 if (!base::win::TaskbarUnpinShortcutLink(shortcut_path.value().c_str())) {
1254 VLOG(1) << shortcut_path.value() << " wasn't pinned (or the unpin failed).";
1255 // No error, since shortcut might not be pinned.
1256 }
1257 return true;
1258 }
1259
ShortcutOpDelete(const base::FilePath & shortcut_path)1260 bool ShortcutOpDelete(const base::FilePath& shortcut_path) {
1261 bool ret = base::DeleteFile(shortcut_path, false);
1262 LOG_IF(ERROR, !ret) << "Failed to remove " << shortcut_path.value();
1263 return ret;
1264 }
1265
ShortcutOpRetarget(const base::FilePath & old_target,const base::FilePath & new_target,const base::FilePath & shortcut_path)1266 bool ShortcutOpRetarget(const base::FilePath& old_target,
1267 const base::FilePath& new_target,
1268 const base::FilePath& shortcut_path) {
1269 base::win::ShortcutProperties new_prop;
1270 new_prop.set_target(new_target);
1271
1272 // If the old icon matches old target, then update icon while keeping the old
1273 // icon index. Non-fatal if we fail to get the old icon.
1274 base::win::ShortcutProperties old_prop;
1275 if (base::win::ResolveShortcutProperties(
1276 shortcut_path,
1277 base::win::ShortcutProperties::PROPERTIES_ICON,
1278 &old_prop)) {
1279 if (InstallUtil::ProgramCompare(old_target).EvaluatePath(old_prop.icon))
1280 new_prop.set_icon(new_target, old_prop.icon_index);
1281 } else {
1282 LOG(ERROR) << "Failed to resolve " << shortcut_path.value();
1283 }
1284
1285 bool result = base::win::CreateOrUpdateShortcutLink(
1286 shortcut_path, new_prop, base::win::SHORTCUT_UPDATE_EXISTING);
1287 LOG_IF(ERROR, !result) << "Failed to retarget " << shortcut_path.value();
1288 return result;
1289 }
1290
ShortcutOpListOrRemoveUnknownArgs(bool do_removal,std::vector<std::pair<base::FilePath,base::string16>> * shortcuts,const base::FilePath & shortcut_path)1291 bool ShortcutOpListOrRemoveUnknownArgs(
1292 bool do_removal,
1293 std::vector<std::pair<base::FilePath, base::string16> >* shortcuts,
1294 const base::FilePath& shortcut_path) {
1295 base::string16 args;
1296 if (!base::win::ResolveShortcut(shortcut_path, NULL, &args))
1297 return false;
1298
1299 CommandLine current_args(CommandLine::FromString(base::StringPrintf(
1300 L"unused_program %ls", args.c_str())));
1301 const char* const kept_switches[] = {
1302 switches::kApp,
1303 switches::kAppId,
1304 switches::kShowAppList,
1305 switches::kProfileDirectory,
1306 };
1307 CommandLine desired_args(CommandLine::NO_PROGRAM);
1308 desired_args.CopySwitchesFrom(current_args, kept_switches,
1309 arraysize(kept_switches));
1310 if (desired_args.argv().size() == current_args.argv().size())
1311 return true;
1312 if (shortcuts)
1313 shortcuts->push_back(std::make_pair(shortcut_path, args));
1314 if (!do_removal)
1315 return true;
1316 base::win::ShortcutProperties updated_properties;
1317 updated_properties.set_arguments(desired_args.GetArgumentsString());
1318 return base::win::CreateOrUpdateShortcutLink(
1319 shortcut_path, updated_properties, base::win::SHORTCUT_UPDATE_EXISTING);
1320 }
1321
1322 // {|location|, |dist|, |level|} determine |shortcut_folder|.
1323 // For each shortcut in |shortcut_folder| that match |shortcut_filter|, apply
1324 // |shortcut_operation|. Returns true if all operations are successful.
1325 // All intended operations are attempted, even if failures occur.
1326 // This method will abort and return false if |cancel| is non-NULL and gets set
1327 // at any point during this call.
BatchShortcutAction(const ShortcutFilterCallback & shortcut_filter,const ShortcutOperationCallback & shortcut_operation,ShellUtil::ShortcutLocation location,BrowserDistribution * dist,ShellUtil::ShellChange level,const scoped_refptr<ShellUtil::SharedCancellationFlag> & cancel)1328 bool BatchShortcutAction(
1329 const ShortcutFilterCallback& shortcut_filter,
1330 const ShortcutOperationCallback& shortcut_operation,
1331 ShellUtil::ShortcutLocation location,
1332 BrowserDistribution* dist,
1333 ShellUtil::ShellChange level,
1334 const scoped_refptr<ShellUtil::SharedCancellationFlag>& cancel) {
1335 DCHECK(!shortcut_operation.is_null());
1336
1337 // There is no system-level Quick Launch shortcut folder.
1338 if (level == ShellUtil::SYSTEM_LEVEL &&
1339 location == ShellUtil::SHORTCUT_LOCATION_QUICK_LAUNCH) {
1340 return true;
1341 }
1342
1343 base::FilePath shortcut_folder;
1344 if (!ShellUtil::GetShortcutPath(location, dist, level, &shortcut_folder)) {
1345 LOG(WARNING) << "Cannot find path at location " << location;
1346 return false;
1347 }
1348
1349 bool success = true;
1350 base::FileEnumerator enumerator(
1351 shortcut_folder, false, base::FileEnumerator::FILES,
1352 base::string16(L"*") + installer::kLnkExt);
1353 base::FilePath target_path;
1354 base::string16 args;
1355 for (base::FilePath shortcut_path = enumerator.Next();
1356 !shortcut_path.empty();
1357 shortcut_path = enumerator.Next()) {
1358 if (cancel && cancel->data.IsSet())
1359 return false;
1360 if (base::win::ResolveShortcut(shortcut_path, &target_path, &args)) {
1361 if (shortcut_filter.Run(target_path, args) &&
1362 !shortcut_operation.Run(shortcut_path)) {
1363 success = false;
1364 }
1365 } else {
1366 LOG(ERROR) << "Cannot resolve shortcut at " << shortcut_path.value();
1367 success = false;
1368 }
1369 }
1370 return success;
1371 }
1372
1373
1374 // If the folder specified by {|location|, |dist|, |level|} is empty, remove it.
1375 // Otherwise do nothing. Returns true on success, including the vacuous case
1376 // where no deletion occurred because directory is non-empty.
RemoveShortcutFolderIfEmpty(ShellUtil::ShortcutLocation location,BrowserDistribution * dist,ShellUtil::ShellChange level)1377 bool RemoveShortcutFolderIfEmpty(ShellUtil::ShortcutLocation location,
1378 BrowserDistribution* dist,
1379 ShellUtil::ShellChange level) {
1380 // Explicitly whitelist locations, since accidental calls can be very harmful.
1381 if (location != ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_DIR &&
1382 location != ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR &&
1383 location != ShellUtil::SHORTCUT_LOCATION_APP_SHORTCUTS) {
1384 NOTREACHED();
1385 return false;
1386 }
1387
1388 base::FilePath shortcut_folder;
1389 if (!ShellUtil::GetShortcutPath(location, dist, level, &shortcut_folder)) {
1390 LOG(WARNING) << "Cannot find path at location " << location;
1391 return false;
1392 }
1393 if (base::IsDirectoryEmpty(shortcut_folder) &&
1394 !base::DeleteFile(shortcut_folder, true)) {
1395 LOG(ERROR) << "Cannot remove folder " << shortcut_folder.value();
1396 return false;
1397 }
1398 return true;
1399 }
1400
1401 } // namespace
1402
1403 const wchar_t* ShellUtil::kRegDefaultIcon = L"\\DefaultIcon";
1404 const wchar_t* ShellUtil::kRegShellPath = L"\\shell";
1405 const wchar_t* ShellUtil::kRegShellOpen = L"\\shell\\open\\command";
1406 const wchar_t* ShellUtil::kRegStartMenuInternet =
1407 L"Software\\Clients\\StartMenuInternet";
1408 const wchar_t* ShellUtil::kRegClasses = L"Software\\Classes";
1409 const wchar_t* ShellUtil::kRegRegisteredApplications =
1410 L"Software\\RegisteredApplications";
1411 const wchar_t* ShellUtil::kRegVistaUrlPrefs =
1412 L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
1413 L"http\\UserChoice";
1414 const wchar_t* ShellUtil::kAppPathsRegistryKey =
1415 L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
1416 const wchar_t* ShellUtil::kAppPathsRegistryPathName = L"Path";
1417
1418 const wchar_t* ShellUtil::kDefaultFileAssociations[] = {L".htm", L".html",
1419 L".shtml", L".xht", L".xhtml", NULL};
1420 const wchar_t* ShellUtil::kPotentialFileAssociations[] = {L".htm", L".html",
1421 L".shtml", L".xht", L".xhtml", L".webp", NULL};
1422 const wchar_t* ShellUtil::kBrowserProtocolAssociations[] = {L"ftp", L"http",
1423 L"https", NULL};
1424 const wchar_t* ShellUtil::kPotentialProtocolAssociations[] = {L"ftp", L"http",
1425 L"https", L"irc", L"mailto", L"mms", L"news", L"nntp", L"sms", L"smsto",
1426 L"tel", L"urn", L"webcal", NULL};
1427 const wchar_t* ShellUtil::kRegUrlProtocol = L"URL Protocol";
1428 const wchar_t* ShellUtil::kRegApplication = L"\\Application";
1429 const wchar_t* ShellUtil::kRegAppUserModelId = L"AppUserModelId";
1430 const wchar_t* ShellUtil::kRegApplicationDescription =
1431 L"ApplicationDescription";
1432 const wchar_t* ShellUtil::kRegApplicationName = L"ApplicationName";
1433 const wchar_t* ShellUtil::kRegApplicationIcon = L"ApplicationIcon";
1434 const wchar_t* ShellUtil::kRegApplicationCompany = L"ApplicationCompany";
1435 const wchar_t* ShellUtil::kRegExePath = L"\\.exe";
1436 const wchar_t* ShellUtil::kRegVerbOpen = L"open";
1437 const wchar_t* ShellUtil::kRegVerbOpenNewWindow = L"opennewwindow";
1438 const wchar_t* ShellUtil::kRegVerbRun = L"run";
1439 const wchar_t* ShellUtil::kRegCommand = L"command";
1440 const wchar_t* ShellUtil::kRegDelegateExecute = L"DelegateExecute";
1441 const wchar_t* ShellUtil::kRegOpenWithProgids = L"OpenWithProgids";
1442
QuickIsChromeRegisteredInHKLM(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & suffix)1443 bool ShellUtil::QuickIsChromeRegisteredInHKLM(BrowserDistribution* dist,
1444 const base::string16& chrome_exe,
1445 const base::string16& suffix) {
1446 return QuickIsChromeRegistered(dist, chrome_exe, suffix,
1447 CONFIRM_SHELL_REGISTRATION_IN_HKLM);
1448 }
1449
ShortcutLocationIsSupported(ShellUtil::ShortcutLocation location)1450 bool ShellUtil::ShortcutLocationIsSupported(
1451 ShellUtil::ShortcutLocation location) {
1452 switch (location) {
1453 case SHORTCUT_LOCATION_DESKTOP: // Falls through.
1454 case SHORTCUT_LOCATION_QUICK_LAUNCH: // Falls through.
1455 case SHORTCUT_LOCATION_START_MENU_ROOT: // Falls through.
1456 case SHORTCUT_LOCATION_START_MENU_CHROME_DIR: // Falls through.
1457 case SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR:
1458 return true;
1459 case SHORTCUT_LOCATION_TASKBAR_PINS:
1460 return base::win::GetVersion() >= base::win::VERSION_WIN7;
1461 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1462 return base::win::GetVersion() >= base::win::VERSION_WIN8;
1463 default:
1464 NOTREACHED();
1465 return false;
1466 }
1467 }
1468
GetShortcutPath(ShellUtil::ShortcutLocation location,BrowserDistribution * dist,ShellChange level,base::FilePath * path)1469 bool ShellUtil::GetShortcutPath(ShellUtil::ShortcutLocation location,
1470 BrowserDistribution* dist,
1471 ShellChange level,
1472 base::FilePath* path) {
1473 DCHECK(path);
1474 int dir_key = -1;
1475 base::string16 folder_to_append;
1476 switch (location) {
1477 case SHORTCUT_LOCATION_DESKTOP:
1478 dir_key = (level == CURRENT_USER) ? base::DIR_USER_DESKTOP :
1479 base::DIR_COMMON_DESKTOP;
1480 break;
1481 case SHORTCUT_LOCATION_QUICK_LAUNCH:
1482 // There is no support for a system-level Quick Launch shortcut.
1483 DCHECK_EQ(level, CURRENT_USER);
1484 dir_key = base::DIR_USER_QUICK_LAUNCH;
1485 break;
1486 case SHORTCUT_LOCATION_START_MENU_ROOT:
1487 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1488 base::DIR_COMMON_START_MENU;
1489 break;
1490 case SHORTCUT_LOCATION_START_MENU_CHROME_DIR:
1491 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1492 base::DIR_COMMON_START_MENU;
1493 folder_to_append = dist->GetStartMenuShortcutSubfolder(
1494 BrowserDistribution::SUBFOLDER_CHROME);
1495 break;
1496 case SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR:
1497 dir_key = (level == CURRENT_USER) ? base::DIR_START_MENU :
1498 base::DIR_COMMON_START_MENU;
1499 folder_to_append = dist->GetStartMenuShortcutSubfolder(
1500 BrowserDistribution::SUBFOLDER_APPS);
1501 break;
1502 case SHORTCUT_LOCATION_TASKBAR_PINS:
1503 dir_key = base::DIR_TASKBAR_PINS;
1504 break;
1505 case SHORTCUT_LOCATION_APP_SHORTCUTS:
1506 // TODO(huangs): Move GetAppShortcutsFolder() logic into base_paths_win.
1507 return GetAppShortcutsFolder(dist, level, path);
1508
1509 default:
1510 NOTREACHED();
1511 return false;
1512 }
1513
1514 if (!PathService::Get(dir_key, path) || path->empty()) {
1515 NOTREACHED() << dir_key;
1516 return false;
1517 }
1518
1519 if (!folder_to_append.empty())
1520 *path = path->Append(folder_to_append);
1521
1522 return true;
1523 }
1524
CreateOrUpdateShortcut(ShellUtil::ShortcutLocation location,BrowserDistribution * dist,const ShellUtil::ShortcutProperties & properties,ShellUtil::ShortcutOperation operation)1525 bool ShellUtil::CreateOrUpdateShortcut(
1526 ShellUtil::ShortcutLocation location,
1527 BrowserDistribution* dist,
1528 const ShellUtil::ShortcutProperties& properties,
1529 ShellUtil::ShortcutOperation operation) {
1530 // Explicitly whitelist locations to which this is applicable.
1531 if (location != SHORTCUT_LOCATION_DESKTOP &&
1532 location != SHORTCUT_LOCATION_QUICK_LAUNCH &&
1533 location != SHORTCUT_LOCATION_START_MENU_ROOT &&
1534 location != SHORTCUT_LOCATION_START_MENU_CHROME_DIR &&
1535 location != SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR) {
1536 NOTREACHED();
1537 return false;
1538 }
1539
1540 DCHECK(dist);
1541 // |pin_to_taskbar| is only acknowledged when first creating the shortcut.
1542 DCHECK(!properties.pin_to_taskbar ||
1543 operation == SHELL_SHORTCUT_CREATE_ALWAYS ||
1544 operation == SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL);
1545
1546 base::FilePath user_shortcut_path;
1547 base::FilePath system_shortcut_path;
1548 if (location == SHORTCUT_LOCATION_QUICK_LAUNCH) {
1549 // There is no system-level shortcut for Quick Launch.
1550 DCHECK_EQ(properties.level, CURRENT_USER);
1551 } else if (!GetShortcutPath(
1552 location, dist, SYSTEM_LEVEL, &system_shortcut_path)) {
1553 NOTREACHED();
1554 return false;
1555 }
1556
1557 base::string16 shortcut_name(
1558 ExtractShortcutNameFromProperties(dist, properties));
1559 system_shortcut_path = system_shortcut_path.Append(shortcut_name);
1560
1561 base::FilePath* chosen_path;
1562 bool should_install_shortcut = true;
1563 if (properties.level == SYSTEM_LEVEL) {
1564 // Install the system-level shortcut if requested.
1565 chosen_path = &system_shortcut_path;
1566 } else if (operation != SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL ||
1567 system_shortcut_path.empty() ||
1568 !base::PathExists(system_shortcut_path)) {
1569 // Otherwise install the user-level shortcut, unless the system-level
1570 // variant of this shortcut is present on the machine and |operation| states
1571 // not to create a user-level shortcut in that case.
1572 if (!GetShortcutPath(location, dist, CURRENT_USER, &user_shortcut_path)) {
1573 NOTREACHED();
1574 return false;
1575 }
1576 user_shortcut_path = user_shortcut_path.Append(shortcut_name);
1577 chosen_path = &user_shortcut_path;
1578 } else {
1579 // Do not install any shortcut if we are told to install a user-level
1580 // shortcut, but the system-level variant of that shortcut is present.
1581 // Other actions (e.g., pinning) can still happen with respect to the
1582 // existing system-level shortcut however.
1583 chosen_path = &system_shortcut_path;
1584 should_install_shortcut = false;
1585 }
1586
1587 if (chosen_path == NULL || chosen_path->empty()) {
1588 NOTREACHED();
1589 return false;
1590 }
1591
1592 base::win::ShortcutOperation shortcut_operation =
1593 TranslateShortcutOperation(operation);
1594 bool ret = true;
1595 if (should_install_shortcut) {
1596 // Make sure the parent directories exist when creating the shortcut.
1597 if (shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
1598 !base::CreateDirectory(chosen_path->DirName())) {
1599 NOTREACHED();
1600 return false;
1601 }
1602
1603 base::win::ShortcutProperties shortcut_properties(
1604 TranslateShortcutProperties(properties));
1605 ret = base::win::CreateOrUpdateShortcutLink(
1606 *chosen_path, shortcut_properties, shortcut_operation);
1607 }
1608
1609 if (ret && shortcut_operation == base::win::SHORTCUT_CREATE_ALWAYS &&
1610 properties.pin_to_taskbar &&
1611 base::win::GetVersion() >= base::win::VERSION_WIN7) {
1612 ret = base::win::TaskbarPinShortcutLink(chosen_path->value().c_str());
1613 if (!ret) {
1614 LOG(ERROR) << "Failed to pin " << chosen_path->value();
1615 }
1616 }
1617
1618 return ret;
1619 }
1620
FormatIconLocation(const base::string16 & icon_path,int icon_index)1621 base::string16 ShellUtil::FormatIconLocation(const base::string16& icon_path,
1622 int icon_index) {
1623 base::string16 icon_string(icon_path);
1624 icon_string.append(L",");
1625 icon_string.append(base::IntToString16(icon_index));
1626 return icon_string;
1627 }
1628
GetChromeShellOpenCmd(const base::string16 & chrome_exe)1629 base::string16 ShellUtil::GetChromeShellOpenCmd(
1630 const base::string16& chrome_exe) {
1631 return L"\"" + chrome_exe + L"\" -- \"%1\"";
1632 }
1633
GetChromeDelegateCommand(const base::string16 & chrome_exe)1634 base::string16 ShellUtil::GetChromeDelegateCommand(
1635 const base::string16& chrome_exe) {
1636 return L"\"" + chrome_exe + L"\" -- %*";
1637 }
1638
GetRegisteredBrowsers(BrowserDistribution * dist,std::map<base::string16,base::string16> * browsers)1639 void ShellUtil::GetRegisteredBrowsers(
1640 BrowserDistribution* dist,
1641 std::map<base::string16, base::string16>* browsers) {
1642 DCHECK(dist);
1643 DCHECK(browsers);
1644
1645 const base::string16 base_key(ShellUtil::kRegStartMenuInternet);
1646 base::string16 client_path;
1647 RegKey key;
1648 base::string16 name;
1649 base::string16 command;
1650
1651 // HKCU has precedence over HKLM for these registrations: http://goo.gl/xjczJ.
1652 // Look in HKCU second to override any identical values found in HKLM.
1653 const HKEY roots[] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
1654 for (int i = 0; i < arraysize(roots); ++i) {
1655 const HKEY root = roots[i];
1656 for (base::win::RegistryKeyIterator iter(root, base_key.c_str());
1657 iter.Valid(); ++iter) {
1658 client_path.assign(base_key).append(1, L'\\').append(iter.Name());
1659 // Read the browser's name (localized according to install language).
1660 if (key.Open(root, client_path.c_str(),
1661 KEY_QUERY_VALUE) != ERROR_SUCCESS ||
1662 key.ReadValue(NULL, &name) != ERROR_SUCCESS ||
1663 name.empty() ||
1664 name.find(dist->GetBaseAppName()) != base::string16::npos) {
1665 continue;
1666 }
1667 // Read the browser's reinstall command.
1668 if (key.Open(root, (client_path + L"\\InstallInfo").c_str(),
1669 KEY_QUERY_VALUE) == ERROR_SUCCESS &&
1670 key.ReadValue(kReinstallCommand, &command) == ERROR_SUCCESS &&
1671 !command.empty()) {
1672 (*browsers)[name] = command;
1673 }
1674 }
1675 }
1676 }
1677
GetCurrentInstallationSuffix(BrowserDistribution * dist,const base::string16 & chrome_exe)1678 base::string16 ShellUtil::GetCurrentInstallationSuffix(
1679 BrowserDistribution* dist,
1680 const base::string16& chrome_exe) {
1681 // This method is somewhat the opposite of GetInstallationSpecificSuffix().
1682 // In this case we are not trying to determine the current suffix for the
1683 // upcoming installation (i.e. not trying to stick to a currently bad
1684 // registration style if one is present).
1685 // Here we want to determine which suffix we should use at run-time.
1686 // In order of preference, we prefer (for user-level installs):
1687 // 1) Base 32 encoding of the md5 hash of the user's sid (new-style).
1688 // 2) Username (old-style).
1689 // 3) Unsuffixed (even worse).
1690 base::string16 tested_suffix;
1691 if (InstallUtil::IsPerUserInstall(chrome_exe.c_str()) &&
1692 (!GetUserSpecificRegistrySuffix(&tested_suffix) ||
1693 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix,
1694 CONFIRM_PROGID_REGISTRATION)) &&
1695 (!GetOldUserSpecificRegistrySuffix(&tested_suffix) ||
1696 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix,
1697 CONFIRM_PROGID_REGISTRATION)) &&
1698 !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix.erase(),
1699 CONFIRM_PROGID_REGISTRATION)) {
1700 // If Chrome is not registered under any of the possible suffixes (e.g.
1701 // tests, Canary, etc.): use the new-style suffix at run-time.
1702 if (!GetUserSpecificRegistrySuffix(&tested_suffix))
1703 NOTREACHED();
1704 }
1705 return tested_suffix;
1706 }
1707
GetApplicationName(BrowserDistribution * dist,const base::string16 & chrome_exe)1708 base::string16 ShellUtil::GetApplicationName(BrowserDistribution* dist,
1709 const base::string16& chrome_exe) {
1710 base::string16 app_name = dist->GetBaseAppName();
1711 app_name += GetCurrentInstallationSuffix(dist, chrome_exe);
1712 return app_name;
1713 }
1714
GetBrowserModelId(BrowserDistribution * dist,bool is_per_user_install)1715 base::string16 ShellUtil::GetBrowserModelId(BrowserDistribution* dist,
1716 bool is_per_user_install) {
1717 base::string16 app_id(dist->GetBaseAppId());
1718 base::string16 suffix;
1719
1720 // TODO(robertshield): Temporary hack to make the kRegisterChromeBrowserSuffix
1721 // apply to all registry values computed down in these murky depths.
1722 CommandLine& command_line = *CommandLine::ForCurrentProcess();
1723 if (command_line.HasSwitch(
1724 installer::switches::kRegisterChromeBrowserSuffix)) {
1725 suffix = command_line.GetSwitchValueNative(
1726 installer::switches::kRegisterChromeBrowserSuffix);
1727 } else if (is_per_user_install && !GetUserSpecificRegistrySuffix(&suffix)) {
1728 NOTREACHED();
1729 }
1730 // There is only one component (i.e. the suffixed appid) in this case, but it
1731 // is still necessary to go through the appid constructor to make sure the
1732 // returned appid is truncated if necessary.
1733 std::vector<base::string16> components(1, app_id.append(suffix));
1734 return BuildAppModelId(components);
1735 }
1736
BuildAppModelId(const std::vector<base::string16> & components)1737 base::string16 ShellUtil::BuildAppModelId(
1738 const std::vector<base::string16>& components) {
1739 DCHECK_GT(components.size(), 0U);
1740
1741 // Find the maximum numbers of characters allowed in each component
1742 // (accounting for the dots added between each component).
1743 const size_t available_chars =
1744 installer::kMaxAppModelIdLength - (components.size() - 1);
1745 const size_t max_component_length = available_chars / components.size();
1746
1747 // |max_component_length| should be at least 2; otherwise the truncation logic
1748 // below breaks.
1749 if (max_component_length < 2U) {
1750 NOTREACHED();
1751 return (*components.begin()).substr(0, installer::kMaxAppModelIdLength);
1752 }
1753
1754 base::string16 app_id;
1755 app_id.reserve(installer::kMaxAppModelIdLength);
1756 for (std::vector<base::string16>::const_iterator it = components.begin();
1757 it != components.end(); ++it) {
1758 if (it != components.begin())
1759 app_id.push_back(L'.');
1760
1761 const base::string16& component = *it;
1762 DCHECK(!component.empty());
1763 if (component.length() > max_component_length) {
1764 // Append a shortened version of this component. Cut in the middle to try
1765 // to avoid losing the unique parts of this component (which are usually
1766 // at the beginning or end for things like usernames and paths).
1767 app_id.append(component.c_str(), 0, max_component_length / 2);
1768 app_id.append(component.c_str(),
1769 component.length() - ((max_component_length + 1) / 2),
1770 base::string16::npos);
1771 } else {
1772 app_id.append(component);
1773 }
1774 }
1775 // No spaces are allowed in the AppUserModelId according to MSDN.
1776 base::ReplaceChars(app_id, base::ASCIIToUTF16(" "), base::ASCIIToUTF16("_"),
1777 &app_id);
1778 return app_id;
1779 }
1780
GetChromeDefaultState()1781 ShellUtil::DefaultState ShellUtil::GetChromeDefaultState() {
1782 base::FilePath app_path;
1783 if (!PathService::Get(base::FILE_EXE, &app_path)) {
1784 NOTREACHED();
1785 return ShellUtil::UNKNOWN_DEFAULT;
1786 }
1787
1788 return GetChromeDefaultStateFromPath(app_path);
1789 }
1790
GetChromeDefaultStateFromPath(const base::FilePath & chrome_exe)1791 ShellUtil::DefaultState ShellUtil::GetChromeDefaultStateFromPath(
1792 const base::FilePath& chrome_exe) {
1793 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
1794 if (distribution->GetDefaultBrowserControlPolicy() ==
1795 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
1796 return NOT_DEFAULT;
1797 }
1798 // When we check for default browser we don't necessarily want to count file
1799 // type handlers and icons as having changed the default browser status,
1800 // since the user may have changed their shell settings to cause HTML files
1801 // to open with a text editor for example. We also don't want to aggressively
1802 // claim FTP, since the user may have a separate FTP client. It is an open
1803 // question as to how to "heal" these settings. Perhaps the user should just
1804 // re-run the installer or run with the --set-default-browser command line
1805 // flag. There is doubtless some other key we can hook into to cause "Repair"
1806 // to show up in Add/Remove programs for us.
1807 static const wchar_t* const kChromeProtocols[] = { L"http", L"https" };
1808 return ProbeProtocolHandlers(chrome_exe,
1809 kChromeProtocols,
1810 arraysize(kChromeProtocols));
1811 }
1812
GetChromeDefaultProtocolClientState(const base::string16 & protocol)1813 ShellUtil::DefaultState ShellUtil::GetChromeDefaultProtocolClientState(
1814 const base::string16& protocol) {
1815 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
1816 if (distribution->GetDefaultBrowserControlPolicy() ==
1817 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
1818 return NOT_DEFAULT;
1819 }
1820
1821 if (protocol.empty())
1822 return UNKNOWN_DEFAULT;
1823
1824 base::FilePath chrome_exe;
1825 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
1826 NOTREACHED();
1827 return ShellUtil::UNKNOWN_DEFAULT;
1828 }
1829
1830 const wchar_t* const protocols[] = { protocol.c_str() };
1831 return ProbeProtocolHandlers(chrome_exe,
1832 protocols,
1833 arraysize(protocols));
1834 }
1835
1836 // static
CanMakeChromeDefaultUnattended()1837 bool ShellUtil::CanMakeChromeDefaultUnattended() {
1838 return base::win::GetVersion() < base::win::VERSION_WIN8;
1839 }
1840
MakeChromeDefault(BrowserDistribution * dist,int shell_change,const base::string16 & chrome_exe,bool elevate_if_not_admin)1841 bool ShellUtil::MakeChromeDefault(BrowserDistribution* dist,
1842 int shell_change,
1843 const base::string16& chrome_exe,
1844 bool elevate_if_not_admin) {
1845 DCHECK(!(shell_change & ShellUtil::SYSTEM_LEVEL) || IsUserAnAdmin());
1846
1847 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
1848 if (distribution->GetDefaultBrowserControlPolicy() !=
1849 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
1850 return false;
1851 }
1852
1853 // Windows 8 does not permit making a browser default just like that.
1854 // This process needs to be routed through the system's UI. Use
1855 // ShowMakeChromeDefaultSystemUI instead (below).
1856 if (!CanMakeChromeDefaultUnattended()) {
1857 return false;
1858 }
1859
1860 if (!ShellUtil::RegisterChromeBrowser(
1861 dist, chrome_exe, base::string16(), elevate_if_not_admin)) {
1862 return false;
1863 }
1864
1865 bool ret = true;
1866 // First use the new "recommended" way on Vista to make Chrome default
1867 // browser.
1868 base::string16 app_name = GetApplicationName(dist, chrome_exe);
1869
1870 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
1871 // On Windows Vista and Win7 we still can set ourselves via the
1872 // the IApplicationAssociationRegistration interface.
1873 VLOG(1) << "Registering Chrome as default browser on Vista.";
1874 base::win::ScopedComPtr<IApplicationAssociationRegistration> pAAR;
1875 HRESULT hr = pAAR.CreateInstance(CLSID_ApplicationAssociationRegistration,
1876 NULL, CLSCTX_INPROC);
1877 if (SUCCEEDED(hr)) {
1878 for (int i = 0; ShellUtil::kBrowserProtocolAssociations[i] != NULL; i++) {
1879 hr = pAAR->SetAppAsDefault(app_name.c_str(),
1880 ShellUtil::kBrowserProtocolAssociations[i], AT_URLPROTOCOL);
1881 if (!SUCCEEDED(hr)) {
1882 ret = false;
1883 LOG(ERROR) << "Failed to register as default for protocol "
1884 << ShellUtil::kBrowserProtocolAssociations[i]
1885 << " (" << hr << ")";
1886 }
1887 }
1888
1889 for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) {
1890 hr = pAAR->SetAppAsDefault(app_name.c_str(),
1891 ShellUtil::kDefaultFileAssociations[i], AT_FILEEXTENSION);
1892 if (!SUCCEEDED(hr)) {
1893 ret = false;
1894 LOG(ERROR) << "Failed to register as default for file extension "
1895 << ShellUtil::kDefaultFileAssociations[i]
1896 << " (" << hr << ")";
1897 }
1898 }
1899 }
1900 }
1901
1902 if (!RegisterChromeAsDefaultXPStyle(dist, shell_change, chrome_exe))
1903 ret = false;
1904
1905 // Send Windows notification event so that it can update icons for
1906 // file associations.
1907 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
1908 return ret;
1909 }
1910
ShowMakeChromeDefaultSystemUI(BrowserDistribution * dist,const base::string16 & chrome_exe)1911 bool ShellUtil::ShowMakeChromeDefaultSystemUI(
1912 BrowserDistribution* dist,
1913 const base::string16& chrome_exe) {
1914 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1915 if (dist->GetDefaultBrowserControlPolicy() !=
1916 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
1917 return false;
1918 }
1919
1920 if (!RegisterChromeBrowser(dist, chrome_exe, base::string16(), true))
1921 return false;
1922
1923 bool succeeded = true;
1924 bool is_default = (GetChromeDefaultState() == IS_DEFAULT);
1925 if (!is_default) {
1926 // On Windows 8, you can't set yourself as the default handler
1927 // programatically. In other words IApplicationAssociationRegistration
1928 // has been rendered useless. What you can do is to launch
1929 // "Set Program Associations" section of the "Default Programs"
1930 // control panel, which is a mess, or pop the concise "How you want to open
1931 // webpages?" dialog. We choose the latter.
1932 succeeded = LaunchSelectDefaultProtocolHandlerDialog(L"http");
1933 is_default = (succeeded && GetChromeDefaultState() == IS_DEFAULT);
1934 }
1935 if (succeeded && is_default)
1936 RegisterChromeAsDefaultXPStyle(dist, CURRENT_USER, chrome_exe);
1937 return succeeded;
1938 }
1939
MakeChromeDefaultProtocolClient(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & protocol)1940 bool ShellUtil::MakeChromeDefaultProtocolClient(
1941 BrowserDistribution* dist,
1942 const base::string16& chrome_exe,
1943 const base::string16& protocol) {
1944 if (dist->GetDefaultBrowserControlPolicy() !=
1945 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
1946 return false;
1947 }
1948
1949 if (!RegisterChromeForProtocol(
1950 dist, chrome_exe, base::string16(), protocol, true))
1951 return false;
1952
1953 // Windows 8 does not permit making a browser default just like that.
1954 // This process needs to be routed through the system's UI. Use
1955 // ShowMakeChromeDefaultProocolClientSystemUI instead (below).
1956 if (!CanMakeChromeDefaultUnattended())
1957 return false;
1958
1959 bool ret = true;
1960 // First use the new "recommended" way on Vista to make Chrome default
1961 // protocol handler.
1962 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
1963 VLOG(1) << "Registering Chrome as default handler for " << protocol
1964 << " on Vista.";
1965 base::win::ScopedComPtr<IApplicationAssociationRegistration> pAAR;
1966 HRESULT hr = pAAR.CreateInstance(CLSID_ApplicationAssociationRegistration,
1967 NULL, CLSCTX_INPROC);
1968 if (SUCCEEDED(hr)) {
1969 base::string16 app_name = GetApplicationName(dist, chrome_exe);
1970 hr = pAAR->SetAppAsDefault(app_name.c_str(), protocol.c_str(),
1971 AT_URLPROTOCOL);
1972 }
1973 if (!SUCCEEDED(hr)) {
1974 ret = false;
1975 LOG(ERROR) << "Could not make Chrome default protocol client (Vista):"
1976 << " HRESULT=" << hr << ".";
1977 }
1978 }
1979
1980 // Now use the old way to associate Chrome with the desired protocol. This
1981 // should not be required on Vista+, but since some applications still read
1982 // Software\Classes\<protocol> key directly, do this on Vista+ also.
1983 if (!RegisterChromeAsDefaultProtocolClientXPStyle(dist, chrome_exe, protocol))
1984 ret = false;
1985
1986 return ret;
1987 }
1988
ShowMakeChromeDefaultProtocolClientSystemUI(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & protocol)1989 bool ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
1990 BrowserDistribution* dist,
1991 const base::string16& chrome_exe,
1992 const base::string16& protocol) {
1993 DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
1994 if (dist->GetDefaultBrowserControlPolicy() !=
1995 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL) {
1996 return false;
1997 }
1998
1999 if (!RegisterChromeForProtocol(
2000 dist, chrome_exe, base::string16(), protocol, true))
2001 return false;
2002
2003 bool succeeded = true;
2004 bool is_default = (
2005 GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
2006 if (!is_default) {
2007 // On Windows 8, you can't set yourself as the default handler
2008 // programatically. In other words IApplicationAssociationRegistration
2009 // has been rendered useless. What you can do is to launch
2010 // "Set Program Associations" section of the "Default Programs"
2011 // control panel, which is a mess, or pop the concise "How you want to open
2012 // links of this type (protocol)?" dialog. We choose the latter.
2013 succeeded = LaunchSelectDefaultProtocolHandlerDialog(protocol.c_str());
2014 is_default = (succeeded &&
2015 GetChromeDefaultProtocolClientState(protocol) == IS_DEFAULT);
2016 }
2017 if (succeeded && is_default)
2018 RegisterChromeAsDefaultProtocolClientXPStyle(dist, chrome_exe, protocol);
2019 return succeeded;
2020 }
2021
RegisterChromeBrowser(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & unique_suffix,bool elevate_if_not_admin)2022 bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist,
2023 const base::string16& chrome_exe,
2024 const base::string16& unique_suffix,
2025 bool elevate_if_not_admin) {
2026 if (dist->GetDefaultBrowserControlPolicy() ==
2027 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
2028 return false;
2029 }
2030
2031 CommandLine& command_line = *CommandLine::ForCurrentProcess();
2032
2033 base::string16 suffix;
2034 if (!unique_suffix.empty()) {
2035 suffix = unique_suffix;
2036 } else if (command_line.HasSwitch(
2037 installer::switches::kRegisterChromeBrowserSuffix)) {
2038 suffix = command_line.GetSwitchValueNative(
2039 installer::switches::kRegisterChromeBrowserSuffix);
2040 } else if (!GetInstallationSpecificSuffix(dist, chrome_exe, &suffix)) {
2041 return false;
2042 }
2043
2044 RemoveRunVerbOnWindows8(dist, chrome_exe);
2045
2046 bool user_level = InstallUtil::IsPerUserInstall(chrome_exe.c_str());
2047 HKEY root = DetermineRegistrationRoot(user_level);
2048
2049 // Look only in HKLM for system-level installs (otherwise, if a user-level
2050 // install is also present, it will lead IsChromeRegistered() to think this
2051 // system-level install isn't registered properly as it is shadowed by the
2052 // user-level install's registrations).
2053 uint32 look_for_in = user_level ?
2054 RegistryEntry::LOOK_IN_HKCU_THEN_HKLM : RegistryEntry::LOOK_IN_HKLM;
2055
2056 // Check if chrome is already registered with this suffix.
2057 if (IsChromeRegistered(dist, chrome_exe, suffix, look_for_in))
2058 return true;
2059
2060 bool result = true;
2061 if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
2062 // Do the full registration if we can do it at user-level or if the user is
2063 // an admin.
2064 ScopedVector<RegistryEntry> progid_and_appreg_entries;
2065 ScopedVector<RegistryEntry> shell_entries;
2066 RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix,
2067 &progid_and_appreg_entries);
2068 RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix,
2069 &progid_and_appreg_entries);
2070 RegistryEntry::GetShellIntegrationEntries(
2071 dist, chrome_exe, suffix, &shell_entries);
2072 result = (AddRegistryEntries(root, progid_and_appreg_entries) &&
2073 AddRegistryEntries(root, shell_entries));
2074 } else if (elevate_if_not_admin &&
2075 base::win::GetVersion() >= base::win::VERSION_VISTA &&
2076 ElevateAndRegisterChrome(dist, chrome_exe, suffix, base::string16())) {
2077 // If the user is not an admin and OS is between Vista and Windows 7
2078 // inclusively, try to elevate and register. This is only intended for
2079 // user-level installs as system-level installs should always be run with
2080 // admin rights.
2081 result = true;
2082 } else {
2083 // If we got to this point then all we can do is create ProgId and basic app
2084 // registrations under HKCU.
2085 ScopedVector<RegistryEntry> entries;
2086 RegistryEntry::GetProgIdEntries(
2087 dist, chrome_exe, base::string16(), &entries);
2088 // Prefer to use |suffix|; unless Chrome's ProgIds are already registered
2089 // with no suffix (as per the old registration style): in which case some
2090 // other registry entries could refer to them and since we were not able to
2091 // set our HKLM entries above, we are better off not altering these here.
2092 if (!AreEntriesRegistered(entries, RegistryEntry::LOOK_IN_HKCU)) {
2093 if (!suffix.empty()) {
2094 entries.clear();
2095 RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries);
2096 RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, &entries);
2097 }
2098 result = AddRegistryEntries(HKEY_CURRENT_USER, entries);
2099 } else {
2100 // The ProgId is registered unsuffixed in HKCU, also register the app with
2101 // Windows in HKCU (this was not done in the old registration style and
2102 // thus needs to be done after the above check for the unsuffixed
2103 // registration).
2104 entries.clear();
2105 RegistryEntry::GetAppRegistrationEntries(chrome_exe, base::string16(),
2106 &entries);
2107 result = AddRegistryEntries(HKEY_CURRENT_USER, entries);
2108 }
2109 }
2110 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
2111 return result;
2112 }
2113
RegisterChromeForProtocol(BrowserDistribution * dist,const base::string16 & chrome_exe,const base::string16 & unique_suffix,const base::string16 & protocol,bool elevate_if_not_admin)2114 bool ShellUtil::RegisterChromeForProtocol(BrowserDistribution* dist,
2115 const base::string16& chrome_exe,
2116 const base::string16& unique_suffix,
2117 const base::string16& protocol,
2118 bool elevate_if_not_admin) {
2119 if (dist->GetDefaultBrowserControlPolicy() ==
2120 BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
2121 return false;
2122 }
2123
2124 base::string16 suffix;
2125 if (!unique_suffix.empty()) {
2126 suffix = unique_suffix;
2127 } else if (!GetInstallationSpecificSuffix(dist, chrome_exe, &suffix)) {
2128 return false;
2129 }
2130
2131 bool user_level = InstallUtil::IsPerUserInstall(chrome_exe.c_str());
2132 HKEY root = DetermineRegistrationRoot(user_level);
2133
2134 // Look only in HKLM for system-level installs (otherwise, if a user-level
2135 // install is also present, it could lead IsChromeRegisteredForProtocol() to
2136 // think this system-level install isn't registered properly as it may be
2137 // shadowed by the user-level install's registrations).
2138 uint32 look_for_in = user_level ?
2139 RegistryEntry::LOOK_IN_HKCU_THEN_HKLM : RegistryEntry::LOOK_IN_HKLM;
2140
2141 // Check if chrome is already registered with this suffix.
2142 if (IsChromeRegisteredForProtocol(dist, suffix, protocol, look_for_in))
2143 return true;
2144
2145 if (root == HKEY_CURRENT_USER || IsUserAnAdmin()) {
2146 // We can do this operation directly.
2147 // First, make sure Chrome is fully registered on this machine.
2148 if (!RegisterChromeBrowser(dist, chrome_exe, suffix, false))
2149 return false;
2150
2151 // Write in the capabillity for the protocol.
2152 ScopedVector<RegistryEntry> entries;
2153 RegistryEntry::GetProtocolCapabilityEntries(dist, suffix, protocol,
2154 &entries);
2155 return AddRegistryEntries(root, entries);
2156 } else if (elevate_if_not_admin &&
2157 base::win::GetVersion() >= base::win::VERSION_VISTA) {
2158 // Elevate to do the whole job
2159 return ElevateAndRegisterChrome(dist, chrome_exe, suffix, protocol);
2160 } else {
2161 // Admin rights are required to register capabilities before Windows 8.
2162 return false;
2163 }
2164 }
2165
2166 // static
RemoveShortcuts(ShellUtil::ShortcutLocation location,BrowserDistribution * dist,ShellChange level,const base::FilePath & target_exe)2167 bool ShellUtil::RemoveShortcuts(ShellUtil::ShortcutLocation location,
2168 BrowserDistribution* dist,
2169 ShellChange level,
2170 const base::FilePath& target_exe) {
2171 if (!ShellUtil::ShortcutLocationIsSupported(location))
2172 return true; // Vacuous success.
2173
2174 FilterTargetEq shortcut_filter(target_exe, false);
2175 // Main operation to apply to each shortcut in the directory specified.
2176 ShortcutOperationCallback shortcut_operation(
2177 location == SHORTCUT_LOCATION_TASKBAR_PINS ?
2178 base::Bind(&ShortcutOpUnpin) : base::Bind(&ShortcutOpDelete));
2179 bool success = BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
2180 shortcut_operation, location, dist, level,
2181 NULL);
2182 // Remove chrome-specific shortcut folders if they are now empty.
2183 if (success &&
2184 (location == SHORTCUT_LOCATION_START_MENU_CHROME_DIR ||
2185 location == SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR ||
2186 location == SHORTCUT_LOCATION_APP_SHORTCUTS)) {
2187 success = RemoveShortcutFolderIfEmpty(location, dist, level);
2188 }
2189 return success;
2190 }
2191
2192 // static
RetargetShortcutsWithArgs(ShellUtil::ShortcutLocation location,BrowserDistribution * dist,ShellChange level,const base::FilePath & old_target_exe,const base::FilePath & new_target_exe)2193 bool ShellUtil::RetargetShortcutsWithArgs(
2194 ShellUtil::ShortcutLocation location,
2195 BrowserDistribution* dist,
2196 ShellChange level,
2197 const base::FilePath& old_target_exe,
2198 const base::FilePath& new_target_exe) {
2199 if (!ShellUtil::ShortcutLocationIsSupported(location))
2200 return true; // Vacuous success.
2201
2202 FilterTargetEq shortcut_filter(old_target_exe, true);
2203 ShortcutOperationCallback shortcut_operation(
2204 base::Bind(&ShortcutOpRetarget, old_target_exe, new_target_exe));
2205 return BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
2206 shortcut_operation, location, dist, level, NULL);
2207 }
2208
2209 // static
ShortcutListMaybeRemoveUnknownArgs(ShellUtil::ShortcutLocation location,BrowserDistribution * dist,ShellChange level,const base::FilePath & chrome_exe,bool do_removal,const scoped_refptr<SharedCancellationFlag> & cancel,std::vector<std::pair<base::FilePath,base::string16>> * shortcuts)2210 bool ShellUtil::ShortcutListMaybeRemoveUnknownArgs(
2211 ShellUtil::ShortcutLocation location,
2212 BrowserDistribution* dist,
2213 ShellChange level,
2214 const base::FilePath& chrome_exe,
2215 bool do_removal,
2216 const scoped_refptr<SharedCancellationFlag>& cancel,
2217 std::vector<std::pair<base::FilePath, base::string16> >* shortcuts) {
2218 if (!ShellUtil::ShortcutLocationIsSupported(location))
2219 return false;
2220 DCHECK(dist);
2221 FilterTargetEq shortcut_filter(chrome_exe, true);
2222 ShortcutOperationCallback shortcut_operation(
2223 base::Bind(&ShortcutOpListOrRemoveUnknownArgs, do_removal, shortcuts));
2224 return BatchShortcutAction(shortcut_filter.AsShortcutFilterCallback(),
2225 shortcut_operation, location, dist, level, cancel);
2226 }
2227
GetUserSpecificRegistrySuffix(base::string16 * suffix)2228 bool ShellUtil::GetUserSpecificRegistrySuffix(base::string16* suffix) {
2229 // Use a thread-safe cache for the user's suffix.
2230 static base::LazyInstance<UserSpecificRegistrySuffix>::Leaky suffix_instance =
2231 LAZY_INSTANCE_INITIALIZER;
2232 return suffix_instance.Get().GetSuffix(suffix);
2233 }
2234
GetOldUserSpecificRegistrySuffix(base::string16 * suffix)2235 bool ShellUtil::GetOldUserSpecificRegistrySuffix(base::string16* suffix) {
2236 wchar_t user_name[256];
2237 DWORD size = arraysize(user_name);
2238 if (::GetUserName(user_name, &size) == 0 || size < 1) {
2239 NOTREACHED();
2240 return false;
2241 }
2242 suffix->reserve(size);
2243 suffix->assign(1, L'.');
2244 suffix->append(user_name, size - 1);
2245 return true;
2246 }
2247
ByteArrayToBase32(const uint8 * bytes,size_t size)2248 base::string16 ShellUtil::ByteArrayToBase32(const uint8* bytes, size_t size) {
2249 static const char kEncoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
2250
2251 // Eliminate special cases first.
2252 if (size == 0) {
2253 return base::string16();
2254 } else if (size == 1) {
2255 base::string16 ret;
2256 ret.push_back(kEncoding[(bytes[0] & 0xf8) >> 3]);
2257 ret.push_back(kEncoding[(bytes[0] & 0x07) << 2]);
2258 return ret;
2259 } else if (size >= std::numeric_limits<size_t>::max() / 8) {
2260 // If |size| is too big, the calculation of |encoded_length| below will
2261 // overflow.
2262 NOTREACHED();
2263 return base::string16();
2264 }
2265
2266 // Overestimate the number of bits in the string by 4 so that dividing by 5
2267 // is the equivalent of rounding up the actual number of bits divided by 5.
2268 const size_t encoded_length = (size * 8 + 4) / 5;
2269
2270 base::string16 ret;
2271 ret.reserve(encoded_length);
2272
2273 // A bit stream which will be read from the left and appended to from the
2274 // right as it's emptied.
2275 uint16 bit_stream = (bytes[0] << 8) + bytes[1];
2276 size_t next_byte_index = 2;
2277 int free_bits = 0;
2278 while (free_bits < 16) {
2279 // Extract the 5 leftmost bits in the stream
2280 ret.push_back(kEncoding[(bit_stream & 0xf800) >> 11]);
2281 bit_stream <<= 5;
2282 free_bits += 5;
2283
2284 // If there is enough room in the bit stream, inject another byte (if there
2285 // are any left...).
2286 if (free_bits >= 8 && next_byte_index < size) {
2287 free_bits -= 8;
2288 bit_stream += bytes[next_byte_index++] << free_bits;
2289 }
2290 }
2291
2292 DCHECK_EQ(ret.length(), encoded_length);
2293 return ret;
2294 }
2295