diff options
Diffstat (limited to 'tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp')
-rw-r--r-- | tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp | 2166 |
1 files changed, 1687 insertions, 479 deletions
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index 5e8a5420e1..66cbeaac9b 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -1,54 +1,38 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** 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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtNetwork/qtnetworkglobal.h> #include <QTest> #include <QSemaphore> #include <QTestEventLoop> #include <QSignalSpy> +#if QT_CONFIG(process) #include <QProcess> +#endif #include <QTimer> #include <QWaitCondition> #include <QScopeGuard> #include <QBuffer> +#include <QMap> #include <QtCore/QCryptographicHash> #include <QtCore/QDataStream> -#include <QtCore/QUrl> +#include <QtCore/QDateTime> #include <QtCore/QEventLoop> #include <QtCore/QElapsedTimer> #include <QtCore/QFile> +#include <QtCore/QList> #include <QtCore/QRandomGenerator> #include <QtCore/QRegularExpression> #include <QtCore/QRegularExpressionMatch> +#include <QtCore/QSet> #include <QtCore/QSharedPointer> #include <QtCore/QScopedPointer> #include <QtCore/QTemporaryFile> +#include <QtCore/QTimeZone> +#include <QtCore/QUrl> + #include <QtNetwork/QTcpServer> #include <QtNetwork/QTcpSocket> #include <QtNetwork/QLocalSocket> @@ -63,14 +47,16 @@ #include <QtNetwork/qnetworkdiskcache.h> #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/QHttp1Configuration> #include <QtNetwork/qnetworkcookie.h> #include <QtNetwork/QNetworkCookieJar> #include <QtNetwork/QHttpPart> #include <QtNetwork/QHttpMultiPart> #include <QtNetwork/QNetworkProxyQuery> -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) #include <QtNetwork/qsslerror.h> #include <QtNetwork/qsslconfiguration.h> +#include <QtNetwork/qsslsocket.h> #ifdef QT_BUILD_INTERNAL #include <QtNetwork/private/qsslconfiguration_p.h> #endif @@ -82,6 +68,9 @@ Q_DECLARE_METATYPE(QSharedPointer<char>) #endif +#include <memory> +#include <optional> + #ifdef Q_OS_UNIX # include <sys/types.h> # include <unistd.h> // for getuid() @@ -90,23 +79,23 @@ Q_DECLARE_METATYPE(QSharedPointer<char>) #include "../../../network-settings.h" -// Non-OpenSSL backends are not able to report a specific error code -// for self-signed certificates. -#ifndef QT_NO_OPENSSL -#define FLUKE_CERTIFICATE_ERROR QSslError::SelfSignedCertificate -#else -#define FLUKE_CERTIFICATE_ERROR QSslError::CertificateUntrusted +#ifdef Q_OS_INTEGRITY +#include "qplatformdefs.h" #endif Q_DECLARE_METATYPE(QAuthenticator*) -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) Q_DECLARE_METATYPE(QNetworkProxyQuery) #endif typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr; -#ifndef QT_NO_OPENSSL +using namespace Qt::StringLiterals; +using namespace std::chrono_literals; + +#if QT_CONFIG(ssl) QT_BEGIN_NAMESPACE +// Technically, a workaround, and only needed for OpenSSL: void qt_ForceTlsSecurityLevel(); QT_END_NAMESPACE #endif @@ -116,7 +105,7 @@ class tst_QNetworkReply: public QObject { Q_OBJECT -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) struct ProxyData { ProxyData(const QNetworkProxy &p, const QByteArray &t, bool auth) @@ -125,7 +114,7 @@ class tst_QNetworkReply: public QObject QNetworkProxy proxy; bool requiresAuthentication; }; -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) static bool seedCreated; static QString createUniqueExtension() @@ -145,6 +134,29 @@ class tst_QNetworkReply: public QObject "\r\n"; return s; } + static QString movedReplyStr() { + QString s = "HTTP/1.1 301 Moved Permanently\r\n" + "Content-Type: text/plain\r\n" + "location: %1\r\n" + "\r\n"; + return s; + } + + static QString foundReplyStr() { + QString s = "HTTP/1.1 302 Found\r\n" + "Content-Type: text/plain\r\n" + "location: %1\r\n" + "\r\n"; + return s; + } + + static QString permRedirectReplyStr() { + QString s = "HTTP/1.1 308 Permanent Redirect\r\n" + "Content-Type: text/plain\r\n" + "location: %1\r\n" + "\r\n"; + return s; + } static const QByteArray httpEmpty200Response; static const QString filePermissionFileName; @@ -158,16 +170,18 @@ class tst_QNetworkReply: public QObject QString wronlyFileName; #endif QString uniqueExtension; -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) QList<ProxyData> proxies; #endif QNetworkAccessManager manager; MyCookieJar *cookieJar; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QSslConfiguration storedSslConfiguration; QList<QSslError> storedExpectedSslErrors; static const QString certsFilePath; -#endif + bool isSecureTransport = false; + bool isSchannel = false; +#endif // QT_CONFIG(ssl) using QObject::connect; static bool connect(const QNetworkReplyPtr &ptr, const char *signal, const QObject *receiver, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection) @@ -195,7 +209,7 @@ public Q_SLOTS: void pipeliningHelperSlot(); void emitErrorForAllRepliesSlot(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void sslErrors(QNetworkReply*,const QList<QSslError> &); void storeSslConfiguration(); void ignoreSslErrorListSlot(QNetworkReply *reply, const QList<QSslError> &); @@ -226,12 +240,18 @@ private Q_SLOTS: void getFromFtpAfterError(); // QTBUG-40797 void getFromHttp_data(); void getFromHttp(); + void getWithBodyFromHttp_data(); + void getWithBodyFromHttp(); + void getWithAndWithoutBodyFromHttp_data(); + void getWithAndWithoutBodyFromHttp(); + void getWithBodyRedirected_data(); + void getWithBodyRedirected(); void getErrors_data(); void getErrors(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void headFromHttp_data(); void headFromHttp(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void putToFile_data(); void putToFile(); void putToFtp_data(); @@ -243,6 +263,8 @@ private Q_SLOTS: void putToHttpSynchronous(); void putToHttpMultipart_data(); void putToHttpMultipart(); + void putWithoutBody(); + void putWithoutBody_data(); void postToHttp_data(); void postToHttp(); void postToHttpSynchronous_data(); @@ -250,7 +272,9 @@ private Q_SLOTS: void postToHttpMultipart_data(); void postToHttpMultipart(); void multipartSkipIndices(); // QTBUG-32534 -#ifndef QT_NO_SSL + void postWithoutBody_data(); + void postWithoutBody(); +#if QT_CONFIG(ssl) void putToHttps_data(); void putToHttps(); void putToHttpsSynchronous_data(); @@ -277,6 +301,7 @@ private Q_SLOTS: void ioGetFromFileSpecial(); void ioGetFromFile_data(); void ioGetFromFile(); + void ioGetFromFileUrl(); void ioGetFromFtp_data(); void ioGetFromFtp(); void ioGetFromFtpWithReuse(); @@ -289,29 +314,29 @@ private Q_SLOTS: void ioGetFromHttpWithAuth_data(); void ioGetFromHttpWithAuth(); void ioGetFromHttpWithAuthSynchronous(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void ioGetFromHttpWithProxyAuth(); void ioGetFromHttpWithProxyAuthSynchronous(); void ioGetFromHttpWithSocksProxy(); -#endif // !QT_NO_NETWORKPROXY -#ifndef QT_NO_SSL +#endif // QT_CONFIG(networkproxy) +#if QT_CONFIG(ssl) void ioGetFromHttpsWithSslErrors(); void ioGetFromHttpsWithIgnoreSslErrors(); void ioGetFromHttpsWithSslHandshakeError(); #endif void ioGetFromHttpBrokenServer_data(); void ioGetFromHttpBrokenServer(); - void ioGetFromHttpStatus100_data(); - void ioGetFromHttpStatus100(); + void ioGetFromHttpStatusInformational_data(); + void ioGetFromHttpStatusInformational(); void ioGetFromHttpNoHeaders_data(); void ioGetFromHttpNoHeaders(); void ioGetFromHttpWithCache_data(); void ioGetFromHttpWithCache(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void ioGetWithManyProxies_data(); void ioGetWithManyProxies(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void ioPutToFileFromFile_data(); void ioPutToFileFromFile(); @@ -327,12 +352,12 @@ private Q_SLOTS: void ioPutToHttpFromFile(); void ioPostToHttpFromFile_data(); void ioPostToHttpFromFile(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void ioPostToHttpFromSocket_data(); void ioPostToHttpFromSocket(); void ioPostToHttpFromSocketSynchronous(); void ioPostToHttpFromSocketSynchronous_data(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void ioPostToHttpFromMiddleOfFileToEnd(); void ioPostToHttpFromMiddleOfFileFiveBytes(); void ioPostToHttpFromMiddleOfQBufferFiveBytes(); @@ -372,13 +397,13 @@ private Q_SLOTS: void nestedEventLoops(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void httpProxyCommands_data(); void httpProxyCommands(); void httpProxyCommandsSynchronous_data(); void httpProxyCommandsSynchronous(); void proxyChange(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void authorizationError_data(); void authorizationError(); @@ -392,7 +417,7 @@ private Q_SLOTS: void httpRecursiveCreation(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void ioPostToHttpsUploadProgress(); void ignoreSslErrorsList_data(); void ignoreSslErrorsList(); @@ -449,20 +474,27 @@ private Q_SLOTS: void synchronousRequest_data(); void synchronousRequest(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void synchronousRequestSslFailure(); #endif void httpAbort(); + void closeClientSideConnectionEagerlyQtbug20726(); + void varyingCacheExpiry_data(); + void varyingCacheExpiry(); + + void amountOfHttp1ConnectionsQtbug25280_data(); + void amountOfHttp1ConnectionsQtbug25280(); + void dontInsertPartialContentIntoTheCache(); void httpUserAgent(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void authenticationCacheAfterCancel_data(); void authenticationCacheAfterCancel(); void authenticationWithDifferentRealm(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void synchronousAuthenticationCache(); void pipelining(); @@ -489,6 +521,7 @@ private Q_SLOTS: void ioHttpCookiesDuringRedirect(); void ioHttpRedirect_data(); void ioHttpRedirect(); + void ioHttpRedirectWithCache(); void ioHttpRedirectFromLocalToRemote(); void ioHttpRedirectPostPut_data(); void ioHttpRedirectPostPut(); @@ -498,7 +531,7 @@ private Q_SLOTS: void ioHttpRedirectCustom(); void ioHttpRedirectWithUploadDevice_data(); void ioHttpRedirectWithUploadDevice(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void putWithServerClosingConnectionImmediately(); #endif @@ -507,11 +540,33 @@ private Q_SLOTS: void autoDeleteReplies_data(); void autoDeleteReplies(); - void getWithTimeout(); - void postWithTimeout(); + void requestWithTimeout_data(); + void requestWithTimeout(); + + void moreActivitySignals_data(); + void moreActivitySignals(); void contentEncoding_data(); void contentEncoding(); + void contentEncodingBigPayload_data(); + void contentEncodingBigPayload(); + void cacheWithContentEncoding_data(); + void cacheWithContentEncoding(); + void downloadProgressWithContentEncoding_data(); + void downloadProgressWithContentEncoding(); + void contentEncodingError_data(); + void contentEncodingError(); + void compressedReadyRead(); + void notFoundWithCompression_data(); + void notFoundWithCompression(); + + void qhttpPartDebug_data(); + void qhttpPartDebug(); + + void qtbug68821proxyError_data(); + void qtbug68821proxyError(); + + void abortAndError(); // NOTE: This test must be last! void parentingRepliesToTheApp(); @@ -522,6 +577,9 @@ private: bool notEnoughDataForFastSender; bool ftpSupported = false; +#if QT_CONFIG(ssl) + QSslError::SslError flukeCertTlsError = QSslError::CertificateUntrusted; +#endif }; const QByteArray tst_QNetworkReply::httpEmpty200Response = @@ -548,7 +606,7 @@ static bool validateRedirectedResponseHeaders(QNetworkReplyPtr reply) && !reply->header(QNetworkRequest::LocationHeader).isValid(); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) static void setupSslServer(QSslSocket* serverSocket) { QString testDataDir = QFileInfo(QFINDTESTDATA("rfc3252.txt")).absolutePath(); @@ -574,7 +632,7 @@ const QString tst_QNetworkReply::certsFilePath = "/certs/qt-test-server-host-net const QString tst_QNetworkReply::certsFilePath = "/certs/qt-test-server-cacert.pem"; #endif -#endif // !QT_NO_SSL +#endif // QT_CONFIG(ssl) // NOTE: MiniHttpServer has a very limited support of PUT/POST requests! Make // sure you understand the server's code before PUTting/POSTing data (and @@ -594,7 +652,8 @@ public: int totalConnections; bool stopTransfer = false; - bool hasContent = false; + bool checkedContentLength = false; + bool foundContentLength = false; int contentRead = 0; int contentLength = 0; @@ -626,13 +685,14 @@ public: { contentLength = 0; receivedData.clear(); + foundContentLength = false; } protected: void incomingConnection(qintptr socketDescriptor) override { //qDebug() << "incomingConnection" << socketDescriptor << "doSsl:" << doSsl << "ipv6:" << ipv6; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) if (doSsl) { QSslSocket *serverSocket = new QSslSocket(this); if (!serverSocket->setSocketDescriptor(socketDescriptor)) { @@ -682,8 +742,13 @@ private: void parseContentLength() { - int index = receivedData.indexOf("Content-Length:"); - index += sizeof("Content-Length:") - 1; + int index = receivedData.indexOf("content-length:"); + if (index == -1) + return; + + foundContentLength = true; + + index += sizeof("content-length:") - 1; const auto end = std::find(receivedData.cbegin() + index, receivedData.cend(), '\r'); auto num = receivedData.mid(index, std::distance(receivedData.cbegin() + index, end)); bool ok; @@ -693,7 +758,7 @@ private: } private slots: -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void slotSslErrors(const QList<QSslError>& errors) { QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender()); @@ -723,12 +788,14 @@ public slots: if (doubleEndlPos != -1) { const int endOfHeader = doubleEndlPos + 4; - hasContent = receivedData.startsWith("POST") || receivedData.startsWith("PUT") - || receivedData.startsWith("CUSTOM_WITH_PAYLOAD"); - if (hasContent && contentLength == 0) + contentRead = receivedData.size() - endOfHeader; + + if (!checkedContentLength) { parseContentLength(); - contentRead = receivedData.length() - endOfHeader; - if (hasContent && contentRead < contentLength) + checkedContentLength = true; + } + + if (contentRead < contentLength) return; // multiple requests incoming. remove the bytes of the current one @@ -764,7 +831,7 @@ public: { QNetworkCookieJar::setAllCookies(cookieList); } }; -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) class MyProxyFactory: public QNetworkProxyFactory { public: @@ -787,7 +854,7 @@ public: return toReturn; } }; -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) class MyMemoryCache: public QAbstractNetworkCache { @@ -829,7 +896,7 @@ public: qint64 cacheSize() const override { qint64 total = 0; - foreach (const CachedContent &entry, cache) + for (const auto &[_, entry] : cache.asKeyValueRange()) total += entry.second.size(); return total; } @@ -871,9 +938,15 @@ public: { } - QIODevice *data(const QUrl &) override + QIODevice *data(const QUrl &url) override { - return 0; + QIODevice *device = nullptr; + auto it = m_buffers.constFind(url); + if (it != m_buffers.cend()) { + device = *it; + device->seek(0); + } + return device; } bool remove(const QUrl &url) override @@ -900,7 +973,6 @@ public: { QUrl url = buffer->property("url").toUrl(); m_insertedUrls << url; - delete m_buffers.take(url); } void clear() override { m_insertedUrls.clear(); } @@ -1000,7 +1072,7 @@ public: } virtual void incomingConnection(qintptr socketDescriptor) override { -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) if (doSsl) { QSslSocket *serverSocket = new QSslSocket; serverSocket->setParent(this); @@ -1016,7 +1088,7 @@ public: } private slots: -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void slotSslErrors(const QList<QSslError>& errors) { qDebug() << "slotSslErrors" << sslSocket->errorString() << errors; @@ -1286,12 +1358,17 @@ tst_QNetworkReply::tst_QNetworkReply() { qRegisterMetaType<QNetworkReply *>(); // for QSignalSpy qRegisterMetaType<QAuthenticator *>(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) qRegisterMetaType<QNetworkProxy>(); #endif -#ifndef QT_NO_SSL + +#if QT_CONFIG(ssl) qRegisterMetaType<QList<QSslError> >(); + isSecureTransport = QSslSocket::activeBackend() == QStringLiteral("securetransport"); + if (!isSecureTransport) + isSchannel = QSslSocket::activeBackend() == QStringLiteral("schannel"); #endif + qRegisterMetaType<QNetworkReply::NetworkError>(); uniqueExtension = createUniqueExtension(); @@ -1299,7 +1376,7 @@ tst_QNetworkReply::tst_QNetworkReply() cookieJar = new MyCookieJar; manager.setCookieJar(cookieJar); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::httpProxyServerName()); proxies << ProxyData(QNetworkProxy::NoProxy, "", false); @@ -1318,13 +1395,13 @@ tst_QNetworkReply::tst_QNetworkReply() << ProxyData(QNetworkProxy(QNetworkProxy::Socks5Proxy, socksProxy, 1081), "+socksauth", true); } else { -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) fprintf(stderr, "==================================================================\n"); fprintf(stderr, "Proxy could not be looked up. No proxy will be used while testing!\n"); fprintf(stderr, "==================================================================\n"); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) ftpSupported = manager.supportedSchemes().contains("ftp"); } @@ -1346,7 +1423,7 @@ void tst_QNetworkReply::proxyAuthenticationRequired(const QNetworkProxy &, QAuth auth->setPassword("password"); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors) { reply->ignoreSslErrors(); @@ -1444,11 +1521,11 @@ QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, while (!reply->isFinished()) { QTimer::singleShot(20000, loop, SLOT(quit())); code = loop->exec(); - if (count == spy.count() && !reply->isFinished()) { + if (count == spy.size() && !reply->isFinished()) { code = Timeout; break; } - count = spy.count(); + count = spy.size(); } delete loop; loop = 0; @@ -1514,11 +1591,11 @@ int tst_QNetworkReply::waitForFinish(QNetworkReplyPtr &reply) QSignalSpy spy(reply.data(), SIGNAL(downloadProgress(qint64,qint64))); while (!reply->isFinished()) { QTimer::singleShot(5000, loop, SLOT(quit())); - if (loop->exec() == Timeout && count == spy.count() && !reply->isFinished()) { + if (loop->exec() == Timeout && count == spy.size() && !reply->isFinished()) { returnCode = Timeout; break; } - count = spy.count(); + count = spy.size(); } delete loop; loop = 0; @@ -1546,8 +1623,10 @@ void tst_QNetworkReply::initTestCase() testDataDir = QCoreApplication::applicationDirPath(); #if defined(QT_TEST_SERVER) - QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpServerName(), 21)); - QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpProxyServerName(), 2121)); + if (ftpSupported) { + QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpServerName(), 21)); + QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpProxyServerName(), 2121)); + } QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 80)); QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 443)); QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpProxyServerName(), 3128)); @@ -1568,7 +1647,7 @@ void tst_QNetworkReply::initTestCase() #endif QDir::setSearchPaths("testdata", QStringList() << testDataDir); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QSslConfiguration::defaultConfiguration().caCertificates(); //preload certificates #endif @@ -1577,10 +1656,18 @@ void tst_QNetworkReply::initTestCase() QString::fromLatin1("Couldn't find echo dir starting from %1.").arg(QDir::currentPath()))); cleanupTestData(); -#ifndef QT_NO_OPENSSL +#if QT_CONFIG(ssl) QT_PREPEND_NAMESPACE(qt_ForceTlsSecurityLevel)(); -#endif // QT_NO_OPENSSL + if (QSslSocket::activeBackend() == QStringLiteral("openssl")) + flukeCertTlsError = QSslError::SelfSignedCertificate; +#endif + + // For content encoding tests + Q_INIT_RESOURCE(gzip); +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + Q_INIT_RESOURCE(zstandard); +#endif } void tst_QNetworkReply::cleanupTestCase() @@ -1589,6 +1676,12 @@ void tst_QNetworkReply::cleanupTestCase() if (!wronlyFileName.isNull()) QFile::remove(wronlyFileName); #endif + + // For content encoding tests +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + Q_CLEANUP_RESOURCE(zstandard); +#endif + Q_CLEANUP_RESOURCE(gzip); } void tst_QNetworkReply::cleanupTestData() @@ -1598,7 +1691,7 @@ void tst_QNetworkReply::cleanupTestData() // clear the internal cache manager.clearAccessCache(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) manager.setProxy(QNetworkProxy()); #endif manager.setCache(0); @@ -1607,7 +1700,7 @@ void tst_QNetworkReply::cleanupTestData() cookieJar->setAllCookies(QList<QNetworkCookie>()); // disconnect manager signals -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) manager.disconnect(SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); #endif manager.disconnect(SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); @@ -1946,7 +2039,254 @@ void tst_QNetworkReply::getFromHttp() QCOMPARE(reply->readAll(), reference.readAll()); } -#ifndef QT_NO_NETWORKPROXY +void tst_QNetworkReply::getWithBodyFromHttp_data() +{ + QTest::addColumn<QByteArray>("dataFromClientToServer"); + QTest::addColumn<bool>("useDevice"); + QTest::newRow("with-bytearray") << QByteArray("Body 1") << false; + QTest::newRow("with-bytearray2") << QByteArray("Body 2") << false; + QTest::newRow("with-bytearray3") << QByteArray("Body 3") << false; + QTest::newRow("with-device") << QByteArray("Body 1") << true; + QTest::newRow("with-device2") << QByteArray("Body 2") << true; + QTest::newRow("with-device3") << QByteArray("Body 3") << true; +} + +void tst_QNetworkReply::getWithBodyFromHttp() +{ + QFETCH(QByteArray, dataFromClientToServer); + QFETCH(bool, useDevice); + + QBuffer buff; + buff.setData(dataFromClientToServer); + buff.open(QIODevice::ReadOnly); + + QByteArray dataFromServerToClient = QByteArray("Long first line\r\nLong second line"); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(dataFromServerToClient.size()); + httpResponse += "\r\n\r\n"; + httpResponse += dataFromServerToClient; + + MiniHttpServer server(httpResponse); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply; + + if (useDevice) + reply.reset(manager.get(request, &buff)); + else + reply.reset(manager.get(request, dataFromClientToServer)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(server.contentLength, dataFromClientToServer.size()); + QCOMPARE(server.receivedData.right(dataFromClientToServer.size()), dataFromClientToServer); + QByteArray content = reply->readAll(); + QCOMPARE(content, dataFromServerToClient); +} + +void tst_QNetworkReply::getWithAndWithoutBodyFromHttp_data() +{ + QTest::addColumn<QByteArray>("dataFromClientToServer"); + QTest::addColumn<bool>("alwaysCache"); + QTest::addColumn<tst_QNetworkReply::RunSimpleRequestReturn>("requestReturn"); + QTest::addColumn<bool>("useDevice"); + QTest::newRow("with-bytearray") << QByteArray("Body 1") << false << Success << false; + QTest::newRow("with-bytearray2") << QByteArray("Body 2") << false << Success << false; + QTest::newRow("with-bytearray3") << QByteArray("Body 3") << false << Success << false; + QTest::newRow("with-bytearray-cache") << QByteArray("Body 1") << true << Failure << false; + QTest::newRow("with-bytearray-cache2") << QByteArray("Body 2") << true << Failure << false; + QTest::newRow("with-bytearray-cache3") << QByteArray("Body 3") << true << Failure << false; + QTest::newRow("with-device") << QByteArray("Body 1") << false << Success << true; + QTest::newRow("with-device2") << QByteArray("Body 2") << false << Success << true; + QTest::newRow("with-device3") << QByteArray("Body 3") << false << Success << true; + QTest::newRow("with-device-cache") << QByteArray("Body 1") << true << Failure << true; + QTest::newRow("with-device-cache2") << QByteArray("Body 2") << true << Failure << true; + QTest::newRow("with-device-cache3") << QByteArray("Body 3") << true << Failure << true; +} + +void tst_QNetworkReply::getWithAndWithoutBodyFromHttp() +{ + QFETCH(QByteArray, dataFromClientToServer); + QFETCH(bool, alwaysCache); + QFETCH(tst_QNetworkReply::RunSimpleRequestReturn, requestReturn); + QFETCH(bool, useDevice); + + QBuffer buff; + buff.setData(dataFromClientToServer); + buff.open(QIODevice::ReadOnly); + + QNetworkAccessManager qnam; + MyMemoryCache *memoryCache = new MyMemoryCache(&qnam); + qnam.setCache(memoryCache); + + const int sizeOfDataFromServerToClient =3; + QByteArray dataFromServerToClient1 = QByteArray("aaa"); + QByteArray dataFromServerToClient2 = QByteArray("bbb"); + QByteArray dataFromServerToClient3 = QByteArray("ccc"); + + QByteArray baseHttpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + baseHttpResponse += QByteArray::number(sizeOfDataFromServerToClient); + baseHttpResponse += "\r\n\r\n"; + + MiniHttpServer server(baseHttpResponse + dataFromServerToClient1); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + + // Send request without body + QNetworkReplyPtr reply(manager.get(request)); + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QByteArray content = reply->readAll(); + QCOMPARE(content, dataFromServerToClient1); + + if (alwaysCache) { + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::AlwaysCache); + } + + server.dataToTransmit = baseHttpResponse + dataFromServerToClient2; + + // Send request with body + QNetworkReplyPtr reply2; + if (useDevice) + reply2.reset(manager.get(request, &buff)); + else + reply2.reset(manager.get(request, dataFromClientToServer)); + + QVERIFY2(waitForFinish(reply2) == requestReturn, msgWaitForFinished(reply2)); + content = reply2->readAll(); + + if (alwaysCache) + QVERIFY(content.isEmpty()); + else + QCOMPARE(content, dataFromServerToClient2); + + QCOMPARE(reply2->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), false); + + if (alwaysCache) { + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::PreferNetwork); + } + + server.dataToTransmit = baseHttpResponse + dataFromServerToClient3; + + // Send another request without a body + QNetworkReplyPtr reply3(manager.get(request)); + QVERIFY2(waitForFinish(reply3) == Success, msgWaitForFinished(reply3)); + content = reply3->readAll(); + QCOMPARE(content, dataFromServerToClient3); + QCOMPARE(reply3->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), false); +} + +void tst_QNetworkReply::getWithBodyRedirected_data() +{ + QTest::addColumn<QByteArray>("dataFromClientToServer"); + QTest::addColumn<bool>("useDevice"); + QTest::addColumn<int>("status"); + QTest::newRow("with-bytearray - 301") << QByteArray("Body 1") << false << 301; + QTest::newRow("with-bytearray2 - 301") << QByteArray("Body 2") << false << 301; + QTest::newRow("with-bytearray3 - 301") << QByteArray("Body 3") << false << 301; + QTest::newRow("with-device - 301") << QByteArray("Body 1") << true << 301; + QTest::newRow("with-device2 - 301") << QByteArray("Body 2") << true << 301; + QTest::newRow("with-device3 - 301") << QByteArray("Body 3") << true << 301; + QTest::newRow("with-bytearray - 302") << QByteArray("Body 1") << false << 302; + QTest::newRow("with-bytearray2 - 302") << QByteArray("Body 2") << false << 302; + QTest::newRow("with-bytearray3 - 302") << QByteArray("Body 3") << false << 302; + QTest::newRow("with-device - 302") << QByteArray("Body 1") << true << 302; + QTest::newRow("with-device2 - 302") << QByteArray("Body 2") << true << 302; + QTest::newRow("with-device3 - 302") << QByteArray("Body 3") << true << 302; + QTest::newRow("with-bytearray - 307") << QByteArray("Body 1") << false << 307; + QTest::newRow("with-bytearray2 - 307") << QByteArray("Body 2") << false << 307; + QTest::newRow("with-bytearray3 - 307") << QByteArray("Body 3") << false << 307; + QTest::newRow("with-device - 307") << QByteArray("Body 1") << true << 307; + QTest::newRow("with-device2 - 307") << QByteArray("Body 2") << true << 307; + QTest::newRow("with-device3 - 307") << QByteArray("Body 3") << true << 307; + QTest::newRow("with-bytearray - 308") << QByteArray("Body 1") << false << 308; + QTest::newRow("with-bytearray2 - 308") << QByteArray("Body 2") << false << 308; + QTest::newRow("with-bytearray3 - 308") << QByteArray("Body 3") << false << 308; + QTest::newRow("with-device - 308") << QByteArray("Body 1") << true << 308; + QTest::newRow("with-device2 - 308") << QByteArray("Body 2") << true << 308; + QTest::newRow("with-device3 - 308") << QByteArray("Body 3") << true << 308; +} + +void tst_QNetworkReply::getWithBodyRedirected() +{ + QFETCH(QByteArray, dataFromClientToServer); + QFETCH(bool, useDevice); + QFETCH(int, status); + + QBuffer buff; + buff.setData(dataFromClientToServer); + buff.open(QIODevice::ReadOnly); + + QUrl localhost = QUrl("http://localhost"); + + // Setup server to which the second server will redirect to + MiniHttpServer server2(httpEmpty200Response); + + QUrl redirectUrl = QUrl(localhost); + redirectUrl.setPort(server2.serverPort()); + + QByteArray redirectReply; + switch (status) { + case 301: redirectReply = + foundReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + case 302: redirectReply = + movedReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + case 307: redirectReply = + tempRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + case 308: redirectReply = + permRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + default: QFAIL("Unexpected status code"); break; + } + + // Setup redirect server + MiniHttpServer server(redirectReply); + + localhost.setPort(server.serverPort()); + QNetworkRequest request(localhost); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, + QNetworkRequest::NoLessSafeRedirectPolicy); + + QNetworkReplyPtr reply; + if (useDevice) + reply.reset(manager.get(request, &buff)); + else + reply.reset(manager.get(request, dataFromClientToServer)); + + QSignalSpy redSpy(reply.data(), SIGNAL(redirected(QUrl))); + QSignalSpy finSpy(reply.data(), SIGNAL(finished())); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + + // Redirected and finished should be emitted exactly once + QCOMPARE(redSpy.size(), 1); + QCOMPARE(finSpy.size(), 1); + + // Original URL should not be changed after redirect + QCOMPARE(request.url(), localhost); + + // Verify Redirect url + QList<QVariant> args = redSpy.takeFirst(); + QCOMPARE(args.at(0).toUrl(), redirectUrl); + + // Reply url is set to the redirect url + QCOMPARE(reply->url(), redirectUrl); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(validateRedirectedResponseHeaders(reply)); + + // Verify that the message body has arrived to the server + if (status > 302) { + QVERIFY(server2.contentLength != 0); + QCOMPARE(server2.contentLength, dataFromClientToServer.size()); + QCOMPARE(server2.receivedData.right(dataFromClientToServer.size()), dataFromClientToServer); + } else { + // In these cases the message body should not reach the server + QVERIFY(server2.contentLength == 0); + } +} + +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::headFromHttp_data() { QTest::addColumn<qint64>("referenceSize"); @@ -1965,7 +2305,7 @@ void tst_QNetworkReply::headFromHttp_data() QString httpServer = QtNetworkSettings::httpServerName(); //testing proxies, mainly for the 407 response from http proxy - for (int i = 0; i < proxies.count(); ++i) { + for (int i = 0; i < proxies.size(); ++i) { QTest::newRow("rfc" + proxies.at(i).tag) << rfcsize << QUrl("http://" + httpServer + "/qtest/rfc3252.txt") @@ -2035,7 +2375,7 @@ void tst_QNetworkReply::headFromHttp() if (reply->header(QNetworkRequest::ContentTypeHeader).isValid()) QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toString(), contentType); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void tst_QNetworkReply::getErrors_data() { @@ -2247,9 +2587,9 @@ void tst_QNetworkReply::putToFtp() QSignalSpy spy(r, SIGNAL(downloadProgress(qint64,qint64))); while (!r->isFinished()) { QTestEventLoop::instance().enterLoop(10); - if (count == spy.count() && !r->isFinished()) + if (count == spy.size() && !r->isFinished()) break; - count = spy.count(); + count = spy.size(); } QObject::disconnect(r, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -2364,6 +2704,48 @@ void tst_QNetworkReply::putToHttpSynchronous() QCOMPARE(uploadedData, data); } +void tst_QNetworkReply::putWithoutBody_data() +{ + QTest::addColumn<bool>("client_data"); + + QTest::newRow("client_has_data") << true; + QTest::newRow("client_does_not_have_data") << false; +} + +void tst_QNetworkReply::putWithoutBody() +{ + QFETCH(bool, client_data); + + QBuffer buff; + + if (client_data) { + buff.setData("Dummy data from client to server"); + buff.open(QIODevice::ReadOnly); + } + + QByteArray dataFromServerToClient = QByteArray("Some ridiculous dummy data"); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(dataFromServerToClient.size()); + httpResponse += "\r\n\r\n"; + httpResponse += dataFromServerToClient; + + MiniHttpServer server(httpResponse); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + + QNetworkReplyPtr reply; + if (client_data) + reply.reset(manager.put(request, &buff)); + else + reply.reset(manager.put(request, nullptr)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(server.foundContentLength, client_data); +} + + void tst_QNetworkReply::postToHttp_data() { putToFile_data(); @@ -2681,8 +3063,8 @@ void tst_QNetworkReply::postToHttpMultipart() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); @@ -2733,6 +3115,47 @@ void tst_QNetworkReply::multipartSkipIndices() // QTBUG-32534 multiPart->deleteLater(); } +void tst_QNetworkReply::postWithoutBody_data() +{ + QTest::addColumn<bool>("client_data"); + + QTest::newRow("client_has_data") << true; + QTest::newRow("client_does_not_have_data") << false; +} + +void tst_QNetworkReply::postWithoutBody() +{ + QFETCH(bool, client_data); + + QBuffer buff; + + if (client_data) { + buff.setData("Dummy data from client to server"); + buff.open(QIODevice::ReadOnly); + } + + QByteArray dataFromServerToClient = QByteArray("Some ridiculous dummy data"); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(dataFromServerToClient.size()); + httpResponse += "\r\n\r\n"; + httpResponse += dataFromServerToClient; + + MiniHttpServer server(httpResponse); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + + QNetworkReplyPtr reply; + if (client_data) + reply.reset(manager.post(request, &buff)); + else + reply.reset(manager.post(request, nullptr)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(server.foundContentLength, client_data); +} + void tst_QNetworkReply::putToHttpMultipart_data() { postToHttpMultipart_data(); @@ -2769,8 +3192,8 @@ void tst_QNetworkReply::putToHttpMultipart() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); @@ -2778,12 +3201,12 @@ void tst_QNetworkReply::putToHttpMultipart() QCOMPARE(replyData, expectedReplyData); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::putToHttps_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + uniqueExtension = createUniqueExtension(); putToFile_data(); } @@ -2825,9 +3248,9 @@ void tst_QNetworkReply::putToHttps() void tst_QNetworkReply::putToHttpsSynchronous_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvalueate() retruns recoverable error, update the server's certificate"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvalueate() retruns recoverable error, update the server's certificate"); + uniqueExtension = createUniqueExtension(); putToFile_data(); } @@ -2873,9 +3296,9 @@ void tst_QNetworkReply::putToHttpsSynchronous() void tst_QNetworkReply::postToHttps_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + putToFile_data(); } @@ -2907,9 +3330,9 @@ void tst_QNetworkReply::postToHttps() void tst_QNetworkReply::postToHttpsSynchronous_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + putToFile_data(); } @@ -2946,9 +3369,9 @@ void tst_QNetworkReply::postToHttpsSynchronous() void tst_QNetworkReply::postToHttpsMultipart_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + postToHttpMultipart_data(); } @@ -2987,15 +3410,15 @@ void tst_QNetworkReply::postToHttpsMultipart() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); QCOMPARE(replyData, expectedReplyData); } -#endif // QT_NO_SSL +#endif // QT_CONFIG(ssl) void tst_QNetworkReply::deleteFromHttp_data() { @@ -3124,7 +3547,7 @@ void tst_QNetworkReply::connectToIPv6Address() if (!QtNetworkSettings::hasIPv6()) QSKIP("system doesn't support ipv6!"); - QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\ncontent-length: "); httpResponse += QByteArray::number(dataToSend.size()); httpResponse += "\r\n\r\n"; httpResponse += dataToSend; @@ -3139,7 +3562,7 @@ void tst_QNetworkReply::connectToIPv6Address() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QByteArray content = reply->readAll(); //qDebug() << server.receivedData; - QByteArray hostinfo = "\r\nHost: " + hostfield + ':' + QByteArray::number(server.serverPort()) + "\r\n"; + QByteArray hostinfo = "\r\nhost: " + hostfield + ':' + QByteArray::number(server.serverPort()) + "\r\n"; QVERIFY(server.receivedData.contains(hostinfo)); QCOMPARE(content, dataToSend); QCOMPARE(reply->url(), request.url()); @@ -3301,6 +3724,18 @@ void tst_QNetworkReply::ioGetFromFile() QCOMPARE(reader.data, data); } +void tst_QNetworkReply::ioGetFromFileUrl() +{ + // This immediately fails on non-windows platforms: + QNetworkRequest request(QUrl("file://unc-server/some/path")); + QNetworkReplyPtr reply(manager.get(request)); + QSignalSpy finishedSpy(reply.get(), &QNetworkReply::finished); + // QTBUG-105618: This would, on non-Windows platforms, never happen because the signal + // was emitted before the constructor finished, leaving no chance at all to connect to the + // signal + QVERIFY(finishedSpy.wait()); +} + void tst_QNetworkReply::ioGetFromFtp_data() { if (!ftpSupported) @@ -3318,7 +3753,9 @@ void tst_QNetworkReply::ioGetFromFtp() { QFETCH(QString, fileName); QFile reference(fileName); - reference.open(QIODevice::ReadOnly); // will fail for bigfile + const bool ok = reference.open(QIODevice::ReadOnly); // will fail for bigfile + if (fileName != "bigfile") + QVERIFY(ok); QNetworkRequest request("ftp://" + QtNetworkSettings::ftpServerName() + "/qtest/" + fileName); QNetworkReplyPtr reply(manager.get(request)); @@ -3343,7 +3780,7 @@ void tst_QNetworkReply::ioGetFromFtpWithReuse() QSKIP("FTP is not supported"); QString fileName = testDataDir + "/rfc3252.txt"; QFile reference(fileName); - reference.open(QIODevice::ReadOnly); + QVERIFY(reference.open(QIODevice::ReadOnly)); QNetworkRequest request(QUrl("ftp://" + QtNetworkSettings::ftpServerName() + "/qtest/rfc3252.txt")); @@ -3473,7 +3910,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth_data() QTest::addColumn<int>("expectedAuth"); QFile reference(testDataDir + "/rfc3252.txt"); - reference.open(QIODevice::ReadOnly); + QVERIFY(reference.open(QIODevice::ReadOnly)); QByteArray referenceData = reference.readAll(); QString httpServer = QtNetworkSettings::httpServerName(); QTest::newRow("basic") @@ -3547,7 +3984,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() QCOMPARE(reader1.data, expectedData); QCOMPARE(reader2.data, expectedData); - QCOMPARE(authspy.count(), (expectedAuth ? 1 : 0)); + QCOMPARE(authspy.size(), (expectedAuth ? 1 : 0)); expectedAuth = qMax(0, expectedAuth - 1); } @@ -3568,7 +4005,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, expectedData); - QCOMPARE(authspy.count(), (expectedAuth ? 1 : 0)); + QCOMPARE(authspy.size(), (expectedAuth ? 1 : 0)); expectedAuth = qMax(0, expectedAuth - 1); } @@ -3585,7 +4022,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() // bad credentials in a synchronous request should just fail QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); } else { - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); // we cannot use a data reader here, since that connects to the readyRead signal, // just use readAll() @@ -3611,7 +4048,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() // bad credentials in a synchronous request should just fail QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); } else { - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); // we cannot use a data reader here, since that connects to the readyRead signal, // just use readAll() @@ -3637,11 +4074,11 @@ void tst_QNetworkReply::ioGetFromHttpWithAuthSynchronous() QNetworkReplyPtr replySync(manager.get(request)); QVERIFY(replySync->isFinished()); // synchronous QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 401); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() { // This test sends three requests @@ -3678,7 +4115,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QCOMPARE(reader1.data, referenceData); QCOMPARE(reader2.data, referenceData); - QCOMPARE(authspy.count(), 1); + QCOMPARE(authspy.size(), 1); } reference.seek(0); @@ -3701,7 +4138,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } // now check with synchronous calls: @@ -3714,7 +4151,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); QNetworkReplyPtr replySync(manager.get(request)); QVERIFY(replySync->isFinished()); // synchronous - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); // we cannot use a data reader here, since that connects to the readyRead signal, // just use readAll() @@ -3742,7 +4179,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuthSynchronous() manager.setProxy(QNetworkProxy()); // reset QVERIFY(replySync->isFinished()); // synchronous QCOMPARE(replySync->error(), QNetworkReply::ProxyAuthenticationRequiredError); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 407); } @@ -3774,7 +4211,7 @@ void tst_QNetworkReply::ioGetFromHttpWithSocksProxy() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } // set an invalid proxy just to make sure that we can't load @@ -3798,15 +4235,14 @@ void tst_QNetworkReply::ioGetFromHttpWithSocksProxy() QVERIFY(reader.data.isEmpty()); QVERIFY(int(reply->error()) > 0); - QEXPECT_FAIL("", "QTcpSocket doesn't return enough information yet", Continue); QCOMPARE(int(reply->error()), int(QNetworkReply::ProxyConnectionRefusedError)); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::ioGetFromHttpsWithSslErrors() { QFile reference(testDataDir + "/rfc3252.txt"); @@ -3829,7 +4265,7 @@ void tst_QNetworkReply::ioGetFromHttpsWithSslErrors() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(sslspy.count(), 1); + QCOMPARE(sslspy.size(), 1); QVERIFY(!storedSslConfiguration.isNull()); QVERIFY(!reply->sslConfiguration().isNull()); @@ -3857,7 +4293,7 @@ void tst_QNetworkReply::ioGetFromHttpsWithIgnoreSslErrors() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(sslspy.count(), 1); + QCOMPARE(sslspy.size(), 1); QVERIFY(!storedSslConfiguration.isNull()); QVERIFY(!reply->sslConfiguration().isNull()); @@ -3880,7 +4316,7 @@ void tst_QNetworkReply::ioGetFromHttpsWithSslHandshakeError() QCOMPARE(waitForFinish(reply), int(Failure)); QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); - QCOMPARE(sslspy.count(), 0); + QCOMPARE(sslspy.size(), 0); } #endif @@ -3938,11 +4374,11 @@ void tst_QNetworkReply::ioGetFromHttpBrokenServer() QCOMPARE(waitForFinish(reply), int(Failure)); QCOMPARE(reply->url(), request.url()); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QVERIFY(reply->error() != QNetworkReply::NoError); } -void tst_QNetworkReply::ioGetFromHttpStatus100_data() +void tst_QNetworkReply::ioGetFromHttpStatusInformational_data() { QTest::addColumn<QByteArray>("dataToSend"); QTest::addColumn<int>("statusCode"); @@ -3953,9 +4389,25 @@ void tst_QNetworkReply::ioGetFromHttpStatus100_data() QTest::newRow("minimal+404") << QByteArray("HTTP/1.1 100 Continue\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; QTest::newRow("with_headers") << QByteArray("HTTP/1.1 100 Continue\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; QTest::newRow("with_headers2") << QByteArray("HTTP/1.1 100 Continue\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + + QTest::newRow("normal-custom") << QByteArray("HTTP/1.1 133 Custom\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal2-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.0 200 OK\r\n\r\n") << 200; + QTest::newRow("minimal3-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.0 200 OK\n\n") << 200; + QTest::newRow("minimal+404-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; + QTest::newRow("with_headers-custom") << QByteArray("HTTP/1.1 133 Custom\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("with_headers2-custom") << QByteArray("HTTP/1.1 133 Custom\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + + QTest::newRow("normal-custom2") << QByteArray("HTTP/1.1 179 Custom2\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal2-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.0 200 OK\r\n\r\n") << 200; + QTest::newRow("minimal3-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.0 200 OK\n\n") << 200; + QTest::newRow("minimal+404-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; + QTest::newRow("with_headers-custom2") << QByteArray("HTTP/1.1 179 Custom2\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("with_headers2-custom2") << QByteArray("HTTP/1.1 179 Custom2\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; } -void tst_QNetworkReply::ioGetFromHttpStatus100() +void tst_QNetworkReply::ioGetFromHttpStatusInformational() { QFETCH(QByteArray, dataToSend); QFETCH(int, statusCode); @@ -4193,7 +4645,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache() QCOMPARE(reply->readAll().constData(), qPrintable(body)); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::ioGetWithManyProxies_data() { QTest::addColumn<QList<QNetworkProxy> >("proxyList"); @@ -4254,7 +4706,7 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::NoError; } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // HTTPS with HTTP transparent proxy proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3129); @@ -4274,15 +4726,15 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() // Tests that fail: - // HTTP request with FTP caching proxy - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); - QTest::newRow("http-on-ftp") - << proxyList << QNetworkProxy() - << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::ProxyNotFoundError; - if (ftpSupported) { + // HTTP request with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); + QTest::newRow("http-on-ftp") + << proxyList << QNetworkProxy() + << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + // FTP request with HTTP caching proxy proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, @@ -4304,7 +4756,7 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::ProxyNotFoundError; } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // HTTPS with HTTP caching proxy proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129); @@ -4313,13 +4765,15 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" << QNetworkReply::ProxyNotFoundError; - // HTTPS with FTP caching proxy - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); - QTest::newRow("https-on-ftp") - << proxyList << QNetworkProxy() - << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::ProxyNotFoundError; + if (ftpSupported) { + // HTTPS with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); + QTest::newRow("https-on-ftp") + << proxyList << QNetworkProxy() + << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + } #endif // Complex requests: @@ -4342,15 +4796,17 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" << QNetworkReply::NoError; - // HTTP request with FTP + HTTP + SOCKS - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) - << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) - << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1081); - QTest::newRow("http-on-ftp+http+socks") - << proxyList << proxyList.at(1) // second proxy should be used - << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::NoError; + if (ftpSupported) { + // HTTP request with FTP + HTTP + SOCKS + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) + << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1081); + QTest::newRow("http-on-ftp+http+socks") + << proxyList << proxyList.at(1) // second proxy should be used + << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + } // HTTP request with NoProxy + HTTP proxyList.clear(); @@ -4362,15 +4818,15 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::NoError; // HTTP request with FTP + NoProxy - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) - << QNetworkProxy(QNetworkProxy::NoProxy); - QTest::newRow("http-on-ftp+noproxy") - << proxyList << proxyList.at(1) // second proxy should be used - << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::NoError; - if (ftpSupported) { + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) + << QNetworkProxy(QNetworkProxy::NoProxy); + QTest::newRow("http-on-ftp+noproxy") + << proxyList << proxyList.at(1) // second proxy should be used + << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + // FTP request with HTTP Caching + FTP proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, @@ -4383,7 +4839,7 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::NoError; } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // HTTPS request with HTTP Caching + HTTP transparent proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) @@ -4393,15 +4849,17 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" << QNetworkReply::NoError; - // HTTPS request with FTP + HTTP C + HTTP T - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) - << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) - << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3129); - QTest::newRow("https-on-ftp+httpcaching+http") - << proxyList << proxyList.at(2) // skip the first two - << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::NoError; + if (ftpSupported) { + // HTTPS request with FTP + HTTP C + HTTP T + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) + << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3129); + QTest::newRow("https-on-ftp+httpcaching+http") + << proxyList << proxyList.at(2) // skip the first two + << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + } #endif } @@ -4427,7 +4885,7 @@ void tst_QNetworkReply::ioGetWithManyProxies() QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -4436,7 +4894,7 @@ void tst_QNetworkReply::ioGetWithManyProxies() manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) manager.disconnect(SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -4456,19 +4914,19 @@ void tst_QNetworkReply::ioGetWithManyProxies() // now verify that the proxies worked: QFETCH(QNetworkProxy, proxyUsed); if (proxyUsed.type() == QNetworkProxy::NoProxy) { - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } else { if (QByteArray(QTest::currentDataTag()).startsWith("ftp-")) return; // No authentication with current FTP or with FTP proxies - QCOMPARE(authspy.count(), 1); + QCOMPARE(authspy.size(), 1); QCOMPARE(qvariant_cast<QNetworkProxy>(authspy.at(0).at(0)), proxyUsed); } } else { // request failed - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void tst_QNetworkReply::ioPutToFileFromFile_data() { @@ -4571,10 +5029,6 @@ void tst_QNetworkReply::ioPutToFileFromLocalSocket() QNetworkReplyPtr reply(manager.put(QNetworkRequest(url), passive)); passive->setParent(reply.data()); -#ifdef Q_OS_WIN - if (!data.isEmpty()) - QEXPECT_FAIL("", "QTBUG-18385", Abort); -#endif QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QCOMPARE(reply->error(), QNetworkReply::NoError); @@ -4637,6 +5091,10 @@ void tst_QNetworkReply::ioPutToFileFromProcess() QByteArray contents = file.readAll(); QCOMPARE(contents, data); + if (process.state() == QProcess::Running) + QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitCode(), 0); + #endif // QT_CONFIG(process) } @@ -4765,7 +5223,7 @@ void tst_QNetworkReply::ioPostToHttpFromFile() QCOMPARE(reply->readAll().trimmed(), md5sum(sourceFile.readAll()).toHex()); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::ioPostToHttpFromSocket_data() { QTest::addColumn<QByteArray>("data"); @@ -4775,7 +5233,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocket_data() QTest::addColumn<int>("authenticationRequiredCount"); QTest::addColumn<int>("proxyAuthenticationRequiredCount"); - for (int i = 0; i < proxies.count(); ++i) + for (int i = 0; i < proxies.size(); ++i) for (int auth = 0; auth < 2; ++auth) { QUrl url; if (auth) @@ -4853,8 +5311,8 @@ void tst_QNetworkReply::ioPostToHttpFromSocket() QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); - QTEST(int(authenticationRequiredSpy.count()), "authenticationRequiredCount"); - QTEST(int(proxyAuthenticationRequiredSpy.count()), "proxyAuthenticationRequiredCount"); + QTEST(int(authenticationRequiredSpy.size()), "authenticationRequiredCount"); + QTEST(int(proxyAuthenticationRequiredSpy.size()), "proxyAuthenticationRequiredCount"); } void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous_data() @@ -4916,7 +5374,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous() QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) // this tests checks if rewinding the POST-data to some place in the middle // worked. @@ -5033,7 +5491,7 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag() QCOMPARE(reply->error(), QNetworkReply::ContentReSendError); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) class SslServer : public QTcpServer { Q_OBJECT @@ -5149,7 +5607,7 @@ void tst_QNetworkReply::ioGetFromBuiltinHttp_data() QTest::addColumn<int>("bufferSize"); QTest::newRow("http+unlimited") << false << 0; QTest::newRow("http+limited") << false << 4096; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QTest::newRow("https+unlimited") << true << 0; QTest::newRow("https+limited") << true << 4096; #endif @@ -5326,13 +5784,9 @@ void tst_QNetworkReply::emitAllUploadProgressSignals() QNetworkRequest catchAllSignalsRequest(normalRequest); catchAllSignalsRequest.setAttribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute, true); - QList<QNetworkRequest> requests; - requests << normalRequest << catchAllSignalsRequest; - QList<int> signalCount; - foreach (const QNetworkRequest &request, requests) { - + for (const QNetworkRequest &request : {normalRequest, catchAllSignalsRequest}) { sourceFile.seek(0); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); @@ -5353,7 +5807,7 @@ void tst_QNetworkReply::emitAllUploadProgressSignals() QVERIFY(!QTestEventLoop::instance().timeout()); incomingSocket->close(); - signalCount.append(spy.count()); + signalCount.append(spy.size()); reply->deleteLater(); } server.close(); @@ -5399,7 +5853,7 @@ void tst_QNetworkReply::ioPostToHttpEmptyUploadProgress() QVERIFY(!QTestEventLoop::instance().timeout()); // final check: only 1 uploadProgress has been emitted - QCOMPARE(spy.length(), 1); + QCOMPARE(spy.size(), 1); QList<QVariant> args = spy.last(); QVERIFY(!args.isEmpty()); QCOMPARE(args.at(0).toLongLong(), buffer.size()); @@ -5437,7 +5891,7 @@ void tst_QNetworkReply::lastModifiedHeaderForHttp() QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); QDateTime realDate = QDateTime::fromString("2007-05-22T12:04:57", Qt::ISODate); - realDate.setTimeSpec(Qt::UTC); + realDate.setTimeZone(QTimeZone::UTC); QCOMPARE(header, realDate); } @@ -5570,7 +6024,7 @@ void tst_QNetworkReply::downloadProgress() QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(reply->isFinished()); - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); //final progress should have equal current & total QList<QVariant> args = spy.takeLast(); @@ -5616,14 +6070,14 @@ void tst_QNetworkReply::uploadProgress() QVERIFY(server.hasPendingConnections()); QTcpSocket *receiver = server.nextPendingConnection(); - if (finished.count() == 0) { + if (finished.size() == 0) { // it's not finished yet, so wait for it to be QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); } delete receiver; - QVERIFY(finished.count() > 0); - QVERIFY(spy.count() > 0); + QVERIFY(finished.size() > 0); + QVERIFY(spy.size() > 0); QList<QVariant> args = spy.last(); QCOMPARE(args.at(0).toInt(), data.size()); @@ -5905,11 +6359,11 @@ void tst_QNetworkReply::nestedEventLoops() QTestEventLoop::instance().enterLoop(20); QVERIFY2(!QTestEventLoop::instance().timeout(), "Network timeout"); - QCOMPARE(finishedspy.count(), 1); - QCOMPARE(errorspy.count(), 0); + QCOMPARE(finishedspy.size(), 1); + QCOMPARE(errorspy.size(), 0); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::httpProxyCommands_data() { QTest::addColumn<QUrl>("url"); @@ -5920,7 +6374,7 @@ void tst_QNetworkReply::httpProxyCommands_data() << QUrl("http://0.0.0.0:4443/http-request") << QByteArray("HTTP/1.0 200 OK\r\nProxy-Connection: close\r\nContent-Length: 1\r\n\r\n1") << "GET http://0.0.0.0:4443/http-request HTTP/1."; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QTest::newRow("https") << QUrl("https://0.0.0.0:4443/https-request") << QByteArray("HTTP/1.0 200 Connection Established\r\n\r\n") @@ -5939,7 +6393,7 @@ void tst_QNetworkReply::httpProxyCommands() manager.setProxy(proxy); QNetworkRequest request(url); - request.setRawHeader("User-Agent", "QNetworkReplyAutoTest/1.0"); + request.setRawHeader("user-agent", "QNetworkReplyAutoTest/1.0"); QNetworkReplyPtr reply(manager.get(request)); // wait for the finished signal @@ -5952,14 +6406,15 @@ void tst_QNetworkReply::httpProxyCommands() // especially since it won't succeed in the HTTPS case // so just check that the command was correct - QString receivedHeader = proxyServer.receivedData.left(expectedCommand.length()); + QString receivedHeader = proxyServer.receivedData.left(expectedCommand.size()); QCOMPARE(receivedHeader, expectedCommand); //QTBUG-17223 - make sure the user agent from the request is sent to proxy server even for CONNECT - int uapos = proxyServer.receivedData.indexOf("User-Agent"); + const QByteArray cUserAgent = "user-agent: "; + int uapos = proxyServer.receivedData.toLower().indexOf(cUserAgent) + cUserAgent.size(); int uaend = proxyServer.receivedData.indexOf("\r\n", uapos); QByteArray uaheader = proxyServer.receivedData.mid(uapos, uaend - uapos); - QCOMPARE(uaheader, QByteArray("User-Agent: QNetworkReplyAutoTest/1.0")); + QCOMPARE(uaheader, QByteArray("QNetworkReplyAutoTest/1.0")); } class ProxyChangeHelper : public QObject @@ -5982,7 +6437,7 @@ void tst_QNetworkReply::httpProxyCommandsSynchronous_data() { httpProxyCommands_data(); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) struct QThreadCleanup { @@ -5996,15 +6451,7 @@ struct QThreadCleanup } }; -struct QDeleteLaterCleanup -{ - static inline void cleanup(QObject *o) - { - o->deleteLater(); - } -}; - -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::httpProxyCommandsSynchronous() { QFETCH(QUrl, url); @@ -6015,7 +6462,7 @@ void tst_QNetworkReply::httpProxyCommandsSynchronous() // the server thread, because the client is never returning to the // event loop QScopedPointer<QThread, QThreadCleanup> serverThread(new QThread); - QScopedPointer<MiniHttpServer, QDeleteLaterCleanup> proxyServer(new MiniHttpServer(responseToSend, false, serverThread.data())); + QScopedPointer<MiniHttpServer, QScopedPointerDeleteLater> proxyServer(new MiniHttpServer(responseToSend, false, serverThread.data())); QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", proxyServer->serverPort()); manager.setProxy(proxy); @@ -6036,7 +6483,7 @@ void tst_QNetworkReply::httpProxyCommandsSynchronous() // especially since it won't succeed in the HTTPS case // so just check that the command was correct - QString receivedHeader = proxyServer->receivedData.left(expectedCommand.length()); + QString receivedHeader = proxyServer->receivedData.left(expectedCommand.size()); QCOMPARE(receivedHeader, expectedCommand); } @@ -6094,7 +6541,7 @@ void tst_QNetworkReply::proxyChange() QVERIFY(int(reply3->error()) > 0); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void tst_QNetworkReply::authorizationError_data() { @@ -6133,9 +6580,9 @@ void tst_QNetworkReply::authorizationError() QCOMPARE(waitForFinish(reply), int(Failure)); QFETCH(int, errorSignalCount); - QCOMPARE(errorSpy.count(), errorSignalCount); + QCOMPARE(errorSpy.size(), errorSignalCount); QFETCH(int, finishedSignalCount); - QCOMPARE(finishedSpy.count(), finishedSignalCount); + QCOMPARE(finishedSpy.size(), finishedSignalCount); QFETCH(int, error); QCOMPARE(reply->error(), QNetworkReply::NetworkError(error)); @@ -6173,7 +6620,6 @@ void tst_QNetworkReply::httpConnectionCount() } QVERIFY(server->listen()); - QCoreApplication::instance()->processEvents(); QUrl url("http://127.0.0.1:" + QString::number(server->serverPort()) + QLatin1Char('/')); if (encrypted) @@ -6184,7 +6630,7 @@ void tst_QNetworkReply::httpConnectionCount() QUrl urlCopy = url; urlCopy.setPath(u'/' + QString::number(i)); // Differentiate the requests a bit QNetworkRequest request(urlCopy); - request.setAttribute(QNetworkRequest::Http2AllowedAttribute, http2Enabled); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, http2Enabled); QNetworkReply* reply = manager.get(request); reply->setParent(server.data()); if (encrypted) @@ -6192,26 +6638,40 @@ void tst_QNetworkReply::httpConnectionCount() } int pendingConnectionCount = 0; - QElapsedTimer timer; - timer.start(); - while(pendingConnectionCount <= 20) { - QTestEventLoop::instance().enterLoop(1); + using namespace std::chrono_literals; + const auto newPendingConnection = [&server]() { return server->hasPendingConnections(); }; + // If we have http2 enabled then the second connection will take a little + // longer to be established because we will wait for the first one to finish + // to see if we should upgrade: + const int rampDown = http2Enabled ? 2 : 1; + while (pendingConnectionCount <= 6) { + if (!QTest::qWaitFor(newPendingConnection, pendingConnectionCount >= rampDown ? 3s : 7s)) + break; QTcpSocket *socket = server->nextPendingConnection(); - while (socket != 0) { - if (pendingConnectionCount == 0) { - // respond to the first connection so we know to transition to HTTP/1.1 when using - // HTTP/2 - socket->write(httpEmpty200Response); + while (socket) { + if (pendingConnectionCount == 0 && http2Enabled) { + // Respond to the first connection so we know to transition to HTTP/1.1 when using + // HTTP/2. + // Because of some internal state machinery we need to wait until the request has + // actually been written to the server before we can reply. + auto connection = std::make_shared<QMetaObject::Connection>(); + auto replyOnRequest = [=, buffer = QByteArray()]() mutable { + buffer += socket->readAll(); + if (!buffer.contains("\r\n\r\n")) + return; + socket->write(httpEmpty200Response); + QObject::disconnect(*connection); + }; + *connection = QObject::connect(socket, &QTcpSocket::readyRead, socket, + std::move(replyOnRequest)); + if (socket->bytesAvailable()) // If we already have data, check it now + emit socket->readyRead(); } pendingConnectionCount++; socket->setParent(server.data()); socket = server->nextPendingConnection(); } - - // at max. wait 10 sec - if (timer.elapsed() > 10000) - break; } QCOMPARE(pendingConnectionCount, 6); @@ -6391,7 +6851,7 @@ void tst_QNetworkReply::httpRecursiveCreation() QVERIFY(!QTestEventLoop::instance().timeout()); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::ignoreSslErrorsList_data() { QTest::addColumn<QList<QSslError> >("expectedSslErrors"); @@ -6399,8 +6859,8 @@ void tst_QNetworkReply::ignoreSslErrorsList_data() QList<QSslError> expectedSslErrors; QList<QSslCertificate> certs = QSslCertificate::fromPath(testDataDir + certsFilePath); - QSslError rightError(FLUKE_CERTIFICATE_ERROR, certs.at(0)); - QSslError wrongError(FLUKE_CERTIFICATE_ERROR); + QSslError rightError(flukeCertTlsError, certs.at(0)); + QSslError wrongError(flukeCertTlsError); QTest::newRow("SSL-failure-empty-list") << expectedSslErrors << QNetworkReply::SslHandshakeFailedError; expectedSslErrors.append(wrongError); @@ -6467,23 +6927,23 @@ void tst_QNetworkReply::sslConfiguration_data() QTest::newRow("empty") << QSslConfiguration() << false; QSslConfiguration conf = QSslConfiguration::defaultConfiguration(); QTest::newRow("default") << conf << false; // does not contain test server cert -#if QT_CONFIG(securetransport) - qWarning("SecTrustEvaluate() will fail, update the certificate on server"); -#else - QList<QSslCertificate> testServerCert = QSslCertificate::fromPath(testDataDir + certsFilePath); - conf.setCaCertificates(testServerCert); + if (isSecureTransport) { + qWarning("SecTrustEvaluate() will fail, update the certificate on server"); + } else { + QList<QSslCertificate> testServerCert = QSslCertificate::fromPath(testDataDir + certsFilePath); + conf.setCaCertificates(testServerCert); - QTest::newRow("set-root-cert") << conf << true; - conf.setProtocol(QSsl::SecureProtocols); - QTest::newRow("secure") << conf << true; -#endif + QTest::newRow("set-root-cert") << conf << true; + conf.setProtocol(QSsl::SecureProtocols); + QTest::newRow("secure") << conf << true; + } } void tst_QNetworkReply::encrypted() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvalute() fails with old server certificate"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvalute() fails with old server certificate"); + QUrl url("https://" + QtNetworkSettings::httpServerName()); QNetworkRequest request(url); QNetworkReply *reply = manager.get(request); @@ -6494,7 +6954,7 @@ void tst_QNetworkReply::encrypted() QTestEventLoop::instance().enterLoop(20); QVERIFY(!QTestEventLoop::instance().timeout()); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); reply->deleteLater(); } @@ -6509,8 +6969,7 @@ void tst_QNetworkReply::abortOnEncrypted() server.connect(&server, &SslServer::newEncryptedConnection, [&server]() { // MSVC 201X C4573-misunderstands connect() or QObject::connect(), so use server.connect(): server.connect(server.socket, &QTcpSocket::readyRead, server.socket, []() { - // This slot must not be invoked! - QVERIFY(false); + QFAIL("This slot must not be invoked!"); }); }); @@ -6524,7 +6983,7 @@ void tst_QNetworkReply::abortOnEncrypted() }); QSignalSpy spyEncrypted(reply, &QNetworkReply::encrypted); - QTRY_COMPARE(spyEncrypted.count(), 1); + QTRY_COMPARE(spyEncrypted.size(), 1); // Wait for the socket to be closed again in order to be sure QTcpSocket::readyRead would have been emitted. QTRY_VERIFY(server.socket != nullptr); @@ -6556,9 +7015,8 @@ void tst_QNetworkReply::sslSessionSharing_data() void tst_QNetworkReply::sslSessionSharing() { -#if QT_CONFIG(schannel) || defined(QT_SECURETRANSPORT) - QSKIP("Not implemented with SecureTransport/Schannel"); -#endif + if (isSchannel || isSecureTransport) + QSKIP("Not implemented with SecureTransport/Schannel"); QString urlString("https://" + QtNetworkSettings::httpServerName()); QList<QNetworkReplyPtr> replies; @@ -6627,9 +7085,8 @@ void tst_QNetworkReply::sslSessionSharingFromPersistentSession_data() void tst_QNetworkReply::sslSessionSharingFromPersistentSession() { -#if QT_CONFIG(schannel) || defined(QT_SECURETRANSPORT) - QSKIP("Not implemented with SecureTransport/Schannel"); -#endif + if (isSchannel || isSecureTransport) + QSKIP("Not implemented with SecureTransport/Schannel"); QString urlString("https://" + QtNetworkSettings::httpServerName()); @@ -6682,7 +7139,7 @@ void tst_QNetworkReply::sslSessionSharingFromPersistentSession() } #endif // QT_BUILD_INTERNAL -#endif // QT_NO_SSL +#endif // QT_CONFIG(ssl) void tst_QNetworkReply::getAndThenDeleteObject_data() { @@ -6720,7 +7177,20 @@ void tst_QNetworkReply::getAndThenDeleteObject() // see https://bugs.webkit.org/show_bug.cgi?id=38935 void tst_QNetworkReply::symbianOpenCDataUrlCrash() { - QString requestUrl("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAWCAYAAAA1vze2AAAAB3RJTUUH2AUSEgolrgBvVQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAARnQU1BAACxjwv8YQUAAAHlSURBVHja5VbNShxBEK6ZaXtnHTebQPA1gngNmfaeq+QNPIlIXkC9iQdJxJNvEHLN3VkxhxxE8gTmEhAVddXZ6Z3f9Ndriz89/sHmkBQUVVT1fB9d9c3uOERUKTunIdn3HzstxGpYBDS4wZk7TAJj/wlJ90J+jnuygqs8svSj+/rGHBos3rE18XBvfU3no7NzlJfUaY/5whAwl8Lr/WDUv4ODxTMb+P5xLExe5LmO559WqTX/MQR4WZYEAtSePS4pE0qSnuhnRUcBU5Gm2k9XljU4Z26I3NRxBrd80rj2fh+KNE0FY4xevRgTjREvPFpasAK8Xli6MUbbuKw3afAGgSBXozo5u4hkmncAlkl5wx8iMGbdyQjnCFEiEwGiosj1UQA/x2rVddiVoi+l4IxE0PTDnx+mrQBvvnx9cFz3krhVvuhzFn579/aq/n5rW8fbtTqiWhIQZEo17YBvbkxOXNVndnYpTvod7AtiuN2re0+siwcB9oH8VxxrNwQQAhzyRs30n7wTI2HIN2g2QtQwjjhJIQatOq7E8bIVCLwzpl83Lvtvl+NohWWlE8UZTWEMAGCcR77fHKhPnZF5tYie6dfdxCphACmLPM+j8bYfmTryg64kV9Vh3mV8jP0b/4wO/YUPiT/8i0MLf55lSQAAAABJRU5ErkJggg=="); + QString requestUrl("data:image/" + "png;base64," + "iVBORw0KGgoAAAANSUhEUgAAABkAAAAWCAYAAAA1vze2AAAAB3RJTUUH2AUSEgolrgBvVQAAAAl" + "wSFlzAAALEwAACxMBAJqcGAAAAARnQU1BAACxjwv8YQUAAAHlSURBVHja5VbNShxBEK6ZaXtnHT" + "ebQPA1gngNmfaeq+QNPIlIXkC9iQdJxJNvEHLN3VkxhxxE8gTmEhAVddXZ6Z3f9Ndriz89/" + "sHmkBQUVVT1fB9d9c3uOERUKTunIdn3HzstxGpYBDS4wZk7TAJj/wlJ90J+jnuygqs8svSj+/" + "rGHBos3rE18XBvfU3no7NzlJfUaY/5whAwl8Lr/WDUv4ODxTMb+P5xLExe5LmO559WqTX/" + "MQR4WZYEAtSePS4pE0qSnuhnRUcBU5Gm2k9XljU4Z26I3NRxBrd80rj2fh+" + "KNE0FY4xevRgTjREvPFpasAK8Xli6MUbbuKw3afAGgSBXozo5u4hkmncAlkl5wx8iMGbdyQjnCF" + "EiEwGiosj1UQA/x2rVddiVoi+l4IxE0PTDnx+mrQBvvnx9cFz3krhVvuhzFn579/aq/" + "n5rW8fbtTqiWhIQZEo17YBvbkxOXNVndnYpTvod7AtiuN2re0+" + "siwcB9oH8VxxrNwQQAhzyRs30n7wTI2HIN2g2QtQwjjhJIQatOq7E8bIVCLwzpl83Lvtvl+" + "NohWWlE8UZTWEMAGCcR77fHKhPnZF5tYie6dfdxCphACmLPM+j8bYfmTryg64kV9Vh3mV8jP0b/" + "4wO/YUPiT/8i0MLf55lSQAAAABJRU5ErkJggg=="); QUrl url = QUrl::fromEncoded(requestUrl.toLatin1()); QNetworkRequest req(url); QNetworkReplyPtr reply; @@ -7037,8 +7507,7 @@ void tst_QNetworkReply::qtbug12908compressedHttpReply() QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); // QDecompressHelper will abort the download if the compressed to decompressed size ratio // differs too much, so we override it - request.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User - 1), - QByteArray("__qdecompresshelper_ignore_download_ratio")); + request.setDecompressedSafetyCheckThreshold(-1); QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -7066,7 +7535,7 @@ void tst_QNetworkReply::compressedHttpReplyBrokenGzip() QCOMPARE(waitForFinish(reply), int(Failure)); - QCOMPARE(reply->error(), QNetworkReply::ProtocolFailure); + QCOMPARE(reply->error(), QNetworkReply::UnknownContentError); } // TODO add similar test for FTP @@ -7110,25 +7579,25 @@ void tst_QNetworkReply::qtbug4121unknownAuthentication() QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); - QCOMPARE(authSpy.count(), 0); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(errorSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(errorSpy.size(), 1); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::authenticationCacheAfterCancel_data() { QTest::addColumn<QNetworkProxy>("proxy"); QTest::addColumn<bool>("proxyAuth"); QTest::addColumn<QUrl>("url"); - for (int i = 0; i < proxies.count(); ++i) { + for (int i = 0; i < proxies.size(); ++i) { QTest::newRow("http" + proxies.at(i).tag) << proxies.at(i).proxy << proxies.at(i).requiresAuthentication << QUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/rfcs-auth/rfc3252.txt"); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QTest::newRow("https" + proxies.at(i).tag) << proxies.at(i).proxy << proxies.at(i).requiresAuthentication @@ -7184,7 +7653,7 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QFETCH(bool, proxyAuth); QFETCH(QUrl, url); QNetworkAccessManager manager; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -7206,8 +7675,8 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::ProxyAuthenticationRequiredError); - QCOMPARE(authSpy.count(), 0); - QCOMPARE(proxyAuthSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(proxyAuthSpy.size(), 1); proxyAuthSpy.clear(); //should fail due to bad credentials @@ -7221,8 +7690,8 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() // Work round known quirk in the old test server (danted -v < v1.1.19): if (reply->error() != QNetworkReply::HostNotFoundError) QCOMPARE(reply->error(), QNetworkReply::ProxyAuthenticationRequiredError); - QCOMPARE(authSpy.count(), 0); - QVERIFY(proxyAuthSpy.count() > 0); + QCOMPARE(authSpy.size(), 0); + QVERIFY(proxyAuthSpy.size() > 0); proxyAuthSpy.clear(); // QTBUG-23136 workaround (needed even with danted v1.1.19): @@ -7247,10 +7716,10 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); - QVERIFY(authSpy.count() > 0); + QVERIFY(authSpy.size() > 0); authSpy.clear(); if (proxyAuth) { - QVERIFY(proxyAuthSpy.count() > 0); + QVERIFY(proxyAuthSpy.size() > 0); proxyAuthSpy.clear(); } @@ -7263,11 +7732,11 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); - QVERIFY(authSpy.count() > 0); + QVERIFY(authSpy.size() > 0); authSpy.clear(); if (proxyAuth) { //should be supplied from cache - QCOMPARE(proxyAuthSpy.count(), 0); + QCOMPARE(proxyAuthSpy.size(), 0); proxyAuthSpy.clear(); } @@ -7281,11 +7750,11 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::NoError); - QVERIFY(authSpy.count() > 0); + QVERIFY(authSpy.size() > 0); authSpy.clear(); if (proxyAuth) { //should be supplied from cache - QCOMPARE(proxyAuthSpy.count(), 0); + QCOMPARE(proxyAuthSpy.size(), 0); proxyAuthSpy.clear(); } @@ -7297,11 +7766,11 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QCOMPARE(reply->error(), QNetworkReply::NoError); //should be supplied from cache - QCOMPARE(authSpy.count(), 0); + QCOMPARE(authSpy.size(), 0); authSpy.clear(); if (proxyAuth) { //should be supplied from cache - QCOMPARE(proxyAuthSpy.count(), 0); + QCOMPARE(proxyAuthSpy.size(), 0); proxyAuthSpy.clear(); } @@ -7311,7 +7780,7 @@ void tst_QNetworkReply::authenticationWithDifferentRealm() { AuthenticationCacheHelper helper; QNetworkAccessManager manager; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -7338,7 +7807,7 @@ void tst_QNetworkReply::authenticationWithDifferentRealm() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::NoError); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) class QtBug13431Helper : public QObject { @@ -7403,8 +7872,8 @@ void tst_QNetworkReply::httpWithNoCredentialUsage() QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); // credentials in URL, so don't expect authentication signal - QCOMPARE(authSpy.count(), 0); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(finishedSpy.size(), 1); finishedSpy.clear(); } @@ -7414,8 +7883,8 @@ void tst_QNetworkReply::httpWithNoCredentialUsage() QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); // credentials in cache, so don't expect authentication signal - QCOMPARE(authSpy.count(), 0); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(finishedSpy.size(), 1); finishedSpy.clear(); } @@ -7432,9 +7901,9 @@ void tst_QNetworkReply::httpWithNoCredentialUsage() QVERIFY(!QTestEventLoop::instance().timeout()); // We check if authenticationRequired was emitted, however we do not anything in it so it should be 401 - QCOMPARE(authSpy.count(), 1); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(errorSpy.count(), 1); + QCOMPARE(authSpy.size(), 1); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(errorSpy.size(), 1); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); } @@ -7719,8 +8188,8 @@ void tst_QNetworkReply::qtbug45581WrongReplyStatusCode() QCOMPARE(reply->readAll(), expectedContent); - QCOMPARE(finishedSpy.count(), 0); - QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(finishedSpy.size(), 0); + QCOMPARE(sslErrorsSpy.size(), 0); QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), expectedContent.size()); @@ -7752,17 +8221,17 @@ void tst_QNetworkReply::synchronousRequest_data() // ### we would need to enflate (un-deflate) the file content and compare the sizes << QString("text/plain"); -#ifndef QT_NO_SSL -#if QT_CONFIG(securetransport) - qWarning("Skipping https scheme, SecTrustEvalue() fails, update the certificate on server"); -#else - QTest::newRow("https") - << QUrl("https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt") - << QString("file:" + testDataDir + "/rfc3252.txt") - << true - << QString("text/plain"); -#endif -#endif +#if QT_CONFIG(ssl) + if (isSecureTransport) { + qWarning("Skipping https scheme, SecTrustEvalue() fails, update the certificate on server"); + } else { + QTest::newRow("https") + << QUrl("https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt") + << QString("file:" + testDataDir + "/rfc3252.txt") + << true + << QString("text/plain"); + } +#endif // QT_CONFIG(ssl) QTest::newRow("data") << QUrl(QString::fromLatin1("data:text/plain,hello world")) @@ -7787,7 +8256,7 @@ void tst_QNetworkReply::synchronousRequest() QNetworkRequest request(url); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // workaround for HTTPS requests: add self-signed server cert to list of CA certs, // since we cannot react to the sslErrors() signal // to fix this properly we would need to have an ignoreSslErrors() method in the @@ -7809,8 +8278,8 @@ void tst_QNetworkReply::synchronousRequest() QSignalSpy sslErrorsSpy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0)); QVERIFY(reply->isFinished()); - QCOMPARE(finishedSpy.count(), 0); - QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(finishedSpy.size(), 0); + QCOMPARE(sslErrorsSpy.size(), 0); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toString(), mimeType); @@ -7819,7 +8288,7 @@ void tst_QNetworkReply::synchronousRequest() if (expected.startsWith("file:")) { QString path = expected.mid(5); QFile file(path); - file.open(QIODevice::ReadOnly); + QVERIFY(file.open(QIODevice::ReadOnly)); expectedContent = file.readAll(); } else if (expected.startsWith("data:")) { expectedContent = expected.mid(5).toUtf8(); @@ -7832,7 +8301,7 @@ void tst_QNetworkReply::synchronousRequest() reply->deleteLater(); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::synchronousRequestSslFailure() { // test that SSL won't be accepted with self-signed certificate, @@ -7849,7 +8318,7 @@ void tst_QNetworkReply::synchronousRequestSslFailure() runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0); QVERIFY(reply->isFinished()); QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); - QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(sslErrorsSpy.size(), 0); } #endif @@ -7911,6 +8380,218 @@ void tst_QNetworkReply::httpAbort() QCOMPARE(reply3->error(), QNetworkReply::NoError); } +void tst_QNetworkReply::closeClientSideConnectionEagerlyQtbug20726() +{ + QNetworkAccessManager manager; // function local instance + // Setup HTTP servers + MiniHttpServer server("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n", false); + server.doClose = false; // server should not disconnect. + + MiniHttpServer serverNotEagerClientClose("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n", false); + serverNotEagerClientClose.doClose = false; // server should not disconnect. + QUrl urlNotEager(QLatin1String("http://localhost")); + urlNotEager.setPort(serverNotEagerClientClose.serverPort()); + QNetworkRequest requestNotEager(urlNotEager); + QNetworkReplyPtr replyNotEager(manager.get(requestNotEager)); + QCOMPARE(waitForFinish(replyNotEager), Success); + // The reply was finished, the connection should be hanging and waiting to be expired + + // Another server not eager to close, the connection should be hanging and waiting to be expired + MiniHttpServer serverNotEagerClientClose2("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n", false); + serverNotEagerClientClose2.doClose = false; // server should not disconnect. + QUrl urlNotEager2(QLatin1String("http://localhost")); + urlNotEager2.setPort(serverNotEagerClientClose2.serverPort()); + QNetworkRequest requestNotEager2(urlNotEager2); + QNetworkReplyPtr replyNotEager2(manager.get(requestNotEager2)); + QCOMPARE(waitForFinish(replyNotEager2), Success); + + // However for this one we want to eagerly close. + QUrl url(QLatin1String("http://localhost")); + url.setPort(server.serverPort()); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, 0); + QNetworkReplyPtr reply(manager.get(request)); + // The server just uses a normal TCP socket and prints out this error when the client disconnects: + QTest::ignoreMessage(QtDebugMsg, + "slotError QAbstractSocket::RemoteHostClosedError " + "\"The remote host closed the connection\""); + QCOMPARE(waitForFinish(reply), Success); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + // Socket from server to QNAM still connected? + QVERIFY (!server.client.isNull()); + QVERIFY (server.client->state() == QTcpSocket::ConnectedState); + // Wait a bit + QTest::qWait(1*1000); + // The QNAM should have disconnected the socket, so on our server it's disconnected now. + QVERIFY (!server.client.isNull()); + QVERIFY (server.client->state() != QTcpSocket::ConnectedState); + + // Now we check the not eager reply, it should still be connected. + QCOMPARE(replyNotEager->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(serverNotEagerClientClose.client->state(), QTcpSocket::ConnectedState); + QCOMPARE(replyNotEager2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(serverNotEagerClientClose2.client->state(), QTcpSocket::ConnectedState); +} + +void tst_QNetworkReply::varyingCacheExpiry_data() +{ + QTest::addColumn<int>("firstExpiry"); + QTest::addColumn<int>("secondExpiry"); + QTest::addColumn<int>("thirdExpiry"); + QTest::addColumn<int>("fourthExpiry"); + + // The datatags signify the Keep-Alive time-outs of the successive requests: + QTest::newRow("1-2-3-4") << 1 << 2 << 3 << 4; + QTest::newRow("4-1-2-3") << 4 << 1 << 2 << 3; + QTest::newRow("3-4-1-2") << 3 << 4 << 1 << 2; + QTest::newRow("2-3-4-1") << 2 << 3 << 4 << 1; + QTest::newRow("1-2-2-1") << 1 << 2 << 2 << 1; +} + +// Test creating a few requests with various expiry timeouts. +// We do this because the internal QNetworkAccessCache inserts them in sorted +// order, so make sure it gets it right. +void tst_QNetworkReply::varyingCacheExpiry() +{ + // Local QNAM instance because there may be leftover entries from other + // tests. Which wouldn't be a big deal, it would just get in the way of our + // pattern + QNetworkAccessManager qnam; + QFETCH(int, firstExpiry); + QFETCH(int, secondExpiry); + QFETCH(int, thirdExpiry); + QFETCH(int, fourthExpiry); + + int expiryTimes[4] = { + firstExpiry, + secondExpiry, + thirdExpiry, + fourthExpiry, + }; + + // We need multiple servers because we want to have multiple connections + // in the QNetworkAccessCache, not to just reuse one connection from the + // cache. + MiniHttpServer servers[4] = { + { httpEmpty200Response }, + { httpEmpty200Response }, + { httpEmpty200Response }, + { httpEmpty200Response }, + }; + for (MiniHttpServer &server : servers) + server.doClose = false; + + QUrl urls[4] = { + u"http://localhost"_s, + u"http://localhost"_s, + u"http://localhost"_s, + u"http://localhost"_s, + }; + for (size_t i = 0; i < std::size(urls); ++i) + urls[i].setPort(servers[i].serverPort()); + + // After the initial request is completed the connection is kept alive + // (Keep-Alive). Internally they are added to a sorted linked-list based on + // expiry. So, set the requests to be torn down at varying timeouts. + + QNetworkRequest requests[4] = { + QNetworkRequest(urls[0]), + QNetworkRequest(urls[1]), + QNetworkRequest(urls[2]), + QNetworkRequest(urls[3]), + }; + + for (int i = 0; i < 4; ++i) { + requests[i].setAttribute(QNetworkRequest::Http2AllowedAttribute, + QVariant::fromValue(false)); + requests[i].setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, + expiryTimes[i]); + } + + // The server just uses a normal TCP socket and prints out this error when the client + // disconnects: + for (int i = 0; i < 4; ++i) { + QTest::ignoreMessage(QtDebugMsg, + "slotError QAbstractSocket::RemoteHostClosedError " + "\"The remote host closed the connection\""); + } + + // Start each request and wait for it to finish before starting the next + // one, we must do this because the connections are only added to the expiry + // list once finished + for (const auto &request : requests) { + QNetworkReplyPtr reply(qnam.get(request)); + QCOMPARE(waitForFinish(reply), Success); + } + + int lastExpiry = *std::max_element(std::begin(expiryTimes), std::end(expiryTimes)); + auto allServersDisconnected = [&servers]() { + auto socketDisconnected = [](const MiniHttpServer &s) { + return s.client->state() == QAbstractSocket::UnconnectedState; + }; + return std::all_of(std::begin(servers), std::end(servers), socketDisconnected); + }; + // At least +5 seconds due to CI. Completes much faster locally + bool success = QTest::qWaitFor(allServersDisconnected, lastExpiry * 1000 + 5000); + + QVERIFY(success); +} + +class Qtbug25280Server : public MiniHttpServer +{ +public: + Qtbug25280Server(QByteArray qba) : MiniHttpServer(qba, false) {} + QSet<QTcpSocket*> receivedSockets; + void reply() override + { + // Save sockets in a list + receivedSockets.insert((QTcpSocket*)sender()); + qobject_cast<QTcpSocket*>(sender())->write(dataToTransmit); + //qDebug() << "count=" << receivedSockets.count(); + } +}; + +void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280_data() +{ + QTest::addColumn<int>("amount"); + QTest::addRow("default") << 6; + QTest::addRow("minimize") << 1; + QTest::addRow("increase") << 12; +} + +// Also kind of QTBUG-8468 +void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280() +{ + QFETCH(const int, amount); + QNetworkAccessManager manager; // function local instance + Qtbug25280Server server(tst_QNetworkReply::httpEmpty200Response); + server.doClose = false; + server.multiple = true; + QUrl url(QLatin1String("http://127.0.0.1")); // not "localhost" to prevent "Happy Eyeballs" + // from skewing the counting + url.setPort(server.serverPort()); + std::optional<QHttp1Configuration> http1Configuration; + if (amount != 6) // don't set if it's the default + http1Configuration.emplace().setNumberOfConnectionsPerHost(amount); + constexpr int NumRequests = 200; // send a lot more than we have sockets + int finished = 0; + std::array<std::unique_ptr<QNetworkReply>, NumRequests> replies; + for (auto &reply : replies) { + QNetworkRequest request(url); + if (http1Configuration) + request.setHttp1Configuration(*http1Configuration); + reply.reset(manager.get(request)); + QObject::connect(reply.get(), &QNetworkReply::finished, + [&finished] { ++finished; }); + } + QTRY_COMPARE_WITH_TIMEOUT(finished, NumRequests, 60'000); + for (const auto &reply : replies) { + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + } + QCOMPARE(server.receivedSockets.size(), amount); +} + void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() { QByteArray reply206 = @@ -7939,7 +8620,7 @@ void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() QVERIFY(server.totalConnections > 0); QCOMPARE(reply->readAll().constData(), "load"); - QCOMPARE(memoryCache->m_insertedUrls.count(), 0); + QCOMPARE(memoryCache->m_insertedUrls.size(), 0); } void tst_QNetworkReply::httpUserAgent() @@ -7956,7 +8637,7 @@ void tst_QNetworkReply::httpUserAgent() QVERIFY(reply->isFinished()); QCOMPARE(reply->error(), QNetworkReply::NoError); - QVERIFY(server.receivedData.contains("\r\nUser-Agent: abcDEFghi\r\n")); + QVERIFY(server.receivedData.contains("\r\nuser-agent: abcDEFghi\r\n")); } void tst_QNetworkReply::synchronousAuthenticationCache() @@ -7976,7 +8657,7 @@ void tst_QNetworkReply::synchronousAuthenticationCache() "Content-Type: text/plain\r\n" "\r\n" "auth"; - QRegularExpression rx("Authorization: Basic ([^\r\n]*)\r\n"); + QRegularExpression rx("authorization: Basic ([^\r\n]*)\r\n"); QRegularExpressionMatch match = rx.match(receivedData); if (match.hasMatch()) { if (QByteArray::fromBase64(match.captured(1).toLatin1()) == "login:password") { @@ -7997,7 +8678,7 @@ void tst_QNetworkReply::synchronousAuthenticationCache() // the server thread, because the client is never returning to the // event loop QScopedPointer<QThread, QThreadCleanup> serverThread(new QThread); - QScopedPointer<MiniHttpServer, QDeleteLaterCleanup> server(new MiniAuthServer(serverThread.data())); + QScopedPointer<MiniHttpServer, QScopedPointerDeleteLater> server(new MiniAuthServer(serverThread.data())); server->doClose = true; //1) URL without credentials, we are not authenticated @@ -8136,31 +8817,33 @@ void tst_QNetworkReply::ftpAuthentication() void tst_QNetworkReply::emitErrorForAllReplies() // QTBUG-36890 { // port 100 is not well-known and should be closed - QList<QUrl> urls = QList<QUrl>() << QUrl("http://localhost:100/request1") - << QUrl("http://localhost:100/request2") - << QUrl("http://localhost:100/request3"); - QList<QNetworkReply *> replies; - QList<QSignalSpy *> errorSpies; - QList<QSignalSpy *> finishedSpies; - for (int a = 0; a < urls.count(); ++a) { - QNetworkRequest request(urls.at(a)); + const QUrl urls[] = { + QUrl("http://localhost:100/request1"), + QUrl("http://localhost:100/request2"), + QUrl("http://localhost:100/request3"), + }; + constexpr auto NUrls = std::size(urls); + + std::unique_ptr<QNetworkReply, QScopedPointerDeleteLater> replies[NUrls]; + std::optional<QSignalSpy> errorSpies[NUrls]; + std::optional<QSignalSpy> finishedSpies[NUrls]; + + for (size_t i = 0; i < NUrls; ++i) { + QNetworkRequest request(urls[i]); QNetworkReply *reply = manager.get(request); - replies.append(reply); - QSignalSpy *errorSpy = new QSignalSpy(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - errorSpies.append(errorSpy); - QSignalSpy *finishedSpy = new QSignalSpy(reply, SIGNAL(finished())); - finishedSpies.append(finishedSpy); + replies[i].reset(reply); + errorSpies[i].emplace(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError))); + finishedSpies[i].emplace(reply, SIGNAL(finished())); QObject::connect(reply, SIGNAL(finished()), SLOT(emitErrorForAllRepliesSlot())); } + QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); - for (int a = 0; a < urls.count(); ++a) { - QVERIFY(replies.at(a)->isFinished()); - QCOMPARE(errorSpies.at(a)->count(), 1); - errorSpies.at(a)->deleteLater(); - QCOMPARE(finishedSpies.at(a)->count(), 1); - finishedSpies.at(a)->deleteLater(); - replies.at(a)->deleteLater(); + + for (size_t i = 0; i < NUrls; ++i) { + QVERIFY(replies[i]->isFinished()); + QCOMPARE(errorSpies[i]->size(), 1); + QCOMPARE(finishedSpies[i]->size(), 1); } } @@ -8208,7 +8891,7 @@ public: return ret; } virtual bool atEnd() const override { return buffer.atEnd(); } - virtual qint64 size() const override { return data.length(); } + virtual qint64 size() const override { return data.size(); } qint64 bytesAvailable() const override { return buffer.bytesAvailable() + QIODevice::bytesAvailable(); @@ -8229,9 +8912,9 @@ protected slots: void tst_QNetworkReply::putWithRateLimiting() { QFile reference(testDataDir + "/rfc3252.txt"); - reference.open(QIODevice::ReadOnly); + QVERIFY(reference.open(QIODevice::ReadOnly)); QByteArray data = reference.readAll(); - QVERIFY(data.length() > 0); + QVERIFY(data.size() > 0); QUrl url = QUrl::fromUserInput("http://" + QtNetworkSettings::httpServerName()+ "/qtest/cgi-bin/echo.cgi?"); @@ -8246,7 +8929,7 @@ void tst_QNetworkReply::putWithRateLimiting() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QByteArray uploadedData = reply->readAll(); - QCOMPARE(uploadedData.length(), data.length()); + QCOMPARE(uploadedData.size(), data.size()); QCOMPARE(uploadedData, data); } @@ -8278,8 +8961,8 @@ void tst_QNetworkReply::ioHttpSingleRedirect() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); // Redirected and finished should be emitted exactly once - QCOMPARE(redSpy.count(), 1); - QCOMPARE(finSpy.count(), 1); + QCOMPARE(redSpy.size(), 1); + QCOMPARE(finSpy.size(), 1); // Original URL should not be changed after redirect QCOMPARE(request.url(), localhost); @@ -8325,8 +9008,8 @@ void tst_QNetworkReply::ioHttpChangeMaxRedirects() QCOMPARE(waitForFinish(reply), int(Failure)); - QCOMPARE(redSpy.count(), request.maximumRedirectsAllowed()); - QCOMPARE(spy.count(), 1); + QCOMPARE(redSpy.size(), request.maximumRedirectsAllowed()); + QCOMPARE(spy.size(), 1); QCOMPARE(reply->error(), QNetworkReply::TooManyRedirectsError); // Increase max redirects to allow successful completion @@ -8337,7 +9020,7 @@ void tst_QNetworkReply::ioHttpChangeMaxRedirects() QVERIFY2(waitForFinish(reply2) == Success, msgWaitForFinished(reply2)); - QCOMPARE(redSpy2.count(), 2); + QCOMPARE(redSpy2.size(), 2); QCOMPARE(reply2->url(), server3Url); QCOMPARE(reply2->error(), QNetworkReply::NoError); QVERIFY(validateRedirectedResponseHeaders(reply2)); @@ -8470,8 +9153,8 @@ void tst_QNetworkReply::ioHttpRedirectPolicy() QSignalSpy redirectSpy(reply.data(), SIGNAL(redirected(QUrl))); QSignalSpy finishedSpy(reply.data(), SIGNAL(finished())); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(redirectSpy.count(), redirectCount); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(redirectSpy.size(), redirectCount); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode); QVERIFY(validateRedirectedResponseHeaders(reply) || statusCode != 200); } @@ -8554,7 +9237,7 @@ void tst_QNetworkReply::ioHttpRedirectPolicyErrors() QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); QCOMPARE(waitForFinish(reply), int(Failure)); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(reply->error(), expectedError); } @@ -8604,7 +9287,7 @@ void tst_QNetworkReply::ioHttpUserVerifiedRedirect() QSignalSpy finishedSpy(reply.data(), SIGNAL(finished())); waitForFinish(reply); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(finishedSpy.size(), 1); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode); QVERIFY(validateRedirectedResponseHeaders(reply) || statusCode != 200); } @@ -8616,7 +9299,7 @@ void tst_QNetworkReply::ioHttpCookiesDuringRedirect() const QString cookieHeader = QStringLiteral("Set-Cookie: hello=world; Path=/;\r\n"); QString redirect = tempRedirectReplyStr(); // Insert 'cookieHeader' before the final \r\n - redirect.insert(redirect.length() - 2, cookieHeader); + redirect.insert(redirect.size() - 2, cookieHeader); QUrl url("http://localhost/"); url.setPort(target.serverPort()); @@ -8633,7 +9316,7 @@ void tst_QNetworkReply::ioHttpCookiesDuringRedirect() manager.setRedirectPolicy(oldRedirectPolicy); QVERIFY(waitForFinish(reply) == Success); - QVERIFY(target.receivedData.contains("\r\nCookie: hello=world\r\n")); + QVERIFY(target.receivedData.contains("\r\ncookie: hello=world\r\n")); QVERIFY(validateRedirectedResponseHeaders(reply)); } @@ -8676,6 +9359,64 @@ void tst_QNetworkReply::ioHttpRedirect() QVERIFY(validateRedirectedResponseHeaders(reply)); } +/* + Test that, if we load a redirect from cache, we don't treat the request to + the destination of the redirect as a redirect. + + If it was treated as a redirect the finished() signal was never emitted! +*/ +void tst_QNetworkReply::ioHttpRedirectWithCache() +{ + // Disallow caching the result so that the second request must also send the request + QByteArray http200ResponseNoCache = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Cache-Control: no-cache\r\n" + "\r\nHello"; + + MiniHttpServer target(http200ResponseNoCache, false); + QUrl targetUrl("http://localhost/"); + targetUrl.setPort(target.serverPort()); + + // A cache-able redirect reply + QString redirectReply = QStringLiteral("HTTP/1.1 308\r\n" + "Content-Type: text/plain\r\n" + "location: %1\r\n" + "Cache-Control: max-age=3600\r\n" + "\r\nYou're being redirected").arg(targetUrl.toString()); + MiniHttpServer redirectServer(redirectReply.toLatin1(), false); + QUrl url("http://localhost/"); + url.setPort(redirectServer.serverPort()); + + QTemporaryDir tempDir(QDir::tempPath() + "/tmp_cache_28035"); + QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString())); + tempDir.setAutoRemove(true); + + QNetworkDiskCache *diskCache = new QNetworkDiskCache(); + diskCache->setCacheDirectory(tempDir.path()); + // Manager takes ownership of the cache: + manager.setCache(diskCache); + QCOMPARE(diskCache->cacheSize(), 0); + + // Send the first request, we end up caching the redirect reply + QNetworkRequest request(url); + QNetworkReplyPtr reply(manager.get(request)); + + QCOMPARE(waitForFinish(reply), int(Success)); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(validateRedirectedResponseHeaders(reply)); + + QVERIFY(diskCache->cacheSize() != 0); + + // Now for the second request, we will use the cache, and we test that the finished() + // signal is still emitted. + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + reply.reset(manager.get(request)); + + QCOMPARE(waitForFinish(reply), int(Success)); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(validateRedirectedResponseHeaders(reply)); +} + void tst_QNetworkReply::ioHttpRedirectFromLocalToRemote() { QUrl targetUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt"); @@ -8821,8 +9562,8 @@ void tst_QNetworkReply::ioHttpRedirectMultipartPost() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 OK - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); @@ -8943,7 +9684,7 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() } } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) class PutWithServerClosingConnectionImmediatelyHandler: public QObject { @@ -8985,9 +9726,9 @@ public slots: //qDebug() << m_receivedData.left(m_receivedData.indexOf("\r\n\r\n")); m_receivedData = m_receivedData.mid(m_receivedData.indexOf("\r\n\r\n")+4); // check only actual data } - if (m_receivedData.length() > 0 && !m_expectedData.startsWith(m_receivedData)) { + if (m_receivedData.size() > 0 && !m_expectedData.startsWith(m_receivedData)) { // We had received some data but it is corrupt! - qDebug() << "CORRUPT" << m_receivedData.count(); + qDebug() << "CORRUPT" << m_receivedData.size(); #if 0 // Use this to track down the pattern of the corruption and conclude the source QFile a("/tmp/corrupt"); @@ -9109,8 +9850,12 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute_data() { QTest::addColumn<QUrl>("destination"); - QTest::newRow("http") << QUrl("http://QInvalidDomain.qt/test"); - QTest::newRow("https") << QUrl("https://QInvalidDomain.qt/test"); + QUrl webServerUrl = QtNetworkSettings::httpServerIp().toString(); + webServerUrl.setPath("/notfound"); + webServerUrl.setScheme("http"); + QTest::newRow("http") << webServerUrl; + webServerUrl.setScheme("https"); + QTest::newRow("https") << webServerUrl; if (ftpSupported) QTest::newRow("ftp") << QUrl("ftp://QInvalidDomain.qt/test"); QTest::newRow("file") << QUrl("file:///thisfolderdoesn'texist/probably.txt"); @@ -9133,7 +9878,7 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } { @@ -9144,7 +9889,7 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } // Now repeated, but without the attribute to make sure it does not get deleted automatically. @@ -9158,10 +9903,10 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } { // Post @@ -9170,10 +9915,10 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } } @@ -9194,7 +9939,7 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } { @@ -9204,7 +9949,7 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } // Here we repeat the test, but override the auto-deletion in the QNetworkRequest @@ -9219,10 +9964,10 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } { // Post @@ -9232,10 +9977,10 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } // Now we repeat the test with autoDeleteReplies set to false cleanup.dismiss(); @@ -9247,10 +9992,10 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } { // Post @@ -9259,89 +10004,138 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } } -void tst_QNetworkReply::getWithTimeout() +void tst_QNetworkReply::requestWithTimeout_data() { - MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); + using Operation = QNetworkAccessManager::Operation; + QTest::addColumn<Operation>("method"); + QTest::addColumn<int>("reqInt"); + QTest::addColumn<std::chrono::milliseconds>("reqChrono"); + QTest::addColumn<int>("mgrInt"); + QTest::addColumn<std::chrono::milliseconds>("mgrChrono"); - QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); - QNetworkReplyPtr reply(manager.get(request)); - QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply), int(Success)); - - QCOMPARE(spy.count(), 0); - QVERIFY(reply->error() == QNetworkReply::NoError); - - request.setTransferTimeout(1000); - server.stopTransfer = true; - - QNetworkReplyPtr reply2(manager.get(request)); - QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); + QTest::addRow("get_req_int") << Operation::GetOperation << 500 << 0ms << 0 << 0ms; + QTest::addRow("get_req_chrono") << Operation::GetOperation << 0 << 500ms << 0 << 0ms; + QTest::addRow("get_mgr_int") << Operation::GetOperation << 0 << 0ms << 500 << 0ms; + QTest::addRow("get_mgr_chrono") << Operation::GetOperation << 0 << 0ms << 0 << 500ms; - QCOMPARE(waitForFinish(reply2), int(Failure)); - - QCOMPARE(spy2.count(), 1); - QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError); - - request.setTransferTimeout(0); - manager.setTransferTimeout(1000); - - QNetworkReplyPtr reply3(manager.get(request)); - QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply3), int(Failure)); - - QCOMPARE(spy3.count(), 1); - QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError); - - manager.setTransferTimeout(0); + QTest::addRow("post_req_int") << Operation::PostOperation << 500 << 0ms << 0 << 0ms; + QTest::addRow("post_req_chrono") << Operation::PostOperation << 0 << 500ms << 0 << 0ms; + QTest::addRow("post_mgr_int") << Operation::PostOperation << 0 << 0ms << 500 << 0ms; + QTest::addRow("post_mgr_chrono") << Operation::PostOperation << 0 << 0ms << 0 << 500ms; } -void tst_QNetworkReply::postWithTimeout() +void tst_QNetworkReply::requestWithTimeout() { + QFETCH(QNetworkAccessManager::Operation, method); + QFETCH(int, reqInt); + QFETCH(int, mgrInt); + QFETCH(std::chrono::milliseconds, reqChrono); + QFETCH(std::chrono::milliseconds, mgrChrono); + const auto data = "some data"_ba; + // Manager instance remains between case runs => always reset it's transferTimeout to + // ensure setting its transferTimeout in this case has effect + manager.setTransferTimeout(0ms); + auto cleanup = qScopeGuard([this] { manager.setTransferTimeout(0ms); }); + MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); + server.stopTransfer = true; QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); request.setRawHeader("Content-Type", "application/octet-stream"); - QByteArray postData("Just some nonsense"); - QNetworkReplyPtr reply(manager.post(request, postData)); - QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply), int(Success)); - - QCOMPARE(spy.count(), 0); - QVERIFY(reply->error() == QNetworkReply::NoError); - - request.setTransferTimeout(1000); - server.stopTransfer = true; - - QNetworkReplyPtr reply2(manager.post(request, postData)); - QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply2), int(Failure)); - - QCOMPARE(spy2.count(), 1); - QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError); - - request.setTransferTimeout(0); - manager.setTransferTimeout(1000); + if (reqInt > 0) + request.setTransferTimeout(reqInt); + if (reqChrono > 0ms) + request.setTransferTimeout(reqChrono); + if (mgrInt > 0) + manager.setTransferTimeout(mgrInt); + if (mgrChrono > 0ms) + manager.setTransferTimeout(mgrChrono); - QNetworkReplyPtr reply3(manager.post(request, postData)); - QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); + QNetworkReplyPtr reply; + if (method == QNetworkAccessManager::GetOperation) + reply.reset(manager.get(request)); + else if (method == QNetworkAccessManager::PostOperation) + reply.reset(manager.post(request, data)); + QVERIFY(reply); - QCOMPARE(waitForFinish(reply3), int(Failure)); + QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred); + QCOMPARE(waitForFinish(reply), int(Failure)); + QCOMPARE(spy.size(), 1); + QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); +} - QCOMPARE(spy3.count(), 1); - QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError); +void tst_QNetworkReply::moreActivitySignals_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<bool>("useipv6"); + QTest::addColumn<bool>("postWithData"); + QTest::addRow("local4") << QUrl("http://127.0.0.1") << false << false; + QTest::addRow("local6") << QUrl("http://[::1]") << true << false; + if (qEnvironmentVariable("QTEST_ENVIRONMENT").split(' ').contains("ci")) { + // On CI server + QTest::addRow("localDns") << QUrl("http://localhost") << false << false; // will find v6 + } else { + // For manual testing + QTest::addRow("localDns4") << QUrl("http://localhost") << true << false; // will find both v4 and v6 + QTest::addRow("localDns6") << QUrl("http://localhost") << false << false; // will find both v4 and v6 + } + QTest::addRow("post-with-data") << QUrl("http://[::1]") << true << true; +} - manager.setTransferTimeout(0); +void tst_QNetworkReply::moreActivitySignals() +{ + QFETCH(QUrl, url); + QFETCH(bool, useipv6); + QFETCH(bool, postWithData); + MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false, nullptr/*thread*/, useipv6); + server.doClose = false; + url.setPort(server.serverPort()); + QNetworkRequest request(url); + QNetworkReplyPtr reply; + if (postWithData) { + request.setRawHeader("Content-Type", "text/plain"); + reply.reset(manager.post(request, "Hello, world!")); + } else { + reply.reset(manager.get(request)); + } + QSignalSpy spy1(reply.data(), SIGNAL(socketStartedConnecting())); + QSignalSpy spy2(reply.data(), SIGNAL(requestSent())); + QSignalSpy spy3(reply.data(), SIGNAL(metaDataChanged())); + QSignalSpy spy4(reply.data(), SIGNAL(finished())); + spy1.wait(); + QCOMPARE(spy1.size(), 1); + spy2.wait(); + QCOMPARE(spy2.size(), 1); + spy3.wait(); + QCOMPARE(spy3.size(), 1); + spy4.wait(); + QCOMPARE(spy4.size(), 1); + QVERIFY(reply->error() == QNetworkReply::NoError); + // Second request will not send socketStartedConnecting because of keep-alive, so don't check it. + QNetworkReplyPtr secondreply; + if (postWithData) { + request.setRawHeader("Content-Type", "text/plain"); + secondreply.reset(manager.post(request, "Hello, world!")); + } else { + secondreply.reset(manager.get(request)); + } + QSignalSpy secondspy2(secondreply.data(), SIGNAL(requestSent())); + QSignalSpy secondspy3(secondreply.data(), SIGNAL(metaDataChanged())); + QSignalSpy secondspy4(secondreply.data(), SIGNAL(finished())); + secondspy2.wait(); + QCOMPARE(secondspy2.size(), 1); + secondspy3.wait(); + QCOMPARE(secondspy3.size(), 1); + secondspy4.wait(); + QCOMPARE(secondspy4.size(), 1); + QVERIFY(secondreply->error() == QNetworkReply::NoError); } void tst_QNetworkReply::contentEncoding_data() @@ -9349,25 +10143,53 @@ void tst_QNetworkReply::contentEncoding_data() QTest::addColumn<QByteArray>("encoding"); QTest::addColumn<QByteArray>("body"); QTest::addColumn<QByteArray>("expected"); + QTest::addColumn<bool>("decompress"); + + const QByteArray helloWorld = "hello world"; + const QByteArray gzipBody = QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA=="); QTest::newRow("gzip-hello-world") << QByteArray("gzip") - << QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA==") - << QByteArray("hello world"); + << gzipBody + << helloWorld + << true; + QTest::newRow("gzip-hello-world-no-decompress") + << QByteArray("gzip") + << gzipBody + << helloWorld + << false; + const QByteArray deflateBody = QByteArray::fromBase64("eJzLSM3JyVcozy/KSQEAGgsEXQ=="); QTest::newRow("deflate-hello-world") - << QByteArray("deflate") << QByteArray::fromBase64("eJzLSM3JyVcozy/KSQEAGgsEXQ==") - << QByteArray("hello world"); + << QByteArray("deflate") << deflateBody + << helloWorld + << true; + QTest::newRow("deflate-hello-world-no-decompress") + << QByteArray("deflate") << deflateBody + << helloWorld + << false; #if QT_CONFIG(brotli) + const QByteArray brotliBody = QByteArray::fromBase64("DwWAaGVsbG8gd29ybGQD"); QTest::newRow("brotli-hello-world") - << QByteArray("br") << QByteArray::fromBase64("DwWAaGVsbG8gd29ybGQD") - << QByteArray("hello world"); + << QByteArray("br") << brotliBody + << helloWorld + << true; + QTest::newRow("brotli-hello-world-no-decompress") + << QByteArray("br") << brotliBody + << helloWorld + << false; #endif #if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + const QByteArray zstdBody = QByteArray::fromBase64("KLUv/QRYWQAAaGVsbG8gd29ybGRoaR6y"); QTest::newRow("zstandard-hello-world") - << QByteArray("zstd") << QByteArray::fromBase64("KLUv/QRYWQAAaGVsbG8gd29ybGRoaR6y") - << QByteArray("hello world"); + << QByteArray("zstd") << zstdBody + << helloWorld + << true; + QTest::newRow("zstandard-hello-world-no-decompress") + << QByteArray("zstd") << zstdBody + << helloWorld + << false; #else qDebug("Note: ZStandard testdata is only available for developer builds."); #endif @@ -9377,12 +10199,19 @@ void tst_QNetworkReply::contentEncoding() { QFETCH(QByteArray, encoding); QFETCH(QByteArray, body); + QFETCH(bool, decompress); QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); header = header.arg(encoding, QString::number(body.size())); MiniHttpServer server(header.toLatin1() + body); QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + if (!decompress) { + // This disables decompression of the received content: + request.setRawHeader("accept-encoding", QLatin1String("%1").arg(encoding).toLatin1()); + // This disables the zerocopy optimization + request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 0); + } QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -9391,7 +10220,7 @@ void tst_QNetworkReply::contentEncoding() { // Check that we included the content encoding method in our Accept-Encoding header const QByteArray &receivedData = server.receivedData; - int start = receivedData.indexOf("Accept-Encoding"); + int start = receivedData.indexOf("accept-encoding"); QVERIFY(start != -1); int end = receivedData.indexOf("\r\n", start); QVERIFY(end != -1); @@ -9403,12 +10232,391 @@ void tst_QNetworkReply::contentEncoding() QVERIFY2(list.contains(encoding), acceptedEncoding.data()); } + if (decompress) { + QFETCH(QByteArray, expected); + QCOMPARE(reply->bytesAvailable(), expected.size()); + QCOMPARE(reply->readAll(), expected); + } else { + QCOMPARE(reply->bytesAvailable(), body.size()); + QCOMPARE(reply->readAll(), body); + } +} + +void tst_QNetworkReply::contentEncodingBigPayload_data() +{ + QTest::addColumn<QByteArray>("encoding"); + QTest::addColumn<QString>("path"); + QTest::addColumn<qint64>("expectedSize"); + + qint64 fourGiB = 4ll * 1024ll * 1024ll * 1024ll; + + QTest::addRow("gzip-4GB") << QByteArray("gzip") << (":/4G.gz") << fourGiB; + +#if QT_CONFIG(brotli) + QTest::addRow("brotli-4GB") << QByteArray("br") << (testDataDir + "/4G.br") << fourGiB; +#endif +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + QTest::addRow("zstd-4GB") << QByteArray("zstd") << (":/4G.zst") << fourGiB; +#else + qDebug("Note: ZStandard testdata is only available for developer builds."); +#endif +} + +void tst_QNetworkReply::contentEncodingBigPayload() +{ + QFETCH(QString, path); + QFile compressedFile(path); + QVERIFY(compressedFile.open(QIODevice::ReadOnly)); + QByteArray body = compressedFile.readAll(); + + QFETCH(QByteArray, encoding); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + // QDecompressHelper will abort the download if the compressed to decompressed size ratio + // differs too much, so we override it + request.setDecompressedSafetyCheckThreshold(-1); + QNetworkReplyPtr reply(manager.get(request)); + + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QFETCH(qint64, expectedSize); + + QCOMPARE(reply->bytesAvailable(), expectedSize); + QByteArray output(512 * 1024 * 1024, Qt::Uninitialized); + qint64 total = 0; + while (reply->bytesAvailable()) { + qint64 read = reply->read(output.data(), output.size()); + QVERIFY(read != -1); + total += read; + + static const auto isZero = [](char c) { return c == '\0'; }; + bool allZero = std::all_of(output.cbegin(), output.cbegin() + read, isZero); + QVERIFY(allZero); + } + QCOMPARE(total, expectedSize); +} + +void tst_QNetworkReply::cacheWithContentEncoding_data() +{ + contentEncoding_data(); +} + +void tst_QNetworkReply::cacheWithContentEncoding() +{ + QFETCH(QByteArray, encoding); + QFETCH(QByteArray, body); + QFETCH(QByteArray, expected); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + MySpyMemoryCache *cache = new MySpyMemoryCache(this); + manager.setCache(cache); + auto unsetCache = qScopeGuard([this](){ manager.setCache(nullptr); }); + + QUrl url("http://localhost:" + QString::number(server.serverPort())); + QNetworkRequest request(url); + QNetworkReplyPtr reply(manager.get(request)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QByteArray output = reply->readAll(); + QIODevice *device = cache->data(url); + QVERIFY(device); + QByteArray fromCache = device->readAll(); + QCOMPARE(output, expected); + QCOMPARE(fromCache, expected); +} + +void tst_QNetworkReply::downloadProgressWithContentEncoding_data() +{ + contentEncoding_data(); +} + +void tst_QNetworkReply::downloadProgressWithContentEncoding() +{ + QFETCH(QByteArray, encoding); + QFETCH(QByteArray, body); QFETCH(QByteArray, expected); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); - QCOMPARE(reply->bytesAvailable(), expected.size()); + QUrl url("http://localhost:" + QString::number(server.serverPort())); + QNetworkRequest request(url); + QNetworkReplyPtr reply(manager.get(request)); + // Limit the amount of bytes we read so we get more than one downloadProgress emission + reply->setReadBufferSize(5); + + qint64 bytesReceived = -1; + connect(reply.data(), &QNetworkReply::downloadProgress, this, + [reply = reply.data(), &expected, &bytesReceived](qint64 recv, qint64 /*total*/) { + qint64 previous = bytesReceived; + bytesReceived = recv; + if (bytesReceived > expected.size()) { + qWarning("bytesReceived greater than expected size!"); + reply->abort(); + } + if (bytesReceived < previous) { + qWarning("bytesReceived shrank!"); + reply->abort(); + } + }); + + SlowReader reader(reply.data()); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(bytesReceived, expected.size()); +} + +void tst_QNetworkReply::contentEncodingError_data() +{ + QTest::addColumn<QByteArray>("encoding"); + QTest::addColumn<QString>("path"); + QTest::addColumn<QNetworkReply::NetworkError>("expectedError"); + + QTest::addRow("archive-bomb") << QByteArray("gzip") << (":/4G.gz") + << QNetworkReply::UnknownContentError; +} + +void tst_QNetworkReply::contentEncodingError() +{ + QFETCH(QString, path); + QFile compressedFile(path); + QVERIFY(compressedFile.open(QIODevice::ReadOnly)); + QByteArray body = compressedFile.readAll(); + + QFETCH(QByteArray, encoding); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QNetworkRequest request( + QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); + QNetworkReplyPtr reply(manager.get(request)); + + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QTEST(reply->error(), "expectedError"); +} + +// When this test is failing it will appear flaky because it relies on the +// timing of delivery from one socket to another in the OS. +// + we have to send all the data at once, so the readyRead emissions are +// compressed into a single emission, so we cannot artificially time it with +// waits and sleeps. +void tst_QNetworkReply::compressedReadyRead() +{ + // There were historically an issue where a mix of signal compression and + // data decompression made it so we accidentally didn't emit the final + // readyRead signal before emitting finished(). Test this here to make sure + // it happens: + const QByteArray gzipPayload = + QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA=="); + const QByteArray expected = "hello world"; + + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: %1\r\n\r\n"); + header = header.arg(gzipPayload.size()); + MiniHttpServer server(header.toLatin1()); // only send header automatically + server.doClose = false; // don't close and delete client socket right away + + QNetworkRequest request( + QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); + QNetworkReplyPtr reply(manager.get(request)); + + QObject::connect(reply.get(), &QNetworkReply::metaDataChanged, reply.get(), + [&server, &gzipPayload]() { + // Client received headers, now send data: + // We do this awkward write,flush,write dance to try to + // make sure the data does not all arrive at the same + // time. By design we send the final "=" byte by itself + qsizetype boundary = gzipPayload.size() - 1; + server.client->write(gzipPayload.sliced(0, boundary)); + server.client->flush(); + // Let the server take care of deleting the client once + // the rest of the data is written: + server.doClose = true; + server.client->write(gzipPayload.sliced(boundary)); + }); + + QByteArray received; + QObject::connect(reply.get(), &QNetworkReply::readyRead, reply.get(), + [reply = reply.get(), &received]() { + received += reply->readAll(); + }); + QTRY_VERIFY(reply->isFinished()); + QCOMPARE(received, expected); +} + +void tst_QNetworkReply::notFoundWithCompression_data() +{ + contentEncoding_data(); +} + +void tst_QNetworkReply::notFoundWithCompression() +{ + QFETCH(QByteArray, encoding); + QFETCH(QByteArray, body); + QString header("HTTP/1.0 404 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QNetworkRequest request( + QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); + QNetworkReplyPtr reply(manager.get(request)); + + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QCOMPARE(reply->error(), QNetworkReply::ContentNotFoundError); + + QFETCH(QByteArray, expected); QCOMPARE(reply->readAll(), expected); } +void tst_QNetworkReply::qhttpPartDebug_data() +{ + QTest::addColumn<QByteArray>("header_data"); + QTest::addColumn<QByteArray>("raw_header_data"); + QTest::addColumn<QList<QByteArray>>("expected_header_values"); + QTest::addColumn<bool>("overwrite"); + + QTest::newRow("header-data-set") << "form-data; name=\"prompt\""_ba << ""_ba + << (QList<QByteArray>() << "form-data; name=\"prompt\""_ba) << false; + QTest::newRow("raw-header-data-set") << ""_ba << "thisismykeyherebutnotreally"_ba + << (QList<QByteArray>() << "thisismykeyherebutnotreally"_ba) << false; + QTest::newRow("both-set") << "form-data; name=\"prompt\""_ba + << "thisismykeyherebutnotreally"_ba + << (QList<QByteArray>() + << "form-data; name=\"prompt\""_ba + << "thisismykeyherebutnotreally"_ba) << false; + QTest::newRow("overwrite") << "form-data; name=\"prompt\""_ba + << "thisismykeyherebutnotreally"_ba + << (QList<QByteArray>() + << "thisismykeyherebutnotreally"_ba + << "thisismykeyherebutnotreally"_ba) << true; +} + +void tst_QNetworkReply::qhttpPartDebug() +{ + QFETCH(const QByteArray, header_data); + QFETCH(const QByteArray, raw_header_data); + QFETCH(const QList<QByteArray>, expected_header_values); + QFETCH(bool, overwrite); + + QHttpPart httpPart; + + if (!header_data.isEmpty()) + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, header_data); + + if (!raw_header_data.isEmpty()) + httpPart.setRawHeader("Authorization", raw_header_data); + + if (overwrite) + httpPart.setRawHeader("Content-Disposition", raw_header_data); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + for (const auto &value : expected_header_values) + QVERIFY2(msg.contains(value), "Missing header value: " + value); +} + +void tst_QNetworkReply::qtbug68821proxyError_data() +{ + QTest::addColumn<QString>("proxyHost"); + QTest::addColumn<QString>("scheme"); + QTest::addColumn<QNetworkReply::NetworkError>("error"); + + QTest::newRow("invalidhost+http") << "this-host-will-never-exist.qt-project.org" + << "http" << QNetworkReply::ProxyNotFoundError; + QTest::newRow("localhost+http") << "localhost" + << "http" << QNetworkReply::ProxyConnectionRefusedError; +#ifndef QT_NO_SSL + QTest::newRow("invalidhost+https") << "this-host-will-never-exist.qt-project.org" + << "https" << QNetworkReply::ProxyNotFoundError; + QTest::newRow("localhost+https") << "localhost" + << "https" << QNetworkReply::ProxyConnectionRefusedError; +#endif +} + +void tst_QNetworkReply::qtbug68821proxyError() +{ + auto getUnusedPort = []() -> std::optional<quint16> { + QTcpServer probeServer; + if (!probeServer.listen()) + return std::nullopt; + // If we can listen on it, it was unused, and hopefully is also + // still unused after we stop listening. + return probeServer.serverPort(); + }; + + auto proxyPort = getUnusedPort(); + QVERIFY(proxyPort); + + QFETCH(QString, proxyHost); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, proxyHost, proxyPort.value()); + + manager.setProxy(proxy); + + QFETCH(QString, scheme); + QNetworkReply *reply = manager.get(QNetworkRequest(QUrl(scheme + "://example.com"))); + QSignalSpy spy(reply, &QNetworkReply::errorOccurred); + QVERIFY(spy.isValid()); + + QVERIFY(spy.wait(15000)); + + QFETCH(QNetworkReply::NetworkError, error); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0), error); +} + +void tst_QNetworkReply::abortAndError() +{ + const QByteArray response = + R"(HTTP/1.0 500 Internal Server Error +Content-Length: 12 +Content-Type: text/plain + +Hello World!)"_ba; + + MiniHttpServer server(response); + + QNetworkAccessManager manager; + QNetworkRequest req(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()))); + std::unique_ptr<QNetworkReply> reply(manager.post(req, "my data goes here"_ba)); + QSignalSpy errorSignal(reply.get(), &QNetworkReply::errorOccurred); + QSignalSpy finishedSignal(reply.get(), &QNetworkReply::finished); + + reply->abort(); + + // We don't want to print this warning in this case because it is impossible + // for users to avoid it. + QTest::failOnWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only " + "be called once."); + // Process any signals from the http thread: + QTest::qWait(1s); + if (QTest::currentTestFailed()) + return; + + QCOMPARE(finishedSignal.count(), 1); + QCOMPARE(errorSignal.count(), 1); + QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); +} + // NOTE: This test must be last testcase in tst_qnetworkreply! void tst_QNetworkReply::parentingRepliesToTheApp() { |