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