Skip to content

Commit dabf55e

Browse files
committed
Client Hints: Improve version lists
There are several issues affecting Sec-CH-UA and -Full-Version-List, because the general algorithm which extends and shuffles these lists was not used in WebEngine. At the current version of Chromium we can't use the corresponding user agent utils functions, because they only allow one extra brand/version pair besides the two generated (Chromium and Not-A-Brand) ones. This change implements a simplified method to extend and shuffle the version lists to follow the behavior of Chromium and support more than three elements. QTBUG-133799: The order of brands and versions are still not customizable by the API user, but it is permutated in each major version of Chromium to comply with the standards. To allow customization of the order seems to be impossible without changing our public API. QTBUG-133708: The missing values of Sec-CH-UA are generated from the full version list, not customizable individually. This way it keeps the same values and the same algorithmically generated order as the full version list. Task-number: QTBUG-133711 Task-number: QTBUG-133708 Task-number: QTBUG-133777 Task-number: QTBUG-133799 Change-Id: I96f214ce54190666a34779130a04b56f600abef7 Reviewed-by: Peter Varga <[email protected]> Reviewed-by: Moss Heim <[email protected]>
1 parent 0e1b14e commit dabf55e

File tree

6 files changed

+86
-19
lines changed

6 files changed

+86
-19
lines changed

src/core/api/qwebengineclienthints.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,14 @@ QString QWebEngineClientHints::bitness() const
132132

133133
/*!
134134
\property QWebEngineClientHints::fullVersionList
135-
The value of the \c{Sec-CH-UA-Full-Version-List} HTTP header and \c{fullVersionList} member of NavigatorUAData in JavaScript.
135+
The value of the \c{Sec-CH-UA-Full-Version-List} HTTP header and \c{fullVersionList} member of
136+
NavigatorUAData in JavaScript.
136137
137-
It holds brand name and version number pairs in a QVariantMap. The provided values will be automatically extended by the currently used version
138-
of Chromium and a semi-random brand.
138+
The value of Sec-CH-UA header will also be generated from this by truncating the version
139+
numbers.
140+
141+
It holds brand name and version number pairs in a QVariantMap. The provided values will be
142+
automatically extended by the currently used version of Chromium and a semi-random brand.
139143
*/
140144
QVariantMap QWebEngineClientHints::fullVersionList() const
141145
{

src/core/profile_adapter.cpp

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "base/files/file_util.h"
77
#include "base/task/cancelable_task_tracker.h"
88
#include "base/threading/thread_restrictions.h"
9+
#include "base/version_info/version_info.h"
910
#include "components/embedder_support/user_agent_utils.h"
1011
#include "components/favicon/core/favicon_service.h"
1112
#include "components/history/content/browser/history_database_helper.h"
@@ -53,6 +54,36 @@ inline QString buildLocationFromStandardPath(const QString &standardPath, const
5354
location += "/QtWebEngine/"_L1 % name;
5455
return location;
5556
}
57+
58+
void PopulateBrandVersionLists(const QJsonObject &fullVersionList,
59+
blink::UserAgentMetadata &userAgentMetadata)
60+
{
61+
userAgentMetadata.brand_version_list.clear();
62+
userAgentMetadata.brand_full_version_list.clear();
63+
64+
for (const QString &key : fullVersionList.keys()) {
65+
std::string version = fullVersionList.value(key).toString().toStdString();
66+
userAgentMetadata.brand_full_version_list.push_back({ key.toStdString(), version });
67+
version = version.substr(0, version.find('.'));
68+
userAgentMetadata.brand_version_list.push_back({ key.toStdString(), version });
69+
}
70+
71+
// Shuffle the lists
72+
int permutations = 1;
73+
for (int i = 2; i <= fullVersionList.size(); i++)
74+
permutations *= i;
75+
// We keep the brand lists identical throughout the lifetime of each major version of Chromium.
76+
permutations = version_info::GetMajorVersionNumberAsInt() % permutations;
77+
auto compare = [](blink::UserAgentBrandVersion &a, blink::UserAgentBrandVersion &b) {
78+
return a.brand + a.version < b.brand + b.version;
79+
};
80+
for (int i = 0; i < permutations; i++) {
81+
std::next_permutation(userAgentMetadata.brand_version_list.begin(),
82+
userAgentMetadata.brand_version_list.end(), compare);
83+
std::next_permutation(userAgentMetadata.brand_full_version_list.begin(),
84+
userAgentMetadata.brand_full_version_list.end(), compare);
85+
}
86+
}
5687
}
5788

