diff options
Diffstat (limited to 'tests/auto/network/access/qnetworkreply')
4 files changed, 748 insertions, 199 deletions
diff --git a/tests/auto/network/access/qnetworkreply/CMakeLists.txt b/tests/auto/network/access/qnetworkreply/CMakeLists.txt index ee6f4d1ffd..9bfd90cd56 100644 --- a/tests/auto/network/access/qnetworkreply/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkreply/CMakeLists.txt @@ -1,5 +1,11 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkreply LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + add_subdirectory(echo) add_subdirectory(test) diff --git a/tests/auto/network/access/qnetworkreply/echo/main.cpp b/tests/auto/network/access/qnetworkreply/echo/main.cpp index 770b7bc47b..b10eaa745c 100644 --- a/tests/auto/network/access/qnetworkreply/echo/main.cpp +++ b/tests/auto/network/access/qnetworkreply/echo/main.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtCore/QFile> @@ -12,11 +12,13 @@ int main(int argc, char **) } QFile file; - file.open(stdin, QFile::ReadWrite); + if (!file.open(stdin, QFile::ReadWrite)) + return 1; QByteArray data = file.readAll(); file.close(); - file.open(stdout, QFile::WriteOnly); + if (!file.open(stdout, QFile::WriteOnly)) + return 1; file.write(data); file.close(); return 0; diff --git a/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt b/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt index 583b250932..fa353b2769 100644 --- a/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt @@ -29,6 +29,7 @@ qt_internal_add_test(tst_qnetworkreply Qt::NetworkPrivate TESTDATA ${test_data} QT_TEST_SERVER_LIST "vsftpd" "apache2" "ftp-proxy" "danted" "squid" + BUNDLE_ANDROID_OPENSSL_LIBS ) add_dependencies(tst_qnetworkreply echo) diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index 7e35712a87..97e6fa8518 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtNetwork/qtnetworkglobal.h> @@ -91,6 +91,7 @@ Q_DECLARE_METATYPE(QNetworkProxyQuery) typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr; using namespace Qt::StringLiterals; +using namespace std::chrono_literals; #if QT_CONFIG(ssl) QT_BEGIN_NAMESPACE @@ -128,7 +129,30 @@ class tst_QNetworkReply: public QObject static QString tempRedirectReplyStr() { QString s = "HTTP/1.1 307 Temporary Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" + "location: %1\r\n" + "\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; @@ -216,6 +240,12 @@ 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(); #if QT_CONFIG(networkproxy) @@ -233,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(); @@ -240,6 +272,8 @@ private Q_SLOTS: void postToHttpMultipart_data(); void postToHttpMultipart(); void multipartSkipIndices(); // QTBUG-32534 + void postWithoutBody_data(); + void postWithoutBody(); #if QT_CONFIG(ssl) void putToHttps_data(); void putToHttps(); @@ -292,8 +326,8 @@ private Q_SLOTS: #endif void ioGetFromHttpBrokenServer_data(); void ioGetFromHttpBrokenServer(); - void ioGetFromHttpStatus100_data(); - void ioGetFromHttpStatus100(); + void ioGetFromHttpStatusInformational_data(); + void ioGetFromHttpStatusInformational(); void ioGetFromHttpNoHeaders_data(); void ioGetFromHttpNoHeaders(); void ioGetFromHttpWithCache_data(); @@ -506,8 +540,8 @@ private Q_SLOTS: void autoDeleteReplies_data(); void autoDeleteReplies(); - void getWithTimeout(); - void postWithTimeout(); + void requestWithTimeout_data(); + void requestWithTimeout(); void moreActivitySignals_data(); void moreActivitySignals(); @@ -526,6 +560,14 @@ private Q_SLOTS: 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(); private: @@ -610,7 +652,8 @@ public: int totalConnections; bool stopTransfer = false; - bool hasContent = false; + bool checkedContentLength = false; + bool foundContentLength = false; int contentRead = 0; int contentLength = 0; @@ -642,6 +685,7 @@ public: { contentLength = 0; receivedData.clear(); + foundContentLength = false; } protected: @@ -698,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; @@ -739,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) - parseContentLength(); contentRead = receivedData.size() - endOfHeader; - if (hasContent && contentRead < contentLength) + + if (!checkedContentLength) { + parseContentLength(); + checkedContentLength = true; + } + + if (contentRead < contentLength) return; // multiple requests incoming. remove the bytes of the current one @@ -845,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; } @@ -1988,6 +2039,253 @@ void tst_QNetworkReply::getFromHttp() QCOMPARE(reply->readAll(), reference.readAll()); } +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() { @@ -2406,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(); @@ -2416,7 +2756,7 @@ void tst_QNetworkReply::postToHttp() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; QFETCH(QByteArray, data); @@ -2443,7 +2783,7 @@ void tst_QNetworkReply::postToHttpSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, @@ -2504,8 +2844,8 @@ void tst_QNetworkReply::postToHttpMultipart_data() QHttpMultiPart *customMultiPart = new QHttpMultiPart; customMultiPart->append(textPart); - expectedData = "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + expectedData = "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text\"'\n" "content: 7 bytes\n" "\n"; QTest::newRow("text-custom") << url << customMultiPart << expectedData << QByteArray("custom"); @@ -2541,18 +2881,18 @@ void tst_QNetworkReply::postToHttpMultipart_data() multiPart3->append(textPart); multiPart3->append(textPart2); multiPart3->append(textPart3); - expectedData = "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + expectedData = "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text\"'\n" "content: 7 bytes\n" "\n" - "header: Content-Type, value: 'text/plain'\n" - "header: myRawHeader, value: 'myValue'\n" - "header: Content-Disposition, value: 'form-data; name=\"text2\"'\n" + "header: content-type, value: 'text/plain'\n" + "header: myrawheader, value: 'myValue'\n" + "header: content-disposition, value: 'form-data; name=\"text2\"'\n" "content: some more bytes\n" "\n" - "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text3\"'\n" - "header: Content-Location, value: 'http://my.test.location.tld'\n" + "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text3\"'\n" + "header: content-location, value: 'http://my.test.location.tld'\n" "content: even more bytes\n\n"; QTest::newRow("text-text-text") << url << multiPart3 << expectedData << QByteArray("alternative"); @@ -2775,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(); @@ -2930,7 +3311,7 @@ void tst_QNetworkReply::postToHttps() QSslConfiguration conf; conf.setCaCertificates(certs); request.setSslConfiguration(conf); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; QFETCH(QByteArray, data); @@ -2964,7 +3345,7 @@ void tst_QNetworkReply::postToHttpsSynchronous() QSslConfiguration conf; conf.setCaCertificates(certs); request.setSslConfiguration(conf); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, @@ -3166,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; @@ -3181,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()); @@ -3372,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)); @@ -3397,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")); @@ -3527,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") @@ -3852,7 +4235,6 @@ 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.size(), 0); @@ -3996,7 +4378,7 @@ void tst_QNetworkReply::ioGetFromHttpBrokenServer() QVERIFY(reply->error() != QNetworkReply::NoError); } -void tst_QNetworkReply::ioGetFromHttpStatus100_data() +void tst_QNetworkReply::ioGetFromHttpStatusInformational_data() { QTest::addColumn<QByteArray>("dataToSend"); QTest::addColumn<int>("statusCode"); @@ -4007,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); @@ -4063,7 +4461,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache_data() QByteArray reply200 = "HTTP/1.0 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-store\r\n" "Content-length: 8\r\n" "\r\n" @@ -4198,7 +4596,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache_data() QByteArray reply206 = "HTTP/1.0 206\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-cache\r\n" "Content-Range: bytes 2-6/8\r\n" "Content-length: 4\r\n" @@ -4693,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) } @@ -4803,7 +5205,7 @@ void tst_QNetworkReply::ioPostToHttpFromFile() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); @@ -4880,7 +5282,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocket() socketpair.endPoints[0]->write(data); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); manager.setProxy(proxy); QNetworkReplyPtr reply(manager.post(request, socketpair.endPoints[1])); @@ -4954,7 +5356,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, true); @@ -4985,7 +5387,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileToEnd() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), @@ -5011,7 +5413,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileFiveBytes() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); // only send 5 bytes request.setHeader(QNetworkRequest::ContentLengthHeader, 5); QVERIFY(request.header(QNetworkRequest::ContentLengthHeader).isValid()); @@ -5042,7 +5444,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfQBufferFiveBytes() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &uploadBuffer)); connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), @@ -5070,7 +5472,7 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); // disallow buffering request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true); request.setHeader(QNetworkRequest::ContentLengthHeader, data.size()); @@ -5155,7 +5557,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress() QUrl url = QUrl(QLatin1String("https://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); @@ -5309,7 +5711,7 @@ void tst_QNetworkReply::ioPostToHttpUploadProgress() // create the request QUrl url = QUrl(QString("http://127.0.0.1:%1/").arg(server.serverPort())); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -5377,18 +5779,14 @@ void tst_QNetworkReply::emitAllUploadProgressSignals() QUrl url = QUrl(QLatin1String("http://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest normalRequest(url); - normalRequest.setRawHeader("Content-Type", "application/octet-stream"); + normalRequest.setRawHeader("content-type", "application/octet-stream"); 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))); @@ -5432,7 +5830,7 @@ void tst_QNetworkReply::ioPostToHttpEmptyUploadProgress() // create the request QUrl url = QUrl(QLatin1String("http://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &buffer)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -5478,7 +5876,11 @@ void tst_QNetworkReply::lastModifiedHeaderForFile() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); - QCOMPARE(header, fileInfo.lastModified()); + QDateTime expected = fileInfo.lastModified(); + // remove msecs, HTTP dates don't support it + expected = expected.addMSecs(-expected.time().msec()); + + QCOMPARE(header.toUTC(), expected.toUTC()); } void tst_QNetworkReply::lastModifiedHeaderForHttp() @@ -5784,7 +6186,7 @@ void tst_QNetworkReply::receiveCookiesFromHttp() QByteArray data = cookieString.toLatin1() + '\n'; QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/set-cookie.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PostOperation, request, reply, data)); @@ -5812,7 +6214,7 @@ void tst_QNetworkReply::receiveCookiesFromHttpSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/set-cookie.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, true); @@ -5995,7 +6397,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 @@ -6012,10 +6414,11 @@ void tst_QNetworkReply::httpProxyCommands() 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 @@ -6052,14 +6455,6 @@ struct QThreadCleanup } }; -struct QDeleteLaterCleanup -{ - static inline void cleanup(QObject *o) - { - o->deleteLater(); - } -}; - #if QT_CONFIG(networkproxy) void tst_QNetworkReply::httpProxyCommandsSynchronous() { @@ -6071,7 +6466,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); @@ -6229,7 +6624,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) @@ -6240,7 +6634,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) @@ -6248,26 +6642,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); @@ -6773,7 +7181,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; @@ -7629,7 +8050,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray getReply = "HTTP/1.1 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "\r\n" "GET"; @@ -7637,7 +8058,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray postReply = "HTTP/1.1 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "Content-length: 4\r\n" "\r\n" @@ -7646,7 +8067,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray putReply = "HTTP/1.1 201\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "\r\n"; @@ -7684,7 +8105,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { server.clearHeaderParserState(); server.setDataToTransmit(postReply); - request.setRawHeader("Content-Type", "text/plain"); + request.setRawHeader("content-type", "text/plain"); reply.reset(manager.post(request, postData)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -7871,7 +8292,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(); @@ -8180,7 +8601,7 @@ void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() QByteArray reply206 = "HTTP/1.0 206\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-cache\r\n" "Content-Range: bytes 2-6/8\r\n" "Content-length: 4\r\n" @@ -8220,7 +8641,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() @@ -8237,16 +8658,16 @@ void tst_QNetworkReply::synchronousAuthenticationCache() "WWW-Authenticate: Basic realm=\"QNetworkAccessManager Test Realm\"\r\n" "Content-Length: 4\r\n" "Connection: close\r\n" - "Content-Type: text/plain\r\n" + "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") { dataToTransmit = "HTTP/1.0 200 OK\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Content-Length: 2\r\n" "\r\n" "OK"; @@ -8261,7 +8682,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 @@ -8400,31 +8821,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.size(); ++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.size(); ++a) { - QVERIFY(replies.at(a)->isFinished()); - QCOMPARE(errorSpies.at(a)->size(), 1); - errorSpies.at(a)->deleteLater(); - QCOMPARE(finishedSpies.at(a)->size(), 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); } } @@ -8493,7 +8916,7 @@ 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.size() > 0); @@ -8614,7 +9037,7 @@ void tst_QNetworkReply::ioHttpRedirectErrors_data() QTest::addColumn<QNetworkReply::NetworkError>("error"); QString tempRedirectReply = QString("HTTP/1.1 307 Temporary Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: http://localhost:%1\r\n\r\n"); QTest::newRow("too-many-redirects") << "http://localhost" << tempRedirectReply << QNetworkReply::TooManyRedirectsError; @@ -8897,7 +9320,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)); } @@ -8922,7 +9345,7 @@ void tst_QNetworkReply::ioHttpRedirect() targetUrl.setPort(target.serverPort()); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1(), false); @@ -8950,7 +9373,7 @@ 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" + "content-type: text/plain\r\n" "Cache-Control: no-cache\r\n" "\r\nHello"; @@ -8960,7 +9383,7 @@ void tst_QNetworkReply::ioHttpRedirectWithCache() // A cache-able redirect reply QString redirectReply = QStringLiteral("HTTP/1.1 308\r\n" - "Content-Type: text/plain\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()); @@ -9072,7 +9495,7 @@ void tst_QNetworkReply::ioHttpRedirectPostPut() QUrl targetUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1()); @@ -9226,7 +9649,7 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() targetUrl.setPort(target.serverPort()); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1()); @@ -9260,8 +9683,8 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() // we shouldn't send Content-Length with not content (esp. for GET) QVERIFY2(!target.receivedData.contains("Content-Length"), "Target server should not have received a Content-Length header"); - QVERIFY2(!target.receivedData.contains("Content-Type"), - "Target server should not have received a Content-Type header"); + QVERIFY2(!target.receivedData.contains("content-type"), + "Target server should not have received a content-type header"); } } @@ -9592,82 +10015,64 @@ void tst_QNetworkReply::autoDeleteReplies() } } -void tst_QNetworkReply::getWithTimeout() +void tst_QNetworkReply::requestWithTimeout_data() { - MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); - - QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); - QNetworkReplyPtr reply(manager.get(request)); - QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); + 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"); - QCOMPARE(waitForFinish(reply), int(Success)); - - QCOMPARE(spy.size(), 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))); - - QCOMPARE(waitForFinish(reply2), int(Failure)); - - QCOMPARE(spy2.size(), 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.size(), 1); - QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError); + 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; - 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() { - MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); + 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); }); - 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.size(), 0); - QVERIFY(reply->error() == QNetworkReply::NoError); - - request.setTransferTimeout(1000); + MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); server.stopTransfer = true; - QNetworkReplyPtr reply2(manager.post(request, postData)); - QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply2), int(Failure)); - - QCOMPARE(spy2.size(), 1); - QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError); - - request.setTransferTimeout(0); - manager.setTransferTimeout(1000); - - QNetworkReplyPtr reply3(manager.post(request, postData)); - QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply3), int(Failure)); + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + request.setRawHeader("content-type", "application/octet-stream"); + if (reqInt > 0) + request.setTransferTimeout(reqInt); + if (reqChrono > 0ms) + request.setTransferTimeout(reqChrono); + if (mgrInt > 0) + manager.setTransferTimeout(mgrInt); + if (mgrChrono > 0ms) + manager.setTransferTimeout(mgrChrono); - QCOMPARE(spy3.size(), 1); - QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError); + QNetworkReplyPtr reply; + if (method == QNetworkAccessManager::GetOperation) + reply.reset(manager.get(request)); + else if (method == QNetworkAccessManager::PostOperation) + reply.reset(manager.post(request, data)); + QVERIFY(reply); - manager.setTransferTimeout(0); + QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred); + QCOMPARE(waitForFinish(reply), int(Failure)); + QCOMPARE(spy.size(), 1); + QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); } void tst_QNetworkReply::moreActivitySignals_data() @@ -9699,7 +10104,7 @@ void tst_QNetworkReply::moreActivitySignals() QNetworkRequest request(url); QNetworkReplyPtr reply; if (postWithData) { - request.setRawHeader("Content-Type", "text/plain"); + request.setRawHeader("content-type", "text/plain"); reply.reset(manager.post(request, "Hello, world!")); } else { reply.reset(manager.get(request)); @@ -9720,7 +10125,7 @@ void tst_QNetworkReply::moreActivitySignals() // 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"); + request.setRawHeader("content-type", "text/plain"); secondreply.reset(manager.post(request, "Hello, world!")); } else { secondreply.reset(manager.get(request)); @@ -9807,7 +10212,7 @@ void tst_QNetworkReply::contentEncoding() 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()); + request.setRawHeader("accept-encoding", QLatin1String("%1").arg(encoding).toLatin1()); // This disables the zerocopy optimization request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 0); } @@ -9819,7 +10224,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); @@ -10081,6 +10486,141 @@ void tst_QNetworkReply::notFoundWithCompression() 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() { |