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