diff options
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/access/access.pri | 6 | ||||
-rw-r--r-- | src/network/access/qhsts.cpp | 180 | ||||
-rw-r--r-- | src/network/access/qhsts_p.h | 81 | ||||
-rw-r--r-- | src/network/access/qhstspolicy.cpp | 218 | ||||
-rw-r--r-- | src/network/access/qhstspolicy.h | 82 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 41 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.h | 3 | ||||
-rw-r--r-- | src/network/access/qnetworkdiskcache.cpp | 2 | ||||
-rw-r--r-- | src/network/bearer/qnetworkconfiguration.cpp | 2 | ||||
-rw-r--r-- | src/network/kernel/qhostinfo.cpp | 148 | ||||
-rw-r--r-- | src/network/kernel/qhostinfo.h | 63 | ||||
-rw-r--r-- | src/network/kernel/qhostinfo_p.h | 42 | ||||
-rw-r--r-- | src/network/kernel/qnetworkproxy.cpp | 19 | ||||
-rw-r--r-- | src/network/kernel/qnetworkproxy.h | 4 |
14 files changed, 730 insertions, 161 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 766d72590c..13d52ea44a 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -40,7 +40,8 @@ HEADERS += \ access/qhttpmultipart_p.h \ access/qnetworkfile_p.h \ access/qhttp2protocolhandler_p.h \ - access/qhsts_p.h + access/qhsts_p.h \ + access/qhstspolicy.h SOURCES += \ access/qftp.cpp \ @@ -74,7 +75,8 @@ SOURCES += \ access/qhttpmultipart.cpp \ access/qnetworkfile.cpp \ access/qhttp2protocolhandler.cpp \ - access/qhsts.cpp + access/qhsts.cpp \ + access/qhstspolicy.cpp mac: LIBS_PRIVATE += -framework Security 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 diff --git a/src/network/access/qhsts_p.h b/src/network/access/qhsts_p.h index f3d5da9d23..5d95f39b96 100644 --- a/src/network/access/qhsts_p.h +++ b/src/network/access/qhsts_p.h @@ -51,17 +51,16 @@ // We mean it. // +#include <QtNetwork/qhstspolicy.h> + #include <QtCore/qbytearray.h> #include <QtCore/qdatetime.h> #include <QtCore/qstring.h> #include <QtCore/qglobal.h> -#include <QtCore/qvector.h> #include <QtCore/qlist.h> #include <QtCore/qpair.h> #include <QtCore/qurl.h> - -#include <algorithm> -#include <vector> +#include <QtCore/qmap.h> QT_BEGIN_NAMESPACE @@ -69,72 +68,48 @@ class Q_AUTOTEST_EXPORT QHstsCache { public: - QHstsCache(); - void updateFromHeaders(const QList<QPair<QByteArray, QByteArray>> &headers, const QUrl &url); + void updateFromPolicies(const QList<QHstsPolicy> &hosts); void updateKnownHost(const QUrl &url, const QDateTime &expires, bool includeSubDomains); bool isKnownHost(const QUrl &url) const; - void clear(); -private: - - using size_type = std::vector<int>::size_type; - - struct DomainLabel - { - DomainLabel(const QString &name = QString()) : label(name), domainIndex(0) {} + QList<QHstsPolicy> policies() const; - bool operator < (const DomainLabel &rhs) const - { return label < rhs.label; } +private: - QString label; - size_type domainIndex; - }; + void updateKnownHost(const QString &hostName, const QDateTime &expires, + bool includeSubDomains); - struct Domain + struct HostName { - void setHostPolicy(const QDateTime &expiration, bool subs) - { - expirationTime = expiration; - isKnownHost = expirationTime.isValid() - && expirationTime > QDateTime::currentDateTimeUtc(); - includeSubDomains = subs; - } + explicit HostName(const QString &n) : name(n) { } + explicit HostName(const QStringRef &r) : fragment(r) { } - bool validateHostPolicy() + bool operator < (const HostName &rhs) const { - if (!isKnownHost) - return false; - - if (expirationTime > QDateTime::currentDateTimeUtc()) - return true; - - isKnownHost = false; - includeSubDomains = false; - return false; + if (fragment.size()) { + if (rhs.fragment.size()) + return fragment < rhs.fragment; + return fragment < QStringRef(&rhs.name); + } + + if (rhs.fragment.size()) + return QStringRef(&name) < rhs.fragment; + return name < rhs.name; } - bool isKnownHost = false; - bool includeSubDomains = false; - QDateTime expirationTime; - std::vector<DomainLabel> labels; + // We use 'name' for a HostName object contained in our dictionary; + // we use 'fragment' only during lookup, when chopping the complete host + // name, removing subdomain names (such HostName object is 'transient', it + // must not outlive the original QString object. + QString name; + QStringRef fragment; }; - /* - Each Domain represents a DNS name or prefix thereof; each entry in its - std::vector<DomainLabel> labels pairs the next fragment of a DNS name - with the index into 'children' at which to find another Domain object. - The root Domain, children[0], has top-level-domain DomainLabel entries, - such as "com", "org" and "net"; the entry in 'children' at the index it - pairs with "com" is the Domain entry for .com; if that has "example" in - its labels, it'll be paired with the index of the entry in 'children' - that represents example.com; from which, in turn, we can find the - Domain object for www.example.com, and so on. - */ - mutable std::vector<Domain> children; + mutable QMap<HostName, QHstsPolicy> knownHosts; }; class Q_AUTOTEST_EXPORT QHstsHeaderParser diff --git a/src/network/access/qhstspolicy.cpp b/src/network/access/qhstspolicy.cpp new file mode 100644 index 0000000000..2cec587f4d --- /dev/null +++ b/src/network/access/qhstspolicy.cpp @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhstspolicy.h" + +#include <QtCore/qdatetime.h> +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QHstsPolicy + \brief The QHstsPolicy class specifies that a host supports HTTP Strict Transport + Security policy (HSTS). + \since 5.9 + \ingroup network + \inmodule QtNetwork + + HSTS policy defines a period of time during which QNetworkAccessManager + should only access a host in a secure fashion. HSTS policy is defined by + RFC6797. + + You can set expiry time and host name for this policy, and control whether it + applies to subdomains, either in the constructor or by calling setExpiry(), + setHost() and setIncludesSubdomains(). + + \sa QNetworkAccessManager::enableStrictTransportSecurity() +*/ + +class QHstsPolicyPrivate +{ +public: + QUrl url; + QDateTime expiry; + bool includeSubDomains = false; + + bool operator == (const QHstsPolicyPrivate &other) const + { + return url.host() == other.url.host() && expiry == other.expiry + && includeSubDomains == other.includeSubDomains; + } +}; + +/*! + Constructs an invalid (expired) policy with empty host name and subdomains + not included. +*/ +QHstsPolicy::QHstsPolicy() : d(new QHstsPolicyPrivate) +{ +} + +/*! + Constructs QHstsPolicy with \a expiry (in UTC); \a includeSubDomains parameter + defines if this policy must also include subdomains, \a host data is interpreted + according to \a mode. + + \sa QUrl::setHost(), QUrl::ParsingMode +*/ +QHstsPolicy::QHstsPolicy(const QDateTime &expiry, bool includeSubDomains, const QString &host, + QUrl::ParsingMode mode) + : d(new QHstsPolicyPrivate) +{ + d->url.setHost(host, mode); + d->expiry = expiry; + d->includeSubDomains = includeSubDomains; +} + +/*! + Creates a copy of \a other object. +*/ +QHstsPolicy::QHstsPolicy(const QHstsPolicy &other) + : d(new QHstsPolicyPrivate(*other.d)) +{ +} + +/*! + Destructor. +*/ +QHstsPolicy::~QHstsPolicy() +{ +} + +/*! + Copy-assignment operator, makes a copy of \a other. +*/ +QHstsPolicy &QHstsPolicy::operator=(const QHstsPolicy &other) +{ + *d = *other.d; + return *this; +} + + +/*! + Move-assignment operator. +*/ +QHstsPolicy &QHstsPolicy::operator=(QHstsPolicy &&other) Q_DECL_NOTHROW +{ + qSwap(d, other.d); + return *this; +} + +/*! + Sets a host, \a host data is interpreted according to \a mode parameter. + + \sa host(), QUrl::setHost(), QUrl::ParsingMode +*/ +void QHstsPolicy::setHost(const QString &host, QUrl::ParsingMode mode) +{ + d->url.setHost(host, mode); +} + +/*! + Returns a host for a given policy, formatted according to \a options. + + \sa setHost(), QUrl::host(), QUrl::ComponentFormattingOptions +*/ +QString QHstsPolicy::host(QUrl::ComponentFormattingOptions options) const +{ + return d->url.host(options); +} + +/*! + Sets the expiration date for the policy (in UTC). + + \sa expiry() +*/ +void QHstsPolicy::setExpiry(const QDateTime &expiry) +{ + d->expiry = expiry; +} + +/*! + Returns the expiration date for the policy (in UTC). + + \sa setExpiry() +*/ +QDateTime QHstsPolicy::expiry() const +{ + return d->expiry; +} + +/*! + Includes or excludes subdomains for this policy. + + \sa includeSubdomains() +*/ +void QHstsPolicy::setIncludesSubDomains(bool include) +{ + d->includeSubDomains = include; +} + +/*! + Returns \c true if this policy also includes subdomains. + + \sa setIncludesSubDomains() + */ +bool QHstsPolicy::includesSubDomains() const +{ + return d->includeSubDomains; +} + +/*! + Returns \c true if the two policies have the same host and expriration date + while agreeing on whether to include or exclude subdomains. +*/ +bool QHstsPolicy::operator==(const QHstsPolicy &other) const +{ + return *d == *other.d; +} + +/*! + Return \c true if this policy has a valid expiration date and this date + is greater than QDateTime::currentGetDateTimeUtc(). + + \sa setExpiry(), expiry() +*/ +bool QHstsPolicy::isExpired() const +{ + return !d->expiry.isValid() || d->expiry <= QDateTime::currentDateTimeUtc(); +} + +QT_END_NAMESPACE diff --git a/src/network/access/qhstspolicy.h b/src/network/access/qhstspolicy.h new file mode 100644 index 0000000000..4260ac278c --- /dev/null +++ b/src/network/access/qhstspolicy.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHSTSPOLICY_H +#define QHSTSPOLICY_H + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QtCore/qscopedpointer.h> +#include <QtCore/qurl.h> + +QT_BEGIN_NAMESPACE + +class QHstsPolicyPrivate; +class QDateTime; +class QString; +class Q_NETWORK_EXPORT QHstsPolicy +{ +public: + + QHstsPolicy(); + QHstsPolicy(const QDateTime &expiry, bool includeSubDomains, const QString &host, + QUrl::ParsingMode mode = QUrl::DecodedMode); + QHstsPolicy(const QHstsPolicy &rhs); + QHstsPolicy &operator=(const QHstsPolicy &rhs); + QHstsPolicy &operator=(QHstsPolicy &&rhs) Q_DECL_NOTHROW; + ~QHstsPolicy(); + + void setHost(const QString &host, QUrl::ParsingMode mode = QUrl::DecodedMode); + QString host(QUrl::ComponentFormattingOptions options = QUrl::FullyDecoded) const; + void setExpiry(const QDateTime &expiry); + QDateTime expiry() const; + void setIncludesSubDomains(bool include); + bool includesSubDomains() const; + + bool operator==(const QHstsPolicy &rhs) const; + bool isExpired() const; + +private: + + QScopedPointer<QHstsPolicyPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QHSTSPOLICY_H diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 7aa8e61d26..19e9ecc265 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -45,6 +45,8 @@ #include "qnetworkcookie.h" #include "qnetworkcookiejar.h" #include "qabstractnetworkcache.h" +#include "qhstspolicy.h" +#include "qhsts_p.h" #include "QtNetwork/qnetworksession.h" #include "QtNetwork/private/qsharednetworksession_p.h" @@ -742,6 +744,45 @@ bool QNetworkAccessManager::strictTransportSecurityEnabled() const } /*! + \since 5.9 + + Adds HTTP Strict Transport Security policies into HSTS cache. + + \note An expired policy will remove a known host from the cache, if previously + present. + + \note While processing HTTP responses, QNetworkAccessManager can also update + the HSTS cache, removing or updating exitsting policies or introducing new + known hosts. The current implementation thus is server-driven, client code + can provide QNetworkAccessManager with previously known or discovered + policies, but this information can be overridden by "Strict-Transport-Security" + response headers. + + \sa addStrictTransportSecurityHosts(), QHstsPolicy +*/ + +void QNetworkAccessManager::addStrictTransportSecurityHosts(const QList<QHstsPolicy> &knownHosts) +{ + Q_D(QNetworkAccessManager); + d->stsCache.updateFromPolicies(knownHosts); +} + +/*! + \since 5.9 + + Returns the list of HTTP Strict Transport Security policies. This list can + differ from what was initially set via addStrictTransportSecurityHosts() if + HSTS cache was updated from a "Strict-Transport-Security" response header. + + \sa addStrictTransportSecurityHosts(), QHstsPolicy +*/ +QList<QHstsPolicy> QNetworkAccessManager::strictTransportSecurityHosts() const +{ + Q_D(const QNetworkAccessManager); + return d->stsCache.policies(); +} + +/*! Posts a request to obtain the network headers for \a request and returns a new QNetworkReply object which will contain such headers. diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index 143407fb25..52769627f3 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -61,6 +61,7 @@ class QNetworkReply; class QNetworkProxy; class QNetworkProxyFactory; class QSslError; +class QHstsPolicy; #ifndef QT_NO_BEARERMANAGEMENT class QNetworkConfiguration; #endif @@ -123,6 +124,8 @@ public: void enableStrictTransportSecurity(); void disableStrictTransportSecurity(); bool strictTransportSecurityEnabled() const; + void addStrictTransportSecurityHosts(const QList<QHstsPolicy> &knownHosts); + QList<QHstsPolicy> strictTransportSecurityHosts() const; QNetworkReply *head(const QNetworkRequest &request); QNetworkReply *get(const QNetworkRequest &request); diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp index ce3b773c64..d72791c1f0 100644 --- a/src/network/access/qnetworkdiskcache.cpp +++ b/src/network/access/qnetworkdiskcache.cpp @@ -606,7 +606,7 @@ QString QNetworkDiskCachePrivate::uniqueFileName(const QUrl &url) QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(cleanUrl.toEncoded()); // convert sha1 to base36 form and return first 8 bytes for use as string - QByteArray id = QByteArray::number(*(qlonglong*)hash.result().data(), 36).left(8); + const QByteArray id = QByteArray::number(*(qlonglong*)hash.result().constData(), 36).left(8); // generates <one-char subdir>/<8-char filname.d> uint code = (uint)id.at(id.length()-1) % 16; QString pathFragment = QString::number(code, 16) + QLatin1Char('/') diff --git a/src/network/bearer/qnetworkconfiguration.cpp b/src/network/bearer/qnetworkconfiguration.cpp index d2feffb316..f1619ab7c0 100644 --- a/src/network/bearer/qnetworkconfiguration.cpp +++ b/src/network/bearer/qnetworkconfiguration.cpp @@ -343,7 +343,7 @@ int QNetworkConfiguration::connectTimeout() const /*! \since 5.9 - Sets the connect timeout of this configuration. + Sets the connect timeout of this configuration to \a timeout. This allows control of the timeout used by \c QAbstractSocket to establish a connection. diff --git a/src/network/kernel/qhostinfo.cpp b/src/network/kernel/qhostinfo.cpp index 88df65dbcb..193c990d8c 100644 --- a/src/network/kernel/qhostinfo.cpp +++ b/src/network/kernel/qhostinfo.cpp @@ -95,6 +95,38 @@ std::pair<OutputIt1, OutputIt2> separate_if(InputIt first, InputIt last, OutputI } return std::make_pair(dest1, dest2); } + +int get_signal_index() +{ + static auto senderMetaObject = &QHostInfoResult::staticMetaObject; + static auto signal = &QHostInfoResult::resultsReady; + int signal_index = -1; + void *args[] = { &signal_index, &signal }; + senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args); + return signal_index + QMetaObjectPrivate::signalOffset(senderMetaObject); +} + +void emit_results_ready(const QHostInfo &hostInfo, const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj) +{ + static const int signal_index = get_signal_index(); + auto result = new QHostInfoResult(receiver, slotObj); + Q_CHECK_PTR(result); + const int nargs = 2; + auto types = reinterpret_cast<int *>(malloc(nargs * sizeof(int))); + Q_CHECK_PTR(types); + types[0] = QMetaType::type("void"); + types[1] = QMetaType::type("QHostInfo"); + auto args = reinterpret_cast<void **>(malloc(nargs * sizeof(void *))); + Q_CHECK_PTR(args); + args[0] = 0; + args[1] = QMetaType::create(types[1], &hostInfo); + Q_CHECK_PTR(args[1]); + auto metaCallEvent = new QMetaCallEvent(slotObj, nullptr, signal_index, nargs, types, args); + Q_CHECK_PTR(metaCallEvent); + qApp->postEvent(result, metaCallEvent); +} + } /*! @@ -243,6 +275,67 @@ int QHostInfo::lookupHost(const QString &name, QObject *receiver, } /*! + \fn int QHostInfo::lookupHost(const QString &name, const QObject *receiver, PointerToMemberFunction function) + + \since 5.9 + + \overload + + Looks up the IP address(es) associated with host name \a name, and + returns an ID for the lookup. When the result of the lookup is + ready, the slot or signal \a function in \a receiver is called with + a QHostInfo argument. The QHostInfo object can then be inspected + to get the results of the lookup. + + \note There is no guarantee on the order the signals will be emitted + if you start multiple requests with lookupHost(). + + \sa abortHostLookup(), addresses(), error(), fromName() +*/ + +/*! + \fn int QHostInfo::lookupHost(const QString &name, Functor functor) + + \since 5.9 + + \overload + + Looks up the IP address(es) associated with host name \a name, and + returns an ID for the lookup. When the result of the lookup is + ready, the \a functor is called with a QHostInfo argument. The + QHostInfo object can then be inspected to get the results of the + lookup. + \note There is no guarantee on the order the signals will be emitted + if you start multiple requests with lookupHost(). + + \sa abortHostLookup(), addresses(), error(), fromName() +*/ + +/*! + \fn int QHostInfo::lookupHost(const QString &name, const QObject *context, Functor functor) + + \since 5.9 + + \overload + + Looks up the IP address(es) associated with host name \a name, and + returns an ID for the lookup. When the result of the lookup is + ready, the \a functor is called with a QHostInfo argument. The + QHostInfo object can then be inspected to get the results of the + lookup. + + If \a context is destroyed before the lookup completes, the + \a functor will not be called. The \a functor will be run in the + thread of \a context. The context's thread must have a running Qt + event loop. + + \note There is no guarantee on the order the signals will be emitted + if you start multiple requests with lookupHost(). + + \sa abortHostLookup(), addresses(), error(), fromName() +*/ + +/*! Aborts the host lookup with the ID \a id, as returned by lookupHost(). \sa lookupHost(), lookupId() @@ -487,11 +580,66 @@ QString QHostInfo::localHostName() \sa hostName() */ +int QHostInfo::lookupHostImpl(const QString &name, + const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj) +{ +#if defined QHOSTINFO_DEBUG + qDebug("QHostInfo::lookupHost(\"%s\", %p, %p)", + name.toLatin1().constData(), receiver, slotObj); +#endif + + if (!QAbstractEventDispatcher::instance(QThread::currentThread())) { + qWarning("QHostInfo::lookupHost() called with no event dispatcher"); + return -1; + } + + qRegisterMetaType<QHostInfo>(); + + int id = theIdCounter.fetchAndAddRelaxed(1); // generate unique ID + + if (Q_UNLIKELY(name.isEmpty())) { + QHostInfo hostInfo(id); + hostInfo.setError(QHostInfo::HostNotFound); + hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given")); + emit_results_ready(hostInfo, receiver, slotObj); + return id; + } + + QHostInfoLookupManager *manager = theHostInfoLookupManager(); + + if (Q_LIKELY(manager)) { + // the application is still alive + if (manager->cache.isEnabled()) { + // check cache first + bool valid = false; + QHostInfo info = manager->cache.get(name, &valid); + if (valid) { + info.setLookupId(id); + emit_results_ready(info, receiver, slotObj); + return id; + } + } + + // cache is not enabled or it was not in the cache, do normal lookup + QHostInfoRunnable* runnable = new QHostInfoRunnable(name, id, receiver, slotObj); + manager->scheduleLookup(runnable); + } + return id; +} + QHostInfoRunnable::QHostInfoRunnable(const QString &hn, int i) : toBeLookedUp(hn), id(i) { setAutoDelete(true); } +QHostInfoRunnable::QHostInfoRunnable(const QString &hn, int i, const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj) : + toBeLookedUp(hn), id(i), resultEmitter(receiver, slotObj) +{ + setAutoDelete(true); +} + // the QHostInfoLookupManager will at some point call this via a QThreadPool void QHostInfoRunnable::run() { diff --git a/src/network/kernel/qhostinfo.h b/src/network/kernel/qhostinfo.h index 9b4a4853d9..4484d718bd 100644 --- a/src/network/kernel/qhostinfo.h +++ b/src/network/kernel/qhostinfo.h @@ -87,8 +87,71 @@ public: static QString localHostName(); static QString localDomainName(); +#ifdef Q_QDOC + template<typename PointerToMemberFunction> + static int QHostInfo::lookupHost(const QString &name, const QObject *receiver, + PointerToMemberFunction function); + template<typename Functor> + static int QHostInfo::lookupHost(const QString &name, Functor functor); + template<typename Functor> + static int QHostInfo::lookupHost(const QString &name, const QObject *context, Functor functor); +#else + // lookupHost to a QObject slot + template <typename Func> + static inline int lookupHost(const QString &name, + const typename QtPrivate::FunctionPointer<Func>::Object *receiver, + Func slot) + { + typedef QtPrivate::FunctionPointer<Func> SlotType; + + typedef QtPrivate::FunctionPointer<void (*)(QHostInfo)> SignalType; + Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), + "The slot requires more arguments than the signal provides."); + Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, + typename SlotType::Arguments>::value), + "Signal and slot arguments are not compatible."); + Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, + typename SignalType::ReturnType>::value), + "Return type of the slot is not compatible " + "with the return type of the signal."); + + auto slotObj = new QtPrivate::QSlotObject<Func, typename SlotType::Arguments, void>(slot); + return lookupHostImpl(name, receiver, slotObj); + } + + // lookupHost to a callable (without context) + template <typename Func> + static inline typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction && + !std::is_same<const char *, Func>::value, int>::type + lookupHost(const QString &name, Func slot) + { + return lookupHost(name, nullptr, slot); + } + + // lookupHost to a functor or function pointer (with context) + template <typename Func1> + static inline typename std::enable_if<!QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction && + !std::is_same<const char*, Func1>::value, int>::type + lookupHost(const QString &name, QObject *context, Func1 slot) + { + typedef QtPrivate::FunctionPointer<Func1> SlotType; + + Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) <= 1, + "The slot must not require more than one argument"); + + auto slotObj = new QtPrivate::QFunctorSlotObject<Func1, 1, + typename QtPrivate::List<QHostInfo>, + void>(slot); + return lookupHostImpl(name, context, slotObj); + } +#endif // Q_QDOC + private: QScopedPointer<QHostInfoPrivate> d; + + static int lookupHostImpl(const QString &name, + const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj); }; QT_END_NAMESPACE diff --git a/src/network/kernel/qhostinfo_p.h b/src/network/kernel/qhostinfo_p.h index ba342bf533..dd46818a19 100644 --- a/src/network/kernel/qhostinfo_p.h +++ b/src/network/kernel/qhostinfo_p.h @@ -54,6 +54,7 @@ #include <QtNetwork/private/qtnetworkglobal_p.h> #include "QtCore/qcoreapplication.h" #include "private/qcoreapplication_p.h" +#include "private/qmetaobject_p.h" #include "QtNetwork/qhostinfo.h" #include "QtCore/qmutex.h" #include "QtCore/qwaitcondition.h" @@ -77,10 +78,47 @@ QT_BEGIN_NAMESPACE class QHostInfoResult : public QObject { Q_OBJECT + + QPointer<const QObject> receiver = nullptr; + QtPrivate::QSlotObjectBase *slotObj = nullptr; + +public: + QHostInfoResult() = default; + QHostInfoResult(const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj) : + receiver(receiver), + slotObj(slotObj) + { + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, + &QObject::deleteLater); + if (slotObj && receiver) + moveToThread(receiver->thread()); + } + public Q_SLOTS: inline void emitResultsReady(const QHostInfo &info) { - emit resultsReady(info); + if (slotObj) { + QHostInfo copy = info; + void *args[2] = { 0, reinterpret_cast<void *>(©) }; + slotObj->call(const_cast<QObject*>(receiver.data()), args); + slotObj->destroyIfLastRef(); + } else { + emit resultsReady(info); + } + } + +protected: + bool event(QEvent *event) + { + if (event->type() == QEvent::MetaCall) { + auto metaCallEvent = static_cast<QMetaCallEvent *>(event); + auto args = metaCallEvent->args(); + auto hostInfo = reinterpret_cast<QHostInfo *>(args[1]); + emitResultsReady(*hostInfo); + deleteLater(); + return true; + } + return QObject::event(event); } Q_SIGNALS: @@ -154,6 +192,8 @@ class QHostInfoRunnable : public QRunnable { public: QHostInfoRunnable(const QString &hn, int i); + QHostInfoRunnable(const QString &hn, int i, const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj); void run() Q_DECL_OVERRIDE; QString toBeLookedUp; diff --git a/src/network/kernel/qnetworkproxy.cpp b/src/network/kernel/qnetworkproxy.cpp index 0be8a7f79e..11e8fa6264 100644 --- a/src/network/kernel/qnetworkproxy.cpp +++ b/src/network/kernel/qnetworkproxy.cpp @@ -1636,10 +1636,6 @@ QList<QNetworkProxy> QNetworkProxyFactory::proxyForQuery(const QNetworkProxyQuer } #ifndef QT_NO_DEBUG_STREAM -/*! - \since 5.0 - Outputs a QNetworkProxy details to a debug stream -*/ QDebug operator<<(QDebug debug, const QNetworkProxy &proxy) { QDebugStateSaver saver(debug); @@ -1688,6 +1684,21 @@ QDebug operator<<(QDebug debug, const QNetworkProxy &proxy) debug << '[' << scaps.join(QLatin1Char(' ')) << ']'; return debug; } + +QDebug operator<<(QDebug debug, const QNetworkProxyQuery &proxyQuery) +{ + QDebugStateSaver saver(debug); + debug.resetFormat().nospace() + << "ProxyQuery(" + << "type: " << proxyQuery.queryType() + << ", protocol: " << proxyQuery.protocolTag() + << ", peerPort: " << proxyQuery.peerPort() + << ", peerHostName: " << proxyQuery.peerHostName() + << ", localPort: " << proxyQuery.localPort() + << ", url: " << proxyQuery.url() + << ')'; + return debug; +} #endif QT_END_NAMESPACE diff --git a/src/network/kernel/qnetworkproxy.h b/src/network/kernel/qnetworkproxy.h index 8fcb7e33cf..8699c313e9 100644 --- a/src/network/kernel/qnetworkproxy.h +++ b/src/network/kernel/qnetworkproxy.h @@ -56,6 +56,8 @@ class QNetworkConfiguration; class QNetworkProxyQueryPrivate; class Q_NETWORK_EXPORT QNetworkProxyQuery { + Q_GADGET + public: enum QueryType { TcpSocket, @@ -65,6 +67,7 @@ public: UrlRequest, SctpServer }; + Q_ENUM(QueryType) QNetworkProxyQuery(); explicit QNetworkProxyQuery(const QUrl &requestUrl, QueryType queryType = UrlRequest); @@ -222,6 +225,7 @@ public: #ifndef QT_NO_DEBUG_STREAM Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QNetworkProxy &proxy); +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QNetworkProxyQuery &proxyQuery); #endif QT_END_NAMESPACE |