diff options
Diffstat (limited to 'src/network/access/qhsts.cpp')
-rw-r--r-- | src/network/access/qhsts.cpp | 180 |
1 files changed, 81 insertions, 99 deletions
diff --git a/src/network/access/qhsts.cpp b/src/network/access/qhsts.cpp index 2352c3e4f2..5e4f75b0ed 100644 --- a/src/network/access/qhsts.cpp +++ b/src/network/access/qhsts.cpp @@ -45,17 +45,8 @@ QT_BEGIN_NAMESPACE -static bool expired_policy(const QDateTime &expires) +static bool is_valid_domain_name(const QString &host) { - return !expires.isValid() || expires <= QDateTime::currentDateTimeUtc(); -} - -static bool has_valid_domain_name(const QUrl &url) -{ - if (!url.isValid()) - return false; - - const QString host(url.host()); if (!host.size()) return false; @@ -82,117 +73,106 @@ static bool has_valid_domain_name(const QUrl &url) return true; } -QHstsCache::QHstsCache() -{ - // Top-level domain without any label. - children.push_back(Domain()); -} - void QHstsCache::updateFromHeaders(const QList<QPair<QByteArray, QByteArray>> &headers, const QUrl &url) { - if (!has_valid_domain_name(url)) + if (!url.isValid()) return; QHstsHeaderParser parser; if (parser.parse(headers)) - updateKnownHost(url, parser.expirationDate(), parser.includeSubDomains()); + updateKnownHost(url.host(), parser.expirationDate(), parser.includeSubDomains()); +} + +void QHstsCache::updateFromPolicies(const QList<QHstsPolicy> &policies) +{ + for (const auto &policy : policies) + updateKnownHost(policy.host(), policy.expiry(), policy.includesSubDomains()); } -void QHstsCache::updateKnownHost(const QUrl &originalUrl, const QDateTime &expires, +void QHstsCache::updateKnownHost(const QUrl &url, const QDateTime &expires, bool includeSubDomains) { - if (!has_valid_domain_name(originalUrl)) + if (!url.isValid()) return; - // HSTS is a per-host policy, regardless of protocol, port or any of the other - // details in an URL; so we only want the host part. We still package this as - // a QUrl since this handles IDNA 2003 (RFC3490) for us, as required by - // HSTS (RFC6797, section 10). - QUrl url; - url.setHost(originalUrl.host()); - - // 1. Update our hosts: - QStringList labels(url.host().split(QLatin1Char('.'))); - std::reverse(labels.begin(), labels.end()); - - size_type domainIndex = 0; - for (int i = 0, e = labels.size(); i < e; ++i) { - Q_ASSERT(domainIndex < children.size()); - auto &subDomains = children[domainIndex].labels; - const auto &label = labels[i]; - auto pos = std::lower_bound(subDomains.begin(), subDomains.end(), label); - if (pos == subDomains.end() || pos->label != label) { - // A new, previously unknown host. - if (expired_policy(expires)) { - // Nothing to do at all - we did not know this host previously, - // we do not have to - since its policy expired. - return; - } - - pos = subDomains.insert(pos, label); - domainIndex = children.size(); - pos->domainIndex = domainIndex; - children.resize(children.size() + (e - i)); + updateKnownHost(url.host(), expires, includeSubDomains); +} - for (int j = i + 1; j < e; ++j) { - auto &newDomain = children[domainIndex]; - newDomain.labels.push_back(labels[j]); - newDomain.labels.back().domainIndex = ++domainIndex; - } +void QHstsCache::updateKnownHost(const QString &host, const QDateTime &expires, + bool includeSubDomains) +{ + if (!is_valid_domain_name(host)) + return; - break; + // HSTS is a per-host policy, regardless of protocol, port or any of the other + // details in an URL; so we only want the host part. QUrl::host handles + // IDNA 2003 (RFC3490) for us, as required by HSTS (RFC6797, section 10). + const HostName hostName(host); + const auto pos = knownHosts.find(hostName); + const QHstsPolicy newPolicy(expires, includeSubDomains, hostName.name); + if (pos == knownHosts.end()) { + // A new, previously unknown host. + if (newPolicy.isExpired()) { + // Nothing to do at all - we did not know this host previously, + // we do not have to - since its policy expired. + return; } - domainIndex = pos->domainIndex; + knownHosts.insert(pos, hostName, newPolicy); + return; } - Q_ASSERT(domainIndex > 0 && domainIndex < children.size()); - children[domainIndex].setHostPolicy(expires, includeSubDomains); + if (newPolicy.isExpired()) + knownHosts.erase(pos); + else + *pos = std::move(newPolicy); } -bool QHstsCache::isKnownHost(const QUrl &originalUrl) const +bool QHstsCache::isKnownHost(const QUrl &url) const { - if (!has_valid_domain_name(originalUrl)) + if (!url.isValid() || !is_valid_domain_name(url.host())) return false; - QUrl url; - url.setHost(originalUrl.host()); - - QStringList labels(url.host().split(QLatin1Char('.'))); - std::reverse(labels.begin(), labels.end()); + /* + RFC6797, 8.2. Known HSTS Host Domain Name Matching + + * Superdomain Match + If a label-for-label match between an entire Known HSTS Host's + domain name and a right-hand portion of the given domain name + is found, then this Known HSTS Host's domain name is a + superdomain match for the given domain name. There could be + multiple superdomain matches for a given domain name. + * Congruent Match + If a label-for-label match between a Known HSTS Host's domain + name and the given domain name is found -- i.e., there are no + further labels to compare -- then the given domain name + congruently matches this Known HSTS Host. + + We start from the congruent match, and then chop labels and dots and + proceed with superdomain match. While RFC6797 recommends to start from + superdomain, the result is the same - some valid policy will make a host + known. + */ + + bool superDomainMatch = false; + const QString hostNameAsString(url.host()); + HostName nameToTest(static_cast<QStringRef>(&hostNameAsString)); + while (nameToTest.fragment.size()) { + auto const pos = knownHosts.find(nameToTest); + if (pos != knownHosts.end()) { + if (pos.value().isExpired()) + knownHosts.erase(pos); + else if (!superDomainMatch || pos.value().includesSubDomains()) + return true; + } - Q_ASSERT(children.size()); - size_type domainIndex = 0; - for (int i = 0, e = labels.size(); i < e; ++i) { - Q_ASSERT(domainIndex < children.size()); - const auto &subDomains = children[domainIndex].labels; - auto pos = std::lower_bound(subDomains.begin(), subDomains.end(), labels[i]); - if (pos == subDomains.end() || pos->label != labels[i]) - return false; + const int dot = nameToTest.fragment.indexOf(QLatin1Char('.')); + if (dot == -1) + break; - Q_ASSERT(pos->domainIndex < children.size()); - domainIndex = pos->domainIndex; - auto &domain = children[domainIndex]; - if (domain.validateHostPolicy() && (i + 1 == e || domain.includeSubDomains)) { - /* - RFC6797, 8.2. Known HSTS Host Domain Name Matching - - * Superdomain Match - If a label-for-label match between an entire Known HSTS Host's - domain name and a right-hand portion of the given domain name - is found, then this Known HSTS Host's domain name is a - superdomain match for the given domain name. There could be - multiple superdomain matches for a given domain name. - * Congruent Match - If a label-for-label match between a Known HSTS Host's domain - name and the given domain name is found -- i.e., there are no - further labels to compare -- then the given domain name - congruently matches this Known HSTS Host. - */ - - return true; - } + nameToTest.fragment = nameToTest.fragment.mid(dot + 1); + superDomainMatch = true; } return false; @@ -200,10 +180,12 @@ bool QHstsCache::isKnownHost(const QUrl &originalUrl) const void QHstsCache::clear() { - children.resize(1); - children[0].labels.clear(); - // Top-level is never known: - Q_ASSERT(!children[0].isKnownHost); + knownHosts.clear(); +} + +QList<QHstsPolicy> QHstsCache::policies() const +{ + return knownHosts.values(); } // The parser is quite simple: 'nextToken' knowns exactly what kind of tokens |