Backed out 4 changesets (Bug 1703578). r=bytesized
Differential Revision: https://phabricator.services.mozilla.com/D114553
This commit is contained in:
@@ -260,12 +260,6 @@ pref("browser.shell.mostRecentDateSetAsDefault", "");
|
||||
pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
|
||||
pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false);
|
||||
pref("browser.shell.defaultBrowserCheckCount", 0);
|
||||
#if defined(XP_WIN)
|
||||
// Attempt to set the default browser on Windows 10 using the UserChoice registry keys,
|
||||
// before falling back to launching the modern Settings dialog.
|
||||
pref("browser.shell.setDefaultBrowserUserChoice", false);
|
||||
#endif
|
||||
|
||||
|
||||
// 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
|
||||
// The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
|
||||
|
||||
@@ -19,18 +19,6 @@ ChromeUtils.defineModuleGetter(
|
||||
"resource://gre/modules/WindowsRegistry.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Subprocess: "resource://gre/modules/Subprocess.jsm",
|
||||
setTimeout: "resource://gre/modules/Timer.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"XreDirProvider",
|
||||
"@mozilla.org/xre/directory-provider;1",
|
||||
"nsIXREDirProvider"
|
||||
);
|
||||
|
||||
/**
|
||||
* Internal functionality to save and restore the docShell.allow* properties.
|
||||
*/
|
||||
@@ -124,114 +112,6 @@ let ShellServiceInternal = {
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Set the default browser through the UserChoice registry keys on Windows.
|
||||
*
|
||||
* NOTE: This does NOT open the System Settings app for manual selection
|
||||
* in case of failure. If that is desired, catch the exception and call
|
||||
* setDefaultBrowser().
|
||||
*
|
||||
* @return Promise, resolves when successful, rejects with Error on failure.
|
||||
*/
|
||||
async setAsDefaultUserChoice() {
|
||||
if (AppConstants.platform != "win") {
|
||||
throw new Error("Windows-only");
|
||||
}
|
||||
|
||||
// We launch the WDBA to handle the registry writes, see
|
||||
// SetDefaultBrowserUserChoice() in
|
||||
// toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp.
|
||||
// This is external in case an overzealous antimalware product decides to
|
||||
// quarrantine any program that writes UserChoice, though this has not
|
||||
// occurred during extensive testing.
|
||||
|
||||
let telemetryResult = "ErrOther";
|
||||
|
||||
try {
|
||||
if (!ShellService.checkAllProgIDsExist()) {
|
||||
telemetryResult = "ErrProgID";
|
||||
throw new Error("checkAllProgIDsExist() failed");
|
||||
}
|
||||
|
||||
if (!ShellService.checkBrowserUserChoiceHashes()) {
|
||||
telemetryResult = "ErrHash";
|
||||
throw new Error("checkBrowserUserChoiceHashes() failed");
|
||||
}
|
||||
|
||||
const wdba = Services.dirsvc.get("XREExeF", Ci.nsIFile);
|
||||
wdba.leafName = "default-browser-agent.exe";
|
||||
const aumi = XreDirProvider.getInstallHash();
|
||||
|
||||
telemetryResult = "ErrLaunchExe";
|
||||
const exeProcess = await Subprocess.call({
|
||||
command: wdba.path,
|
||||
arguments: ["set-default-browser-user-choice", aumi],
|
||||
});
|
||||
telemetryResult = "ErrOther";
|
||||
|
||||
// Exit codes, see toolkit/mozapps/defaultagent/SetDefaultBrowser.h
|
||||
const S_OK = 0;
|
||||
const STILL_ACTIVE = 0x103;
|
||||
const MOZ_E_NO_PROGID = 0xa0000001;
|
||||
const MOZ_E_HASH_CHECK = 0xa0000002;
|
||||
const MOZ_E_REJECTED = 0xa0000003;
|
||||
|
||||
const exeWaitTimeoutMs = 2000; // 2 seconds
|
||||
const exeWaitPromise = exeProcess.wait();
|
||||
const timeoutPromise = new Promise(function(resolve, reject) {
|
||||
setTimeout(() => resolve({ exitCode: STILL_ACTIVE }), exeWaitTimeoutMs);
|
||||
});
|
||||
const { exitCode } = await Promise.race([exeWaitPromise, timeoutPromise]);
|
||||
|
||||
if (exitCode != S_OK) {
|
||||
telemetryResult =
|
||||
new Map([
|
||||
[STILL_ACTIVE, "ErrExeTimeout"],
|
||||
[MOZ_E_NO_PROGID, "ErrExeProgID"],
|
||||
[MOZ_E_HASH_CHECK, "ErrExeHash"],
|
||||
[MOZ_E_REJECTED, "ErrExeRejected"],
|
||||
]).get(exitCode) ?? "ErrExeOther";
|
||||
throw new Error(
|
||||
`WDBA nonzero exit code ${exitCode}: ${telemetryResult}`
|
||||
);
|
||||
}
|
||||
|
||||
telemetryResult = "Success";
|
||||
} finally {
|
||||
try {
|
||||
const histogram = Services.telemetry.getHistogramById(
|
||||
"BROWSER_SET_DEFAULT_USER_CHOICE_RESULT"
|
||||
);
|
||||
histogram.add(telemetryResult);
|
||||
} catch (ex) {}
|
||||
}
|
||||
},
|
||||
|
||||
// override nsIShellService.setDefaultBrowser() on the ShellService proxy.
|
||||
setDefaultBrowser(claimAllTypes, forAllUsers) {
|
||||
// On Windows 10, our best chance is to set UserChoice, so try that first.
|
||||
if (
|
||||
AppConstants.platform == "win" &&
|
||||
Services.prefs.getBoolPref(
|
||||
"browser.shell.setDefaultBrowserUserChoice",
|
||||
false
|
||||
) &&
|
||||
Services.sysinfo.getProperty("version") == "10.0"
|
||||
) {
|
||||
// nsWindowsShellService::SetDefaultBrowser() kicks off several
|
||||
// operations, but doesn't wait for their result. So we don't need to
|
||||
// await the result of setAsDefaultUserChoice() here, either, we just need
|
||||
// to fall back in case it fails.
|
||||
this.setAsDefaultUserChoice().catch(err => {
|
||||
Cu.reportError(err);
|
||||
this.shellService.setDefaultBrowser(claimAllTypes, forAllUsers);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.shellService.setDefaultBrowser(claimAllTypes, forAllUsers);
|
||||
},
|
||||
|
||||
setAsDefault() {
|
||||
let claimAllTypes = true;
|
||||
let setAsDefaultError = false;
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Generate and check the UserChoice Hash, which protects file and protocol
|
||||
* associations on Windows 10.
|
||||
*
|
||||
* NOTE: This is also used in the WDBA, so it avoids XUL and XPCOM.
|
||||
*
|
||||
* References:
|
||||
* - PS-SFTA by Danysys <https://github.com/DanysysTeam/PS-SFTA>
|
||||
* - based on a PureBasic version by LMongrain
|
||||
* <https://github.com/DanysysTeam/SFTA>
|
||||
* - AssocHashGen by "halfmeasuresdisabled", see bug 1225660 and
|
||||
* <https://www.reddit.com/r/ReverseEngineering/comments/3t7q9m/assochashgen_a_reverse_engineered_version_of/>
|
||||
* - SetUserFTA changelog
|
||||
* <https://kolbi.cz/blog/2017/10/25/setuserfta-userchoice-hash-defeated-set-file-type-associations-per-user/>
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <sddl.h> // for ConvertSidToStringSidW
|
||||
#include <wincrypt.h> // for CryptoAPI base64
|
||||
#include <bcrypt.h> // for CNG MD5
|
||||
#include <winternl.h> // for NT_SUCCESS()
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#include "WindowsUserChoice.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
UniquePtr<wchar_t[]> GetCurrentUserStringSid() {
|
||||
HANDLE rawProcessToken;
|
||||
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY,
|
||||
&rawProcessToken)) {
|
||||
return nullptr;
|
||||
}
|
||||
nsAutoHandle processToken(rawProcessToken);
|
||||
|
||||
DWORD userSize = 0;
|
||||
if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0,
|
||||
&userSize) &&
|
||||
GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto userBytes = MakeUnique<unsigned char[]>(userSize);
|
||||
if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.get(),
|
||||
userSize, &userSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
wchar_t* rawSid = nullptr;
|
||||
if (!::ConvertSidToStringSidW(
|
||||
reinterpret_cast<PTOKEN_USER>(userBytes.get())->User.Sid, &rawSid)) {
|
||||
return nullptr;
|
||||
}
|
||||
UniquePtr<wchar_t, LocalFreeDeleter> sid(rawSid);
|
||||
|
||||
// Copy instead of passing UniquePtr<wchar_t, LocalFreeDeleter> back to
|
||||
// the caller.
|
||||
int sidLen = ::lstrlenW(sid.get()) + 1;
|
||||
auto outSid = MakeUnique<wchar_t[]>(sidLen);
|
||||
memcpy(outSid.get(), sid.get(), sidLen * sizeof(wchar_t));
|
||||
|
||||
return outSid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the string which becomes the input to the UserChoice hash.
|
||||
*
|
||||
* @see GenerateUserChoiceHash() for parameters.
|
||||
*
|
||||
* @return The formatted string, nullptr on failure.
|
||||
*
|
||||
* NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing),
|
||||
* used at least since 1803.
|
||||
* There was at least one older version, not currently supported: On Win10 RTM
|
||||
* (build 10240, aka 1507) the hash function is the same, but the timestamp and
|
||||
* User Experience string aren't included; instead (for protocols) the string
|
||||
* ends with the exe path. The changelog of SetUserFTA suggests the algorithm
|
||||
* changed in 1703, so there may be two versions: before 1703, and 1703 to now.
|
||||
*/
|
||||
static UniquePtr<wchar_t[]> FormatUserChoiceString(const wchar_t* aExt,
|
||||
const wchar_t* aUserSid,
|
||||
const wchar_t* aProgId,
|
||||
SYSTEMTIME aTimestamp) {
|
||||
aTimestamp.wSecond = 0;
|
||||
aTimestamp.wMilliseconds = 0;
|
||||
|
||||
FILETIME fileTime = {0};
|
||||
if (!::SystemTimeToFileTime(&aTimestamp, &fileTime)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This string is built into Windows as part of the UserChoice hash algorithm.
|
||||
// It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or
|
||||
// across builds of the same SKU, but this is the only currently known
|
||||
// version. There isn't any known way of deriving it, so we assume this
|
||||
// constant value. If we are wrong, we will not be able to generate correct
|
||||
// UserChoice hashes.
|
||||
const wchar_t* userExperience =
|
||||
L"User Choice set via Windows User Experience "
|
||||
L"{D18B6DD5-6124-4341-9318-804003BAFA0B}";
|
||||
|
||||
const wchar_t* userChoiceFmt =
|
||||
L"%s%s%s"
|
||||
L"%08lx"
|
||||
L"%08lx"
|
||||
L"%s";
|
||||
int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId,
|
||||
fileTime.dwHighDateTime,
|
||||
fileTime.dwLowDateTime, userExperience);
|
||||
userChoiceLen += 1; // _scwprintf does not include the terminator
|
||||
|
||||
auto userChoice = MakeUnique<wchar_t[]>(userChoiceLen);
|
||||
_snwprintf_s(userChoice.get(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt,
|
||||
aUserSid, aProgId, fileTime.dwHighDateTime,
|
||||
fileTime.dwLowDateTime, userExperience);
|
||||
|
||||
::CharLowerW(userChoice.get());
|
||||
|
||||
return userChoice;
|
||||
}
|
||||
|
||||
// @return The MD5 hash of the input, nullptr on failure.
|
||||
static UniquePtr<DWORD[]> CNG_MD5(const unsigned char* bytes, ULONG bytesLen) {
|
||||
constexpr ULONG MD5_BYTES = 16;
|
||||
constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD);
|
||||
UniquePtr<DWORD[]> hash;
|
||||
|
||||
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||
if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM,
|
||||
nullptr, 0))) {
|
||||
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||
// As of Windows 7 the hash handle will manage its own object buffer when
|
||||
// pbHashObject is nullptr and cbHashObject is 0.
|
||||
if (NT_SUCCESS(
|
||||
::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) {
|
||||
// BCryptHashData promises not to modify pbInput.
|
||||
if (NT_SUCCESS(::BCryptHashData(hHash, const_cast<unsigned char*>(bytes),
|
||||
bytesLen, 0))) {
|
||||
hash = MakeUnique<DWORD[]>(MD5_DWORDS);
|
||||
if (!NT_SUCCESS(::BCryptFinishHash(
|
||||
hHash, reinterpret_cast<unsigned char*>(hash.get()),
|
||||
MD5_DWORDS * sizeof(DWORD), 0))) {
|
||||
hash.reset();
|
||||
}
|
||||
}
|
||||
::BCryptDestroyHash(hHash);
|
||||
}
|
||||
::BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
// @return The input bytes encoded as base64, nullptr on failure.
|
||||
static UniquePtr<wchar_t[]> CryptoAPI_Base64Encode(const unsigned char* bytes,
|
||||
DWORD bytesLen) {
|
||||
DWORD base64Len = 0;
|
||||
if (!::CryptBinaryToStringW(bytes, bytesLen,
|
||||
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
|
||||
nullptr, &base64Len)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto base64 = MakeUnique<wchar_t[]>(base64Len);
|
||||
if (!::CryptBinaryToStringW(bytes, bytesLen,
|
||||
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
|
||||
base64.get(), &base64Len)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return base64;
|
||||
}
|
||||
|
||||
static inline DWORD WordSwap(DWORD v) { return (v >> 16) | (v << 16); }
|
||||
|
||||
/*
|
||||
* Generate the UserChoice Hash.
|
||||
*
|
||||
* This implementation is based on the references listed above.
|
||||
* It is organized to show the logic as clearly as possible, but at some
|
||||
* point the reasoning is just "this is how it works".
|
||||
*
|
||||
* @param inputString A null-terminated string to hash.
|
||||
*
|
||||
* @return The base64-encoded hash, or nullptr on failure.
|
||||
*/
|
||||
static UniquePtr<wchar_t[]> HashString(const wchar_t* inputString) {
|
||||
auto inputBytes = reinterpret_cast<const unsigned char*>(inputString);
|
||||
int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t);
|
||||
|
||||
constexpr size_t DWORDS_PER_BLOCK = 2;
|
||||
constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK;
|
||||
// Incomplete blocks are ignored.
|
||||
int blockCount = inputByteCount / BLOCK_SIZE;
|
||||
|
||||
if (blockCount == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers
|
||||
// in the scramble below.
|
||||
auto md5 = CNG_MD5(inputBytes, inputByteCount);
|
||||
if (!md5) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The following loop effectively computes two checksums, scrambled like a
|
||||
// hash after every DWORD is added.
|
||||
|
||||
// Constant multipliers for the scramble, one set for each DWORD in a block.
|
||||
const DWORD C0s[DWORDS_PER_BLOCK][5] = {
|
||||
{md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL},
|
||||
{md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL}};
|
||||
const DWORD C1s[DWORDS_PER_BLOCK][5] = {
|
||||
{md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL},
|
||||
{md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL}};
|
||||
|
||||
// The checksums.
|
||||
DWORD h0 = 0;
|
||||
DWORD h1 = 0;
|
||||
// Accumulated total of the checksum after each DWORD.
|
||||
DWORD h0Acc = 0;
|
||||
DWORD h1Acc = 0;
|
||||
|
||||
for (int i = 0; i < blockCount; ++i) {
|
||||
for (int j = 0; j < DWORDS_PER_BLOCK; ++j) {
|
||||
const DWORD* C0 = C0s[j];
|
||||
const DWORD* C1 = C1s[j];
|
||||
|
||||
DWORD input;
|
||||
memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)],
|
||||
sizeof(DWORD));
|
||||
|
||||
h0 += input;
|
||||
// Scramble 0
|
||||
h0 *= C0[0];
|
||||
h0 = WordSwap(h0) * C0[1];
|
||||
h0 = WordSwap(h0) * C0[2];
|
||||
h0 = WordSwap(h0) * C0[3];
|
||||
h0 = WordSwap(h0) * C0[4];
|
||||
h0Acc += h0;
|
||||
|
||||
h1 += input;
|
||||
// Scramble 1
|
||||
h1 = WordSwap(h1) * C1[1] + h1 * C1[0];
|
||||
h1 = (h1 >> 16) * C1[2] + h1 * C1[3];
|
||||
h1 = WordSwap(h1) * C1[4] + h1;
|
||||
h1Acc += h1;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD hash[2] = {h0 ^ h1, h0Acc ^ h1Acc};
|
||||
|
||||
return CryptoAPI_Base64Encode(reinterpret_cast<const unsigned char*>(hash),
|
||||
sizeof(hash));
|
||||
}
|
||||
|
||||
UniquePtr<wchar_t[]> GetAssociationKeyPath(const wchar_t* aExt) {
|
||||
const wchar_t* keyPathFmt;
|
||||
if (aExt[0] == L'.') {
|
||||
keyPathFmt =
|
||||
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s";
|
||||
} else {
|
||||
keyPathFmt =
|
||||
L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\"
|
||||
L"UrlAssociations\\%s";
|
||||
}
|
||||
|
||||
int keyPathLen = _scwprintf(keyPathFmt, aExt);
|
||||
keyPathLen += 1; // _scwprintf does not include the terminator
|
||||
|
||||
auto keyPath = MakeUnique<wchar_t[]>(keyPathLen);
|
||||
_snwprintf_s(keyPath.get(), keyPathLen, _TRUNCATE, keyPathFmt, aExt);
|
||||
|
||||
return keyPath;
|
||||
}
|
||||
|
||||
UniquePtr<wchar_t[]> GenerateUserChoiceHash(const wchar_t* aExt,
|
||||
const wchar_t* aUserSid,
|
||||
const wchar_t* aProgId,
|
||||
SYSTEMTIME aTimestamp) {
|
||||
auto userChoice = FormatUserChoiceString(aExt, aUserSid, aProgId, aTimestamp);
|
||||
if (!userChoice) {
|
||||
return nullptr;
|
||||
}
|
||||
return HashString(userChoice.get());
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: The passed-in current user SID is used here, instead of getting the SID
|
||||
* for the owner of the key. We are assuming that this key in HKCU is owned by
|
||||
* the current user, since we want to replace that key ourselves. If the key is
|
||||
* owned by someone else, then this check will fail; this is ok because we would
|
||||
* likely not want to replace that other user's key anyway.
|
||||
*/
|
||||
CheckUserChoiceHashResult CheckUserChoiceHash(const wchar_t* aExt,
|
||||
const wchar_t* aUserSid) {
|
||||
auto keyPath = GetAssociationKeyPath(aExt);
|
||||
if (!keyPath) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
|
||||
HKEY rawAssocKey;
|
||||
if (::RegOpenKeyExW(HKEY_CURRENT_USER, keyPath.get(), 0, KEY_READ,
|
||||
&rawAssocKey) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
nsAutoRegKey assocKey(rawAssocKey);
|
||||
|
||||
FILETIME lastWriteFileTime;
|
||||
{
|
||||
HKEY rawUserChoiceKey;
|
||||
if (::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ,
|
||||
&rawUserChoiceKey) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
nsAutoRegKey userChoiceKey(rawUserChoiceKey);
|
||||
|
||||
if (::RegQueryInfoKeyW(userChoiceKey.get(), nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, &lastWriteFileTime) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
SYSTEMTIME lastWriteSystemTime;
|
||||
if (!::FileTimeToSystemTime(&lastWriteFileTime, &lastWriteSystemTime)) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
|
||||
// Read ProgId
|
||||
DWORD dataSizeBytes = 0;
|
||||
if (::RegGetValueW(assocKey.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ,
|
||||
nullptr, nullptr, &dataSizeBytes) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
// +1 in case dataSizeBytes was odd, +1 to ensure termination
|
||||
DWORD dataSizeChars = (dataSizeBytes / sizeof(wchar_t)) + 2;
|
||||
UniquePtr<wchar_t[]> progId(new wchar_t[dataSizeChars]());
|
||||
if (::RegGetValueW(assocKey.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ,
|
||||
nullptr, progId.get(), &dataSizeBytes) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
|
||||
// Read Hash
|
||||
dataSizeBytes = 0;
|
||||
if (::RegGetValueW(assocKey.get(), L"UserChoice", L"Hash", RRF_RT_REG_SZ,
|
||||
nullptr, nullptr, &dataSizeBytes) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
dataSizeChars = (dataSizeBytes / sizeof(wchar_t)) + 2;
|
||||
UniquePtr<wchar_t[]> storedHash(new wchar_t[dataSizeChars]());
|
||||
if (::RegGetValueW(assocKey.get(), L"UserChoice", L"Hash", RRF_RT_REG_SZ,
|
||||
nullptr, storedHash.get(),
|
||||
&dataSizeBytes) != ERROR_SUCCESS) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
|
||||
auto computedHash =
|
||||
GenerateUserChoiceHash(aExt, aUserSid, progId.get(), lastWriteSystemTime);
|
||||
if (!computedHash) {
|
||||
return CheckUserChoiceHashResult::ERR_OTHER;
|
||||
}
|
||||
|
||||
if (::CompareStringOrdinal(computedHash.get(), -1, storedHash.get(), -1,
|
||||
FALSE) != CSTR_EQUAL) {
|
||||
return CheckUserChoiceHashResult::ERR_MISMATCH;
|
||||
}
|
||||
|
||||
return CheckUserChoiceHashResult::OK_V1;
|
||||
}
|
||||
|
||||
bool CheckBrowserUserChoiceHashes() {
|
||||
auto userSid = GetCurrentUserStringSid();
|
||||
if (!userSid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t* exts[] = {L"https", L"http", L".html", L".htm"};
|
||||
|
||||
for (int i = 0; i < ArrayLength(exts); ++i) {
|
||||
switch (CheckUserChoiceHash(exts[i], userSid.get())) {
|
||||
case CheckUserChoiceHashResult::OK_V1:
|
||||
break;
|
||||
case CheckUserChoiceHashResult::ERR_MISMATCH:
|
||||
case CheckUserChoiceHashResult::ERR_OTHER:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UniquePtr<wchar_t[]> FormatProgID(const wchar_t* aProgIDBase,
|
||||
const wchar_t* aAumi) {
|
||||
const wchar_t* progIDFmt = L"%s-%s";
|
||||
int progIDLen = _scwprintf(progIDFmt, aProgIDBase, aAumi);
|
||||
progIDLen += 1; // _scwprintf does not include the terminator
|
||||
|
||||
auto progID = MakeUnique<wchar_t[]>(progIDLen);
|
||||
_snwprintf_s(progID.get(), progIDLen, _TRUNCATE, progIDFmt, aProgIDBase,
|
||||
aAumi);
|
||||
|
||||
return progID;
|
||||
}
|
||||
|
||||
bool CheckProgIDExists(const wchar_t* aProgID) {
|
||||
HKEY key;
|
||||
if (::RegOpenKeyExW(HKEY_CLASSES_ROOT, aProgID, 0, KEY_READ, &key) !=
|
||||
ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
::RegCloseKey(key);
|
||||
return true;
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef SHELL_WINDOWSUSERCHOICE_H__
|
||||
#define SHELL_WINDOWSUSERCHOICE_H__
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
/*
|
||||
* Check the UserChoice Hashes for https, http, .html, .htm
|
||||
*
|
||||
* This should be checked before attempting to set a new default browser via
|
||||
* the UserChoice key, to confirm our understanding of the existing hash.
|
||||
* If an incorrect hash is written, Windows will prompt the user to choose a
|
||||
* new default (or, in recent versions, it will just reset the default to Edge).
|
||||
*
|
||||
* Assuming that the existing hash value is correct (since Windows is fairly
|
||||
* diligent about replacing bad keys), if we can recompute it from scratch,
|
||||
* then we should be able to compute a correct hash for our new UserChoice key.
|
||||
*
|
||||
* @return true if we matched all the hashes, false otherwise.
|
||||
*/
|
||||
bool CheckBrowserUserChoiceHashes();
|
||||
|
||||
/*
|
||||
* Result from CheckUserChoiceHash()
|
||||
*
|
||||
* NOTE: Currently the only positive result is OK_V1 , but the enum
|
||||
* could be extended to indicate different versions of the hash.
|
||||
*/
|
||||
enum class CheckUserChoiceHashResult {
|
||||
OK_V1, // Matched the current version of the hash (as of Win10 20H2).
|
||||
ERR_MISMATCH, // The hash did not match.
|
||||
ERR_OTHER, // Error reading or generating the hash.
|
||||
};
|
||||
|
||||
/*
|
||||
* Generate a UserChoice Hash, compare it with the one that is stored.
|
||||
*
|
||||
* See comments on CheckBrowserUserChoiceHashes(), which calls this to check
|
||||
* each of the browser associations.
|
||||
*
|
||||
* @param aExt File extension or protocol association to check
|
||||
* @param aUserSid String SID of the current user
|
||||
*
|
||||
* @return Result of the check, see CheckUserChoiceHashResult
|
||||
*/
|
||||
CheckUserChoiceHashResult CheckUserChoiceHash(const wchar_t* aExt,
|
||||
const wchar_t* aUserSid);
|
||||
|
||||
/*
|
||||
* Get the registry path for the given association, file extension or protocol.
|
||||
*
|
||||
* @return The path, or nullptr on failure.
|
||||
*/
|
||||
mozilla::UniquePtr<wchar_t[]> GetAssociationKeyPath(const wchar_t* aExt);
|
||||
|
||||
/*
|
||||
* Get the current user's SID
|
||||
*
|
||||
* @return String SID for the user of the current process, nullptr on failure.
|
||||
*/
|
||||
mozilla::UniquePtr<wchar_t[]> GetCurrentUserStringSid();
|
||||
|
||||
/*
|
||||
* Generate the UserChoice Hash
|
||||
*
|
||||
* @param aExt file extension or protocol being registered
|
||||
* @param aUserSid string SID of the current user
|
||||
* @param aProgId ProgId to associate with aExt
|
||||
* @param aTimestamp approximate write time of the UserChoice key (within
|
||||
* the same minute)
|
||||
*
|
||||
* @return UserChoice Hash, nullptr on failure.
|
||||
*/
|
||||
mozilla::UniquePtr<wchar_t[]> GenerateUserChoiceHash(const wchar_t* aExt,
|
||||
const wchar_t* aUserSid,
|
||||
const wchar_t* aProgId,
|
||||
SYSTEMTIME aTimestamp);
|
||||
|
||||
/*
|
||||
* Build a ProgID from a base and AUMI
|
||||
*
|
||||
* @param aProgIDBase A base, such as FirefoxHTML or FirefoxURL
|
||||
* @param aAumi The AUMI of the installation
|
||||
*
|
||||
* @return Formatted ProgID.
|
||||
*/
|
||||
mozilla::UniquePtr<wchar_t[]> FormatProgID(const wchar_t* aProgIDBase,
|
||||
const wchar_t* aAumi);
|
||||
|
||||
/*
|
||||
* Check that the given ProgID exists in HKCR
|
||||
*
|
||||
* @return true if it could be opened for reading, false otherwise.
|
||||
*/
|
||||
bool CheckProgIDExists(const wchar_t* aProgID);
|
||||
|
||||
#endif // SHELL_WINDOWSUSERCHOICE_H__
|
||||
@@ -51,14 +51,11 @@ elif CONFIG["OS_ARCH"] == "WINNT":
|
||||
SOURCES += [
|
||||
"nsWindowsShellService.cpp",
|
||||
"WindowsDefaultBrowser.cpp",
|
||||
"WindowsUserChoice.cpp",
|
||||
]
|
||||
LOCAL_INCLUDES += [
|
||||
"../../../other-licenses/nsis/Contrib/CityHash/cityhash",
|
||||
]
|
||||
OS_LIBS += [
|
||||
"bcrypt",
|
||||
"crypt32",
|
||||
"propsys",
|
||||
]
|
||||
|
||||
@@ -73,12 +70,7 @@ EXTRA_JS_MODULES += [
|
||||
"ShellService.jsm",
|
||||
]
|
||||
|
||||
for var in (
|
||||
"MOZ_APP_DISPLAYNAME",
|
||||
"MOZ_APP_NAME",
|
||||
"MOZ_APP_VERSION",
|
||||
"MOZ_DEFAULT_BROWSER_AGENT",
|
||||
):
|
||||
for var in ("MOZ_APP_DISPLAYNAME", "MOZ_APP_NAME", "MOZ_APP_VERSION"):
|
||||
DEFINES[var] = '"%s"' % CONFIG[var]
|
||||
|
||||
CXXFLAGS += CONFIG["TK_CFLAGS"]
|
||||
|
||||
@@ -89,23 +89,4 @@ interface nsIWindowsShellService : nsISupports
|
||||
* for noncritical telemetry use.
|
||||
*/
|
||||
AString classifyShortcut(in AString aPath);
|
||||
|
||||
/*
|
||||
* Check if setDefaultBrowserUserChoice() is expected to succeed.
|
||||
*
|
||||
* This checks the ProgIDs for this installation, and the hash of the existing
|
||||
* UserChoice association.
|
||||
*
|
||||
* @return true if the check succeeds, false otherwise.
|
||||
*/
|
||||
bool canSetDefaultBrowserUserChoice();
|
||||
|
||||
/*
|
||||
* checkAllProgIDsExist() and checkBrowserUserChoiceHashes() are components
|
||||
* of canSetDefaultBrowserUserChoice(), broken out for telemetry purposes.
|
||||
*
|
||||
* @return true if the check succeeds, false otherwise.
|
||||
*/
|
||||
bool checkAllProgIDsExist();
|
||||
bool checkBrowserUserChoiceHashes();
|
||||
};
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "WindowsDefaultBrowser.h"
|
||||
#include "WindowsUserChoice.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
@@ -232,39 +231,6 @@ nsresult nsWindowsShellService::LaunchControlPanelDefaultPrograms() {
|
||||
return ::LaunchControlPanelDefaultPrograms() ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWindowsShellService::CheckAllProgIDsExist(bool* aResult) {
|
||||
*aResult = false;
|
||||
nsAutoString aumid;
|
||||
if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) {
|
||||
return NS_OK;
|
||||
}
|
||||
*aResult =
|
||||
CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) &&
|
||||
CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWindowsShellService::CheckBrowserUserChoiceHashes(bool* aResult) {
|
||||
*aResult = ::CheckBrowserUserChoiceHashes();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWindowsShellService::CanSetDefaultBrowserUserChoice(bool* aResult) {
|
||||
*aResult = false;
|
||||
// If the WDBA is not available, this could never succeed.
|
||||
#ifdef MOZ_DEFAULT_BROWSER_AGENT
|
||||
bool progIDsExist = false;
|
||||
bool hashOk = false;
|
||||
*aResult = NS_SUCCEEDED(CheckAllProgIDsExist(&progIDsExist)) &&
|
||||
progIDsExist &&
|
||||
NS_SUCCEEDED(CheckBrowserUserChoiceHashes(&hashOk)) && hashOk;
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() {
|
||||
return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@@ -11761,18 +11761,6 @@
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "True if the browser was unable to set Firefox as the default browser"
|
||||
},
|
||||
"BROWSER_SET_DEFAULT_USER_CHOICE_RESULT": {
|
||||
"record_in_processes": ["main"],
|
||||
"products": ["firefox"],
|
||||
"operating_systems": ["windows"],
|
||||
"expires_in_version": "96",
|
||||
"kind": "categorical",
|
||||
"labels": ["Success", "ErrProgID", "ErrHash", "ErrLaunchExe", "ErrExeTimeout", "ErrExeProgID", "ErrExeHash", "ErrExeRejected", "ErrExeOther", "ErrOther"],
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"bug_numbers": [1703578],
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com", "agashlin@mozilla.com"],
|
||||
"description": "Result of each attempt to set the default browser with SetDefaultBrowserUserChoice()"
|
||||
},
|
||||
"BROWSER_IS_ASSIST_DEFAULT": {
|
||||
"record_in_processes": ["main"],
|
||||
"products": ["firefox", "fennec"],
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlobj.h> // for SHChangeNotify and IApplicationAssociationRegistration
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
#include "WindowsUserChoice.h"
|
||||
|
||||
#include "EventLog.h"
|
||||
#include "SetDefaultBrowser.h"
|
||||
|
||||
static bool AddMillisecondsToSystemTime(SYSTEMTIME& aSystemTime,
|
||||
ULONGLONG aIncrementMS) {
|
||||
FILETIME fileTime;
|
||||
ULARGE_INTEGER fileTimeInt;
|
||||
if (!::SystemTimeToFileTime(&aSystemTime, &fileTime)) {
|
||||
return false;
|
||||
}
|
||||
fileTimeInt.LowPart = fileTime.dwLowDateTime;
|
||||
fileTimeInt.HighPart = fileTime.dwHighDateTime;
|
||||
|
||||
// FILETIME is in units of 100ns.
|
||||
fileTimeInt.QuadPart += aIncrementMS * 1000 * 10;
|
||||
|
||||
fileTime.dwLowDateTime = fileTimeInt.LowPart;
|
||||
fileTime.dwHighDateTime = fileTimeInt.HighPart;
|
||||
SYSTEMTIME tmpSystemTime;
|
||||
if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aSystemTime = tmpSystemTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
|
||||
// below minutes.
|
||||
static bool CheckEqualMinutes(SYSTEMTIME aSystemTime1,
|
||||
SYSTEMTIME aSystemTime2) {
|
||||
aSystemTime1.wSecond = 0;
|
||||
aSystemTime1.wMilliseconds = 0;
|
||||
|
||||
aSystemTime2.wSecond = 0;
|
||||
aSystemTime2.wMilliseconds = 0;
|
||||
|
||||
FILETIME fileTime1;
|
||||
FILETIME fileTime2;
|
||||
if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) ||
|
||||
!::SystemTimeToFileTime(&aSystemTime2, &fileTime2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) &&
|
||||
(fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set an association with a UserChoice key
|
||||
*
|
||||
* Removes the old key, creates a new one with ProgID and Hash set to
|
||||
* enable a new asociation.
|
||||
*
|
||||
* @param aExt File type or protocol to associate
|
||||
* @param aSid Current user's string SID
|
||||
* @param aProgID ProgID to use for the asociation
|
||||
*
|
||||
* @return true if successful, false on error.
|
||||
*/
|
||||
static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
|
||||
const wchar_t* aProgID) {
|
||||
SYSTEMTIME hashTimestamp;
|
||||
::GetSystemTime(&hashTimestamp);
|
||||
auto hash = GenerateUserChoiceHash(aExt, aSid, aProgID, hashTimestamp);
|
||||
if (!hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The hash changes at the end of each minute, so check that the hash should
|
||||
// be the same by the time we're done writing.
|
||||
const ULONGLONG kWriteTimingThresholdMilliseconds = 100;
|
||||
// Generating the hash could have taken some time, so start from now.
|
||||
SYSTEMTIME writeEndTimestamp;
|
||||
::GetSystemTime(&writeEndTimestamp);
|
||||
if (!AddMillisecondsToSystemTime(writeEndTimestamp,
|
||||
kWriteTimingThresholdMilliseconds)) {
|
||||
return false;
|
||||
}
|
||||
if (!CheckEqualMinutes(hashTimestamp, writeEndTimestamp)) {
|
||||
LOG_ERROR_MESSAGE(
|
||||
L"Hash is too close to expiration, sleeping until next hash.");
|
||||
::Sleep(kWriteTimingThresholdMilliseconds * 2);
|
||||
|
||||
// For consistency, use the current time.
|
||||
::GetSystemTime(&hashTimestamp);
|
||||
hash = GenerateUserChoiceHash(aExt, aSid, aProgID, hashTimestamp);
|
||||
if (!hash) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto assocKeyPath = GetAssociationKeyPath(aExt);
|
||||
if (!assocKeyPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LSTATUS ls;
|
||||
HKEY rawAssocKey;
|
||||
ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.get(), 0,
|
||||
KEY_READ | KEY_WRITE, &rawAssocKey);
|
||||
if (ls != ERROR_SUCCESS) {
|
||||
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
||||
return false;
|
||||
}
|
||||
nsAutoRegKey assocKey(rawAssocKey);
|
||||
|
||||
// When Windows creates this key, it is read-only (Deny Set Value), so we need
|
||||
// to delete it first.
|
||||
// We don't set any similar special permissions.
|
||||
ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice");
|
||||
if (ls != ERROR_SUCCESS) {
|
||||
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY rawUserChoiceKey;
|
||||
ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr,
|
||||
0 /* options */, KEY_READ | KEY_WRITE,
|
||||
0 /* security attributes */, &rawUserChoiceKey,
|
||||
nullptr);
|
||||
if (ls != ERROR_SUCCESS) {
|
||||
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
||||
return false;
|
||||
}
|
||||
nsAutoRegKey userChoiceKey(rawUserChoiceKey);
|
||||
|
||||
DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t);
|
||||
ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ,
|
||||
reinterpret_cast<const unsigned char*>(aProgID),
|
||||
progIdByteCount);
|
||||
if (ls != ERROR_SUCCESS) {
|
||||
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD hashByteCount = (::lstrlenW(hash.get()) + 1) * sizeof(wchar_t);
|
||||
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ,
|
||||
reinterpret_cast<const unsigned char*>(hash.get()),
|
||||
hashByteCount);
|
||||
if (ls != ERROR_SUCCESS) {
|
||||
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VerifyUserDefault(const wchar_t* aExt, const wchar_t* aProgID) {
|
||||
RefPtr<IApplicationAssociationRegistration> pAAR;
|
||||
HRESULT hr = ::CoCreateInstance(
|
||||
CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC,
|
||||
IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR));
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR(hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t* rawRegisteredApp;
|
||||
bool isProtocol = aExt[0] != L'.';
|
||||
// Note: Checks AL_USER instead of AL_EFFECTIVE.
|
||||
hr = pAAR->QueryCurrentDefault(aExt,
|
||||
isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION,
|
||||
AL_USER, &rawRegisteredApp);
|
||||
if (FAILED(hr)) {
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) {
|
||||
LOG_ERROR_MESSAGE(L"UserChoice ProgID %s for %s was rejected", aProgID,
|
||||
aExt);
|
||||
} else {
|
||||
LOG_ERROR(hr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
mozilla::UniquePtr<wchar_t, mozilla::CoTaskMemFreeDeleter> registeredApp(
|
||||
rawRegisteredApp);
|
||||
|
||||
if (::CompareStringOrdinal(registeredApp.get(), -1, aProgID, -1, FALSE) !=
|
||||
CSTR_EQUAL) {
|
||||
LOG_ERROR_MESSAGE(
|
||||
L"Default was %s after writing ProgID %s to UserChoice for %s",
|
||||
registeredApp.get(), aProgID, aExt);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HRESULT SetDefaultBrowserUserChoice(const wchar_t* aAumi) {
|
||||
auto urlProgID = FormatProgID(L"FirefoxURL", aAumi);
|
||||
if (!CheckProgIDExists(urlProgID.get())) {
|
||||
LOG_ERROR_MESSAGE(L"ProgID %s not found", urlProgID.get());
|
||||
return MOZ_E_NO_PROGID;
|
||||
}
|
||||
|
||||
auto htmlProgID = FormatProgID(L"FirefoxHTML", aAumi);
|
||||
if (!CheckProgIDExists(htmlProgID.get())) {
|
||||
LOG_ERROR_MESSAGE(L"ProgID %s not found", htmlProgID.get());
|
||||
return MOZ_E_NO_PROGID;
|
||||
}
|
||||
|
||||
if (!CheckBrowserUserChoiceHashes()) {
|
||||
LOG_ERROR_MESSAGE(L"UserChoice Hash mismatch");
|
||||
return MOZ_E_HASH_CHECK;
|
||||
}
|
||||
|
||||
auto sid = GetCurrentUserStringSid();
|
||||
if (!sid) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
bool defaultRejected = false;
|
||||
|
||||
struct {
|
||||
const wchar_t* ext;
|
||||
const wchar_t* progID;
|
||||
} associations[] = {{L"https", urlProgID.get()},
|
||||
{L"http", urlProgID.get()},
|
||||
{L".html", htmlProgID.get()},
|
||||
{L".htm", htmlProgID.get()}};
|
||||
for (int i = 0; i < mozilla::ArrayLength(associations); ++i) {
|
||||
if (!SetUserChoice(associations[i].ext, sid.get(),
|
||||
associations[i].progID)) {
|
||||
ok = false;
|
||||
break;
|
||||
} else if (!VerifyUserDefault(associations[i].ext,
|
||||
associations[i].progID)) {
|
||||
defaultRejected = true;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify shell to refresh icons
|
||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
|
||||
if (!ok) {
|
||||
LOG_ERROR_MESSAGE(L"Failed setting default with %s", aAumi);
|
||||
if (defaultRejected) {
|
||||
return MOZ_E_REJECTED;
|
||||
}
|
||||
return E_FAIL;
|
||||
} else {
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DEFAULT_BROWSER_SET_DEFAULT_BROWSER_H__
|
||||
#define DEFAULT_BROWSER_SET_DEFAULT_BROWSER_H__
|
||||
|
||||
/*
|
||||
* Set the default browser by writing the UserChoice registry keys.
|
||||
*
|
||||
* This sets the associations for https, http, .html, and .htm.
|
||||
*
|
||||
* When the agent is run with set-default-browser-user-choice,
|
||||
* the exit code is the result of this function.
|
||||
*
|
||||
* @param aAumi The AUMI of the installation to set as default.
|
||||
*
|
||||
* @return S_OK All associations set and checked successfully.
|
||||
* MOZ_E_NO_PROGID The ProgID classes had not been registered.
|
||||
* MOZ_E_HASH_CHECK The existing UserChoice Hash could not be verified.
|
||||
* MOZ_E_REJECTED UserChoice was set, but checking the default
|
||||
* did not return our ProgID.
|
||||
* E_FAIL other failure
|
||||
*/
|
||||
HRESULT SetDefaultBrowserUserChoice(const wchar_t* aAumi);
|
||||
|
||||
/*
|
||||
* Additional HRESULT error codes from SetDefaultBrowserUserChoice
|
||||
*
|
||||
* 0x20000000 is set to put these in the customer-defined range.
|
||||
*/
|
||||
const HRESULT MOZ_E_NO_PROGID = 0xa0000001L;
|
||||
const HRESULT MOZ_E_HASH_CHECK = 0xa0000002L;
|
||||
const HRESULT MOZ_E_REJECTED = 0xa0000003L;
|
||||
|
||||
#endif // DEFAULT_BROWSER_SET_DEFAULT_BROWSER_H__
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "Registry.h"
|
||||
#include "RemoteSettings.h"
|
||||
#include "ScheduledTask.h"
|
||||
#include "SetDefaultBrowser.h"
|
||||
#include "Telemetry.h"
|
||||
|
||||
// The AGENT_REGKEY_NAME is dependent on MOZ_APP_VENDOR and MOZ_APP_BASENAME,
|
||||
@@ -251,8 +250,6 @@ static bool CheckIfAppRanRecently(bool* aResult) {
|
||||
// Actually performs the default agent task, which currently means generating
|
||||
// and sending our telemetry ping and possibly showing a notification to the
|
||||
// user if their browser has switched from Firefox to Edge with Blink.
|
||||
// set-default-browser-user-choice [app-user-model-id]
|
||||
// Set the default browser via the UserChoice registry keys.
|
||||
int wmain(int argc, wchar_t** argv) {
|
||||
if (argc < 2 || !argv[1]) {
|
||||
return E_INVALIDARG;
|
||||
@@ -385,12 +382,6 @@ int wmain(int argc, wchar_t** argv) {
|
||||
MaybeShowNotification(browserInfo, argv[2]);
|
||||
|
||||
return SendDefaultBrowserPing(browserInfo, activitiesPerformed);
|
||||
} else if (!wcscmp(argv[1], L"set-default-browser-user-choice")) {
|
||||
if (argc < 3 || !argv[2]) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
return SetDefaultBrowserUserChoice(argv[2]);
|
||||
} else {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
@@ -23,13 +23,11 @@ UNIFIED_SOURCES += [
|
||||
"Registry.cpp",
|
||||
"RemoteSettings.cpp",
|
||||
"ScheduledTask.cpp",
|
||||
"SetDefaultBrowser.cpp",
|
||||
"Telemetry.cpp",
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
"/browser/components/shell/WindowsDefaultBrowser.cpp",
|
||||
"/browser/components/shell/WindowsUserChoice.cpp",
|
||||
"/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
|
||||
"/third_party/WinToast/wintoastlib.cpp",
|
||||
"/toolkit/mozapps/update/common/readstrings.cpp",
|
||||
@@ -52,8 +50,6 @@ LOCAL_INCLUDES += [
|
||||
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"bcrypt",
|
||||
"crypt32",
|
||||
"kernel32",
|
||||
"netapi32",
|
||||
"ole32",
|
||||
@@ -73,9 +69,9 @@ DEFINES["IMPL_MFBT"] = True
|
||||
DEFINES["UNICODE"] = True
|
||||
DEFINES["_UNICODE"] = True
|
||||
|
||||
# If defines are added to this list that are required by the Cache,
|
||||
# SetDefaultBrowser, or their dependencies (Registry, EventLog, common),
|
||||
# tests/gtest/moz.build will need to be updated as well.
|
||||
# If defines are added to this list that are required by the Cache or its
|
||||
# dependencies (Registry, EventLog, common), tests/gtest/moz.build will need to
|
||||
# be updated as well.
|
||||
for var in ("MOZ_APP_BASENAME", "MOZ_APP_DISPLAYNAME", "MOZ_APP_VENDOR"):
|
||||
DEFINES[var] = '"%s"' % CONFIG[var]
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "WindowsUserChoice.h"
|
||||
|
||||
#include "SetDefaultBrowser.h"
|
||||
|
||||
TEST(SetDefaultBrowserUserChoice, Hash)
|
||||
{
|
||||
// Hashes set by System Settings on 64-bit Windows 10 Pro 20H2 (19042.928).
|
||||
const wchar_t* sid = L"S-1-5-21-636376821-3290315252-1794850287-1001";
|
||||
|
||||
// length mod 8 = 0
|
||||
EXPECT_STREQ(
|
||||
GenerateUserChoiceHash(L"https", sid, L"FirefoxURL-308046B0AF4A39CB",
|
||||
(SYSTEMTIME){2021, 4, 1, 19, 23, 7, 56, 506})
|
||||
.get(),
|
||||
L"uzpIsMVyZ1g=");
|
||||
|
||||
// length mod 8 = 2 (confirm that the incomplete last block is dropped)
|
||||
EXPECT_STREQ(
|
||||
GenerateUserChoiceHash(L".html", sid, L"FirefoxHTML-308046B0AF4A39CB",
|
||||
(SYSTEMTIME){2021, 4, 1, 19, 23, 7, 56, 519})
|
||||
.get(),
|
||||
L"7fjRtUPASlc=");
|
||||
|
||||
// length mod 8 = 4
|
||||
EXPECT_STREQ(
|
||||
GenerateUserChoiceHash(L"https", sid, L"MSEdgeHTM",
|
||||
(SYSTEMTIME){2021, 4, 1, 19, 23, 3, 48, 119})
|
||||
.get(),
|
||||
L"Fz0kA3Ymmps=");
|
||||
|
||||
// length mod 8 = 6
|
||||
EXPECT_STREQ(
|
||||
GenerateUserChoiceHash(L".html", sid, L"ChromeHTML",
|
||||
(SYSTEMTIME){2021, 4, 1, 19, 23, 6, 3, 628})
|
||||
.get(),
|
||||
L"R5TD9LGJ5Xw=");
|
||||
|
||||
// non-ASCII
|
||||
EXPECT_STREQ(
|
||||
GenerateUserChoiceHash(L".html", sid, L"FirefoxHTML-ÀBÇDË😀†",
|
||||
(SYSTEMTIME){2021, 4, 2, 20, 0, 38, 55, 101})
|
||||
.get(),
|
||||
L"F3NsK3uNv5E=");
|
||||
}
|
||||
@@ -15,18 +15,14 @@ UNIFIED_SOURCES += [
|
||||
"../../EventLog.cpp",
|
||||
"../../Registry.cpp",
|
||||
"CacheTest.cpp",
|
||||
"SetDefaultBrowserTest.cpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/browser/components/shell/",
|
||||
"/toolkit/mozapps/defaultagent",
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"bcrypt",
|
||||
"crypt32",
|
||||
"kernel32",
|
||||
"rpcrt4",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user