5889
namespace QtWebEngineCore {
@@ -713,13 +744,7 @@ void ProfileAdapter::setClientHint(ClientHint clientHint, const QVariant &value)
713744
userAgentMetadata.bitness = value.toString().toStdString();
714745
break;
715746
case ProfileAdapter::UAFullVersionList: {
716-
userAgentMetadata.brand_full_version_list.clear();
717-
QJsonObject fullVersionList = value.toJsonObject();
718-
for (const QString &key : fullVersionList.keys())
719-
userAgentMetadata.brand_full_version_list.push_back({
720-
key.toStdString(),
721-
fullVersionList.value(key).toString().toStdString()
722-
});
747+
PopulateBrandVersionLists(value.toJsonObject(), userAgentMetadata);
723748
break;
724749
}
725750
case ProfileAdapter::UAWOW64:
@@ -758,7 +783,7 @@ void ProfileAdapter::setClientHintsEnabled(bool enabled)
758783

759784
void ProfileAdapter::resetClientHints()
760785
{
761-
m_profile->m_userAgentMetadata = embedder_support::GetUserAgentMetadata();
786+
m_profile->initUserAgentMetadata();
762787
std::vector<content::WebContentsImpl *> list = content::WebContentsImpl::GetAllWebContents();
763788
for (content::WebContentsImpl *web_contents : list) {
764789
if (web_contents->GetBrowserContext() == m_profile.data()) {

src/core/profile_adapter.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ class Q_WEBENGINECORE_EXPORT ProfileAdapter : public QObject
200200
void setClientHintsEnabled(bool enabled);
201201
void resetClientHints();
202202

203-
204203
void clearHttpCache();
205204

206205
#if QT_CONFIG(ssl)

src/core/profile_qt.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "base/path_service.h"
2121
#include "base/files/file_util.h"
2222
#include "base/task/thread_pool.h"
23+
#include "base/version_info/version_info.h"
2324
#include "components/keyed_service/content/browser_context_dependency_manager.h"
2425
#include "components/prefs/pref_service.h"
2526
#include "components/user_prefs/user_prefs.h"
@@ -48,9 +49,7 @@ enum {
4849
};
4950

5051
ProfileQt::ProfileQt(ProfileAdapter *profileAdapter)
51-
: m_profileIOData(new ProfileIODataQt(this))
52-
, m_profileAdapter(profileAdapter)
53-
, m_userAgentMetadata(embedder_support::GetUserAgentMetadata())
52+
: m_profileIOData(new ProfileIODataQt(this)), m_profileAdapter(profileAdapter)
5453
{
5554
profile_metrics::SetBrowserProfileType(this, IsOffTheRecord()
5655
? profile_metrics::BrowserProfileType::kIncognito
@@ -68,6 +67,8 @@ ProfileQt::ProfileQt(ProfileAdapter *profileAdapter)
6867
#if BUILDFLAG(ENABLE_EXTENSIONS)
6968
static_cast<extensions::ExtensionSystemQt*>(extensions::ExtensionSystem::Get(this))->InitForRegularProfile(true);
7069
#endif
70+
71+
initUserAgentMetadata();
7172
}
7273

7374
ProfileQt::~ProfileQt()
@@ -300,6 +301,35 @@ const PrefServiceAdapter &ProfileQt::prefServiceAdapter() const
300301
return m_prefServiceAdapter;
301302
}
302303

304+
void ProfileQt::initUserAgentMetadata()
305+
{
306+
m_userAgentMetadata = embedder_support::GetUserAgentMetadata();
307+
m_userAgentMetadata.brand_version_list.clear();
308+
m_userAgentMetadata.brand_full_version_list.clear();
309+
310+
// Chromium version
311+
m_userAgentMetadata.brand_version_list.emplace_back(
312+
blink::UserAgentBrandVersion("Chromium", version_info::GetMajorVersionNumber()));
313+
m_userAgentMetadata.brand_full_version_list.emplace_back(blink::UserAgentBrandVersion(
314+
"Chromium", std::string(version_info::GetVersionNumber())));
315+
316+
// We keep the brand lists identical throughout the lifetime of each major version of Chromium.
317+
int seed = version_info::GetMajorVersionNumberAsInt();
318+
// Generate a greasey version
319+
const std::vector<std::vector<int>> orders{ { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 },
320+
{ 1, 2, 0 }, { 2, 0, 1 }, { 2, 1, 0 } };
321+
const std::vector<int> order = orders[seed % 6];
322+
m_userAgentMetadata.brand_version_list.emplace_back(
323+
embedder_support::GetGreasedUserAgentBrandVersion(
324+
order, seed, std::nullopt, std::nullopt, true,
325+
blink::UserAgentBrandVersionType::kMajorVersion));
326+
327+
m_userAgentMetadata.brand_full_version_list.emplace_back(
328+
embedder_support::GetGreasedUserAgentBrandVersion(
329+
order, seed, std::nullopt, std::nullopt, true,
330+
blink::UserAgentBrandVersionType::kFullVersion));
331+
}
332+
303333
const blink::UserAgentMetadata &ProfileQt::userAgentMetadata()
304334
{
305335
return m_userAgentMetadata;

src/core/profile_qt.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class ProfileQt : public Profile
7575
PrefServiceAdapter &prefServiceAdapter();
7676
const PrefServiceAdapter &prefServiceAdapter() const;
7777

78+
void initUserAgentMetadata();
7879
const blink::UserAgentMetadata &userAgentMetadata();
7980

8081
private:

tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5482,12 +5482,16 @@ void tst_QWebEnginePage::clientHints()
54825482
QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
54835483

54845484
QWebEngineClientHints *clientHints = page.profile()->clientHints();
5485+
// fullVersionList is extended by Chromium and a greased brand by default
5486+
QCOMPARE(clientHints->fullVersionList().size(), 2);
5487+
QVERIFY(clientHints->fullVersionList().contains("Chromium"));
54855488
clientHints->setAllClientHintsEnabled(clientHintsEnabled);
54865489

54875490
HttpServer server;
54885491
int requestCount = 0;
5489-
connect(&server, &HttpServer::newRequest, [&] (HttpReqRep *r) {
5490-
// Platform and Mobile hints are always sent and can't be disabled with this API
5492+
connect(&server, &HttpServer::newRequest, [&](HttpReqRep *r) {
5493+
// UA, Platform and Mobile hints are always sent and can't be disabled with this API
5494+
QVERIFY(r->hasRequestHeader("Sec-CH-UA"));
54915495
QVERIFY(r->hasRequestHeader("Sec-CH-UA-Platform"));
54925496
QVERIFY(r->hasRequestHeader("Sec-CH-UA-Mobile"));
54935497
if (!clientHintsEnabled) {
@@ -5512,9 +5516,10 @@ void tst_QWebEnginePage::clientHints()
55125516
QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Platform-Version")).remove("\""), platformVersion.toLower());
55135517
QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Bitness")).remove("\""), bitness.toLower());
55145518
QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Wow64")).remove("\""), isWOW64 ? "?1" : "?0");
5515-
for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i)
5519+
for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) {
5520+
QVERIFY(QString(r->requestHeader("Sec-CH-UA")).contains(i.key().toLower()));
55165521
QVERIFY(QString(r->requestHeader("Sec-CH-UA-Full-Version-List")).contains(i.key().toLower()));
5517-
5522+
}
55185523
for (auto formFactor : formFactors)
55195524
QVERIFY(QString(r->requestHeader("Sec-CH-UA-Form-Factors")).contains(formFactor.toLower()));
55205525
}
@@ -5549,8 +5554,10 @@ void tst_QWebEnginePage::clientHints()
55495554
QCOMPARE(clientHints->bitness(), bitness);
55505555
QCOMPARE(clientHints->isWow64(), isWOW64);
55515556
QCOMPARE(clientHints->formFactors(), formFactors);
5557+
QCOMPARE(clientHints->fullVersionList().size(), fullVersionList.size());
55525558
for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i)
55535559
QCOMPARE(clientHints->fullVersionList()[i.key()], i.value());
5560+
QVERIFY(!clientHints->fullVersionList().contains("Chromium"));
55545561

55555562
// A new user agent string should not override/disable client hints
55565563
page.profile()->setHttpUserAgent(QStringLiteral("Custom user agent"));
@@ -5571,6 +5578,7 @@ void tst_QWebEnginePage::clientHints()
55715578
QCOMPARE_NE(clientHints->platformVersion(), platformVersion);
55725579
QCOMPARE_NE(clientHints->bitness(), bitness);
55735580
QCOMPARE_NE(clientHints->formFactors(), formFactors);
5581+
QCOMPARE(clientHints->fullVersionList().size(), 2);
55745582
for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i)
55755583
QVERIFY(!clientHints->fullVersionList().contains(i.key()));
55765584
QVERIFY(clientHints->fullVersionList().contains("Chromium"));

0 commit comments

Comments
 (0)