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