| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/shell_integration.h" |
| |
| #include <windows.h> |
| #include <shobjidl.h> |
| #include <propkey.h> |
| #include <propvarutil.h> |
| |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/string_util.h" |
| #include "base/task.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_comptr.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_paths_internal.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "chrome/installer/util/create_reg_key_work_item.h" |
| #include "chrome/installer/util/set_reg_value_work_item.h" |
| #include "chrome/installer/util/shell_util.h" |
| #include "chrome/installer/util/util_constants.h" |
| #include "chrome/installer/util/work_item.h" |
| #include "chrome/installer/util/work_item_list.h" |
| #include "content/browser/browser_thread.h" |
| |
| namespace { |
| |
| // Helper function for ShellIntegration::GetAppId to generates profile id |
| // from profile path. "profile_id" is composed of sanitized basenames of |
| // user data dir and profile dir joined by a ".". |
| std::wstring GetProfileIdFromPath(const FilePath& profile_path) { |
| // Return empty string if profile_path is empty |
| if (profile_path.empty()) |
| return std::wstring(); |
| |
| FilePath default_user_data_dir; |
| // Return empty string if profile_path is in default user data |
| // dir and is the default profile. |
| if (chrome::GetDefaultUserDataDirectory(&default_user_data_dir) && |
| profile_path.DirName() == default_user_data_dir && |
| profile_path.BaseName().value() == |
| ASCIIToUTF16(chrome::kNotSignedInProfile)) { |
| return std::wstring(); |
| } |
| |
| // Get joined basenames of user data dir and profile. |
| std::wstring basenames = profile_path.DirName().BaseName().value() + |
| L"." + profile_path.BaseName().value(); |
| |
| std::wstring profile_id; |
| profile_id.reserve(basenames.size()); |
| |
| // Generate profile_id from sanitized basenames. |
| for (size_t i = 0; i < basenames.length(); ++i) { |
| if (IsAsciiAlpha(basenames[i]) || |
| IsAsciiDigit(basenames[i]) || |
| basenames[i] == L'.') |
| profile_id += basenames[i]; |
| } |
| |
| return profile_id; |
| } |
| |
| // Migrates existing chromium shortucts for Win7. |
| class MigrateChromiumShortcutsTask : public Task { |
| public: |
| MigrateChromiumShortcutsTask() { } |
| |
| virtual void Run(); |
| |
| private: |
| void MigrateWin7Shortcuts(); |
| void MigrateWin7ShortcutsInPath(const FilePath& path) const; |
| |
| // Get expected app id for given chrome shortcut. |
| // Returns true if the shortcut point to chrome and expected app id is |
| // successfully derived. |
| bool GetExpectedAppId(IShellLink* shell_link, |
| std::wstring* expected_app_id) const; |
| |
| // Get app id associated with given shortcut. |
| bool GetShortcutAppId(IShellLink* shell_link, std::wstring* app_id) const; |
| |
| FilePath chrome_exe_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MigrateChromiumShortcutsTask); |
| }; |
| |
| void MigrateChromiumShortcutsTask::Run() { |
| // This should run on the file thread. |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| MigrateWin7Shortcuts(); |
| } |
| |
| void MigrateChromiumShortcutsTask::MigrateWin7Shortcuts() { |
| // Get full path of chrome. |
| if (!PathService::Get(base::FILE_EXE, &chrome_exe_)) |
| return; |
| |
| // Locations to check for shortcuts migration. |
| static const struct { |
| int location_id; |
| const wchar_t* sub_dir; |
| } kLocations[] = { |
| { |
| base::DIR_APP_DATA, |
| L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" |
| }, { |
| chrome::DIR_USER_DESKTOP, |
| NULL |
| }, { |
| base::DIR_START_MENU, |
| NULL |
| }, { |
| base::DIR_APP_DATA, |
| L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\StartMenu" |
| } |
| }; |
| |
| for (int i = 0; i < arraysize(kLocations); ++i) { |
| FilePath path; |
| if (!PathService::Get(kLocations[i].location_id, &path)) { |
| NOTREACHED(); |
| continue; |
| } |
| |
| if (kLocations[i].sub_dir) |
| path = path.Append(kLocations[i].sub_dir); |
| |
| MigrateWin7ShortcutsInPath(path); |
| } |
| } |
| |
| void MigrateChromiumShortcutsTask::MigrateWin7ShortcutsInPath( |
| const FilePath& path) const { |
| // Enumerate all pinned shortcuts in the given path directly. |
| file_util::FileEnumerator shortcuts_enum( |
| path, |
| false, // not recursive |
| file_util::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.lnk")); |
| |
| for (FilePath shortcut = shortcuts_enum.Next(); !shortcut.empty(); |
| shortcut = shortcuts_enum.Next()) { |
| // Load the shortcut. |
| base::win::ScopedComPtr<IShellLink> shell_link; |
| if (FAILED(shell_link.CreateInstance(CLSID_ShellLink, |
| NULL, |
| CLSCTX_INPROC_SERVER))) { |
| NOTREACHED(); |
| return; |
| } |
| |
| base::win::ScopedComPtr<IPersistFile> persist_file; |
| if (FAILED(persist_file.QueryFrom(shell_link)) || |
| FAILED(persist_file->Load(shortcut.value().c_str(), STGM_READ))) { |
| NOTREACHED(); |
| return; |
| } |
| |
| // Get expected app id from shortcut. |
| std::wstring expected_app_id; |
| if (!GetExpectedAppId(shell_link, &expected_app_id) || |
| expected_app_id.empty()) |
| continue; |
| |
| // Get existing app id from shortcut if any. |
| std::wstring existing_app_id; |
| GetShortcutAppId(shell_link, &existing_app_id); |
| |
| if (expected_app_id != existing_app_id) { |
| file_util::UpdateShortcutLink(NULL, |
| shortcut.value().c_str(), |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| 0, |
| expected_app_id.c_str()); |
| } |
| } |
| } |
| |
| bool MigrateChromiumShortcutsTask::GetExpectedAppId( |
| IShellLink* shell_link, |
| std::wstring* expected_app_id) const { |
| DCHECK(shell_link); |
| DCHECK(expected_app_id); |
| |
| expected_app_id->clear(); |
| |
| // Check if the shortcut points to chrome_exe. |
| std::wstring source; |
| if (FAILED(shell_link->GetPath(WriteInto(&source, MAX_PATH), |
| MAX_PATH, |
| NULL, |
| SLGP_RAWPATH)) || |
| lstrcmpi(chrome_exe_.value().c_str(), source.c_str())) |
| return false; |
| |
| std::wstring arguments; |
| if (FAILED(shell_link->GetArguments(WriteInto(&arguments, MAX_PATH), |
| MAX_PATH))) |
| return false; |
| |
| // Get expected app id from shortcut command line. |
| CommandLine command_line = CommandLine::FromString(StringPrintf( |
| L"\"%ls\" %ls", source.c_str(), arguments.c_str())); |
| |
| FilePath profile_path; |
| if (command_line.HasSwitch(switches::kUserDataDir)) { |
| profile_path = |
| command_line.GetSwitchValuePath(switches::kUserDataDir).AppendASCII( |
| chrome::kNotSignedInProfile); |
| } |
| |
| std::wstring app_name; |
| if (command_line.HasSwitch(switches::kApp)) { |
| app_name = UTF8ToWide(web_app::GenerateApplicationNameFromURL( |
| GURL(command_line.GetSwitchValueASCII(switches::kApp)))); |
| } else if (command_line.HasSwitch(switches::kAppId)) { |
| app_name = UTF8ToWide(web_app::GenerateApplicationNameFromExtensionId( |
| command_line.GetSwitchValueASCII(switches::kAppId))); |
| } else { |
| app_name = BrowserDistribution::GetDistribution()->GetBrowserAppId(); |
| } |
| |
| expected_app_id->assign(ShellIntegration::GetAppId(app_name, profile_path)); |
| return true; |
| } |
| |
| bool MigrateChromiumShortcutsTask::GetShortcutAppId( |
| IShellLink* shell_link, |
| std::wstring* app_id) const { |
| DCHECK(shell_link); |
| DCHECK(app_id); |
| |
| app_id->clear(); |
| |
| base::win::ScopedComPtr<IPropertyStore> property_store; |
| if (FAILED(property_store.QueryFrom(shell_link))) |
| return false; |
| |
| PROPVARIANT appid_value; |
| PropVariantInit(&appid_value); |
| if (FAILED(property_store->GetValue(PKEY_AppUserModel_ID, |
| &appid_value))) |
| return false; |
| |
| if (appid_value.vt == VT_LPWSTR || |
| appid_value.vt == VT_BSTR) |
| app_id->assign(appid_value.pwszVal); |
| |
| PropVariantClear(&appid_value); |
| return true; |
| } |
| |
| }; |
| |
| bool ShellIntegration::SetAsDefaultBrowser() { |
| FilePath chrome_exe; |
| if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { |
| LOG(ERROR) << "Error getting app exe path"; |
| return false; |
| } |
| |
| // From UI currently we only allow setting default browser for current user. |
| BrowserDistribution* dist = BrowserDistribution::GetDistribution(); |
| if (!ShellUtil::MakeChromeDefault(dist, ShellUtil::CURRENT_USER, |
| chrome_exe.value(), true)) { |
| LOG(ERROR) << "Chrome could not be set as default browser."; |
| return false; |
| } |
| |
| VLOG(1) << "Chrome registered as default browser."; |
| return true; |
| } |
| |
| ShellIntegration::DefaultBrowserState ShellIntegration::IsDefaultBrowser() { |
| // First determine the app path. If we can't determine what that is, we have |
| // bigger fish to fry... |
| FilePath app_path; |
| if (!PathService::Get(base::FILE_EXE, &app_path)) { |
| LOG(ERROR) << "Error getting app exe path"; |
| return UNKNOWN_DEFAULT_BROWSER; |
| } |
| // When we check for default browser we don't necessarily want to count file |
| // type handlers and icons as having changed the default browser status, |
| // since the user may have changed their shell settings to cause HTML files |
| // to open with a text editor for example. We also don't want to aggressively |
| // claim FTP, since the user may have a separate FTP client. It is an open |
| // question as to how to "heal" these settings. Perhaps the user should just |
| // re-run the installer or run with the --set-default-browser command line |
| // flag. There is doubtless some other key we can hook into to cause "Repair" |
| // to show up in Add/Remove programs for us. |
| const std::wstring kChromeProtocols[] = {L"http", L"https"}; |
| |
| if (base::win::GetVersion() >= base::win::VERSION_VISTA) { |
| IApplicationAssociationRegistration* pAAR; |
| HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, |
| NULL, CLSCTX_INPROC, __uuidof(IApplicationAssociationRegistration), |
| (void**)&pAAR); |
| if (!SUCCEEDED(hr)) |
| return NOT_DEFAULT_BROWSER; |
| |
| BrowserDistribution* dist = BrowserDistribution::GetDistribution(); |
| std::wstring app_name = dist->GetApplicationName(); |
| // If a user specific default browser entry exists, we check for that |
| // app name being default. If not, then default browser is just called |
| // Google Chrome or Chromium so we do not append suffix to app name. |
| std::wstring suffix; |
| if (ShellUtil::GetUserSpecificDefaultBrowserSuffix(dist, &suffix)) |
| app_name += suffix; |
| |
| for (int i = 0; i < _countof(kChromeProtocols); i++) { |
| BOOL result = TRUE; |
| hr = pAAR->QueryAppIsDefault(kChromeProtocols[i].c_str(), AT_URLPROTOCOL, |
| AL_EFFECTIVE, app_name.c_str(), &result); |
| if (!SUCCEEDED(hr) || result == FALSE) { |
| pAAR->Release(); |
| return NOT_DEFAULT_BROWSER; |
| } |
| } |
| pAAR->Release(); |
| } else { |
| std::wstring short_app_path; |
| GetShortPathName(app_path.value().c_str(), |
| WriteInto(&short_app_path, MAX_PATH), |
| MAX_PATH); |
| |
| // open command for protocol associations |
| for (int i = 0; i < _countof(kChromeProtocols); i++) { |
| // Check in HKEY_CLASSES_ROOT that is the result of merge between |
| // HKLM and HKCU |
| HKEY root_key = HKEY_CLASSES_ROOT; |
| // Check <protocol>\shell\open\command |
| std::wstring key_path(kChromeProtocols[i] + ShellUtil::kRegShellOpen); |
| base::win::RegKey key(root_key, key_path.c_str(), KEY_READ); |
| std::wstring value; |
| if (!key.Valid() || (key.ReadValue(L"", &value) != ERROR_SUCCESS)) |
| return NOT_DEFAULT_BROWSER; |
| // Need to normalize path in case it's been munged. |
| CommandLine command_line = CommandLine::FromString(value); |
| std::wstring short_path; |
| GetShortPathName(command_line.GetProgram().value().c_str(), |
| WriteInto(&short_path, MAX_PATH), MAX_PATH); |
| if (!FilePath::CompareEqualIgnoreCase(short_path, short_app_path)) |
| return NOT_DEFAULT_BROWSER; |
| } |
| } |
| return IS_DEFAULT_BROWSER; |
| } |
| |
| // There is no reliable way to say which browser is default on a machine (each |
| // browser can have some of the protocols/shortcuts). So we look for only HTTP |
| // protocol handler. Even this handler is located at different places in |
| // registry on XP and Vista: |
| // - HKCR\http\shell\open\command (XP) |
| // - HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\ |
| // http\UserChoice (Vista) |
| // This method checks if Firefox is defualt browser by checking these |
| // locations and returns true if Firefox traces are found there. In case of |
| // error (or if Firefox is not found)it returns the default value which |
| // is false. |
| bool ShellIntegration::IsFirefoxDefaultBrowser() { |
| bool ff_default = false; |
| if (base::win::GetVersion() >= base::win::VERSION_VISTA) { |
| std::wstring app_cmd; |
| base::win::RegKey key(HKEY_CURRENT_USER, |
| ShellUtil::kRegVistaUrlPrefs, KEY_READ); |
| if (key.Valid() && (key.ReadValue(L"Progid", &app_cmd) == ERROR_SUCCESS) && |
| app_cmd == L"FirefoxURL") |
| ff_default = true; |
| } else { |
| std::wstring key_path(L"http"); |
| key_path.append(ShellUtil::kRegShellOpen); |
| base::win::RegKey key(HKEY_CLASSES_ROOT, key_path.c_str(), KEY_READ); |
| std::wstring app_cmd; |
| if (key.Valid() && (key.ReadValue(L"", &app_cmd) == ERROR_SUCCESS) && |
| std::wstring::npos != StringToLowerASCII(app_cmd).find(L"firefox")) |
| ff_default = true; |
| } |
| return ff_default; |
| } |
| |
| std::wstring ShellIntegration::GetAppId(const std::wstring& app_name, |
| const FilePath& profile_path) { |
| std::wstring app_id(app_name); |
| |
| std::wstring profile_id(GetProfileIdFromPath(profile_path)); |
| if (!profile_id.empty()) { |
| app_id += L"."; |
| app_id += profile_id; |
| } |
| |
| // App id should be less than 128 chars. |
| DCHECK(app_id.length() < 128); |
| return app_id; |
| } |
| |
| std::wstring ShellIntegration::GetChromiumAppId(const FilePath& profile_path) { |
| return GetAppId(BrowserDistribution::GetDistribution()->GetBrowserAppId(), |
| profile_path); |
| } |
| |
| void ShellIntegration::MigrateChromiumShortcuts() { |
| if (base::win::GetVersion() < base::win::VERSION_WIN7) |
| return; |
| |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, new MigrateChromiumShortcutsTask()); |
| } |