diff options
27 files changed, 637 insertions, 84 deletions
diff --git a/mkspecs/features/ctest_testcase_common.prf b/mkspecs/features/ctest_testcase_common.prf index cdc5cca1d7..4c7e03f0f7 100644 --- a/mkspecs/features/ctest_testcase_common.prf +++ b/mkspecs/features/ctest_testcase_common.prf @@ -27,22 +27,8 @@ isEmpty(CTEST_VERSION) { } CMAKE_VERSION = $$last(CMAKE_VERSION) -CMAKE_VERSION_MAJOR = $$section(CMAKE_VERSION, ., 0, 0) -CMAKE_VERSION_MINOR = $$section(CMAKE_VERSION, ., 1, 1) -CMAKE_VERSION_PATCH = $$section(CMAKE_VERSION, ., 2, 2) -# CMake can report versions like 2.8.11-rc1, so strip off the rc part. -CMAKE_VERSION_PATCH ~= s,-.*,, - -VERSION_OK = -greaterThan(CMAKE_VERSION_MAJOR, 2) { - VERSION_OK = 1 -} else:greaterThan(CMAKE_VERSION_MAJOR, 1):greaterThan(CMAKE_VERSION_MINOR, 8) { - VERSION_OK = 1 -} else:greaterThan(CMAKE_VERSION_MAJOR, 1):greaterThan(CMAKE_VERSION_MINOR, 7):greaterThan(CMAKE_VERSION_PATCH, 2) { - VERSION_OK = 1 -} -isEmpty(VERSION_OK) { +!versionAtLeast(CMAKE_VERSION, 2.8.3) { message("cmake $$CMAKE_VERSION is too old for this test.") return() } diff --git a/mkspecs/features/qt_common.prf b/mkspecs/features/qt_common.prf index e66c24b027..65574d2635 100644 --- a/mkspecs/features/qt_common.prf +++ b/mkspecs/features/qt_common.prf @@ -55,9 +55,8 @@ host_build:cross_compile: return() # -Wdate-time: warn if we use __DATE__ or __TIME__ (we want to be able to reproduce the exact same binary) # -Wvla: use of variable-length arrays (an extension to C++) clang { - # Clang 3.5 introduced -Wdate-time - # The conditional assumes we aren't compiling against Clang 2.x anymore - greaterThan(QT_CLANG_MAJOR_VERSION, 3)|greaterThan(QT_CLANG_MINOR_VERSION, 4): \ + clang_ver = $${QT_CLANG_MAJOR_VERSION}.$${QT_CLANG_MINOR_VERSION} + versionAtLeast(clang_ver, 3.5): \ QMAKE_CXXFLAGS_WARN_ON += -Wdate-time } else: gcc:!intel_icc { QMAKE_CXXFLAGS_WARN_ON += -Wvla diff --git a/mkspecs/features/qt_module.prf b/mkspecs/features/qt_module.prf index 36f632e8ca..8a8c17f01b 100644 --- a/mkspecs/features/qt_module.prf +++ b/mkspecs/features/qt_module.prf @@ -195,7 +195,7 @@ sse2:!contains(QT_CPU_FEATURES.$$QT_ARCH, sse2):!host_build:!if(static:qtConfig( clang { apple_clang_ver = $${QT_APPLE_CLANG_MAJOR_VERSION}.$${QT_APPLE_CLANG_MINOR_VERSION} reg_clang_ver = $${QT_CLANG_MAJOR_VERSION}.$${QT_CLANG_MINOR_VERSION} - !lessThan(apple_clang_ver, "5.1")|!lessThan(reg_clang_ver, "3.4"): \ + versionAtLeast(apple_clang_ver, 5.1)|versionAtLeast(reg_clang_ver, 3.4): \ CONFIG += compiler_supports_fpmath } else: gcc { CONFIG += compiler_supports_fpmath diff --git a/mkspecs/features/qt_module_headers.prf b/mkspecs/features/qt_module_headers.prf index e5b99e1406..6cc72bb5e3 100644 --- a/mkspecs/features/qt_module_headers.prf +++ b/mkspecs/features/qt_module_headers.prf @@ -214,20 +214,15 @@ headersclean:!internal_module { !contains(QT_ARCH, arm):!contains(QT_ARCH, mips): \ hcleanFLAGS += -Wcast-align - greaterThan(QT_CLANG_MAJOR_VERSION, 3) { - hcleanFLAGS += -Wdouble-promotion - } greaterThan(QT_CLANG_MAJOR_VERSION, 2):greaterThan(QT_CLANG_MINOR_VERSION, 7) { - hcleanFLAGS += -Wdouble-promotion - } + clang_ver = $${QT_CLANG_MAJOR_VERSION}.$${QT_CLANG_MINOR_VERSION} + versionAtLeast(clang_ver, 3.8): hcleanFLAGS += -Wdouble-promotion !clang { # options accepted only by GCC - greaterThan(QT_GCC_MAJOR_VERSION, 4) { - hcleanFLAGS += -Wdouble-promotion - } greaterThan(QT_GCC_MAJOR_VERSION, 3):greaterThan(QT_GCC_MINOR_VERSION, 4) { - hcleanFLAGS += -Wdouble-promotion - } + gcc_ver = $${QT_GCC_MAJOR_VERSION}.$${QT_GCC_MINOR_VERSION} + versionAtLeast(gcc_ver, 4.5): hcleanFLAGS += -Wdouble-promotion + c++11 { # only enabled for actual c++11 builds due to # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52806 diff --git a/mkspecs/features/uikit/bitcode.prf b/mkspecs/features/uikit/bitcode.prf index df298171c0..f1fd33981a 100644 --- a/mkspecs/features/uikit/bitcode.prf +++ b/mkspecs/features/uikit/bitcode.prf @@ -1,4 +1,4 @@ -lessThan(QMAKE_XCODE_VERSION, "7.0") { +!versionAtLeast(QMAKE_XCODE_VERSION, 7.0) { warning("You need to update Xcode to version 7 or newer to support bitcode") } else: !macx-xcode { # Simulator builds and all debug builds SHOULD use -fembed-bitcode-marker, diff --git a/mkspecs/features/uikit/default_post.prf b/mkspecs/features/uikit/default_post.prf index f7245e48b1..c1b6f38a6c 100644 --- a/mkspecs/features/uikit/default_post.prf +++ b/mkspecs/features/uikit/default_post.prf @@ -39,7 +39,7 @@ macx-xcode { qmake_launch_images.files = $$qmake_copy_image.output QMAKE_BUNDLE_DATA += qmake_launch_images - lessThan(QMAKE_XCODE_VERSION, "6.0") { + !versionAtLeast(QMAKE_XCODE_VERSION, 6.0) { warning("You need to update Xcode to version 6 or newer to fully support iPhone6/6+") } else { # Set up default LaunchScreen to support iPhone6/6+ diff --git a/mkspecs/features/uikit/default_pre.prf b/mkspecs/features/uikit/default_pre.prf index 00e29a5c8b..6a44a67bca 100644 --- a/mkspecs/features/uikit/default_pre.prf +++ b/mkspecs/features/uikit/default_pre.prf @@ -21,8 +21,8 @@ unset(sim_and_dev) load(default_pre) # Check for supported Xcode versions -lessThan(QMAKE_XCODE_VERSION, "4.3"): \ +!versionAtLeast(QMAKE_XCODE_VERSION, 4.3): \ error("This mkspec requires Xcode 4.3 or later") -ios:shared:lessThan(QMAKE_IOS_DEPLOYMENT_TARGET, "8.0"): \ +ios:shared:!versionAtLeast(QMAKE_IOS_DEPLOYMENT_TARGET, 8.0): \ QMAKE_IOS_DEPLOYMENT_TARGET = 8.0 diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp index 037deb8942..b7a5440224 100644 --- a/src/corelib/io/qfsfileengine.cpp +++ b/src/corelib/io/qfsfileengine.cpp @@ -923,10 +923,6 @@ bool QFSFileEngine::supportsExtension(Extension extension) const \reimp */ -/*! \fn QDateTime QFSFileEngine::fileTime(FileTime time) const - \reimp -*/ - /*! \fn QString QFSFileEngine::homePath() Returns the home path of the current user. diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index 29af0647ea..b73c83bc49 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -1459,7 +1459,11 @@ Q_CORE_EXPORT void QVariantPrivate::registerHandler(const int /* Modules::Names /*! \fn QVariant::QVariant(Type type) - Constructs a null variant of type \a type. + Constructs an uninitialized variant of type \a type. This will create a + variant in a special null state that if accessed will return a default + constructed value of the \a type. + + \sa isNull() */ @@ -3305,17 +3309,20 @@ bool QVariant::canConvert(int targetTypeId) const /*! Casts the variant to the requested type, \a targetTypeId. If the cast cannot be - done, the variant is cleared. Returns \c true if the current type of - the variant was successfully cast; otherwise returns \c false. + done, the variant is still changed to the requested type, but is left in a cleared + null state similar to that constructed by QVariant(Type). + + Returns \c true if the current type of the variant was successfully cast; + otherwise returns \c false. A QVariant containing a pointer to a type derived from QObject will also convert and return true for this function if a qobject_cast to the type described by \a targetTypeId would succeed. Note that this only works for QObject subclasses which use the Q_OBJECT macro. - \warning For historical reasons, converting a null QVariant results - in a null value of the desired type (e.g., an empty string for - QString) and a result of false. + \note converting QVariants that are null due to not being initialized or having + failed a previous conversion will always fail, changing the type, remaining null, + and returning \c false. \sa canConvert(), clear() */ @@ -3332,7 +3339,8 @@ bool QVariant::convert(int targetTypeId) return false; create(targetTypeId, 0); - if (oldValue.isNull()) + // Fail if the value is not initialized or was forced null by a previous failed convert. + if (oldValue.d.is_null) return false; if ((QMetaType::typeFlags(oldValue.userType()) & QMetaType::PointerToQObject) && (QMetaType::typeFlags(targetTypeId) & QMetaType::PointerToQObject)) { @@ -3732,12 +3740,14 @@ void* QVariant::data() /*! Returns \c true if this is a null variant, false otherwise. A variant is - considered null if it contains a default constructed value or a built-in - type instance that has an isNull method, in which case the result - would be the same as calling isNull on the wrapped object. + considered null if it contains no initialized value or it contains an instance + of built-in type that has an isNull method, in which case the result would be + the same as calling isNull on the wrapped object. + + \warning Null variants is not a single state and two null variants may easily + return \c false on the == operator if they do not contain similar null values. - \warning The result of the function doesn't affect == operator, which means - that two values can be equal even if one of them is null and another is not. + \sa QVariant(Type), convert(int) */ bool QVariant::isNull() const { diff --git a/src/corelib/thread/qthread.cpp b/src/corelib/thread/qthread.cpp index b662475003..1ec626a53b 100644 --- a/src/corelib/thread/qthread.cpp +++ b/src/corelib/thread/qthread.cpp @@ -291,7 +291,7 @@ QThreadPrivate::~QThreadPrivate() \fn int QThread::idealThreadCount() Returns the ideal number of threads that can be run on the system. This is done querying - the number of processor cores, both real and logical, in the system. This function returns -1 + the number of processor cores, both real and logical, in the system. This function returns 1 if the number of processor cores could not be detected. */ diff --git a/src/corelib/thread/qthreadpool.cpp b/src/corelib/thread/qthreadpool.cpp index f3ce1f258f..9937201619 100644 --- a/src/corelib/thread/qthreadpool.cpp +++ b/src/corelib/thread/qthreadpool.cpp @@ -74,7 +74,9 @@ public: */ QThreadPoolThread::QThreadPoolThread(QThreadPoolPrivate *manager) :manager(manager), runnable(0) -{ } +{ + setStackSize(manager->stackSize); +} /* \internal @@ -154,11 +156,6 @@ void QThreadPoolThread::registerThreadInactive() \internal */ QThreadPoolPrivate:: QThreadPoolPrivate() - : isExiting(false), - expiryTimeout(30000), - maxThreadCount(qAbs(QThread::idealThreadCount())), - reservedThreads(0), - activeThreads(0) { } bool QThreadPoolPrivate::tryStart(QRunnable *task) @@ -609,6 +606,30 @@ void QThreadPool::reserveThread() ++d->reservedThreads; } +/*! \property QThreadPool::stacksize + + This property contains the stack size for the thread pool worker + threads. + + The value of the property is uses when the thread pool creates + new threads only. Changing it has no effect for already created + or running threads. + + The default value is 0, which makes QThread use the operating + system default stack stize. +*/ +void QThreadPool::setStackSize(uint stackSize) +{ + Q_D(QThreadPool); + d->stackSize = stackSize; +} + +uint QThreadPool::stackSize() const +{ + Q_D(const QThreadPool); + return d->stackSize; +} + /*! Releases a thread previously reserved by a call to reserveThread(). diff --git a/src/corelib/thread/qthreadpool.h b/src/corelib/thread/qthreadpool.h index 09b7f96f48..a65eacc996 100644 --- a/src/corelib/thread/qthreadpool.h +++ b/src/corelib/thread/qthreadpool.h @@ -58,6 +58,7 @@ class Q_CORE_EXPORT QThreadPool : public QObject Q_PROPERTY(int expiryTimeout READ expiryTimeout WRITE setExpiryTimeout) Q_PROPERTY(int maxThreadCount READ maxThreadCount WRITE setMaxThreadCount) Q_PROPERTY(int activeThreadCount READ activeThreadCount) + Q_PROPERTY(uint stackSize READ stackSize WRITE setStackSize) friend class QFutureInterfaceBase; public: @@ -77,6 +78,9 @@ public: int activeThreadCount() const; + void setStackSize(uint stackSize); + uint stackSize() const; + void reserveThread(); void releaseThread(); diff --git a/src/corelib/thread/qthreadpool_p.h b/src/corelib/thread/qthreadpool_p.h index 4a9f9e5cfa..8b6a8cc476 100644 --- a/src/corelib/thread/qthreadpool_p.h +++ b/src/corelib/thread/qthreadpool_p.h @@ -53,6 +53,7 @@ // #include "QtCore/qmutex.h" +#include "QtCore/qthread.h" #include "QtCore/qwaitcondition.h" #include "QtCore/qset.h" #include "QtCore/qqueue.h" @@ -91,11 +92,12 @@ public: QVector<QPair<QRunnable *, int> > queue; QWaitCondition noActiveThreads; - bool isExiting; - int expiryTimeout; - int maxThreadCount; - int reservedThreads; - int activeThreads; + int expiryTimeout = 30000; + int maxThreadCount = QThread::idealThreadCount(); + int reservedThreads = 0; + int activeThreads = 0; + uint stackSize = 0; + bool isExiting = false; }; QT_END_NAMESPACE diff --git a/src/corelib/tools/qbytearray.cpp b/src/corelib/tools/qbytearray.cpp index 3ecb2ac662..9b648adb06 100644 --- a/src/corelib/tools/qbytearray.cpp +++ b/src/corelib/tools/qbytearray.cpp @@ -686,14 +686,6 @@ QByteArray qCompress(const uchar* data, int nbytes, int compressionLevel) \sa qCompress() */ -/*! \relates QByteArray - - \overload - - Uncompresses the first \a nbytes of \a data and returns a new byte - array with the uncompressed data. -*/ - #ifndef QT_NO_COMPRESS namespace { struct QByteArrayDataDeleter @@ -709,6 +701,13 @@ static QByteArray invalidCompressedData() return QByteArray(); } +/*! \relates QByteArray + + \overload + + Uncompresses the first \a nbytes of \a data and returns a new byte + array with the uncompressed data. +*/ QByteArray qUncompress(const uchar* data, int nbytes) { if (!data) { diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 13d52ea44a..4281a870a7 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -41,7 +41,8 @@ HEADERS += \ access/qnetworkfile_p.h \ access/qhttp2protocolhandler_p.h \ access/qhsts_p.h \ - access/qhstspolicy.h + access/qhstspolicy.h \ + access/qhstsstore_p.h SOURCES += \ access/qftp.cpp \ @@ -76,7 +77,8 @@ SOURCES += \ access/qnetworkfile.cpp \ access/qhttp2protocolhandler.cpp \ access/qhsts.cpp \ - access/qhstspolicy.cpp + access/qhstspolicy.cpp \ + access/qhstsstore.cpp mac: LIBS_PRIVATE += -framework Security diff --git a/src/network/access/qhsts.cpp b/src/network/access/qhsts.cpp index ca9f3b977b..6a731afc2f 100644 --- a/src/network/access/qhsts.cpp +++ b/src/network/access/qhsts.cpp @@ -37,6 +37,7 @@ ** ****************************************************************************/ +#include "qhstsstore_p.h" #include "qhsts_p.h" #include "QtCore/private/qipaddress_p.h" @@ -80,14 +81,24 @@ void QHstsCache::updateFromHeaders(const QList<QPair<QByteArray, QByteArray>> &h return; QHstsHeaderParser parser; - if (parser.parse(headers)) + if (parser.parse(headers)) { updateKnownHost(url.host(), parser.expirationDate(), parser.includeSubDomains()); + if (hstsStore) + hstsStore->synchronize(); + } } void QHstsCache::updateFromPolicies(const QVector<QHstsPolicy> &policies) { for (const auto &policy : policies) updateKnownHost(policy.host(), policy.expiry(), policy.includesSubDomains()); + + if (hstsStore && policies.size()) { + // These policies are coming either from store or from QNAM's setter + // function. As a result we can notice expired or new policies, time + // to sync ... + hstsStore->synchronize(); + } } void QHstsCache::updateKnownHost(const QUrl &url, const QDateTime &expires, @@ -97,6 +108,8 @@ void QHstsCache::updateKnownHost(const QUrl &url, const QDateTime &expires, return; updateKnownHost(url.host(), expires, includeSubDomains); + if (hstsStore) + hstsStore->synchronize(); } void QHstsCache::updateKnownHost(const QString &host, const QDateTime &expires, @@ -124,13 +137,20 @@ void QHstsCache::updateKnownHost(const QString &host, const QDateTime &expires, } knownHosts.insert(pos, hostName, newPolicy); + if (hstsStore) + hstsStore->addToObserved(newPolicy); return; } if (newPolicy.isExpired()) knownHosts.erase(pos); - else + else if (*pos != newPolicy) *pos = std::move(newPolicy); + else + return; + + if (hstsStore) + hstsStore->addToObserved(newPolicy); } bool QHstsCache::isKnownHost(const QUrl &url) const @@ -165,10 +185,15 @@ bool QHstsCache::isKnownHost(const QUrl &url) const while (nameToTest.fragment.size()) { auto const pos = knownHosts.find(nameToTest); if (pos != knownHosts.end()) { - if (pos.value().isExpired()) + if (pos.value().isExpired()) { knownHosts.erase(pos); - else if (!superDomainMatch || pos.value().includesSubDomains()) + if (hstsStore) { + // Inform our store that this policy has expired. + hstsStore->addToObserved(pos.value()); + } + } else if (!superDomainMatch || pos.value().includesSubDomains()) { return true; + } } const int dot = nameToTest.fragment.indexOf(QLatin1Char('.')); @@ -196,6 +221,34 @@ QVector<QHstsPolicy> QHstsCache::policies() const return values; } +void QHstsCache::setStore(QHstsStore *store) +{ + // Caller retains ownership of store, which must outlive this cache. + if (store != hstsStore) { + hstsStore = store; + + if (!hstsStore) + return; + + // First we augment our store with the policies we already know about + // (and thus the cached policy takes priority over whatever policy we + // had in the store for the same host, if any). + if (knownHosts.size()) { + const QVector<QHstsPolicy> observed(policies()); + for (const auto &policy : observed) + hstsStore->addToObserved(policy); + hstsStore->synchronize(); + } + + // Now we update the cache with anything we have not observed yet, but + // the store knows about (well, it can happen we synchronize again as a + // result if some policies managed to expire or if we add a new one + // from the store to cache): + const QVector<QHstsPolicy> restored(store->readPolicies()); + updateFromPolicies(restored); + } +} + // The parser is quite simple: 'nextToken' knowns exactly what kind of tokens // are valid and it will return false if something else was found; then // we immediately stop parsing. 'parseDirective' knows how these tokens can diff --git a/src/network/access/qhsts_p.h b/src/network/access/qhsts_p.h index ab3ca536fb..2feb73b446 100644 --- a/src/network/access/qhsts_p.h +++ b/src/network/access/qhsts_p.h @@ -51,6 +51,8 @@ // We mean it. // +#include <QtNetwork/private/qtnetworkglobal_p.h> + #include <QtNetwork/qhstspolicy.h> #include <QtCore/qbytearray.h> @@ -66,6 +68,8 @@ QT_BEGIN_NAMESPACE template<typename T> class QList; template <typename T> class QVector; +class QHstsStore; + class Q_AUTOTEST_EXPORT QHstsCache { public: @@ -80,6 +84,8 @@ public: QVector<QHstsPolicy> policies() const; + void setStore(QHstsStore *store); + private: void updateKnownHost(const QString &hostName, const QDateTime &expires, @@ -112,6 +118,7 @@ private: }; mutable QMap<HostName, QHstsPolicy> knownHosts; + QHstsStore *hstsStore = nullptr; }; class Q_AUTOTEST_EXPORT QHstsHeaderParser diff --git a/src/network/access/qhstsstore.cpp b/src/network/access/qhstsstore.cpp new file mode 100644 index 0000000000..239a52b7a4 --- /dev/null +++ b/src/network/access/qhstsstore.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** 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 "qhstsstore_p.h" +#include "qhstspolicy.h" + +#include "qstandardpaths.h" +#include "qdatastream.h" +#include "qbytearray.h" +#include "qdatetime.h" +#include "qvariant.h" +#include "qstring.h" +#include "qdir.h" + +#include <utility> + +QT_BEGIN_NAMESPACE + +static QString host_name_to_settings_key(const QString &hostName) +{ + const QByteArray hostNameAsHex(hostName.toUtf8().toHex()); + return QString::fromLatin1(hostNameAsHex); +} + +static QString settings_key_to_host_name(const QString &key) +{ + const QByteArray hostNameAsUtf8(QByteArray::fromHex(key.toLatin1())); + return QString::fromUtf8(hostNameAsUtf8); +} + +QHstsStore::QHstsStore(const QString &dirName) + : store(absoluteFilePath(dirName), QSettings::IniFormat) +{ + // Disable fallbacks, we do not want to use anything but our own ini file. + store.setFallbacksEnabled(false); +} + +QHstsStore::~QHstsStore() +{ + synchronize(); +} + +QVector<QHstsPolicy> QHstsStore::readPolicies() +{ + // This function only attempts to read policies, making no decision about + // expired policies. It's up to a user (QHstsCache) to mark these policies + // for deletion and sync the store later. But we immediately remove keys/values + // (if the store isWritable) for the policies that we fail to read. + QVector<QHstsPolicy> policies; + + beginHstsGroups(); + + const QStringList keys = store.childKeys(); + for (const auto &key : keys) { + QHstsPolicy restoredPolicy; + if (deserializePolicy(key, restoredPolicy)) { + restoredPolicy.setHost(settings_key_to_host_name(key)); + policies.push_back(std::move(restoredPolicy)); + } else if (isWritable()) { + evictPolicy(key); + } + } + + endHstsGroups(); + + return policies; +} + +void QHstsStore::addToObserved(const QHstsPolicy &policy) +{ + observedPolicies.push_back(policy); +} + +void QHstsStore::synchronize() +{ + if (!isWritable()) + return; + + if (observedPolicies.size()) { + beginHstsGroups(); + for (const QHstsPolicy &policy : observedPolicies) { + const QString key(host_name_to_settings_key(policy.host())); + // If we fail to write a new, updated policy, we also remove the old one. + if (policy.isExpired() || !serializePolicy(key, policy)) + evictPolicy(key); + } + observedPolicies.clear(); + endHstsGroups(); + } + + store.sync(); +} + +bool QHstsStore::isWritable() const +{ + return store.isWritable(); +} + +QString QHstsStore::absoluteFilePath(const QString &dirName) +{ + const QDir dir(dirName.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + : dirName); + return dir.absoluteFilePath(QLatin1String("hstsstore")); +} + +void QHstsStore::beginHstsGroups() +{ + store.beginGroup(QLatin1String("StrictTransportSecurity")); + store.beginGroup(QLatin1String("Policies")); +} + +void QHstsStore::endHstsGroups() +{ + store.endGroup(); + store.endGroup(); +} + +bool QHstsStore::deserializePolicy(const QString &key, QHstsPolicy &policy) +{ + Q_ASSERT(store.contains(key)); + + const QVariant data(store.value(key)); + if (data.isNull() || !data.canConvert<QByteArray>()) + return false; + + const QByteArray serializedData(data.toByteArray()); + QDataStream streamer(serializedData); + qint64 expiryInMS = 0; + streamer >> expiryInMS; + if (streamer.status() != QDataStream::Ok) + return false; + bool includesSubDomains = false; + streamer >> includesSubDomains; + if (streamer.status() != QDataStream::Ok) + return false; + + policy.setExpiry(QDateTime::fromMSecsSinceEpoch(expiryInMS)); + policy.setIncludesSubDomains(includesSubDomains); + + return true; +} + +bool QHstsStore::serializePolicy(const QString &key, const QHstsPolicy &policy) +{ + Q_ASSERT(store.isWritable()); + + QByteArray serializedData; + QDataStream streamer(&serializedData, QIODevice::WriteOnly); + streamer << policy.expiry().toMSecsSinceEpoch(); + streamer << policy.includesSubDomains(); + + if (streamer.status() != QDataStream::Ok) + return false; + + store.setValue(key, serializedData); + return true; +} + +void QHstsStore::evictPolicy(const QString &key) +{ + Q_ASSERT(store.isWritable()); + if (store.contains(key)) + store.remove(key); +} + +QT_END_NAMESPACE diff --git a/src/network/access/qhstsstore_p.h b/src/network/access/qhstsstore_p.h new file mode 100644 index 0000000000..13042839c4 --- /dev/null +++ b/src/network/access/qhstsstore_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 QHSTSSTORE_P_H +#define QHSTSSTORE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtCore/qsettings.h> +#include <QtCore/qvector.h> + +QT_BEGIN_NAMESPACE + +class QHstsPolicy; +class QByteArray; +class QString; + +class Q_AUTOTEST_EXPORT QHstsStore +{ +public: + explicit QHstsStore(const QString &dirName); + ~QHstsStore(); + + QVector<QHstsPolicy> readPolicies(); + void addToObserved(const QHstsPolicy &policy); + void synchronize(); + + bool isWritable() const; + + static QString absoluteFilePath(const QString &dirName); +private: + void beginHstsGroups(); + bool serializePolicy(const QString &key, const QHstsPolicy &policy); + bool deserializePolicy(const QString &key, QHstsPolicy &policy); + void evictPolicy(const QString &key); + void endHstsGroups(); + + QVector<QHstsPolicy> observedPolicies; + QSettings store; + + Q_DISABLE_COPY(QHstsStore) +}; + +QT_END_NAMESPACE + +#endif // QHSTSSTORE_P_H diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 79f0aa8038..eeee82a87c 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -730,6 +730,48 @@ bool QNetworkAccessManager::isStrictTransportSecurityEnabled() const } /*! + \since 5.10 + + If \a enabled is \c true, the internal HSTS cache will use a persistent store + to read and write HSTS policies. \a storeDir defines where this store will be + located. The default location is defined by QStandardPaths::CacheLocation. + If there is no writable QStandartPaths::CacheLocation and \a storeDir is an + empty string, the store will be located in the program's working directory. + + \note If HSTS cache already contains HSTS policies by the time persistent + store is enabled, these policies will be preserved in the store. In case both + cache and store contain the same known hosts, policies from cache are considered + to be more up-to-date (and thus will overwrite the previous values in the store). + If this behavior is undesired, enable HSTS store before enabling Strict Tranport + Security. By default, the persistent store of HSTS policies is disabled. + + \sa isStrictTransportSecurityStoreEnabled(), setStrictTransportSecurityEnabled(), + QStandardPaths::standardLocations() +*/ + +void QNetworkAccessManager::enableStrictTransportSecurityStore(bool enabled, const QString &storeDir) +{ + Q_D(QNetworkAccessManager); + d->stsStore.reset(enabled ? new QHstsStore(storeDir) : nullptr); + d->stsCache.setStore(d->stsStore.data()); +} + +/*! + \since 5.10 + + Returns true if HSTS cache uses a permanent store to load and store HSTS + policies. + + \sa enableStrictTransportSecurityStore() +*/ + +bool QNetworkAccessManager::isStrictTransportSecurityStoreEnabled() const +{ + Q_D(const QNetworkAccessManager); + return bool(d->stsStore.data()); +} + +/*! \since 5.9 Adds HTTP Strict Transport Security policies contained in \a knownHosts into HSTS cache. @@ -744,7 +786,7 @@ bool QNetworkAccessManager::isStrictTransportSecurityEnabled() const policies, but this information can be overridden by "Strict-Transport-Security" response headers. - \sa addStrictTransportSecurityHosts(), QHstsPolicy + \sa addStrictTransportSecurityHosts(), enableStrictTransportSecurityStore(), QHstsPolicy */ void QNetworkAccessManager::addStrictTransportSecurityHosts(const QVector<QHstsPolicy> &knownHosts) diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index f035ac5b00..4806ec0475 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -42,6 +42,7 @@ #include <QtNetwork/qtnetworkglobal.h> #include <QtNetwork/qnetworkrequest.h> +#include <QtCore/QString> #include <QtCore/QVector> #include <QtCore/QObject> #ifndef QT_NO_SSL @@ -124,6 +125,8 @@ public: void setStrictTransportSecurityEnabled(bool enabled); bool isStrictTransportSecurityEnabled() const; + void enableStrictTransportSecurityStore(bool enabled, const QString &storeDir = QString()); + bool isStrictTransportSecurityStoreEnabled() const; void addStrictTransportSecurityHosts(const QVector<QHstsPolicy> &knownHosts); QVector<QHstsPolicy> strictTransportSecurityHosts() const; diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h index 13a26a54f1..e5257251a4 100644 --- a/src/network/access/qnetworkaccessmanager_p.h +++ b/src/network/access/qnetworkaccessmanager_p.h @@ -56,6 +56,7 @@ #include "qnetworkaccesscache_p.h" #include "qnetworkaccessbackend_p.h" #include "qnetworkrequest.h" +#include "qhstsstore_p.h" #include "qhsts_p.h" #include "private/qobject_p.h" #include "QtNetwork/qnetworkproxy.h" @@ -211,6 +212,7 @@ public: Q_AUTOTEST_EXPORT static void clearConnectionCache(QNetworkAccessManager *manager); QHstsCache stsCache; + QScopedPointer<QHstsStore> stsStore; bool stsEnabled = false; #ifndef QT_NO_BEARERMANAGEMENT diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 15906961ac..3e445f0f7f 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -640,7 +640,7 @@ QWindowsWindowData // Capture events before CreateWindowEx() returns. The context is cleared in // the QWindowsWindow constructor. - const QWindowCreationContextPtr context(new QWindowCreationContext(w, rect, data.customMargins, style, exStyle)); + const QWindowCreationContextPtr context(new QWindowCreationContext(w, data.geometry, rect, data.customMargins, style, exStyle)); QWindowsContext::instance()->setWindowCreationContext(context); qCDebug(lcQpaWindows).nospace() @@ -1004,10 +1004,11 @@ void QWindowsForeignWindow::setVisible(bool visible) */ QWindowCreationContext::QWindowCreationContext(const QWindow *w, - const QRect &geometry, + const QRect &geometryIn, const QRect &geometry, const QMargins &cm, DWORD style_, DWORD exStyle_) : geometryHint(w, cm), window(w), style(style_), exStyle(exStyle_), + requestedGeometryIn(geometryIn), requestedGeometry(geometry), obtainedGeometry(geometry), margins(QWindowsGeometryHint::frame(style, exStyle)), customMargins(cm) { @@ -1124,7 +1125,7 @@ void QWindowsWindow::initialize() if (w->type() != Qt::Desktop) { const Qt::WindowState state = w->windowState(); if (state != Qt::WindowMaximized && state != Qt::WindowFullScreen - && creationContext->requestedGeometry != creationContext->obtainedGeometry) { + && creationContext->requestedGeometryIn != creationContext->obtainedGeometry) { QWindowSystemInterface::handleGeometryChange(w, creationContext->obtainedGeometry); } QPlatformScreen *obtainedScreen = screenForGeometry(creationContext->obtainedGeometry); diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 982f3dfd30..f0789e5167 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -81,9 +81,10 @@ struct QWindowsGeometryHint struct QWindowCreationContext { - QWindowCreationContext(const QWindow *w, const QRect &r, - const QMargins &customMargins, - DWORD style, DWORD exStyle); + explicit QWindowCreationContext(const QWindow *w, + const QRect &geometryIn, const QRect &geometry, + const QMargins &customMargins, + DWORD style, DWORD exStyle); void applyToMinMaxInfo(MINMAXINFO *mmi) const { geometryHint.applyToMinMaxInfo(style, exStyle, mmi); } @@ -91,7 +92,8 @@ struct QWindowCreationContext const QWindow *window; DWORD style; DWORD exStyle; - QRect requestedGeometry; + QRect requestedGeometryIn; // QWindow scaled + QRect requestedGeometry; // after QPlatformWindow::initialGeometry() QRect obtainedGeometry; QMargins margins; QMargins customMargins; // User-defined, additional frame for WM_NCCALCSIZE diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index bddc1cd2b2..906587248e 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -277,6 +277,8 @@ private slots: void compareSanity(); void compareRich(); + void nullConvert(); + void accessSequentialContainerKey(); private: @@ -4861,6 +4863,33 @@ void tst_QVariant::compareRich() << QStringLiteral("d")); } +void tst_QVariant::nullConvert() +{ + // Test quirks with QVariants different types of null states. + + // null variant with no initialized value + QVariant nullVar(QVariant::String); + QVERIFY(nullVar.isValid()); + QVERIFY(nullVar.isNull()); + // We can not convert a variant with no value + QVERIFY(!nullVar.convert(QVariant::Url)); + QCOMPARE(nullVar.type(), QVariant::Url); + QVERIFY(nullVar.isNull()); + + // variant initialized with null value + QVariant nullStr = QVariant::fromValue(QString()); + QVERIFY(nullStr.isValid()); + QVERIFY(nullStr.isNull()); + // We can convert an initialized null value however + QVERIFY(nullStr.convert(QVariant::Url)); + QCOMPARE(nullStr.type(), QVariant::Url); + QVERIFY(nullStr.isValid()); + // QUrl does not have an isNull method + QVERIFY(!nullStr.isNull()); + // The URL is not valid however + QVERIFY(!nullStr.toUrl().isValid()); +} + void tst_QVariant::accessSequentialContainerKey() { QString nameResult; @@ -4885,6 +4914,5 @@ void tst_QVariant::accessSequentialContainerKey() QCOMPARE(nameResult, QStringLiteral("Seven")); } - QTEST_MAIN(tst_QVariant) #include "tst_qvariant.moc" diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 1dcd642023..782eed03e8 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -92,6 +92,7 @@ private slots: void tryTake(); void waitForDoneTimeout(); void destroyingWaitsForTasksToFinish(); + void stackSize(); void stressTest(); private: @@ -1136,6 +1137,39 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish() } } +// Verify that QThreadPool::stackSize is used when creating +// new threads. Note that this tests the Qt property only +// since QThread::stackSize() does not reflect the actual +// stack size used by the native thread. +void tst_QThreadPool::stackSize() +{ + uint targetStackSize = 512 * 1024; + uint threadStackSize = 1; // impossible value + + class StackSizeChecker : public QRunnable + { + public: + uint *stackSize; + + StackSizeChecker(uint *stackSize) + :stackSize(stackSize) + { + + } + + void run() + { + *stackSize = QThread::currentThread()->stackSize(); + } + }; + + QThreadPool threadPool; + threadPool.setStackSize(targetStackSize); + threadPool.start(new StackSizeChecker(&threadStackSize)); + QVERIFY(threadPool.waitForDone(30000)); // 30s timeout + QCOMPARE(threadStackSize, targetStackSize); +} + void tst_QThreadPool::stressTest() { class Task : public QRunnable diff --git a/tests/auto/network/access/hsts/tst_qhsts.cpp b/tests/auto/network/access/hsts/tst_qhsts.cpp index 656516f46b..d72991a2eb 100644 --- a/tests/auto/network/access/hsts/tst_qhsts.cpp +++ b/tests/auto/network/access/hsts/tst_qhsts.cpp @@ -32,7 +32,9 @@ #include <QtCore/qvector.h> #include <QtCore/qpair.h> #include <QtCore/qurl.h> +#include <QtCore/qdir.h> +#include <QtNetwork/private/qhstsstore_p.h> #include <QtNetwork/private/qhsts_p.h> QT_USE_NAMESPACE @@ -46,6 +48,7 @@ private Q_SLOTS: void testMultilpeKnownHosts(); void testPolicyExpiration(); void testSTSHeaderParser(); + void testStore(); }; void tst_QHsts::testSingleKnownHost_data() @@ -313,6 +316,75 @@ void tst_QHsts::testSTSHeaderParser() QVERIFY(!parser.expirationDate().isValid()); } +const QLatin1String storeDir("."); + +struct TestStoreDeleter +{ + ~TestStoreDeleter() + { + QDir cwd; + if (!cwd.remove(QHstsStore::absoluteFilePath(storeDir))) + qWarning() << "tst_QHsts::testStore: failed to remove the hsts store file"; + } +}; + +void tst_QHsts::testStore() +{ + // Delete the store's file after we finish the test. + TestStoreDeleter cleaner; + + const QUrl exampleCom(QStringLiteral("http://example.com")); + const QUrl subDomain(QStringLiteral("http://subdomain.example.com")); + const QDateTime validDate(QDateTime::currentDateTimeUtc().addDays(1)); + + { + // We start from an empty cache and empty store: + QHstsCache cache; + QHstsStore store(storeDir); + cache.setStore(&store); + QVERIFY(!cache.isKnownHost(exampleCom)); + QVERIFY(!cache.isKnownHost(subDomain)); + // (1) This will also store the policy: + cache.updateKnownHost(exampleCom, validDate, true); + QVERIFY(cache.isKnownHost(exampleCom)); + QVERIFY(cache.isKnownHost(subDomain)); + } + { + // Test the policy stored at (1): + QHstsCache cache; + QHstsStore store(storeDir); + cache.setStore(&store); + QVERIFY(cache.isKnownHost(exampleCom)); + QVERIFY(cache.isKnownHost(subDomain)); + // (2) Remove subdomains: + cache.updateKnownHost(exampleCom, validDate, false); + QVERIFY(!cache.isKnownHost(subDomain)); + } + { + // Test the previous update (2): + QHstsCache cache; + QHstsStore store(storeDir); + cache.setStore(&store); + QVERIFY(cache.isKnownHost(exampleCom)); + QVERIFY(!cache.isKnownHost(subDomain)); + } + { + QHstsCache cache; + cache.updateKnownHost(subDomain, validDate, false); + QVERIFY(cache.isKnownHost(subDomain)); + QHstsStore store(storeDir); + // (3) This should store policy from cache, over old policy from store: + cache.setStore(&store); + } + { + // Test that (3) was stored: + QHstsCache cache; + QHstsStore store(storeDir); + cache.setStore(&store); + QVERIFY(cache.isKnownHost(subDomain)); + } +} + QTEST_MAIN(tst_QHsts) #include "tst_qhsts.moc" |