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