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