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