9#include "QtCore/private/qipaddress_p.h"
10#include "QtCore/qlist.h"
12#if QT_CONFIG(settings)
13#include "qhstsstore_p.h"
18static bool is_valid_domain_name(
const QString &host)
28 using namespace QIPAddressUtils;
30 IPv4Address ipv4Addr = {};
31 if (parseIp4(ipv4Addr, host.constBegin(), host.constEnd()))
34 IPv6Address ipv6Addr = {};
37 if (!parseIp6(ipv6Addr, host.constBegin(), host.constEnd()))
46void QHstsCache::updateFromHeaders(
const QHttpHeaders &headers,
52 QHstsHeaderParser parser;
53 if (parser.parse(headers)) {
54 updateKnownHost(url.host(), parser.expirationDate(), parser.includeSubDomains());
55#if QT_CONFIG(settings)
57 hstsStore->synchronize();
62void QHstsCache::updateFromPolicies(
const QList<QHstsPolicy> &policies)
64 for (
const auto &policy : policies)
65 updateKnownHost(policy.host(), policy.expiry(), policy.includesSubDomains());
67#if QT_CONFIG(settings)
68 if (hstsStore && policies.size()) {
72 hstsStore->synchronize();
77void QHstsCache::updateKnownHost(
const QUrl &url,
const QDateTime &expires,
78 bool includeSubDomains)
83 updateKnownHost(url.host(), expires, includeSubDomains);
84#if QT_CONFIG(settings)
86 hstsStore->synchronize();
90void QHstsCache::updateKnownHost(
const QString &host,
const QDateTime &expires,
91 bool includeSubDomains)
93 if (!is_valid_domain_name(host))
99 const HostName hostName(host);
100 const auto pos = knownHosts.find(hostName);
101 QHstsPolicy::PolicyFlags flags;
102 if (includeSubDomains)
103 flags = QHstsPolicy::IncludeSubDomains;
105 const QHstsPolicy newPolicy(expires, flags, hostName.name);
106 if (pos == knownHosts.end()) {
108 if (newPolicy.isExpired()) {
114 knownHosts.insert({hostName, newPolicy});
115#if QT_CONFIG(settings)
117 hstsStore->addToObserved(newPolicy);
122 if (newPolicy.isExpired())
123 knownHosts.erase(pos);
124 else if (pos->second != newPolicy)
125 pos->second = newPolicy;
129#if QT_CONFIG(settings)
131 hstsStore->addToObserved(newPolicy);
135bool QHstsCache::isKnownHost(
const QUrl &url)
const
137 if (!url.isValid() || !is_valid_domain_name(url.host()))
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
161 bool superDomainMatch =
false;
162 const QString hostNameAsString(url.host());
163 HostName nameToTest(QStringView{hostNameAsString});
164 while (nameToTest.fragment.size()) {
165 auto const pos = knownHosts.find(nameToTest);
166 if (pos != knownHosts.end()) {
167 if (pos->second.isExpired()) {
168 knownHosts.erase(pos);
169#if QT_CONFIG(settings)
172 hstsStore->addToObserved(pos->second);
175 }
else if (!superDomainMatch || pos->second.includesSubDomains()) {
180 const qsizetype dot = nameToTest.fragment.indexOf(u'.');
184 nameToTest.fragment = nameToTest.fragment.mid(dot + 1);
185 superDomainMatch =
true;
191void QHstsCache::clear()
196QList<QHstsPolicy> QHstsCache::policies()
const
198 QList<QHstsPolicy> values;
199 values.reserve(
int(knownHosts.size()));
200 for (
const auto &host : knownHosts)
201 values << host.second;
205#if QT_CONFIG(settings)
206void QHstsCache::setStore(QHstsStore *store)
209 if (store != hstsStore) {
218 if (knownHosts.size()) {
219 const QList<QHstsPolicy> observed(policies());
220 for (
const auto &policy : observed)
221 hstsStore->addToObserved(policy);
222 hstsStore->synchronize();
229 const QList<QHstsPolicy> restored(store->readPolicies());
230 updateFromPolicies(restored);
248 return c >= 0 && c <= 127;
255 return (c >= 0 && c <= 31) || c == 127;
271 return c ==
' ' || c ==
'\t';
287 static const char separators[] =
"()<>@,;:\\\"/[]?={}";
288 static const char *end = separators +
sizeof separators - 1;
289 return isLWS(c) || std::find(separators, end, c) != end;
294 if (value.size() < 2 || value[0] !=
'"')
297 Q_ASSERT(value[value.size() - 1] ==
'"');
298 return value.mid(1, value.size() - 2);
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
330bool QHstsHeaderParser::parse(
const QHttpHeaders &headers)
332 for (
const auto &value : headers.values(
333 QHttpHeaders::WellKnownHeader::StrictTransportSecurity)) {
346 if (parseSTSHeader() && maxAgeFound) {
347 expiry = QDateTime::currentDateTimeUtc().addSecs(maxAge);
354 subDomainsFound =
false;
359bool QHstsHeaderParser::parseSTSHeader()
361 expiry = QDateTime();
363 subDomainsFound =
false;
368 while (tokenPos < header.size()) {
369 if (!parseDirective())
372 if (token.size() && token !=
";") {
382bool QHstsHeaderParser::parseDirective()
404 if (!isTOKEN(token.at(0)))
407 const QByteArray directiveName = token;
412 QByteArray directiveValue;
414 return processDirective(directiveName, directiveValue);
418 if (!nextToken() || !token.size())
420 directiveValue = token;
421 }
else if (token.size()) {
426 if (!processDirective(directiveName, directiveValue))
433bool QHstsHeaderParser::processDirective(
const QByteArray &name,
const QByteArray &value)
435 Q_ASSERT(name.size());
437 if (name.compare(
"max-age", Qt::CaseInsensitive) == 0) {
449 const QByteArrayView unescapedValue = unescapeMaxAge(value);
450 if (!unescapedValue.size())
454 const qint64 age = unescapedValue.toLongLong(&ok);
460 }
else if (name.compare(
"includesubdomains", Qt::CaseInsensitive) == 0) {
464 if (subDomainsFound) {
470 subDomainsFound =
true;
476bool QHstsHeaderParser::nextToken()
485 while (tokenPos < header.size() && isLWS(header.at(tokenPos)))
488 if (tokenPos == header.size())
491 const char ch = header.at(tokenPos);
492 if (ch ==
';' || ch ==
'=') {
503 int last = tokenPos + 1;
504 while (last < header.size()) {
505 if (header.at(last) ==
'"') {
508 }
else if (header.at(last) ==
'\\') {
510 if (last + 1 < header.size() && isCHAR(header.at(last + 1)))
515 if (!isTEXT(header.at(last)))
521 if (last >= header.size())
524 token = header.mid(tokenPos, last - tokenPos + 1);
535 int last = tokenPos + 1;
536 while (last < header.size() && isTOKEN(header.at(last)))
539 token = header.mid(tokenPos, last - tokenPos);
static QByteArrayView unescapeMaxAge(QByteArrayView value)
static bool isCHAR(int c)
static bool isTEXT(char c)
static bool isTOKEN(char c)
static bool isSeparator(char c)