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