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