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