Index: chrome/installer/util/shell_util.cc |
=================================================================== |
--- chrome/installer/util/shell_util.cc (revision 146242) |
+++ chrome/installer/util/shell_util.cc (working copy) |
@@ -9,15 +9,18 @@ |
#include "chrome/installer/util/shell_util.h" |
+#include <shlobj.h> |
#include <windows.h> |
-#include <shlobj.h> |
+#include <limits> |
#include <list> |
#include "base/command_line.h" |
#include "base/file_path.h" |
#include "base/file_util.h" |
+#include "base/lazy_instance.h" |
#include "base/logging.h" |
+#include "base/md5.h" |
#include "base/memory/scoped_ptr.h" |
#include "base/path_service.h" |
#include "base/stl_util.h" |
@@ -28,6 +31,7 @@ |
#include "base/values.h" |
#include "base/win/registry.h" |
#include "base/win/scoped_comptr.h" |
+#include "base/win/win_util.h" |
#include "base/win/windows_version.h" |
#include "chrome/common/chrome_constants.h" |
#include "chrome/common/chrome_switches.h" |
@@ -85,6 +89,80 @@ |
return VerifyVersionInfo(&min_version_info, type_mask, condition_mask) != 0; |
} |
+// Returns the current (or installed) browser's ProgId (e.g. |
+// "ChromeHTML|suffix|"). |
+// |suffix| can be the empty string. |
+string16 GetBrowserProgId(const string16& suffix) { |
+ string16 chrome_html(ShellUtil::kChromeHTMLProgId); |
+ chrome_html.append(suffix); |
+ |
+ // ProgIds cannot be longer than 39 characters. |
+ // Ref: http://msdn.microsoft.com/en-us/library/aa911706.aspx. |
+ // Make all new registrations comply with this requirement (existing |
+ // registrations must be preserved). |
+ string16 new_style_suffix; |
+ if (ShellUtil::GetUserSpecificRegistrySuffix(&new_style_suffix) && |
+ suffix == new_style_suffix && chrome_html.length() > 39) { |
+ NOTREACHED(); |
+ chrome_html.erase(39); |
+ } |
+ return chrome_html; |
+} |
+ |
+// This class is used to initialize and cache a base 32 encoding of the md5 hash |
+// of this user's sid preceded by a dot. |
+// This is guaranteed to be unique on the machine and 27 characters long |
+// (including the '.'). |
+// This is then meant to be used as a suffix on all registrations that may |
+// conflict with another user-level Chrome install. |
+class UserSpecificRegistrySuffix { |
+ public: |
+ // All the initialization is done in the constructor to be able to build the |
+ // suffix in a thread-safe manner when used in conjunction with a |
+ // LazyInstance. |
+ UserSpecificRegistrySuffix(); |
+ |
+ // Sets |suffix| to the pre-computed suffix cached in this object. |
+ // Returns true unless the initialization originally failed. |
+ bool GetSuffix(string16* suffix); |
+ |
+ private: |
+ string16 suffix_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(UserSpecificRegistrySuffix); |
+}; // class UserSpecificRegistrySuffix |
+ |
+UserSpecificRegistrySuffix::UserSpecificRegistrySuffix() { |
+ string16 user_sid; |
+ if (!base::win::GetUserSidString(&user_sid)) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ COMPILE_ASSERT(sizeof(base::MD5Digest) == 16, size_of_MD5_not_as_expected_); |
+ base::MD5Digest md5_digest; |
+ base::MD5Sum(user_sid.c_str(), user_sid.length(), &md5_digest); |
+ const string16 base32_md5( |
+ ShellUtil::ByteArrayToBase32(md5_digest.a, arraysize(md5_digest.a))); |
+ // The value returned by the base32 algorithm above must never change and |
+ // must always be 26 characters long (i.e. if someone ever moves this to |
+ // base and implements the full base32 algorithm (i.e. with appended '=' |
+ // signs in the output), they must provide a flag to allow this method to |
+ // still request the output with no appended '=' signs). |
+ DCHECK_EQ(base32_md5.length(), 26U); |
+ suffix_.reserve(base32_md5.length() + 1); |
+ suffix_.assign(1, L'.'); |
+ suffix_.append(base32_md5); |
+} |
+ |
+bool UserSpecificRegistrySuffix::GetSuffix(string16* suffix) { |
+ if (suffix_.empty()) { |
+ NOTREACHED(); |
+ return false; |
+ } |
+ suffix->assign(suffix_); |
+ return true; |
+} |
+ |
// This class represents a single registry entry. The objective is to |
// encapsulate all the registry entries required for registering Chrome at one |
// place. This class can not be instantiated outside the class and the objects |
@@ -181,8 +259,7 @@ |
// File association ProgId |
string16 chrome_html_prog_id(ShellUtil::kRegClasses); |
chrome_html_prog_id.push_back(FilePath::kSeparators[0]); |
- chrome_html_prog_id.append(ShellUtil::kChromeHTMLProgId); |
- chrome_html_prog_id.append(suffix); |
+ chrome_html_prog_id.append(GetBrowserProgId(suffix)); |
entries->push_front(new RegistryEntry( |
chrome_html_prog_id, ShellUtil::kChromeHTMLProgIdDesc)); |
entries->push_front(new RegistryEntry( |
@@ -234,7 +311,7 @@ |
std::list<RegistryEntry*>* entries) { |
entries->push_front(new RegistryEntry( |
GetCapabilitiesKey(dist, suffix).append(L"\\URLAssociations"), |
- protocol, string16(ShellUtil::kChromeHTMLProgId).append(suffix))); |
+ protocol, GetBrowserProgId(suffix))); |
return true; |
} |
@@ -295,8 +372,7 @@ |
entries->push_front(new RegistryEntry(capabilities + L"\\Startmenu", |
L"StartMenuInternet", reg_app_name)); |
- string16 html_prog_id(ShellUtil::kChromeHTMLProgId); |
- html_prog_id.append(suffix); |
+ string16 html_prog_id(GetBrowserProgId(suffix)); |
for (int i = 0; ShellUtil::kFileAssociations[i] != NULL; i++) { |
entries->push_front(new RegistryEntry( |
capabilities + L"\\FileAssociations", |
@@ -365,8 +441,7 @@ |
const string16& suffix, |
std::list<RegistryEntry*>* entries) { |
// File extension associations. |
- string16 html_prog_id(ShellUtil::kChromeHTMLProgId); |
- html_prog_id.append(suffix); |
+ string16 html_prog_id(GetBrowserProgId(suffix)); |
for (int i = 0; ShellUtil::kFileAssociations[i] != NULL; i++) { |
string16 ext_key(ShellUtil::kRegClasses); |
ext_key.push_back(FilePath::kSeparators[0]); |
@@ -677,8 +752,7 @@ |
// <root hkey>\Software\Classes\ChromiumHTML[.user]\shell\open\command |
key = ShellUtil::kRegClasses; |
key.push_back(FilePath::kSeparators[0]); |
- key.append(ShellUtil::kChromeHTMLProgId); |
- key.append(installation_suffix); |
+ key.append(GetBrowserProgId(installation_suffix)); |
key.append(ShellUtil::kRegShellOpen); |
InstallUtil::DeleteRegistryValue(root_key, key, |
ShellUtil::kRegDelegateExecute); |
@@ -747,28 +821,13 @@ |
return false; |
} |
-// Sets |suffix| to this user's username preceded by a dot. This suffix is then |
-// meant to be added to all registration that may conflict with another |
-// user-level Chrome install. |
-// Returns true unless the OS call to retrieve the username fails. |
-bool GetUserSpecificRegistrySuffix(string16* suffix) { |
- wchar_t user_name[256]; |
- DWORD size = arraysize(user_name); |
- if (::GetUserName(user_name, &size) == 0 || size < 1) { |
- PLOG(DFATAL) << "GetUserName failed"; |
- return false; |
- } |
- suffix->reserve(size); |
- suffix->assign(1, L'.'); |
- suffix->append(user_name, size - 1); |
- return true; |
-} |
- |
-// Sets |suffix| to the current user's username, preceded by a dot, on |
-// user-level installs. |
-// To support old-style user-level installs however, |suffix| is cleared if |
-// the user currently owns the non-suffixed HKLM registrations. |
-// |suffix| is also cleared on system-level installs. |
+// Sets |suffix| to a 27 character string that is specific to this user on this |
+// machine (on user-level installs only). |
+// To support old-style user-level installs however, |suffix| is cleared if the |
+// user currently owns the non-suffixed HKLM registrations. |
+// |suffix| can also be set to the user's username if the current install is |
+// suffixed as per the old-style registrations. |
+// |suffix| is cleared on system-level installs. |
// |suffix| should then be appended to all Chrome properties that may conflict |
// with other Chrome user-level installs. |
// Returns true unless one of the underlying calls fails. |
@@ -782,9 +841,20 @@ |
// registered with no suffix. |
suffix->clear(); |
return true; |
- } else { |
- return GetUserSpecificRegistrySuffix(suffix); |
} |
+ |
+ // Get the old suffix for the check below. |
+ if (!ShellUtil::GetOldUserSpecificRegistrySuffix(suffix)) { |
+ NOTREACHED(); |
+ return false; |
+ } |
+ if (QuickIsChromeRegistered(dist, chrome_exe, *suffix, |
+ CONFIRM_SHELL_REGISTRATION)) { |
+ // Username suffix for installs that are suffixed as per the old-style. |
+ return true; |
+ } |
+ |
+ return ShellUtil::GetUserSpecificRegistrySuffix(suffix); |
} |
// Returns the root registry key (HKLM or HKCU) into which shell integration |
@@ -1060,12 +1130,29 @@ |
string16 ShellUtil::GetCurrentInstallationSuffix(BrowserDistribution* dist, |
const string16& chrome_exe) { |
+ // This method is somewhat the opposite of GetInstallationSpecificSuffix(). |
+ // In this case we are not trying to determine the current suffix for the |
+ // upcoming installation (i.e. not trying to stick to a currently bad |
+ // registration style if one is present). |
+ // Here we want to determine which suffix we should use at run-time. |
+ // In order of preference, we prefer (for user-level installs): |
+ // 1) Base 32 encoding of the md5 hash of the user's sid (new-style). |
+ // 2) Username (old-style). |
+ // 3) Unsuffixed (even worse). |
string16 tested_suffix; |
- if (!InstallUtil::IsPerUserInstall(chrome_exe.c_str()) || |
- !GetUserSpecificRegistrySuffix(&tested_suffix) || |
- !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix, |
+ if (InstallUtil::IsPerUserInstall(chrome_exe.c_str()) && |
+ (!GetUserSpecificRegistrySuffix(&tested_suffix) || |
+ !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix, |
+ CONFIRM_PROGID_REGISTRATION)) && |
+ (!GetOldUserSpecificRegistrySuffix(&tested_suffix) || |
+ !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix, |
+ CONFIRM_PROGID_REGISTRATION)) && |
+ !QuickIsChromeRegistered(dist, chrome_exe, tested_suffix.erase(), |
CONFIRM_PROGID_REGISTRATION)) { |
- return string16(); |
+ // If Chrome is not registered under any of the possible suffixes (e.g. |
+ // tests, Canary, etc.): use the new-style suffix at run-time. |
+ if (!GetUserSpecificRegistrySuffix(&tested_suffix)) |
+ NOTREACHED(); |
} |
return tested_suffix; |
} |
@@ -1520,3 +1607,71 @@ |
app_id.c_str(), |
ConvertShellUtilShortcutOptionsToFileUtil(options)); |
} |
+ |
+bool ShellUtil::GetUserSpecificRegistrySuffix(string16* suffix) { |
+ // Use a thread-safe cache for the user's suffix. |
+ static base::LazyInstance<UserSpecificRegistrySuffix>::Leaky suffix_instance = |
+ LAZY_INSTANCE_INITIALIZER; |
+ return suffix_instance.Get().GetSuffix(suffix); |
+} |
+ |
+bool ShellUtil::GetOldUserSpecificRegistrySuffix(string16* suffix) { |
+ wchar_t user_name[256]; |
+ DWORD size = arraysize(user_name); |
+ if (::GetUserName(user_name, &size) == 0 || size < 1) { |
+ NOTREACHED(); |
+ return false; |
+ } |
+ suffix->reserve(size); |
+ suffix->assign(1, L'.'); |
+ suffix->append(user_name, size - 1); |
+ return true; |
+} |
+ |
+string16 ShellUtil::ByteArrayToBase32(const uint8* bytes, size_t size) { |
+ static const char kEncoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; |
+ |
+ // Eliminate special cases first. |
+ if (size == 0) { |
+ return string16(); |
+ } else if (size == 1) { |
+ string16 ret; |
+ ret.push_back(kEncoding[(bytes[0] & 0xf8) >> 3]); |
+ ret.push_back(kEncoding[(bytes[0] & 0x07) << 2]); |
+ return ret; |
+ } else if (size >= std::numeric_limits<size_t>::max() / 8) { |
+ // If |size| is too big, the calculation of |encoded_length| below will |
+ // overflow. |
+ NOTREACHED(); |
+ return string16(); |
+ } |
+ |
+ // Overestimate the number of bits in the string by 4 so that dividing by 5 |
+ // is the equivalent of rounding up the actual number of bits divided by 5. |
+ const size_t encoded_length = (size * 8 + 4) / 5; |
+ |
+ string16 ret; |
+ ret.reserve(encoded_length); |
+ |
+ // A bit stream which will be read from the left and appended to from the |
+ // right as it's emptied. |
+ uint16 bit_stream = (bytes[0] << 8) + bytes[1]; |
+ size_t next_byte_index = 2; |
+ int free_bits = 0; |
+ while (free_bits < 16) { |
+ // Extract the 5 leftmost bits in the stream |
+ ret.push_back(kEncoding[(bit_stream & 0xf800) >> 11]); |
+ bit_stream <<= 5; |
+ free_bits += 5; |
+ |
+ // If there is enough room in the bit stream, inject another byte (if there |
+ // are any left...). |
+ if (free_bits >= 8 && next_byte_index < size) { |
+ free_bits -= 8; |
+ bit_stream += bytes[next_byte_index++] << free_bits; |
+ } |
+ } |
+ |
+ DCHECK_EQ(ret.length(), encoded_length); |
+ return ret; |
+} |