#include "useragent.h" #include #include #include 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 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 &entries() { static const QList all = buildEntries(); return all; } QString ¤tName() { static QString name; return name; } } // namespace const QList &presets() { static const QList all = [] { QList 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