add hardening, zoom and useragent options

This commit is contained in:
2026-10-17 10:35:14 +02:00
parent ead23646b2
commit 495a0696ba
15 changed files with 724 additions and 70 deletions
+179
View File
@@ -0,0 +1,179 @@
#include "useragent.h"
#include <QWebEngineClientHints>
#include <QWebEngineProfile>
#include <QtWebEngineCore/qtwebenginecoreglobal.h>
namespace useragent {
namespace {
// Gecko and WebKit versions can't come from the engine, so they are pinned
// here; bump them now and then so att doesn't look like an outdated browser.
constexpr auto kFirefoxVersion = "155.0";
constexpr auto kSafariVersion = "27.0";
// Chrome-based presets claim the engine's own Chromium major version, as
// qutebrowser does: a newer claim would make sites use features the engine
// lacks. Chrome's reduced UA string zeroes the minor parts.
QString chromeVersion()
{
return QString::fromLatin1(qWebEngineChromiumVersion()).section(QLatin1Char('.'), 0, 0)
+ QStringLiteral(".0.0.0");
}
// What Sec-CH-UA-* (and navigator.userAgentData) should say, so they don't
// contradict the User-Agent string. Chromium always sends the low-entropy ones
// (brands, platform, mobile), even with client hints disabled, so every preset
// sets a matching platform. Firefox and Safari send no hints at all, so for
// them the high-entropy ones are disabled.
struct Hints {
bool enabled;
QString brand; // "Google Chrome" etc., next to Chromium's own brands
QString platform;
QString platformVersion;
QString arch;
QString bitness;
bool mobile;
};
// The full version list for a brand: Chrome sends its own brand next to
// Chromium's (and a GREASE "Not A Brand"); an empty brand gives the engine's.
QVariantMap brands(const QString &brand)
{
const QString full = QString::fromLatin1(qWebEngineChromiumVersion());
QVariantMap list{{QStringLiteral("Chromium"), full},
{QStringLiteral("Not=A?Brand"), QStringLiteral("24.0.0.0")}};
if (!brand.isEmpty())
list.insert(brand, full);
return list;
}
struct Entry {
Preset preset;
Hints hints;
};
QList<Entry> buildEntries()
{
const QString chrome = chromeVersion();
const QString firefox = QString::fromLatin1(kFirefoxVersion);
const QString safari = QString::fromLatin1(kSafariVersion);
const auto chromeOn = [&](const QString &platform) {
return QStringLiteral("Mozilla/5.0 (%1) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/%2 Safari/537.36")
.arg(platform, chrome);
};
const auto firefoxOn = [&](const QString &platform) {
return QStringLiteral("Mozilla/5.0 (%1; rv:%2) Gecko/20100101 Firefox/%2")
.arg(platform, firefox);
};
return {
{{QStringLiteral("chrome-linux"), chromeOn(QStringLiteral("X11; Linux x86_64"))},
{true, QStringLiteral("Google Chrome"), QStringLiteral("Linux"), QString(),
QStringLiteral("x86"), QStringLiteral("64"), false}},
{{QStringLiteral("chrome-windows"),
chromeOn(QStringLiteral("Windows NT 10.0; Win64; x64"))},
{true, QStringLiteral("Google Chrome"), QStringLiteral("Windows"),
QStringLiteral("19.0.0"), QStringLiteral("x86"), QStringLiteral("64"), false}},
{{QStringLiteral("chrome-mac"),
chromeOn(QStringLiteral("Macintosh; Intel Mac OS X 10_15_7"))},
{true, QStringLiteral("Google Chrome"), QStringLiteral("macOS"),
QStringLiteral("15.0.0"), QStringLiteral("arm"), QStringLiteral("64"), false}},
{{QStringLiteral("chrome-android"),
QStringLiteral("Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 "
"(KHTML, like Gecko) "
"Chrome/%1 Mobile Safari/537.36")
.arg(chrome)},
{true, QStringLiteral("Google Chrome"), QStringLiteral("Android"),
QStringLiteral("15.0.0"), QString(), QString(), true}},
{{QStringLiteral("edge-windows"),
chromeOn(QStringLiteral("Windows NT 10.0; Win64; x64"))
+ QStringLiteral(" Edg/%1").arg(chrome)},
{true, QStringLiteral("Microsoft Edge"), QStringLiteral("Windows"),
QStringLiteral("19.0.0"), QStringLiteral("x86"), QStringLiteral("64"), false}},
{{QStringLiteral("firefox-linux"), firefoxOn(QStringLiteral("X11; Linux x86_64"))},
{false, {}, QStringLiteral("Linux"), {}, {}, {}, false}},
{{QStringLiteral("firefox-windows"),
firefoxOn(QStringLiteral("Windows NT 10.0; Win64; x64"))},
{false, {}, QStringLiteral("Windows"), {}, {}, {}, false}},
{{QStringLiteral("firefox-mac"),
firefoxOn(QStringLiteral("Macintosh; Intel Mac OS X 10.15"))},
{false, {}, QStringLiteral("macOS"), {}, {}, {}, false}},
{{QStringLiteral("safari-mac"),
QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/605.1.15 "
"(KHTML, like Gecko) Version/%1 Safari/605.1.15")
.arg(safari)},
{false, {}, QStringLiteral("macOS"), {}, {}, {}, false}},
{{QStringLiteral("safari-iphone"),
QStringLiteral("Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) "
"AppleWebKit/605.1.15 "
"(KHTML, like Gecko) Version/%1 Mobile/15E148 Safari/604.1")
.arg(safari)},
{false, {}, QStringLiteral("iOS"), {}, {}, {}, true}},
// QtWebEngine's own string, which names it and gets some sites to
// refuse or degrade service.
{{QStringLiteral("qtwebengine"),
QStringLiteral("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) "
"QtWebEngine/%1 Chrome/%2 Safari/537.36")
.arg(QString::fromLatin1(qVersion()),
QString::fromLatin1(qWebEngineChromiumVersion()))},
{true, {}, QStringLiteral("Linux"), QString(), QStringLiteral("x86"),
QStringLiteral("64"), false}},
};
}
const QList<Entry> &entries()
{
static const QList<Entry> all = buildEntries();
return all;
}
QString &currentName()
{
static QString name;
return name;
}
} // namespace
const QList<Preset> &presets()
{
static const QList<Preset> all = [] {
QList<Preset> list;
for (const Entry &entry : entries())
list.append(entry.preset);
return list;
}();
return all;
}
bool apply(QWebEngineProfile *profile, const QString &name)
{
for (const Entry &entry : entries()) {
if (entry.preset.name != name)
continue;
profile->setHttpUserAgent(entry.preset.userAgent);
QWebEngineClientHints *hints = profile->clientHints();
hints->setAllClientHintsEnabled(entry.hints.enabled);
hints->setPlatform(entry.hints.platform);
hints->setPlatformVersion(entry.hints.platformVersion);
hints->setArch(entry.hints.arch);
hints->setBitness(entry.hints.bitness);
hints->setIsMobile(entry.hints.mobile);
hints->setFullVersionList(brands(entry.hints.brand));
currentName() = name;
return true;
}
return false;
}
QString current()
{
return currentName();
}
} // namespace useragent