diff options
Diffstat (limited to 'tests/auto/network')
150 files changed, 6142 insertions, 1041 deletions
diff --git a/tests/auto/network/access/CMakeLists.txt b/tests/auto/network/access/CMakeLists.txt index 5f2af4ba12..ed99aa8746 100644 --- a/tests/auto/network/access/CMakeLists.txt +++ b/tests/auto/network/access/CMakeLists.txt @@ -1,7 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -add_subdirectory(qnetworkdiskcache) +add_subdirectory(qhttpheaders) +if(QT_FEATURE_networkdiskcache) + add_subdirectory(qnetworkdiskcache) +endif() add_subdirectory(qnetworkcookiejar) add_subdirectory(qnetworkaccessmanager) add_subdirectory(qnetworkcookie) @@ -9,8 +12,16 @@ add_subdirectory(qnetworkrequest) add_subdirectory(qnetworkreply) add_subdirectory(qnetworkcachemetadata) add_subdirectory(qabstractnetworkcache) +if(QT_FEATURE_http) + add_subdirectory(qnetworkreply_local) + add_subdirectory(qformdatabuilder) + add_subdirectory(qnetworkrequestfactory) + add_subdirectory(qrestaccessmanager) +endif() if(QT_FEATURE_private_tests) + add_subdirectory(qhttp2connection) add_subdirectory(qhttpheaderparser) + add_subdirectory(qhttpheadershelper) add_subdirectory(qhttpnetworkconnection) add_subdirectory(qhttpnetworkreply) add_subdirectory(hpack) diff --git a/tests/auto/network/access/hpack/CMakeLists.txt b/tests/auto/network/access/hpack/CMakeLists.txt index 72194deaaf..32cd4b2f06 100644 --- a/tests/auto/network/access/hpack/CMakeLists.txt +++ b/tests/auto/network/access/hpack/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_hpack Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_hpack LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_hpack SOURCES tst_hpack.cpp diff --git a/tests/auto/network/access/hpack/tst_hpack.cpp b/tests/auto/network/access/hpack/tst_hpack.cpp index 1e2d32a27a..e6b43eaed4 100644 --- a/tests/auto/network/access/hpack/tst_hpack.cpp +++ b/tests/auto/network/access/hpack/tst_hpack.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2014 Governikus GmbH & Co. KG. -// 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 <QTest> #include <QRandomGenerator> diff --git a/tests/auto/network/access/hsts/CMakeLists.txt b/tests/auto/network/access/hsts/CMakeLists.txt index 4daf91aa44..821a034940 100644 --- a/tests/auto/network/access/hsts/CMakeLists.txt +++ b/tests/auto/network/access/hsts/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qhsts Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qhsts LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qhsts SOURCES tst_qhsts.cpp diff --git a/tests/auto/network/access/hsts/tst_qhsts.cpp b/tests/auto/network/access/hsts/tst_qhsts.cpp index 97a2d2889e..4e9a5cc53f 100644 --- a/tests/auto/network/access/hsts/tst_qhsts.cpp +++ b/tests/auto/network/access/hsts/tst_qhsts.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 <QTest> @@ -9,6 +9,7 @@ #include <QtCore/qpair.h> #include <QtCore/qurl.h> +#include <QtNetwork/qhttpheaders.h> #include <QtNetwork/private/qhstsstore_p.h> #include <QtNetwork/private/qhsts_p.h> @@ -189,110 +190,108 @@ void tst_QHsts::testPolicyExpiration() void tst_QHsts::testSTSHeaderParser() { QHstsHeaderParser parser; - using Header = QPair<QByteArray, QByteArray>; - using Headers = QList<Header>; QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - Headers list; - QVERIFY(!parser.parse(list)); + QHttpHeaders headers; + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list << Header("Strict-Transport-security", "200"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-security", "200"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); // This header is missing REQUIRED max-age directive, so we'll ignore it: - list << Header("Strict-Transport-Security", "includeSubDomains"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "includeSubDomains"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list.pop_back(); - list << Header("Strict-Transport-Security", "includeSubDomains;max-age=1000"); - QVERIFY(parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", "includeSubDomains;max-age=1000"); + QVERIFY(parser.parse(headers)); QVERIFY(parser.expirationDate() > QDateTime::currentDateTimeUtc()); QVERIFY(parser.includeSubDomains()); - list.pop_back(); - list << Header("strict-transport-security", "includeSubDomains;max-age=1000"); - QVERIFY(parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("strict-transport-security", "includeSubDomains;max-age=1000"); + QVERIFY(parser.parse(headers)); QVERIFY(parser.expirationDate() > QDateTime::currentDateTimeUtc()); QVERIFY(parser.includeSubDomains()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Invalid (includeSubDomains twice): - list << Header("Strict-Transport-Security", "max-age = 1000 ; includeSubDomains;includeSubDomains"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age = 1000 ; includeSubDomains;includeSubDomains"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Invalid (weird number of seconds): - list << Header("Strict-Transport-Security", "max-age=-1000 ; includeSubDomains"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age=-1000 ; includeSubDomains"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Note, directives are case-insensitive + we should ignore unknown directive. - list << Header("Strict-Transport-Security", ";max-age=1000 ;includesubdomains;;" + headers.append("Strict-Transport-Security", ";max-age=1000 ;includesubdomains;;" "nowsomeunknownheader=\"somevaluewithescapes\\;\""); - QVERIFY(parser.parse(list)); + QVERIFY(parser.parse(headers)); QVERIFY(parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Check that we know how to unescape max-age: - list << Header("Strict-Transport-Security", "max-age=\"1000\""); - QVERIFY(parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age=\"1000\""); + QVERIFY(parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // The only STS header, with invalid syntax though, to be ignored: - list << Header("Strict-Transport-Security", "max-age; max-age=15768000"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age; max-age=15768000"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); // Now we check that our parse chosses the first valid STS header and ignores // others: - list.clear(); - list << Header("Strict-Transport-Security", "includeSubdomains; max-age=\"hehehe\";"); - list << Header("Strict-Transport-Security", "max-age=10101"); - QVERIFY(parser.parse(list)); + headers.clear(); + headers.append("Strict-Transport-Security", "includeSubdomains; max-age=\"hehehe\";"); + headers.append("Strict-Transport-Security", "max-age=10101"); + QVERIFY(parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.clear(); - list << Header("Strict-Transport-Security", "max-age=0"); - QVERIFY(parser.parse(list)); + headers.clear(); + headers.append("Strict-Transport-Security", "max-age=0"); + QVERIFY(parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(parser.expirationDate() <= QDateTime::currentDateTimeUtc()); // Parsing is case-insensitive: - list.pop_back(); - list << Header("Strict-Transport-Security", "Max-aGE=1000; InclUdesUbdomains"); - QVERIFY(parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", "Max-aGE=1000; InclUdesUbdomains"); + QVERIFY(parser.parse(headers)); QVERIFY(parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); // Grammar of STS header is quite permissive, let's check we can parse // some weird but valid header: - list.pop_back(); - list << Header("Strict-Transport-Security", ";;; max-age = 17; ; ; ; ;;; ;;" + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", ";;; max-age = 17; ; ; ; ;;; ;;" ";;; ; includeSubdomains ;;thisIsUnknownDirective;;;;"); - QVERIFY(parser.parse(list)); + QVERIFY(parser.parse(headers)); QVERIFY(parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.pop_back(); - list << Header("Strict-Transport-Security", "max-age=1000; includeSubDomains bogon"); - QVERIFY(!parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", "max-age=1000; includeSubDomains bogon"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); } diff --git a/tests/auto/network/access/http2/BLACKLIST b/tests/auto/network/access/http2/BLACKLIST new file mode 100644 index 0000000000..3de8d6d448 --- /dev/null +++ b/tests/auto/network/access/http2/BLACKLIST @@ -0,0 +1,2 @@ +[duplicateRequestsWithAborts] +qnx ci # QTBUG-119616 diff --git a/tests/auto/network/access/http2/CMakeLists.txt b/tests/auto/network/access/http2/CMakeLists.txt index 3ac73b79f6..7ea559940b 100644 --- a/tests/auto/network/access/http2/CMakeLists.txt +++ b/tests/auto/network/access/http2/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_http2 Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_http2 LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_http2 SOURCES http2srv.cpp http2srv.h @@ -14,4 +20,5 @@ qt_internal_add_test(tst_http2 Qt::Network Qt::NetworkPrivate Qt::TestPrivate + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp index 91d57a5caa..b52ea5527b 100644 --- a/tests/auto/network/access/http2/http2srv.cpp +++ b/tests/auto/network/access/http2/http2srv.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 <QTest> @@ -84,6 +84,12 @@ Http2Server::~Http2Server() { } +void Http2Server::setInformationalStatusCode(int code) +{ + if (code == 100 || (102 <= code && code <= 199)) + informationalStatusCode = code; +} + void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path) { pushPromiseEnabled = pushEnabled; @@ -105,6 +111,12 @@ void Http2Server::setAuthenticationHeader(const QByteArray &authentication) authenticationHeader = authentication; } +void Http2Server::setAuthenticationRequired(bool enable) +{ + Q_ASSERT(!enable || authenticationHeader.isEmpty()); + authenticationRequired = enable; +} + void Http2Server::setRedirect(const QByteArray &url, int count) { redirectUrl = url; @@ -316,7 +328,8 @@ void Http2Server::incomingConnection(qintptr socketDescriptor) connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); QFile file(QT_TESTCASE_SOURCEDIR "/certs/fluke.key"); - file.open(QIODevice::ReadOnly); + if (!file.open(QIODevice::ReadOnly)) + qFatal("Cannot open certificate file %s", qPrintable(file.fileName())); QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); sslSocket->setPrivateKey(key); auto localCert = QSslCertificate::fromPath(QT_TESTCASE_SOURCEDIR "/certs/fluke.cert"); @@ -377,16 +390,12 @@ bool Http2Server::verifyProtocolUpgradeRequest() bool settingsOk = false; QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func(); + const auto headers = firstRequestReader->headers(); // That's how we append them, that's what I expect to find: - for (const auto &header : firstRequestReader->headers()) { - if (header.first == "Connection") - connectionOk = header.second.contains("Upgrade, HTTP2-Settings"); - else if (header.first == "Upgrade") - upgradeOk = header.second.contains("h2c"); - else if (header.first == "HTTP2-Settings") - settingsOk = true; - } + connectionOk = headers.combinedValue(QHttpHeaders::WellKnownHeader::Connection).contains("Upgrade, HTTP2-Settings"); + upgradeOk = headers.combinedValue(QHttpHeaders::WellKnownHeader::Upgrade).contains("h2c"); + settingsOk = headers.contains("HTTP2-Settings"); return connectionOk && upgradeOk && settingsOk; } @@ -835,6 +844,25 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) // Now we'll continue with _normal_ response. } + // Create a header with an informational status code and some random header + // fields. The setter ensures that the value is 100 or is between 102 and 199 + // (inclusive) if set - otherwise it is 0 + + if (informationalStatusCode > 0) { + writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID); + + HttpHeader informationalHeader; + informationalHeader.push_back({":status", QByteArray::number(informationalStatusCode)}); + informationalHeader.push_back(HeaderField("a_random_header_field", "it_will_be_dropped")); + informationalHeader.push_back(HeaderField("another_random_header_field", "drop_this_too")); + + HPack::BitOStream ostream(writer.outboundFrame().buffer); + const bool result = encoder.encodeResponse(ostream, informationalHeader); + Q_ASSERT(result); + + writer.writeHEADERS(*socket, maxFrameSize); + } + writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID); if (emptyBody) writer.addFlag(FrameFlag::END_STREAM); @@ -864,7 +892,8 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) } else if (!authenticationHeader.isEmpty() && !hasAuth) { header.push_back({ ":status", "401" }); header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader)); - authenticationHeader.clear(); + } else if (authenticationRequired) { + header.push_back({ ":status", "401" }); } else { header.push_back({":status", "200"}); } diff --git a/tests/auto/network/access/http2/http2srv.h b/tests/auto/network/access/http2/http2srv.h index cc5353c855..dc94318527 100644 --- a/tests/auto/network/access/http2/http2srv.h +++ b/tests/auto/network/access/http2/http2srv.h @@ -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 #ifndef HTTP2SRV_H #define HTTP2SRV_H @@ -58,6 +58,8 @@ public: ~Http2Server(); + // To send responses with status code 1xx + void setInformationalStatusCode(int code); // To be called before server started: void enablePushPromise(bool enabled, const QByteArray &path = QByteArray()); void setResponseBody(const QByteArray &body); @@ -65,6 +67,8 @@ public: void setContentEncoding(const QByteArray &contentEncoding); // No authentication data is generated for the method, the full header value must be set void setAuthenticationHeader(const QByteArray &authentication); + // Authentication always required, no challenge provided + void setAuthenticationRequired(bool enable); // Set the redirect URL and count. The server will return a redirect response with the url // 'count' amount of times void setRedirect(const QByteArray &redirectUrl, int count); @@ -202,11 +206,13 @@ private: QByteArray contentEncoding; QByteArray authenticationHeader; + bool authenticationRequired = false; QByteArray redirectUrl; int redirectCount = 0; bool sendTrailingHEADERS = false; + int informationalStatusCode = 0; protected slots: void ignoreErrorSlot(); }; diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 1b0c074a6e..396a6f2fda 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.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 <QtNetwork/qtnetworkglobal.h> @@ -25,6 +25,7 @@ #include <QtCore/qobject.h> #include <QtCore/qthread.h> #include <QtCore/qurl.h> +#include <QtCore/qset.h> #include <cstdlib> #include <memory> @@ -72,6 +73,8 @@ private slots: void defaultQnamHttp2Configuration(); void singleRequest_data(); void singleRequest(); + void informationalRequest_data(); + void informationalRequest(); void multipleRequests(); void flowControlClientSide(); void flowControlServerSide(); @@ -93,6 +96,8 @@ private slots: void authenticationRequired_data(); void authenticationRequired(); + void unsupportedAuthenticateChallenge(); + void h2cAllowedAttribute_data(); void h2cAllowedAttribute(); @@ -101,6 +106,8 @@ private slots: void trailingHEADERS(); + void duplicateRequestsWithAborts(); + protected slots: // Slots to listen to our in-process server: void serverStarted(quint16 port); @@ -285,7 +292,7 @@ void tst_Http2::singleRequest() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -298,6 +305,70 @@ void tst_Http2::singleRequest() #endif // QT_CONFIG(ssl) } +void tst_Http2::informationalRequest_data() +{ + QTest::addColumn<int>("statusCode"); + + // 'Clear text' that should always work, either via the protocol upgrade + // or as direct. + QTest::addRow("statusCode-100") << 100; + QTest::addRow("statusCode-125") << 125; + QTest::addRow("statusCode-150") << 150; + QTest::addRow("statusCode-175") << 175; +} + +void tst_Http2::informationalRequest() +{ + clearHTTP2State(); + + serverPort = 0; + nRequests = 1; + + ServerPtr srv(newServer(defaultServerSettings, defaultConnectionType())); + + QFETCH(const int, statusCode); + srv->setInformationalStatusCode(statusCode); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + auto url = requestUrl(defaultConnectionType()); + url.setPath("/index.html"); + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + + auto reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(reply->isFinished()); + + const QVariant code(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)); + + // We are discarding informational headers if the status code is in the range of + // 102-199 or if it is 100. As these header fields were part of the informational + // header used for this test case, we should not see them at this point and the + // status code should be 200. + + QCOMPARE(code.value<int>(), 200); + QVERIFY(!reply->hasRawHeader("a_random_header_field")); + QVERIFY(!reply->hasRawHeader("another_random_header_field")); +} + void tst_Http2::multipleRequests() { clearHTTP2State(); @@ -326,7 +397,7 @@ void tst_Http2::multipleRequests() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -371,7 +442,7 @@ void tst_Http2::flowControlClientSide() runEventLoop(120000); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); QVERIFY(windowUpdates > 0); @@ -412,7 +483,7 @@ void tst_Http2::flowControlServerSide() runEventLoop(120000); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -456,7 +527,7 @@ void tst_Http2::pushPromise() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -582,7 +653,7 @@ void tst_Http2::earlyResponse() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -713,7 +784,7 @@ void tst_Http2::connectToHost() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -784,7 +855,7 @@ void tst_Http2::maxFrameSize() // a response into 3 'DATA' frames (16kb + 16kb + 0|END_STREAM). QCOMPARE(frameCounter.size(), 1); - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -936,7 +1007,7 @@ void tst_Http2::moreActivitySignals() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -1049,7 +1120,7 @@ void tst_Http2::contentEncoding() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -1062,13 +1133,18 @@ void tst_Http2::authenticationRequired_data() { QTest::addColumn<bool>("success"); QTest::addColumn<bool>("responseHEADOnly"); + QTest::addColumn<bool>("withChallenge"); - QTest::addRow("failed-auth") << false << true; - QTest::addRow("successful-auth") << true << true; + QTest::addRow("failed-auth") << false << true << true; + QTest::addRow("successful-auth") << true << true << true; // Include a DATA frame in the response from the remote server. An example would be receiving a // JSON response on a request along with the 401 error. - QTest::addRow("failed-auth-with-response") << false << false; - QTest::addRow("successful-auth-with-response") << true << false; + QTest::addRow("failed-auth-with-response") << false << false << true; + QTest::addRow("successful-auth-with-response") << true << false << true; + + // Don't provide a challenge header. This is valid if you are actually just + // denied access for whatever reason. + QTest::addRow("no-challenge") << false << false << false; } void tst_Http2::authenticationRequired() @@ -1079,10 +1155,15 @@ void tst_Http2::authenticationRequired() POSTResponseHEADOnly = responseHEADOnly; QFETCH(const bool, success); + QFETCH(const bool, withChallenge); ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); - targetServer->setResponseBody("Hello"); - targetServer->setAuthenticationHeader("Basic realm=\"Shadow\""); + QByteArray responseBody = "Hello"_ba; + targetServer->setResponseBody(responseBody); + if (withChallenge) + targetServer->setAuthenticationHeader("Basic realm=\"Shadow\""); + else + targetServer->setAuthenticationRequired(true); QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); runEventLoop(); @@ -1118,24 +1199,30 @@ void tst_Http2::authenticationRequired() receivedBody += body; }); - if (success) + if (success) { connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); - else - connect(reply.get(), &QNetworkReply::errorOccurred, this, &tst_Http2::replyFinishedWithError); + } else { + // Use queued connection so that the finished signal can be emitted and the isFinished + // property can be set. + connect(reply.get(), &QNetworkReply::errorOccurred, this, + &tst_Http2::replyFinishedWithError, Qt::QueuedConnection); + } // Since we're using self-signed certificates, // ignore SSL errors: reply->ignoreSslErrors(); runEventLoop(); STOP_ON_FAILURE + QVERIFY2(reply->isFinished(), + "The reply should error out if authentication fails, or finish if it succeeds"); if (!success) QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); // else: no error (is checked in tst_Http2::replyFinished) - QVERIFY(authenticationRequested); + QVERIFY(authenticationRequested || !withChallenge); - const auto isAuthenticated = [](QByteArray bv) { + const auto isAuthenticated = [](const QByteArray &bv) { return bv == "Basic YWRtaW46YWRtaW4="; // admin:admin }; // Get the "authorization" header out from the server and make sure it's as expected: @@ -1143,12 +1230,105 @@ void tst_Http2::authenticationRequired() QCOMPARE(isAuthenticated(reqAuthHeader), success); if (success) QCOMPARE(receivedBody, expectedBody); + if (responseHEADOnly) { + const QVariant contentLenHeader = reply->header(QNetworkRequest::ContentLengthHeader); + QVERIFY2(!contentLenHeader.isValid(), "We expect no DATA frames to be received"); + QCOMPARE(reply->readAll(), QByteArray()); + } else { + const qint32 contentLen = reply->header(QNetworkRequest::ContentLengthHeader).toInt(); + QCOMPARE(contentLen, responseBody.length()); + QCOMPARE(reply->bytesAvailable(), responseBody.length()); + QCOMPARE(reply->readAll(), QByteArray("Hello")); + } // In the `!success` case we need to wait for the server to emit this or it might cause issues // in the next test running after this. In the `success` case we anyway expect it to have been // received. QTRY_VERIFY(serverGotSettingsACK); } +void tst_Http2::unsupportedAuthenticateChallenge() +{ + clearHTTP2State(); + serverPort = 0; + + if (defaultConnectionType() == H2Type::h2c) + QSKIP("This test requires TLS with ALPN to work"); + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + QByteArray responseBody = "Hello"_ba; + targetServer->setResponseBody(responseBody); + targetServer->setAuthenticationHeader("Bearer realm=\"qt.io accounts\""); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + QUrl url = requestUrl(defaultConnectionType()); + url.setPath("/index.html"); + QNetworkRequest request(url); + + QByteArray expectedBody = "Hello, World!"; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->post(request, expectedBody)); + + bool authenticationRequested = false; + connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(), + [&](QNetworkReply *, QAuthenticator *) { + authenticationRequested = true; + }); + + bool finishedReceived = false; + connect(reply.get(), &QNetworkReply::finished, reply.get(), + [&]() { finishedReceived = true; }); + bool errorReceived = false; + connect(reply.get(), &QNetworkReply::errorOccurred, reply.get(), + [&]() { errorReceived = true; }); + + QSet<quint32> receivedDataOnStreams; + connect(targetServer.get(), &Http2Server::receivedDATAFrame, reply.get(), + [&receivedDataOnStreams](quint32 streamID, const QByteArray &body) { + Q_UNUSED(body); + receivedDataOnStreams.insert(streamID); + }); + + // Use queued connection so that the finished signal can be emitted and the + // isFinished property can be set. + connect(reply.get(), &QNetworkReply::errorOccurred, this, + &tst_Http2::replyFinishedWithError, Qt::QueuedConnection); + + // Since we're using self-signed certificates, ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + QVERIFY2(reply->isFinished(), + "The reply should error out if authentication fails, or finish if it succeeds"); + + QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); + QVERIFY(reply->isFinished()); + QVERIFY(errorReceived); + QVERIFY(finishedReceived); + QCOMPARE(receivedDataOnStreams.size(), 1); + QVERIFY(receivedDataOnStreams.contains(1)); // the original, failed, request + + QVERIFY(!authenticationRequested); + + // We should not have sent any authentication headers to the server, since + // we don't support the challenge. + const QByteArray reqAuthHeader = targetServer->requestAuthorizationHeader(); + QVERIFY(reqAuthHeader.isEmpty()); + + // In the `!success` case we need to wait for the server to emit this or it might cause issues + // in the next test running after this. In the `success` case we anyway expect it to have been + // received. + QTRY_VERIFY(serverGotSettingsACK); + +} + void tst_Http2::h2cAllowedAttribute_data() { QTest::addColumn<bool>("h2cAllowed"); @@ -1244,7 +1424,7 @@ void tst_Http2::redirect() QVERIFY(serverPort != 0); - nRequests = 1 + maxRedirects; + nRequests = 1; auto originalUrl = requestUrl(defaultConnectionType()); auto url = originalUrl; @@ -1272,6 +1452,7 @@ void tst_Http2::redirect() runEventLoop(); STOP_ON_FAILURE + QCOMPARE(nRequests, 0); if (success) { QCOMPARE(reply->error(), QNetworkReply::NoError); QCOMPARE(reply->url().toString(), @@ -1317,6 +1498,53 @@ void tst_Http2::trailingHEADERS() QTRY_VERIFY(serverGotSettingsACK); } +void tst_Http2::duplicateRequestsWithAborts() +{ + clearHTTP2State(); + serverPort = 0; + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + constexpr int ExpectedSuccessfulRequests = 1; + nRequests = ExpectedSuccessfulRequests; + + const auto url = requestUrl(defaultConnectionType()); + QNetworkRequest request(url); + // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + + qint32 finishedCount = 0; + auto connectToSlots = [this, &finishedCount](QNetworkReply *reply){ + const auto onFinished = [&finishedCount, reply, this]() { + ++finishedCount; + if (reply->error() == QNetworkReply::NoError) + replyFinished(); + }; + connect(reply, &QNetworkReply::finished, reply, onFinished); + }; + + std::vector<QNetworkReply *> replies; + for (qint32 i = 0; i < 3; ++i) { + auto &reply = replies.emplace_back(manager->get(request)); + connectToSlots(reply); + if (i < 2) // Delete and abort all-but-one: + reply->deleteLater(); + // Since we're using self-signed certificates, ignore SSL errors: + reply->ignoreSslErrors(); + } + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + QCOMPARE(finishedCount, ExpectedSuccessfulRequests); +} + void tst_Http2::serverStarted(quint16 port) { serverPort = port; diff --git a/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt b/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt index f349e01c6f..37c3dbda8a 100644 --- a/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt +++ b/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qabstractnetworkcache Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qabstractnetworkcache LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp b/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp index 28bbf5459a..95f067a66e 100644 --- a/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp +++ b/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.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 <QTemporaryDir> #include <QTest> @@ -50,6 +50,7 @@ private: }; +#if QT_CONFIG(networkdiskcache) class NetworkDiskCache : public QNetworkDiskCache { Q_OBJECT @@ -72,6 +73,7 @@ public: QTemporaryDir tempDir; bool gotData; }; +#endif tst_QAbstractNetworkCache::tst_QAbstractNetworkCache() @@ -254,10 +256,12 @@ void tst_QAbstractNetworkCache::runTest() QFETCH(bool, fetchFromCache); QNetworkAccessManager manager; +#if QT_CONFIG(networkdiskcache) NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString())); manager.setCache(diskCache); QCOMPARE(diskCache->gotData, false); +#endif QUrl realUrl = url.contains("://") ? url : TESTFILE + url; QNetworkRequest request(realUrl); @@ -266,7 +270,9 @@ void tst_QAbstractNetworkCache::runTest() QNetworkReply *reply = manager.get(request); QSignalSpy downloaded1(reply, SIGNAL(finished())); QTRY_COMPARE(downloaded1.size(), 1); +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, false); +#endif QByteArray goodData = reply->readAll(); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl); @@ -293,7 +299,9 @@ void tst_QAbstractNetworkCache::runTest() std::sort(rawHeaderList.begin(), rawHeaderList.end()); std::sort(rawHeaderList2.begin(), rawHeaderList2.end()); } +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, fetchFromCache); +#endif } void tst_QAbstractNetworkCache::checkSynchronous() @@ -305,10 +313,12 @@ void tst_QAbstractNetworkCache::checkSynchronous() QFETCH(bool, fetchFromCache); QNetworkAccessManager manager; +#if QT_CONFIG(networkdiskcache) NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString())); manager.setCache(diskCache); QCOMPARE(diskCache->gotData, false); +#endif QUrl realUrl = url.contains("://") ? url : TESTFILE + url; QNetworkRequest request(realUrl); @@ -320,7 +330,9 @@ void tst_QAbstractNetworkCache::checkSynchronous() // prime the cache QNetworkReply *reply = manager.get(request); QVERIFY(reply->isFinished()); // synchronous +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, false); +#endif QByteArray goodData = reply->readAll(); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl); @@ -348,15 +360,19 @@ void tst_QAbstractNetworkCache::checkSynchronous() std::sort(rawHeaderList.begin(), rawHeaderList.end()); std::sort(rawHeaderList2.begin(), rawHeaderList2.end()); } +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, fetchFromCache); +#endif } void tst_QAbstractNetworkCache::deleteCache() { QNetworkAccessManager manager; +#if QT_CONFIG(networkdiskcache) NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString())); manager.setCache(diskCache); +#endif QString url = "httpcachetest_cachecontrol.cgi?max-age=1000"; QNetworkRequest request(QUrl(TESTFILE + url)); diff --git a/tests/auto/network/access/qdecompresshelper/CMakeLists.txt b/tests/auto/network/access/qdecompresshelper/CMakeLists.txt index c62b2be3c5..09317ca3eb 100644 --- a/tests/auto/network/access/qdecompresshelper/CMakeLists.txt +++ b/tests/auto/network/access/qdecompresshelper/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qdecompresshelper Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdecompresshelper LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdecompresshelper SOURCES gzip.rcc.cpp diff --git a/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp b/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp index 41f435a076..cd5a52c209 100644 --- a/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp +++ b/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 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 <QTest> diff --git a/tests/auto/network/access/qformdatabuilder/CMakeLists.txt b/tests/auto/network/access/qformdatabuilder/CMakeLists.txt new file mode 100644 index 0000000000..dde2dc10e0 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2024 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_qformdatabuilder LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qformdatabuilder + SOURCES + tst_qformdatabuilder.cpp + LIBRARIES + Qt::Core + Qt::Network + TESTDATA + rfc3252.txt + image1.jpg + document.docx + sheet.xlsx +) + diff --git a/tests/auto/network/access/qformdatabuilder/document.docx b/tests/auto/network/access/qformdatabuilder/document.docx Binary files differnew file mode 100644 index 0000000000..49005f14b6 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/document.docx diff --git a/tests/auto/network/access/qformdatabuilder/image1.jpg b/tests/auto/network/access/qformdatabuilder/image1.jpg Binary files differnew file mode 100644 index 0000000000..d1d27bf7cf --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/image1.jpg diff --git a/tests/auto/network/access/qformdatabuilder/rfc3252.txt b/tests/auto/network/access/qformdatabuilder/rfc3252.txt new file mode 100644 index 0000000000..5436ce5b26 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/rfc3252.txt @@ -0,0 +1 @@ +some text for reference diff --git a/tests/auto/network/access/qformdatabuilder/sheet.xlsx b/tests/auto/network/access/qformdatabuilder/sheet.xlsx Binary files differnew file mode 100644 index 0000000000..2cb1ec7361 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/sheet.xlsx diff --git a/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp new file mode 100644 index 0000000000..ad40c79bf9 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp @@ -0,0 +1,203 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qformdatabuilder.h> + +#include <QtCore/qbuffer.h> +#include <QtCore/qfile.h> + +#include <QtTest/qtest.h> + +using namespace Qt::StringLiterals; + +class tst_QFormDataBuilder : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void generateQHttpPartWithDevice_data(); + void generateQHttpPartWithDevice(); + + void escapesBackslashAndQuotesInFilenameAndName_data(); + void escapesBackslashAndQuotesInFilenameAndName(); + + void picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice_data(); + void picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice(); +}; + +void tst_QFormDataBuilder::generateQHttpPartWithDevice_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QString>("real_file_name"); + QTest::addColumn<QString>("body_name_data"); + QTest::addColumn<QByteArray>("expected_content_type_data"); + QTest::addColumn<QByteArray>("expected_content_disposition_data"); + + QTest::newRow("txt-ascii") << "text"_L1 << "rfc3252.txt" << "rfc3252.txt" << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + QTest::newRow("txt-latin") << "text"_L1 << "rfc3252.txt" << "szöveg.txt" << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + QTest::newRow("txt-unicode") << "text"_L1 << "rfc3252.txt" << "テキスト.txt" << "text/plain"_ba + << "form-data; name=\"text\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.txt"_ba; + + QTest::newRow("jpg-ascii") << "image"_L1 << "image1.jpg" << "image1.jpg" << "image/jpeg"_ba + << "form-data; name=\"image\"; filename=image1.jpg"_ba; + QTest::newRow("jpg-latin") << "image"_L1 << "image1.jpg" << "kép.jpg" << "image/jpeg"_ba + << "form-data; name=\"image\"; filename*=ISO-8859-1''k%E9p.jpg"_ba; + QTest::newRow("jpg-unicode") << "image"_L1 << "image1.jpg" << "絵.jpg" << "image/jpeg"_ba + << "form-data; name=\"image\"; filename*=UTF-8''%E7%B5%B5"_ba; + + QTest::newRow("doc-ascii") << "text"_L1 << "document.docx" << "word.docx" + << "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_ba + << "form-data; name=\"text\"; filename=word.docx"_ba; + QTest::newRow("doc-latin") << "text"_L1 << "document.docx" << "szöveg.docx" + << "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.docx"_ba; + QTest::newRow("doc-unicode") << "text"_L1 << "document.docx" << "テキスト.docx" + << "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_ba + << "form-data; name=\"text\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.docx"_ba; + + QTest::newRow("xls-ascii") << "spreadsheet"_L1 << "sheet.xlsx" << "sheet.xlsx" + << "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_ba + << "form-data; name=\"spreadsheet\"; filename=sheet.xlsx"_ba; + QTest::newRow("xls-latin") << "spreadsheet"_L1 << "sheet.xlsx" << "szöveg.xlsx" + << "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_ba + << "form-data; name=\"spreadsheet\"; filename*=ISO-8859-1''sz%F6veg.xlsx"_ba; + QTest::newRow("xls-unicode") << "spreadsheet"_L1 << "sheet.xlsx" << "テキスト.xlsx" + << "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_ba + << "form-data; name=\"spreadsheet\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.xlsx"_ba; + +} + +void tst_QFormDataBuilder::generateQHttpPartWithDevice() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QString, real_file_name); + QFETCH(const QString, body_name_data); + QFETCH(const QByteArray, expected_content_type_data); + QFETCH(const QByteArray, expected_content_disposition_data); + + QString testData = QFileInfo(QFINDTESTDATA(real_file_name)).absoluteFilePath(); + QFile data_file(testData); + + QHttpPart httpPart = QFormDataPartBuilder(name_data, QFormDataPartBuilder::PrivateConstructor()) + .setBodyDevice(&data_file, body_name_data) + .build(); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + +void tst_QFormDataBuilder::escapesBackslashAndQuotesInFilenameAndName_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QString>("body_name_data"); + QTest::addColumn<QByteArray>("expected_content_type_data"); + QTest::addColumn<QByteArray>("expected_content_disposition_data"); + + QTest::newRow("quote") << "t\"ext"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"ext"; filename=rfc3252.txt)"_ba; + + QTest::newRow("slash") << "t\\ext"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\\ext"; filename=rfc3252.txt)"_ba; + + QTest::newRow("quotes") << "t\"e\"xt"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"e\"xt"; filename=rfc3252.txt)"_ba; + + QTest::newRow("slashes") << "t\\\\ext"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\\\\ext"; filename=rfc3252.txt)"_ba; + + QTest::newRow("quote-slash") << "t\"ex\\t"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"ex\\t"; filename=rfc3252.txt)"_ba; + + QTest::newRow("quotes-slashes") << "t\"e\"x\\t\\"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"e\"x\\t\\"; filename=rfc3252.txt)"_ba; +} + +void tst_QFormDataBuilder::escapesBackslashAndQuotesInFilenameAndName() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QString, body_name_data); + QFETCH(const QByteArray, expected_content_type_data); + QFETCH(const QByteArray, expected_content_disposition_data); + + QFile dummy_file(body_name_data); + + QHttpPart httpPart = QFormDataPartBuilder(name_data, QFormDataPartBuilder::PrivateConstructor()) + .setBodyDevice(&dummy_file, body_name_data) + .build(); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + +void tst_QFormDataBuilder::picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QAnyStringView>("body_name_data"); + QTest::addColumn<QByteArray>("expected_content_type_data"); + QTest::addColumn<QByteArray>("expected_content_disposition_data"); + + QTest::newRow("latin1-ascii") << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + QTest::newRow("u8-ascii") << "text"_L1 << QAnyStringView(u8"rfc3252.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + QTest::newRow("u-ascii") << "text"_L1 << QAnyStringView(u"rfc3252.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + + + QTest::newRow("latin1-latin") << "text"_L1 << QAnyStringView("sz\366veg.txt"_L1) << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + QTest::newRow("u8-latin") << "text"_L1 << QAnyStringView(u8"szöveg.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + QTest::newRow("u-latin") << "text"_L1 << QAnyStringView(u"szöveg.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + + QTest::newRow("u8-u8") << "text"_L1 << QAnyStringView(u8"テキスト.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.txt"_ba; +} + +void tst_QFormDataBuilder::picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QAnyStringView, body_name_data); + QFETCH(const QByteArray, expected_content_type_data); + QFETCH(const QByteArray, expected_content_disposition_data); + + QBuffer buff; + + QHttpPart httpPart = QFormDataPartBuilder(name_data, QFormDataPartBuilder::PrivateConstructor()) + .setBodyDevice(&buff, body_name_data) + .build(); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + + +QTEST_MAIN(tst_QFormDataBuilder) +#include "tst_qformdatabuilder.moc" diff --git a/tests/auto/network/access/qhttp2connection/CMakeLists.txt b/tests/auto/network/access/qhttp2connection/CMakeLists.txt new file mode 100644 index 0000000000..9a6e7a064e --- /dev/null +++ b/tests/auto/network/access/qhttp2connection/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2023 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_qhttp2connection LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qhttp2connection + SOURCES + tst_qhttp2connection.cpp + LIBRARIES + Qt::NetworkPrivate + Qt::Test +) diff --git a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp new file mode 100644 index 0000000000..b9d5219ae9 --- /dev/null +++ b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp @@ -0,0 +1,397 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QTest> +#include <QtTest/QSignalSpy> + +#include <QtNetwork/private/qhttp2connection_p.h> +#include <QtNetwork/private/hpack_p.h> +#include <QtNetwork/private/bitstreams_p.h> + +#include <limits> + +using namespace Qt::StringLiterals; + +class tst_QHttp2Connection : public QObject +{ + Q_OBJECT + +private slots: + void construct(); + void constructStream(); + void testSETTINGSFrame(); + void testPING(); + void connectToServer(); + void WINDOW_UPDATE(); + +private: + enum PeerType { Client, Server }; + [[nodiscard]] auto makeFakeConnectedSockets(); + [[nodiscard]] auto getRequiredHeaders(); + [[nodiscard]] QHttp2Connection *makeHttp2Connection(QIODevice *socket, + QHttp2Configuration config, PeerType type); + [[nodiscard]] bool waitForSettingsExchange(QHttp2Connection *client, QHttp2Connection *server); +}; + +class IOBuffer : public QIODevice +{ + Q_OBJECT +public: + IOBuffer(QObject *parent, std::shared_ptr<QBuffer> _in, std::shared_ptr<QBuffer> _out) + : QIODevice(parent), in(std::move(_in)), out(std::move(_out)) + { + connect(in.get(), &QIODevice::readyRead, this, &IOBuffer::readyRead); + connect(out.get(), &QIODevice::bytesWritten, this, &IOBuffer::bytesWritten); + connect(out.get(), &QIODevice::aboutToClose, this, &IOBuffer::readChannelFinished); + connect(out.get(), &QIODevice::aboutToClose, this, &IOBuffer::aboutToClose); + } + + bool open(OpenMode mode) override + { + QIODevice::open(mode); + Q_ASSERT(in->isOpen()); + Q_ASSERT(out->isOpen()); + return false; + } + + bool isSequential() const override { return true; } + + qint64 bytesAvailable() const override { return in->pos() - readHead; } + qint64 bytesToWrite() const override { return 0; } + + qint64 readData(char *data, qint64 maxlen) override + { + qint64 temp = in->pos(); + in->seek(readHead); + qint64 res = in->read(data, std::min(maxlen, temp - readHead)); + readHead += res; + if (readHead == temp) { + // Reached end of buffer, reset + in->seek(0); + in->buffer().resize(0); + readHead = 0; + } else { + in->seek(temp); + } + return res; + } + + qint64 writeData(const char *data, qint64 len) override + { + return out->write(data, len); + } + + std::shared_ptr<QBuffer> in; + std::shared_ptr<QBuffer> out; + + qint64 readHead = 0; +}; + +auto tst_QHttp2Connection::makeFakeConnectedSockets() +{ + auto clientIn = std::make_shared<QBuffer>(); + auto serverIn = std::make_shared<QBuffer>(); + clientIn->open(QIODevice::ReadWrite); + serverIn->open(QIODevice::ReadWrite); + + auto client = std::make_unique<IOBuffer>(this, clientIn, serverIn); + auto server = std::make_unique<IOBuffer>(this, std::move(serverIn), std::move(clientIn)); + + client->open(QIODevice::ReadWrite); + server->open(QIODevice::ReadWrite); + + return std::pair{ std::move(client), std::move(server) }; +} + +auto tst_QHttp2Connection::getRequiredHeaders() +{ + return HPack::HttpHeader{ + { ":authority", "example.com" }, + { ":method", "GET" }, + { ":path", "/" }, + { ":scheme", "https" }, + }; +} + +QHttp2Connection *tst_QHttp2Connection::makeHttp2Connection(QIODevice *socket, + QHttp2Configuration config, + PeerType type) +{ + QHttp2Connection *connection = nullptr; + if (type == PeerType::Server) + connection = QHttp2Connection::createDirectServerConnection(socket, config); + else + connection = QHttp2Connection::createDirectConnection(socket, config); + connect(socket, &QIODevice::readyRead, connection, &QHttp2Connection::handleReadyRead); + return connection; +} + +bool tst_QHttp2Connection::waitForSettingsExchange(QHttp2Connection *client, + QHttp2Connection *server) +{ + bool settingsFrameReceived = false; + bool serverSettingsFrameReceived = false; + + QMetaObject::Connection c = connect(client, &QHttp2Connection::settingsFrameReceived, client, + [&settingsFrameReceived]() { + settingsFrameReceived = true; + }); + QMetaObject::Connection s = connect(server, &QHttp2Connection::settingsFrameReceived, server, + [&serverSettingsFrameReceived]() { + serverSettingsFrameReceived = true; + }); + + client->handleReadyRead(); // handle incoming frames, send response + + bool success = QTest::qWaitFor([&]() { + return settingsFrameReceived && serverSettingsFrameReceived; + }); + + disconnect(c); + disconnect(s); + + return success; +} + +void tst_QHttp2Connection::construct() +{ + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + auto *connection = QHttp2Connection::createDirectConnection(&buffer, {}); + QVERIFY(!connection->isGoingAway()); + QCOMPARE(connection->maxConcurrentStreams(), 100u); + QCOMPARE(connection->maxHeaderListSize(), std::numeric_limits<quint32>::max()); + QVERIFY(!connection->isUpgradedConnection()); + QVERIFY(!connection->getStream(1)); // No stream has been created yet + + auto *upgradedConnection = QHttp2Connection::createUpgradedConnection(&buffer, {}); + QVERIFY(upgradedConnection->isUpgradedConnection()); + // Stream 1 is created by default for an upgraded connection + QVERIFY(upgradedConnection->getStream(1)); +} + +void tst_QHttp2Connection::constructStream() +{ + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + auto connection = QHttp2Connection::createDirectConnection(&buffer, {}); + QHttp2Stream *stream = connection->createStream().unwrap(); + QVERIFY(stream); + QCOMPARE(stream->isPromisedStream(), false); + QCOMPARE(stream->isActive(), false); + QCOMPARE(stream->RST_STREAM_code(), 0u); + QCOMPARE(stream->streamID(), 1u); + QCOMPARE(stream->receivedHeaders(), {}); + QCOMPARE(stream->state(), QHttp2Stream::State::Idle); + QCOMPARE(stream->isUploadBlocked(), false); + QCOMPARE(stream->isUploadingDATA(), false); +} + +void tst_QHttp2Connection::testSETTINGSFrame() +{ + constexpr qint32 PrefaceLength = 24; + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + QHttp2Configuration config; + constexpr quint32 MaxFrameSize = 16394; + constexpr bool ServerPushEnabled = false; + constexpr quint32 StreamReceiveWindowSize = 50000; + constexpr quint32 SessionReceiveWindowSize = 50001; + config.setMaxFrameSize(MaxFrameSize); + config.setServerPushEnabled(ServerPushEnabled); + config.setStreamReceiveWindowSize(StreamReceiveWindowSize); + config.setSessionReceiveWindowSize(SessionReceiveWindowSize); + auto connection = QHttp2Connection::createDirectConnection(&buffer, config); + Q_UNUSED(connection); + QCOMPARE_GE(buffer.size(), PrefaceLength); + + // Preface + QByteArray preface = buffer.data().first(PrefaceLength); + QCOMPARE(preface, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + + // SETTINGS + buffer.seek(PrefaceLength); + const quint32 maxSize = buffer.size() - PrefaceLength; + Http2::FrameReader reader; + Http2::FrameStatus status = reader.read(buffer); + QCOMPARE(status, Http2::FrameStatus::goodFrame); + Http2::Frame f = reader.inboundFrame(); + QCOMPARE(f.type(), Http2::FrameType::SETTINGS); + QCOMPARE_LT(f.payloadSize(), maxSize); + + const qint32 settingsReceived = f.dataSize() / 6; + QCOMPARE_GT(settingsReceived, 0); + QCOMPARE_LE(settingsReceived, 6); + + struct ExpectedSetting + { + Http2::Settings identifier; + quint32 value; + }; + // Commented-out settings are not sent since they are defaults + ExpectedSetting expectedSettings[]{ + // { Http2::Settings::HEADER_TABLE_SIZE_ID, HPack::FieldLookupTable::DefaultSize }, + { Http2::Settings::ENABLE_PUSH_ID, ServerPushEnabled ? 1 : 0 }, + // { Http2::Settings::MAX_CONCURRENT_STREAMS_ID, Http2::maxConcurrentStreams }, + { Http2::Settings::INITIAL_WINDOW_SIZE_ID, StreamReceiveWindowSize }, + { Http2::Settings::MAX_FRAME_SIZE_ID, MaxFrameSize }, + // { Http2::Settings::MAX_HEADER_LIST_SIZE_ID, ??? }, + }; + + QCOMPARE(quint32(settingsReceived), std::size(expectedSettings)); + for (qint32 i = 0; i < settingsReceived; ++i) { + const uchar *it = f.dataBegin() + i * 6; + const quint16 ident = qFromBigEndian<quint16>(it); + const quint32 intVal = qFromBigEndian<quint32>(it + 2); + + ExpectedSetting expectedSetting = expectedSettings[i]; + QVERIFY2(ident == quint16(expectedSetting.identifier), + qPrintable("ident: %1, expected: %2, index: %3"_L1 + .arg(QString::number(ident), + QString::number(quint16(expectedSetting.identifier)), + QString::number(i)))); + QVERIFY2(intVal == expectedSetting.value, + qPrintable("intVal: %1, expected: %2, index: %3"_L1 + .arg(QString::number(intVal), + QString::number(expectedSetting.value), + QString::number(i)))); + } +} + +void tst_QHttp2Connection::testPING() +{ + auto [client, server] = makeFakeConnectedSockets(); + auto connection = makeHttp2Connection(client.get(), {}, Client); + auto serverConnection = makeHttp2Connection(server.get(), {}, Server); + + QVERIFY(waitForSettingsExchange(connection, serverConnection)); + + QSignalSpy serverPingSpy{ serverConnection, &QHttp2Connection::pingFrameRecived }; + QSignalSpy clientPingSpy{ connection, &QHttp2Connection::pingFrameRecived }; + + QByteArray data{"pingpong"}; + connection->sendPing(data); + + QVERIFY(serverPingSpy.wait()); + QVERIFY(clientPingSpy.wait()); + + QCOMPARE(serverPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::Ping)); + QCOMPARE(clientPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::PongSignatureIdentical)); + + serverConnection->sendPing(); + + QVERIFY(clientPingSpy.wait()); + QVERIFY(serverPingSpy.wait()); + + QCOMPARE(clientPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::Ping)); + QCOMPARE(serverPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::PongSignatureIdentical)); +} + +void tst_QHttp2Connection::connectToServer() +{ + auto [client, server] = makeFakeConnectedSockets(); + auto connection = makeHttp2Connection(client.get(), {}, Client); + auto serverConnection = makeHttp2Connection(server.get(), {}, Server); + + QVERIFY(waitForSettingsExchange(connection, serverConnection)); + + QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream }; + QSignalSpy clientIncomingStreamSpy{ connection, &QHttp2Connection::newIncomingStream }; + + QHttp2Stream *clientStream = connection->createStream().unwrap(); + QSignalSpy clientHeaderReceivedSpy{ clientStream, &QHttp2Stream::headersReceived }; + QVERIFY(clientStream); + HPack::HttpHeader headers = getRequiredHeaders(); + clientStream->sendHEADERS(headers, false); + + QVERIFY(newIncomingStreamSpy.wait()); + auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>(); + QVERIFY(serverStream); + const HPack::HttpHeader ExpectedResponseHeaders{ { ":status", "200" } }; + serverStream->sendHEADERS(ExpectedResponseHeaders, true); + + QVERIFY(clientHeaderReceivedSpy.wait()); + const HPack::HttpHeader + headersReceived = clientHeaderReceivedSpy.front().front().value<HPack::HttpHeader>(); + QCOMPARE(headersReceived, ExpectedResponseHeaders); + + QCOMPARE(clientIncomingStreamSpy.count(), 0); +} + +void tst_QHttp2Connection::WINDOW_UPDATE() +{ + auto [client, server] = makeFakeConnectedSockets(); + auto connection = makeHttp2Connection(client.get(), {}, Client); + + QHttp2Configuration config; + config.setStreamReceiveWindowSize(1024); // Small window on server to provoke WINDOW_UPDATE + auto serverConnection = makeHttp2Connection(server.get(), config, Server); + + QVERIFY(waitForSettingsExchange(connection, serverConnection)); + + QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream }; + + QHttp2Stream *clientStream = connection->createStream().unwrap(); + QSignalSpy clientHeaderReceivedSpy{ clientStream, &QHttp2Stream::headersReceived }; + QSignalSpy clientDataReceivedSpy{ clientStream, &QHttp2Stream::dataReceived }; + QVERIFY(clientStream); + HPack::HttpHeader expectedRequestHeaders = HPack::HttpHeader{ + { ":authority", "example.com" }, + { ":method", "POST" }, + { ":path", "/" }, + { ":scheme", "https" }, + }; + clientStream->sendHEADERS(expectedRequestHeaders, false); + + QVERIFY(newIncomingStreamSpy.wait()); + auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>(); + QVERIFY(serverStream); + QSignalSpy serverDataReceivedSpy{ serverStream, &QHttp2Stream::dataReceived }; + + // Since a stream is only opened on the remote side when the header is received, + // we can check the headers now immediately + QCOMPARE(serverStream->receivedHeaders(), expectedRequestHeaders); + + QBuffer *buffer = new QBuffer(clientStream); + QByteArray uploadedData = "Hello World"_ba.repeated(1000); + buffer->setData(uploadedData); + buffer->open(QIODevice::ReadWrite); + clientStream->sendDATA(buffer, true); + + bool streamEnd = false; + QByteArray serverReceivedData; + while (!streamEnd) { // The window is too small to receive all data at once, so loop + QVERIFY(serverDataReceivedSpy.wait()); + auto latestEmission = serverDataReceivedSpy.back(); + serverReceivedData += latestEmission.front().value<QByteArray>(); + streamEnd = latestEmission.back().value<bool>(); + } + QCOMPARE(serverReceivedData.size(), uploadedData.size()); + QCOMPARE(serverReceivedData, uploadedData); + + QCOMPARE(clientStream->state(), QHttp2Stream::State::HalfClosedLocal); + QCOMPARE(serverStream->state(), QHttp2Stream::State::HalfClosedRemote); + + const HPack::HttpHeader ExpectedResponseHeaders{ { ":status", "200" } }; + serverStream->sendHEADERS(ExpectedResponseHeaders, false); + QBuffer *serverBuffer = new QBuffer(serverStream); + serverBuffer->setData(uploadedData); + serverBuffer->open(QIODevice::ReadWrite); + serverStream->sendDATA(serverBuffer, true); + + QVERIFY(clientHeaderReceivedSpy.wait()); + const HPack::HttpHeader + headersReceived = clientHeaderReceivedSpy.front().front().value<HPack::HttpHeader>(); + QCOMPARE(headersReceived, ExpectedResponseHeaders); + + QTRY_COMPARE_GT(clientDataReceivedSpy.count(), 0); + QCOMPARE(clientDataReceivedSpy.count(), 1); // Only one DATA frame since our window is larger + QCOMPARE(clientDataReceivedSpy.front().front().value<QByteArray>(), uploadedData); + + QCOMPARE(clientStream->state(), QHttp2Stream::State::Closed); + QCOMPARE(serverStream->state(), QHttp2Stream::State::Closed); +} + +QTEST_MAIN(tst_QHttp2Connection) + +#include "tst_qhttp2connection.moc" diff --git a/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt b/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt index 3ba22f7995..50deeb3e56 100644 --- a/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt +++ b/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qhttpheaderparser LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp b/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp index a1ea1c8ce7..9ba889fdb3 100644 --- a/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp +++ b/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 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 <QtTest/qtest.h> #include <QObject> diff --git a/tests/auto/network/access/qhttpheaders/CMakeLists.txt b/tests/auto/network/access/qhttpheaders/CMakeLists.txt new file mode 100644 index 0000000000..0de1f96c67 --- /dev/null +++ b/tests/auto/network/access/qhttpheaders/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2023 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_qhttpheaders LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qhttpheaders + SOURCES + tst_qhttpheaders.cpp + LIBRARIES + Qt::Core + Qt::Network +) diff --git a/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp new file mode 100644 index 0000000000..457d30feeb --- /dev/null +++ b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp @@ -0,0 +1,552 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qhttpheaders.h> + +#include <QtTest/qtest.h> + +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +using namespace Qt::StringLiterals; + +class tst_QHttpHeaders : public QObject +{ + Q_OBJECT + +private slots: + void constructors(); + void accessors(); + void wellKnownHeader(); + void headerNameField(); + void headerValueField(); + void valueEncoding(); + void replaceOrAppend(); + +private: + static constexpr QAnyStringView n1{"name1"}; + static constexpr QAnyStringView n2{"name2"}; + static constexpr QAnyStringView n3{"name3"}; + static constexpr QAnyStringView v1{"value1"}; + static constexpr QAnyStringView v2{"value2"}; + static constexpr QAnyStringView v3{"value3"}; + static constexpr QAnyStringView N1{"NAME1"}; + static constexpr QAnyStringView N2{"NAME2"}; + static constexpr QAnyStringView N3{"NAME3"}; + static constexpr QAnyStringView V1{"VALUE1"}; + static constexpr QAnyStringView V2{"VALUE2"}; + static constexpr QAnyStringView V3{"VALUE3"}; +}; + +void tst_QHttpHeaders::constructors() +{ + // Default ctor + QHttpHeaders h1; + QVERIFY(h1.isEmpty()); + + // Copy ctor + QHttpHeaders h2(h1); + QCOMPARE(h2.toListOfPairs(), h1.toListOfPairs()); + + // Copy assignment + QHttpHeaders h3; + h3 = h1; + QCOMPARE(h3.toListOfPairs(), h1.toListOfPairs()); + + // Move assignment + QHttpHeaders h4; + h4 = std::move(h2); + QCOMPARE(h4.toListOfPairs(), h1.toListOfPairs()); + + // Move ctor + QHttpHeaders h5(std::move(h4)); + QCOMPARE(h5.toListOfPairs(), h1.toListOfPairs()); + + // Constructors that are counterparts to 'toXXX()' conversion getters + const QByteArray nb1{"name1"}; + const QByteArray nb2{"name2"}; + const QByteArray nv1{"value1"}; + const QByteArray nv2{"value2"}; + // Initialize three QHttpHeaders with similar content, and verify that they have + // similar header entries +#define CONTAINS_HEADER(NAME, VALUE) \ + QVERIFY(hlist.contains(NAME) && hmap.contains(NAME) && hhash.contains(NAME)); \ + QCOMPARE(hlist.combinedValue(NAME), VALUE); \ + QCOMPARE(hmap.combinedValue(NAME), VALUE); \ + QCOMPARE(hhash.combinedValue(NAME), VALUE); \ + + QList<std::pair<QByteArray, QByteArray>> list{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}}; + QMultiMap<QByteArray, QByteArray> map{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}}; + QMultiHash<QByteArray, QByteArray> hash{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}}; + QHttpHeaders hlist = QHttpHeaders::fromListOfPairs(list); + QHttpHeaders hmap = QHttpHeaders::fromMultiMap(map); + QHttpHeaders hhash = QHttpHeaders::fromMultiHash(hash); + CONTAINS_HEADER(nb1, v1); + CONTAINS_HEADER(nb2, nv2 + ", " + nv2) +#undef CONTAINS_HEADER +} + +void tst_QHttpHeaders::accessors() +{ + QHttpHeaders h1; + + // isEmpty(), clear(), size() + h1.append(n1,v1); + QVERIFY(!h1.isEmpty()); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.append(n1, v1)); + QCOMPARE(h1.size(), 2); + h1.insert(0, n1, v1); + QCOMPARE(h1.size(), 3); + h1.clear(); + QVERIFY(h1.isEmpty()); + + // contains() + h1.append(n1, v1); + QVERIFY(h1.contains(n1)); + QVERIFY(h1.contains(N1)); + QVERIFY(!h1.contains(n2)); + QVERIFY(!h1.contains(QHttpHeaders::WellKnownHeader::Allow)); + h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing"); + QVERIFY(h1.contains(QHttpHeaders::WellKnownHeader::Accept)); + QVERIFY(h1.contains("accept")); + + // values()/value() +#define EXISTS_NOT(H, N) do { \ + QVERIFY(!H.contains(N)); \ + QCOMPARE(H.value(N, "ENOENT"), "ENOENT"); \ + const auto values = H.values(N); \ + QVERIFY(values.isEmpty()); \ + QVERIFY(H.combinedValue(N).isNull()); \ + } while (false) + +#define EXISTS_N_TIMES(X, H, N, ...) do { \ + const std::array expected = { __VA_ARGS__ }; \ + static_assert(std::tuple_size_v<decltype(expected)> == X); \ + QVERIFY(H.contains(N)); \ + QCOMPARE(H.value(N, "ENOENT"), expected.front()); \ + const auto values = H.values(N); \ + QCOMPARE(values.size(), X); \ + QCOMPARE(values.front(), expected.front()); \ + /* ignore in-between */ \ + QCOMPARE(values.back(), expected.back()); \ + QCOMPARE(H.combinedValue(N), values.join(", ")); \ + } while (false) + +#define EXISTS_ONCE(H, N, V) EXISTS_N_TIMES(1, H, N, V) + + EXISTS_ONCE(h1, n1, v1); + EXISTS_ONCE(h1, N1, v1); + EXISTS_ONCE(h1, QHttpHeaders::WellKnownHeader::Accept, "nothing"); + EXISTS_ONCE(h1, "Accept", "nothing"); + + EXISTS_NOT(h1, N2); + EXISTS_NOT(h1, QHttpHeaders::WellKnownHeader::Allow); + + h1.clear(); + + EXISTS_NOT(h1, n1); + + h1.append(n1, v1); + h1.append(n1, v2); + h1.append(n1, v3); + h1.append(n2, v2); + h1.append(n3, ""); // empty value + + EXISTS_N_TIMES(3, h1, n1, v1, v2, v3); + EXISTS_N_TIMES(3, h1, N1, v1, v2, v3); + EXISTS_ONCE(h1, n3, ""); // empty value + + h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing"); + h1.append(QHttpHeaders::WellKnownHeader::Accept, "ever"); + + EXISTS_N_TIMES(2, h1, QHttpHeaders::WellKnownHeader::Accept, "nothing", "ever"); + EXISTS_NOT(h1, "nonexistent"); + +#undef EXISTS_ONCE +#undef EXISTS_N_TIMES +#undef EXISTS_NOT + + // valueAt() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(n3, v3); + QCOMPARE(h1.valueAt(0), v1); + QCOMPARE(h1.valueAt(1), v2); + QCOMPARE(h1.valueAt(2), v3); + + // nameAt() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(n3, v3); + QCOMPARE(h1.nameAt(0), n1); + QCOMPARE(h1.nameAt(1), n2); + QCOMPARE(h1.nameAt(2), n3); + + // removeAll() + h1.clear(); + QVERIFY(h1.append(n1, v1)); + QVERIFY(h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing")); + QVERIFY(h1.append(n1, v1)); + QCOMPARE(h1.size(), 3); + h1.removeAll(n1); + QVERIFY(!h1.contains(n1)); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.contains("accept")); + h1.removeAll(QHttpHeaders::WellKnownHeader::Accept); + QVERIFY(!h1.contains(QHttpHeaders::WellKnownHeader::Accept)); + + // removeAt() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(n3, v3); + + // Valid removals + QVERIFY(h1.contains(n3)); + h1.removeAt(2); + QVERIFY(!h1.contains(n3)); + QVERIFY(h1.contains(n1)); + h1.removeAt(0); + QVERIFY(!h1.contains(n1)); + QVERIFY(h1.contains(n2)); + h1.removeAt(0); + QVERIFY(!h1.contains(n2)); + QVERIFY(h1.isEmpty()); + + // toListOfPairs() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(N3, V3); // uppercase of n3 + auto list = h1.toListOfPairs(); + QCOMPARE(list.size(), h1.size()); + QCOMPARE(list.at(0).first, n1); + QCOMPARE(list.at(0).second, v1); + QCOMPARE(list.at(1).first, n2); + QCOMPARE(list.at(1).second, v2); + QCOMPARE(list.at(2).first, n3); // N3 has been lower-cased + QCOMPARE(list.at(2).second, V3); + + // toMultiMap() + auto map = h1.toMultiMap(); + QCOMPARE(map.size(), h1.size()); + QCOMPARE(map.value(n1.toString().toLatin1()), v1); + QCOMPARE(map.value(n2.toString().toLatin1()), v2); + QCOMPARE(map.value(n3.toString().toLatin1()), V3); + + // toMultiHash() + auto hash = h1.toMultiHash(); + QCOMPARE(hash.size(), h1.size()); + QCOMPARE(hash.value(n1.toString().toLatin1()), v1); + QCOMPARE(hash.value(n2.toString().toLatin1()), v2); + QCOMPARE(hash.value(n3.toString().toLatin1()), V3); + + // insert() + h1.clear(); + h1.append(n3, v3); + QVERIFY(h1.insert(0, n1, v1)); + list = h1.toListOfPairs(); + QCOMPARE(list.size(), 2); + QCOMPARE(list.at(0).first, n1); + QCOMPARE(list.at(0).second, v1); + QCOMPARE(list.at(1).first, n3); + QCOMPARE(list.at(1).second, v3); + QVERIFY(h1.insert(1, n2, v2)); + list = h1.toListOfPairs(); + QCOMPARE(list.size(), 3); + QCOMPARE(list.at(0).first, n1); + QCOMPARE(list.at(0).second, v1); + QCOMPARE(list.at(1).first, n2); + QCOMPARE(list.at(1).second, v2); + QCOMPARE(list.at(2).first, n3); + QCOMPARE(list.at(2).second, v3); + QVERIFY(h1.insert(1, QHttpHeaders::WellKnownHeader::Accept, "nothing")); + QCOMPARE(h1.size(), 4); + list = h1.toListOfPairs(); + QCOMPARE(list.at(1).first, "accept"); + QCOMPARE(list.at(1).second, "nothing"); + QVERIFY(h1.insert(list.size(), "LastName", "lastValue")); + QCOMPARE(h1.size(), 5); + list = h1.toListOfPairs(); + QCOMPARE(list.last().first, "lastname"); + QCOMPARE(list.last().second, "lastValue"); + // Failed insert + QRegularExpression re("HTTP header name contained*"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.insert(0, "a€", "b")); + + // replace + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + QCOMPARE(h1.size(), 2); + QVERIFY(h1.replace(0, n3, v3)); + QVERIFY(h1.replace(1, QHttpHeaders::WellKnownHeader::Accept, "nothing")); + QCOMPARE(h1.size(), 2); + list = h1.toListOfPairs(); + QCOMPARE(list.at(0).first, n3); + QCOMPARE(list.at(0).second, v3); + QCOMPARE(list.at(1).first, "accept"); + QCOMPARE(list.at(1).second, "nothing"); + QVERIFY(h1.replace(1, "ACCEPT", "NOTHING")); + QCOMPARE(h1.size(), 2); + list = h1.toListOfPairs(); + QCOMPARE(list.at(0).first, n3); + QCOMPARE(list.at(0).second, v3); + QCOMPARE(list.at(1).first, "accept"); + QCOMPARE(list.at(1).second, "NOTHING"); + // Failed replace + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.replace(0, "a€", "b")); + +} + +void tst_QHttpHeaders::wellKnownHeader() +{ + QByteArrayView view = QHttpHeaders::wellKnownHeaderName(QHttpHeaders::WellKnownHeader::AIM); + QCOMPARE(view, "a-im"); +} + +#define TEST_ILLEGAL_HEADER_NAME_CHARACTER(NAME) \ + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); \ + QVERIFY(!h1.append(NAME, v1)); \ + QVERIFY(h1.isEmpty()); \ + +void tst_QHttpHeaders::headerNameField() +{ + QHttpHeaders h1; + + // All allowed characters in different encodings and types + // const char[] + h1.append("abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", v1); + QCOMPARE(h1.size(), 1); + // UTF-8 + h1.append(u8"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", + v1); + QCOMPARE(h1.size(), 2); + // UTF-16 + h1.append(u"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", v1); + QCOMPARE(h1.size(), 3); + // QString (UTF-16) + h1.append(u"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~"_s, + v1); + QCOMPARE(h1.size(), 4); + QCOMPARE(h1.nameAt(0), h1.nameAt(1)); + QCOMPARE(h1.nameAt(1), h1.nameAt(2)); + QCOMPARE(h1.nameAt(2), h1.nameAt(3)); + h1.clear(); + + // Error cases + // Header name must contain at least 1 character + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "HTTP header name cannot be empty"); + h1.append("", v1); + QVERIFY(h1.isEmpty()); + // Disallowed ASCII/extended ASCII characters (not exhaustive list) + QRegularExpression re("HTTP header name contained illegal character*"); + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo\x08" "bar"); // BS + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo\x7F" "bar"); // DEL + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo()" "bar"); // parantheses + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foobar" "¿"); // extended ASCII + TEST_ILLEGAL_HEADER_NAME_CHARACTER("©" "foobar"); // extended ASCII + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo,bar"); // comma + // Disallowed UTF-8 characters + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u8"€"); + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u8"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + // Disallowed UTF-16 characters + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u"€"); + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + + // Non-null-terminated name. The 'x' below is to make sure the strings don't + // null-terminate by happenstance + h1.clear(); + constexpr char L1Array[] = {'a','b','c','x'}; + const QLatin1StringView nonNullLatin1{L1Array, sizeof(L1Array) - 1}; // abc + + constexpr char UTF8Array[] = {0x64, 0x65, 0x66, 0x78}; + const QUtf8StringView nonNullUTF8(UTF8Array, sizeof(UTF8Array) - 1); // def + + constexpr QChar UTF16Array[] = {'g', 'h', 'i', 'x'}; + QStringView nonNullUTF16(UTF16Array, sizeof(UTF16Array) / sizeof(QChar) - 1); // ghi + + h1.append(nonNullLatin1, v1); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.contains(nonNullLatin1)); + QCOMPARE(h1.combinedValue(nonNullLatin1), v1); + + h1.append(nonNullUTF8, v2); + QCOMPARE(h1.size(), 2); + QVERIFY(h1.contains(nonNullUTF8)); + QCOMPARE(h1.combinedValue(nonNullUTF8), v2); + + h1.append(nonNullUTF16, v3); + QCOMPARE(h1.size(), 3); + QVERIFY(h1.contains(nonNullUTF16)); + QCOMPARE(h1.combinedValue(nonNullUTF16), v3); +} + +#define TEST_ILLEGAL_HEADER_VALUE_CHARACTER(VALUE) \ +QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); \ + QVERIFY(!h1.append(n1, VALUE)); \ + QVERIFY(h1.isEmpty()); \ + +void tst_QHttpHeaders::headerValueField() +{ + QHttpHeaders h1; + + // Visible ASCII characters and space and horizontal tab + // const char[] + h1.append(n1, "!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"); + QCOMPARE(h1.size(), 1); + // UTF-8 + h1.append(n1, u8"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"); + QCOMPARE(h1.size(), 2); + // UTF-16 + h1.append(n1, u"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"); + QCOMPARE(h1.size(), 3); + // QString / UTF-16 + h1.append(n1, u"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"_s); + QCOMPARE(h1.size(), 4); + const auto values = h1.values(n1); + QVERIFY(!values.isEmpty() && values.size() == 4); + QVERIFY(values[0] == values[1] + && values[1] == values[2] + && values[2] == values[3]); + // Extended ASCII (explicit on Latin-1 to avoid UTF-8 interpretation) + h1.append(n1, "\x80\x09\xB2\xFF"_L1); + QCOMPARE(h1.size(), 5); + // Empty value + h1.append(n1, ""); + QCOMPARE(h1.size(), 6); + // Leading and trailing space + h1.clear(); + h1.append(n1, " foo "); + QCOMPARE(h1.combinedValue(n1), "foo"); + h1.append(n1, "\tbar\t"); + QCOMPARE(h1.combinedValue(n1), "foo, bar"); + QCOMPARE(h1.size(), 2); + + h1.clear(); + QRegularExpression re("HTTP header value contained illegal character*"); + TEST_ILLEGAL_HEADER_VALUE_CHARACTER("foo\x08" "bar"); // BS + TEST_ILLEGAL_HEADER_VALUE_CHARACTER("foo\x1B" "bar"); // ESC + // Disallowed UTF-8 characters + TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u8"€"); + TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u8"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + // Disallowed UTF-16 characters + TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u"€"); + TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + + // Non-null-terminated value. The 'x' below is to make sure the strings don't + // null-terminate by happenstance + h1.clear(); + constexpr char L1Array[] = {'a','b','c','x'}; + const QLatin1StringView nonNullLatin1{L1Array, sizeof(L1Array) - 1}; // abc + + constexpr char UTF8Array[] = {0x64, 0x65, 0x66, 0x78}; + const QUtf8StringView nonNullUTF8(UTF8Array, sizeof(UTF8Array) - 1); // def + + constexpr QChar UTF16Array[] = {'g', 'h', 'i', 'x'}; + QStringView nonNullUTF16(UTF16Array, sizeof(UTF16Array) / sizeof(QChar) - 1); // ghi + + h1.append(n1, nonNullLatin1); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.contains(n1)); + QCOMPARE(h1.combinedValue(n1), "abc"); + + h1.append(n2, nonNullUTF8); + QCOMPARE(h1.size(), 2); + QVERIFY(h1.contains(n2)); + QCOMPARE(h1.combinedValue(n2), "def"); + + h1.append(n3, nonNullUTF16); + QCOMPARE(h1.size(), 3); + QVERIFY(h1.contains(n3)); + QCOMPARE(h1.combinedValue(n3), "ghi"); +} + +void tst_QHttpHeaders::valueEncoding() +{ + // Test that common encodings are possible to set and not blocked by + // header value character filter (ie. don't contain disallowed characters as per RFC 9110) + QHttpHeaders h1; + // Within visible ASCII range + QVERIFY(h1.append(n1, "foo"_ba.toBase64())); + QCOMPARE(h1.values(n1).at(0), "Zm9v"); + h1.replace(0, n1, "foo"_ba.toPercentEncoding()); + QCOMPARE(h1.values(n1).at(0), "foo"); + + // Outside of ASCII/Latin-1 range (€) + h1.replace(0, n1, "foo€"_ba.toBase64()); + QCOMPARE(h1.values(n1).at(0), "Zm9v4oKs"); + h1.replace(0, n1, "foo€"_ba.toPercentEncoding()); + QCOMPARE(h1.values(n1).at(0), "foo%E2%82%AC"); +} + +void tst_QHttpHeaders::replaceOrAppend() +{ + QHttpHeaders h1; + +#define REPLACE_OR_APPEND(NAME, VALUE, INDEX, TOTALSIZE) \ + do { \ + QVERIFY(h1.replaceOrAppend(NAME, VALUE)); \ + QCOMPARE(h1.size(), TOTALSIZE); \ + QCOMPARE(h1.nameAt(INDEX), NAME); \ + QCOMPARE(h1.valueAt(INDEX), VALUE); \ + } while (false) + + // Append to empty container and replace it + REPLACE_OR_APPEND(n1, v1, 0, 1); // Appends + REPLACE_OR_APPEND(n1, v2, 0, 1); // Replaces + + // Replace at beginning, middle, and end + h1.clear(); + REPLACE_OR_APPEND(n1, v1, 0, 1); // Appends + REPLACE_OR_APPEND(n2, v2, 1, 2); // Appends + REPLACE_OR_APPEND(n3, v3, 2, 3); // Appends + REPLACE_OR_APPEND(n1, V1, 0, 3); // Replaces at beginning + REPLACE_OR_APPEND(n2, V2, 1, 3); // Replaces at middle + REPLACE_OR_APPEND(n3, V3, 2, 3); // Replaces at end + + // Pre-existing multiple values (n2) are removed + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); // First n2 is at index 1 + h1.append(n2, v2); + h1.append(n3, v3); + h1.append(n2, v2); + QCOMPARE(h1.size(), 5); + QCOMPARE(h1.combinedValue(n2), "value2, value2, value2"); + REPLACE_OR_APPEND(n2, V2, 1, 3); // Replaces value at index 1, and removes the rest + QCOMPARE(h1.combinedValue(n2), "VALUE2"); +#undef REPLACE_OR_APPEND + + // Implicit sharing / detaching + h1.clear(); + h1.append(n1, v1); + QHttpHeaders h2 = h1; + QCOMPARE(h1.size(), h2.size()); + QCOMPARE(h1.valueAt(0), h2.valueAt(0)); // Iniially values are equal + h1.replaceOrAppend(n1, v2); // Change value in h1 => detaches h1 + QCOMPARE_NE(h1.valueAt(0), h2.valueAt(0)); // Values are no more equal + QCOMPARE(h1.valueAt(0), v2); // Value in h1 changed + QCOMPARE(h2.valueAt(0), v1); // Value in h2 remained + + // Failed attempts + h1.clear(); + h1.append(n1, v1); + QRegularExpression re("HTTP header*"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.replaceOrAppend("", V1)); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.replaceOrAppend(v1, "foo\x08")); +} + +QTEST_MAIN(tst_QHttpHeaders) +#include "tst_qhttpheaders.moc" diff --git a/tests/auto/network/access/qhttpheadershelper/CMakeLists.txt b/tests/auto/network/access/qhttpheadershelper/CMakeLists.txt new file mode 100644 index 0000000000..75935d2844 --- /dev/null +++ b/tests/auto/network/access/qhttpheadershelper/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2024 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_qhttpheadershelper LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qhttpheadershelper + SOURCES + tst_qhttpheadershelper.cpp + LIBRARIES + Qt::NetworkPrivate +) diff --git a/tests/auto/network/access/qhttpheadershelper/tst_qhttpheadershelper.cpp b/tests/auto/network/access/qhttpheadershelper/tst_qhttpheadershelper.cpp new file mode 100644 index 0000000000..b204d0cbe3 --- /dev/null +++ b/tests/auto/network/access/qhttpheadershelper/tst_qhttpheadershelper.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qhttpheaders.h> +#include <QtNetwork/private/qhttpheadershelper_p.h> + +#include <QtTest/qtest.h> + +using namespace Qt::StringLiterals; + +class tst_QHttpHeadersHelper : public QObject +{ + Q_OBJECT + +private slots: + void testCompareStrict(); + +private: + static constexpr QAnyStringView n1{"name1"}; + static constexpr QAnyStringView n2{"name2"}; + static constexpr QAnyStringView v1{"value1"}; + static constexpr QAnyStringView v2{"value2"}; + static constexpr QAnyStringView N1{"NAME1"}; + static constexpr QAnyStringView N2{"NAME2"}; + static constexpr QAnyStringView V1{"VALUE1"}; + static constexpr QAnyStringView V2{"VALUE2"}; +}; + +void tst_QHttpHeadersHelper::testCompareStrict() +{ + using namespace QHttpHeadersHelper; + + // Basic comparisons + QHttpHeaders h1; + QHttpHeaders h2; + QVERIFY(compareStrict(h1, h2)); // empties + h1.append(n1, v1); + QVERIFY(compareStrict(h1, h1)); // self + h2.append(n1, v1); + QVERIFY(compareStrict(h1, h2)); + h1.append(n2, v2); + QVERIFY(!compareStrict(h1, h2)); + h1.removeAll(n2); + QVERIFY(compareStrict(h1, h2)); + + // Order-sensitivity + h1.clear(); + h2.clear(); + // Same headers but in different order + h1.append(n1, v1); + h1.append(n2, v2); + h2.append(n2, v2); + h2.append(n1, v1); + QVERIFY(!compareStrict(h1, h2)); + + // Different number of headers + h1.clear(); + h2.clear(); + h1.append(n1, v1); + h2.append(n1, v1); + h2.append(n2, v2); + QVERIFY(!compareStrict(h1, h2)); + + // Same header name, multiple values + h1.clear(); + h2.clear(); + h1.append(n1, v1); + h1.append(n1, v2); + h2.append(n1, v1); + QVERIFY(!compareStrict(h1, h2)); + h2.append(n1, v2); + QVERIFY(compareStrict(h1, h2)); +} + +QTEST_MAIN(tst_QHttpHeadersHelper) +#include "tst_qhttpheadershelper.moc" diff --git a/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt b/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt index 9bda03a48d..679990062f 100644 --- a/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt +++ b/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qhttpnetworkconnection LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp index 6833eb5bc9..decd442164 100644 --- a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp +++ b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.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 <QTest> @@ -125,7 +125,7 @@ void tst_QHttpNetworkConnection::head() QFETCH(QString, statusString); QFETCH(int, contentLength); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -175,7 +175,7 @@ void tst_QHttpNetworkConnection::get() QFETCH(int, contentLength); QFETCH(int, downloadSize); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -241,7 +241,7 @@ void tst_QHttpNetworkConnection::put() QFETCH(QString, data); QFETCH(bool, succeed); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -323,7 +323,7 @@ void tst_QHttpNetworkConnection::post() QFETCH(int, contentLength); QFETCH(int, downloadSize); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -450,7 +450,7 @@ void tst_QHttpNetworkConnection::get401() QFETCH(QString, password); QFETCH(int, statusCode); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -510,7 +510,7 @@ void tst_QHttpNetworkConnection::compression() QFETCH(bool, autoCompress); QFETCH(QString, contentCoding); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -584,7 +584,7 @@ void tst_QHttpNetworkConnection::ignoresslerror() QFETCH(bool, ignoreFromSignal); QFETCH(int, statusCode); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); if (ignoreInit) @@ -629,7 +629,7 @@ void tst_QHttpNetworkConnection::nossl() QFETCH(bool, encrypt); QFETCH(QNetworkReply::NetworkError, networkError); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); @@ -919,7 +919,7 @@ void tst_QHttpNetworkConnection::getAndThenDeleteObject_data() void tst_QHttpNetworkConnection::getAndThenDeleteObject() { // yes, this will leak if the testcase fails. I don't care. It must not fail then :P - QHttpNetworkConnection *connection = new QHttpNetworkConnection(httpServerName()); + QHttpNetworkConnection *connection = new QHttpNetworkConnection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, httpServerName()); QHttpNetworkRequest request("http://" + httpServerName() + "/qtest/bigfile"); QHttpNetworkReply *reply = connection->sendRequest(request); reply->setDownstreamLimited(true); diff --git a/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt b/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt index 2b0d2f9b5b..b4e4a822ee 100644 --- a/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt +++ b/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qhttpnetworkreply LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp b/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp index 0780ae1f0e..e83d15fdc3 100644 --- a/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp +++ b/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 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 <QTest> diff --git a/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt b/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt index 2088300035..b0fe6eda46 100644 --- a/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkaccessmanager Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkaccessmanager LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkaccessmanager SOURCES tst_qnetworkaccessmanager.cpp diff --git a/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp b/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp index 568aceadd3..43db6d5841 100644 --- a/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp +++ b/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.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 <QTest> diff --git a/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt b/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt index 8d385599c2..2aa918c49c 100644 --- a/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkcachemetadata Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkcachemetadata LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkcachemetadata SOURCES tst_qnetworkcachemetadata.cpp diff --git a/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp b/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp index c3c5c92e55..d49195efc6 100644 --- a/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp +++ b/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.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 <QTest> @@ -29,6 +29,8 @@ private slots: void operatorEqualEqual(); void rawHeaders_data(); void rawHeaders(); + void headers_data(); + void headers(); void saveToDisk_data(); void saveToDisk(); void url_data(); @@ -114,6 +116,12 @@ void tst_QNetworkCacheMetaData::isValid_data() QNetworkCacheMetaData data5; data5.setSaveToDisk(false); QTest::newRow("valid-5") << data5 << true; + + QNetworkCacheMetaData data6; + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data6.setHeaders(httpHeaders); + QTest::newRow("valid-6") << data6 << true; } // public bool isValid() const @@ -153,6 +161,9 @@ void tst_QNetworkCacheMetaData::operatorEqual_data() QNetworkCacheMetaData::RawHeaderList headers; headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar")); data.setRawHeaders(headers); + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data.setHeaders(httpHeaders); data.setLastModified(QDateTime::currentDateTime()); data.setExpirationDate(QDateTime::currentDateTime()); data.setSaveToDisk(false); @@ -212,6 +223,18 @@ void tst_QNetworkCacheMetaData::operatorEqualEqual_data() QTest::newRow("valid-5-4") << data5 << data2 << false; QTest::newRow("valid-5-5") << data5 << data3 << false; QTest::newRow("valid-5-6") << data5 << data4 << false; + + QNetworkCacheMetaData data6; + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data6.setHeaders(httpHeaders); + QTest::newRow("valid-6-1") << data6 << QNetworkCacheMetaData() << false; + QTest::newRow("valid-6-2") << data6 << data6 << true; + QTest::newRow("valid-6-3") << data6 << data1 << false; + QTest::newRow("valid-6-4") << data6 << data2 << false; + QTest::newRow("valid-6-5") << data6 << data3 << false; + QTest::newRow("valid-6-6") << data6 << data4 << false; + QTest::newRow("valid-6-7") << data6 << data5 << false; } // public bool operator==(QNetworkCacheMetaData const& other) const @@ -231,7 +254,11 @@ void tst_QNetworkCacheMetaData::rawHeaders_data() QTest::newRow("null") << QNetworkCacheMetaData::RawHeaderList(); QNetworkCacheMetaData::RawHeaderList headers; headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar")); - QTest::newRow("valie") << headers; + QTest::newRow("valid") << headers; + headers.append(QNetworkCacheMetaData::RawHeader("n1", "V1, v2, v3")); + headers.append(QNetworkCacheMetaData::RawHeader("n2", "V2")); + headers.append(QNetworkCacheMetaData::RawHeader("set-cookie", "v1\nV2\nV3")); + QTest::newRow("valid-2") << headers; } // public QNetworkCacheMetaData::RawHeaderList rawHeaders() const @@ -245,6 +272,25 @@ void tst_QNetworkCacheMetaData::rawHeaders() QCOMPARE(data.rawHeaders(), rawHeaders); } +void tst_QNetworkCacheMetaData::headers_data() +{ + QTest::addColumn<QHttpHeaders>("httpHeaders"); + QTest::newRow("null") << QHttpHeaders(); + QHttpHeaders headers; + headers.append("foo", "Bar"); + QTest::newRow("valid") << headers; +} + +void tst_QNetworkCacheMetaData::headers() +{ + QFETCH(QHttpHeaders, httpHeaders); + + SubQNetworkCacheMetaData data; + + data.setHeaders(httpHeaders); + QCOMPARE(data.headers().toListOfPairs(), httpHeaders.toListOfPairs()); +} + void tst_QNetworkCacheMetaData::saveToDisk_data() { QTest::addColumn<bool>("saveToDisk"); @@ -289,6 +335,9 @@ void tst_QNetworkCacheMetaData::stream() QNetworkCacheMetaData::RawHeaderList headers; headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar")); data.setRawHeaders(headers); + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data.setHeaders(httpHeaders); data.setLastModified(QDateTime::currentDateTime()); data.setExpirationDate(QDateTime::currentDateTime()); data.setSaveToDisk(false); diff --git a/tests/auto/network/access/qnetworkcookie/CMakeLists.txt b/tests/auto/network/access/qnetworkcookie/CMakeLists.txt index 04cbd0255d..91773a83fd 100644 --- a/tests/auto/network/access/qnetworkcookie/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkcookie/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkcookie Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkcookie LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkcookie SOURCES tst_qnetworkcookie.cpp diff --git a/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp b/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp index eadb602498..438c5e6983 100644 --- a/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp +++ b/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.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 <QTest> diff --git a/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt b/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt index 0ac713ac12..0d74a1d84d 100644 --- a/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkcookiejar Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkcookiejar LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data list(APPEND test_data "parser.json" "testdata/publicsuffix/public_suffix_list.dafsa") diff --git a/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa b/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa Binary files differindex 4fcf07eade..f891b16963 100644 --- a/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa +++ b/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa diff --git a/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp b/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp index 72096ceee5..9460060dbf 100644 --- a/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp +++ b/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp @@ -1,6 +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 <QTest> #include <QtCore/QJsonArray> @@ -14,6 +13,8 @@ #include "private/qtldurl_p.h" #endif +#include <memory> + class tst_QNetworkCookieJar: public QObject { Q_OBJECT @@ -235,7 +236,7 @@ void tst_QNetworkCookieJar::setCookiesFromUrl() QFETCH(QList<QNetworkCookie>, preset); QFETCH(QNetworkCookie, newCookie); QFETCH(QString, referenceUrl); - QFETCH(QList<QNetworkCookie>, expectedResult); + QFETCH(const QList<QNetworkCookie>, expectedResult); QFETCH(bool, setCookies); QList<QNetworkCookie> cookieList; @@ -245,11 +246,11 @@ void tst_QNetworkCookieJar::setCookiesFromUrl() QCOMPARE(jar.setCookiesFromUrl(cookieList, referenceUrl), setCookies); QList<QNetworkCookie> result = jar.allCookies(); - foreach (QNetworkCookie cookie, expectedResult) { + for (const QNetworkCookie &cookie : expectedResult) { QVERIFY2(result.contains(cookie), cookie.toRawForm()); result.removeAll(cookie); } - QVERIFY2(result.isEmpty(), QTest::toString(result)); + QVERIFY2(result.isEmpty(), std::unique_ptr<char[]>(QTest::toString(result)).get()); } void tst_QNetworkCookieJar::cookiesForUrl_data() @@ -516,7 +517,7 @@ void tst_QNetworkCookieJar::rfc6265_data() void tst_QNetworkCookieJar::rfc6265() { - QFETCH(QStringList, received); + QFETCH(const QStringList, received); QFETCH(QList<QNetworkCookie>, sent); QFETCH(QString, sentTo); @@ -527,7 +528,7 @@ void tst_QNetworkCookieJar::rfc6265() QNetworkCookieJar jar; QList<QNetworkCookie> receivedCookies; - foreach (const QString &cookieLine, received) + for (const QString &cookieLine : received) receivedCookies.append(QNetworkCookie::parseCookies(cookieLine.toUtf8())); jar.setCookiesFromUrl(receivedCookies, receivedUrl); diff --git a/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt b/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt index c69480efb3..023868f57e 100644 --- a/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkdiskcache Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkdiskcache LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkdiskcache SOURCES tst_qnetworkdiskcache.cpp diff --git a/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp b/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp index d8c6633c2a..ec32c780cd 100644 --- a/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp +++ b/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.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 <QtNetwork/QtNetwork> #include <QTest> @@ -476,9 +476,9 @@ void tst_QNetworkDiskCache::fileMetaData() url.setFragment(QString()); QString cacheDirectory = cache.cacheDirectory(); - QStringList list = countFiles(cacheDirectory); + const QStringList list = countFiles(cacheDirectory); QCOMPARE(list.size(), NUM_SUBDIRECTORIES + 3); - foreach(QString fileName, list) { + for (const QString &fileName : list) { QFileInfo info(fileName); if (info.isFile()) { QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName); @@ -521,9 +521,9 @@ void tst_QNetworkDiskCache::expire() } QString cacheDirectory = cache.cacheDirectory(); - QStringList list = countFiles(cacheDirectory); + const QStringList list = countFiles(cacheDirectory); QStringList cacheList; - foreach(QString fileName, list) { + for (const QString &fileName : list) { QFileInfo info(fileName); if (info.isFile()) { QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName); @@ -569,11 +569,11 @@ void tst_QNetworkDiskCache::oldCacheVersionFile() QVERIFY(!metaData.isValid()); QVERIFY(!QFile::exists(name)); } else { - QStringList files = countFiles(cache.cacheDirectory()); + const QStringList files = countFiles(cache.cacheDirectory()); QCOMPARE(files.size(), NUM_SUBDIRECTORIES + 3); // find the file QString cacheFile; - foreach (QString file, files) { + for (const QString &file : files) { QFileInfo info(file); if (info.isFile()) cacheFile = file; @@ -610,8 +610,8 @@ void tst_QNetworkDiskCache::streamVersion() QString cacheFile; // find the file - QStringList files = countFiles(cache.cacheDirectory()); - foreach (const QString &file, files) { + const QStringList files = countFiles(cache.cacheDirectory()); + for (const QString &file : files) { QFileInfo info(file); if (info.isFile()) { cacheFile = file; @@ -657,6 +657,7 @@ void tst_QNetworkDiskCache::streamVersion() QIODevice *dataDevice = cache.data(url); QVERIFY(dataDevice != 0); QByteArray cachedData = dataDevice->readAll(); + delete dataDevice; QCOMPARE(cachedData, data); } } 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..64e7716e0c 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> @@ -35,8 +35,10 @@ #include <QtNetwork/QTcpServer> #include <QtNetwork/QTcpSocket> +#if QT_CONFIG(localserver) #include <QtNetwork/QLocalSocket> #include <QtNetwork/QLocalServer> +#endif #include <QtNetwork/QHostInfo> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkRequest> @@ -44,14 +46,18 @@ #include <QtNetwork/QAbstractNetworkCache> #include <QtNetwork/qauthenticator.h> #include <QtNetwork/qnetworkaccessmanager.h> +#if QT_CONFIG(networkdiskcache) #include <QtNetwork/qnetworkdiskcache.h> +#endif #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> -#include <QtNetwork/QHttp1Configuration> #include <QtNetwork/qnetworkcookie.h> #include <QtNetwork/QNetworkCookieJar> +#if QT_CONFIG(http) #include <QtNetwork/QHttpPart> #include <QtNetwork/QHttpMultiPart> +#include <QtNetwork/QHttp1Configuration> +#endif #include <QtNetwork/QNetworkProxyQuery> #if QT_CONFIG(ssl) #include <QtNetwork/qsslerror.h> @@ -91,6 +97,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 +135,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; @@ -170,8 +200,10 @@ public: ~tst_QNetworkReply(); QString runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QNetworkReplyPtr &reply, const QByteArray &data = QByteArray()); +#if QT_CONFIG(http) QString runMultipartRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, QHttpMultiPart *multiPart, const QByteArray &verb); +#endif QString runCustomRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, const QByteArray &verb, QIODevice *data); @@ -216,6 +248,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) @@ -231,15 +269,23 @@ private Q_SLOTS: void putToHttp(); void putToHttpSynchronous_data(); void putToHttpSynchronous(); +#if QT_CONFIG(http) void putToHttpMultipart_data(); void putToHttpMultipart(); +#endif + void putWithoutBody(); + void putWithoutBody_data(); void postToHttp_data(); void postToHttp(); void postToHttpSynchronous_data(); void postToHttpSynchronous(); +#if QT_CONFIG(http) void postToHttpMultipart_data(); void postToHttpMultipart(); void multipartSkipIndices(); // QTBUG-32534 +#endif + void postWithoutBody_data(); + void postWithoutBody(); #if QT_CONFIG(ssl) void putToHttps_data(); void putToHttps(); @@ -292,8 +338,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(); @@ -308,8 +354,10 @@ private Q_SLOTS: void ioPutToFileFromFile(); void ioPutToFileFromSocket_data(); void ioPutToFileFromSocket(); +#if QT_CONFIG(localserver) void ioPutToFileFromLocalSocket_data(); void ioPutToFileFromLocalSocket(); +#endif void ioPutToFileFromProcess_data(); void ioPutToFileFromProcess(); void ioPutToFtpFromFile_data(); @@ -415,7 +463,9 @@ private Q_SLOTS: void ioGetFromHttpWithoutContentLength(); void ioGetFromHttpBrokenChunkedEncoding(); +#if QT_CONFIG(http) void qtbug12908compressedHttpReply(); +#endif void compressedHttpReplyBrokenGzip(); void getFromUnreachableIp(); @@ -434,7 +484,9 @@ private Q_SLOTS: void qtbug27161httpHeaderMayBeDamaged_data(); void qtbug27161httpHeaderMayBeDamaged(); +#if QT_CONFIG(networkdiskcache) void qtbug28035browserDoesNotLoadQtProjectOrgCorrectly(); +#endif void qtbug45581WrongReplyStatusCode(); @@ -450,8 +502,10 @@ private Q_SLOTS: void varyingCacheExpiry_data(); void varyingCacheExpiry(); +#if QT_CONFIG(http) void amountOfHttp1ConnectionsQtbug25280_data(); void amountOfHttp1ConnectionsQtbug25280(); +#endif void dontInsertPartialContentIntoTheCache(); @@ -487,12 +541,16 @@ private Q_SLOTS: void ioHttpCookiesDuringRedirect(); void ioHttpRedirect_data(); void ioHttpRedirect(); +#if QT_CONFIG(networkdiskcache) void ioHttpRedirectWithCache(); +#endif void ioHttpRedirectFromLocalToRemote(); void ioHttpRedirectPostPut_data(); void ioHttpRedirectPostPut(); +#if QT_CONFIG(http) void ioHttpRedirectMultipartPost_data(); void ioHttpRedirectMultipartPost(); +#endif void ioHttpRedirectDelete(); void ioHttpRedirectCustom(); void ioHttpRedirectWithUploadDevice_data(); @@ -506,16 +564,20 @@ private Q_SLOTS: void autoDeleteReplies_data(); void autoDeleteReplies(); - void getWithTimeout(); - void postWithTimeout(); +#if QT_CONFIG(http) || defined (Q_OS_WASM) + void requestWithTimeout_data(); + void requestWithTimeout(); +#endif void moreActivitySignals_data(); void moreActivitySignals(); void contentEncoding_data(); void contentEncoding(); +#if QT_CONFIG(http) void contentEncodingBigPayload_data(); void contentEncodingBigPayload(); +#endif void cacheWithContentEncoding_data(); void cacheWithContentEncoding(); void downloadProgressWithContentEncoding_data(); @@ -526,6 +588,16 @@ private Q_SLOTS: void notFoundWithCompression_data(); void notFoundWithCompression(); +#if QT_CONFIG(http) + void qhttpPartDebug_data(); + void qhttpPartDebug(); + + void qtbug68821proxyError_data(); + void qtbug68821proxyError(); +#endif + + void abortAndError(); + // NOTE: This test must be last! void parentingRepliesToTheApp(); private: @@ -610,7 +682,8 @@ public: int totalConnections; bool stopTransfer = false; - bool hasContent = false; + bool checkedContentLength = false; + bool foundContentLength = false; int contentRead = 0; int contentLength = 0; @@ -642,6 +715,7 @@ public: { contentLength = 0; receivedData.clear(); + foundContentLength = false; } protected: @@ -698,8 +772,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 +818,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 +926,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; } @@ -1389,6 +1470,7 @@ void tst_QNetworkReply::storeSslConfiguration() } #endif +#if QT_CONFIG(http) QString tst_QNetworkReply::runMultipartRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, QHttpMultiPart *multiPart, @@ -1420,6 +1502,7 @@ QString tst_QNetworkReply::runMultipartRequest(const QNetworkRequest &request, } return QString(); } +#endif QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, @@ -1988,6 +2071,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 +2736,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 +2788,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 +2815,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, @@ -2465,6 +2837,7 @@ void tst_QNetworkReply::postToHttpSynchronous() QCOMPARE(uploadedData, md5sum.toHex()); } +#if QT_CONFIG(http) void tst_QNetworkReply::postToHttpMultipart_data() { QTest::addColumn<QUrl>("url"); @@ -2504,8 +2877,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 +2914,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"); @@ -2774,7 +3147,50 @@ void tst_QNetworkReply::multipartSkipIndices() // QTBUG-32534 } multiPart->deleteLater(); } +#endif + +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); +} + +#if QT_CONFIG(http) void tst_QNetworkReply::putToHttpMultipart_data() { postToHttpMultipart_data(); @@ -2819,6 +3235,7 @@ void tst_QNetworkReply::putToHttpMultipart() // QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above QCOMPARE(replyData, expectedReplyData); } +#endif #if QT_CONFIG(ssl) void tst_QNetworkReply::putToHttps_data() @@ -2930,7 +3347,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 +3381,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, @@ -2986,6 +3403,7 @@ void tst_QNetworkReply::postToHttpsSynchronous() QCOMPARE(uploadedData, md5sum.toHex()); } +#if QT_CONFIG(http) void tst_QNetworkReply::postToHttpsMultipart_data() { if (isSecureTransport) @@ -3036,7 +3454,7 @@ void tst_QNetworkReply::postToHttpsMultipart() expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); QCOMPARE(replyData, expectedReplyData); } - +#endif #endif // QT_CONFIG(ssl) void tst_QNetworkReply::deleteFromHttp_data() @@ -3166,7 +3584,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 +3599,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 +3790,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 +3817,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 +3947,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 +4272,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 +4415,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 +4426,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 +4498,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 +4633,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" @@ -4601,6 +5036,7 @@ void tst_QNetworkReply::ioPutToFileFromSocket() QCOMPARE(contents, data); } +#if QT_CONFIG(localserver) void tst_QNetworkReply::ioPutToFileFromLocalSocket_data() { putToFile_data(); @@ -4644,6 +5080,7 @@ void tst_QNetworkReply::ioPutToFileFromLocalSocket() QByteArray contents = file.readAll(); QCOMPARE(contents, data); } +#endif // Currently no stdin/out supported for Windows CE. void tst_QNetworkReply::ioPutToFileFromProcess_data() @@ -4693,6 +5130,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 +5244,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 +5321,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 +5395,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 +5426,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 +5452,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 +5483,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 +5511,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 +5596,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 +5750,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 +5818,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 +5869,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 +5915,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 +6225,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 +6253,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 +6436,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 +6453,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 +6494,6 @@ struct QThreadCleanup } }; -struct QDeleteLaterCleanup -{ - static inline void cleanup(QObject *o) - { - o->deleteLater(); - } -}; - #if QT_CONFIG(networkproxy) void tst_QNetworkReply::httpProxyCommandsSynchronous() { @@ -6071,7 +6505,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 +6663,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 +6673,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 +6681,39 @@ void tst_QNetworkReply::httpConnectionCount() } int pendingConnectionCount = 0; - QElapsedTimer timer; - timer.start(); - while(pendingConnectionCount <= 20) { - QTestEventLoop::instance().enterLoop(1); + 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 +7219,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; @@ -7071,6 +7530,7 @@ void tst_QNetworkReply::ioGetFromHttpBrokenChunkedEncoding() QCOMPARE(reply->error(), QNetworkReply::NoError); } +#if QT_CONFIG(http) // TODO: // Prepare a gzip that has one chunk that expands to the size mentioned in the bugreport. // Then have a custom HTTP server that waits after this chunk so the returning gets @@ -7099,6 +7559,7 @@ void tst_QNetworkReply::qtbug12908compressedHttpReply() QCOMPARE(reply->size(), qint64(16384)); QCOMPARE(reply->readAll(), QByteArray(16384, '\0')); } +#endif void tst_QNetworkReply::compressedHttpReplyBrokenGzip() { @@ -7625,11 +8086,12 @@ void tst_QNetworkReply::qtbug27161httpHeaderMayBeDamaged(){ QCOMPARE(reply->readAll(), QByteArray("ABC")); } +#if QT_CONFIG(networkdiskcache) 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 +8099,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 +8108,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 +8146,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)); @@ -7749,6 +8211,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QCOMPARE(reply->readAll(), QByteArray("GET")); QCOMPARE(reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), true); } +#endif void tst_QNetworkReply::qtbug45581WrongReplyStatusCode() { @@ -7871,7 +8334,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(); @@ -8134,6 +8597,7 @@ public: } }; +#if QT_CONFIG(http) void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280_data() { QTest::addColumn<int>("amount"); @@ -8174,13 +8638,14 @@ void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280() } QCOMPARE(server.receivedSockets.size(), amount); } +#endif 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 +8685,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 +8702,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 +8726,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 +8865,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 +8960,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 +9081,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 +9364,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 +9389,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); @@ -8940,6 +9407,7 @@ void tst_QNetworkReply::ioHttpRedirect() QVERIFY(validateRedirectedResponseHeaders(reply)); } +#if QT_CONFIG(networkdiskcache) /* Test that, if we load a redirect from cache, we don't treat the request to the destination of the redirect as a redirect. @@ -8950,7 +9418,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 +9428,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()); @@ -8997,6 +9465,7 @@ void tst_QNetworkReply::ioHttpRedirectWithCache() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QVERIFY(validateRedirectedResponseHeaders(reply)); } +#endif void tst_QNetworkReply::ioHttpRedirectFromLocalToRemote() { @@ -9072,7 +9541,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()); @@ -9091,6 +9560,7 @@ void tst_QNetworkReply::ioHttpRedirectPostPut() QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); } +#if QT_CONFIG(http) void tst_QNetworkReply::ioHttpRedirectMultipartPost_data() { postToHttpMultipart_data(); @@ -9151,6 +9621,7 @@ void tst_QNetworkReply::ioHttpRedirectMultipartPost() // QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above QCOMPARE(replyData, expectedReplyData); } +#endif void tst_QNetworkReply::ioHttpRedirectDelete() { @@ -9226,7 +9697,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 +9731,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,83 +10063,67 @@ void tst_QNetworkReply::autoDeleteReplies() } } -void tst_QNetworkReply::getWithTimeout() +#if QT_CONFIG(http) || defined (Q_OS_WASM) +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))); - - 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); + 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"); - 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); - - 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); + 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); }); - 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); } +#endif void tst_QNetworkReply::moreActivitySignals_data() { @@ -9699,7 +10154,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 +10175,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 +10262,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 +10274,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); @@ -9841,6 +10296,7 @@ void tst_QNetworkReply::contentEncoding() } } +#if QT_CONFIG(http) void tst_QNetworkReply::contentEncodingBigPayload_data() { QTest::addColumn<QByteArray>("encoding"); @@ -9899,6 +10355,7 @@ void tst_QNetworkReply::contentEncodingBigPayload() } QCOMPARE(total, expectedSize); } +#endif void tst_QNetworkReply::cacheWithContentEncoding_data() { @@ -10081,6 +10538,143 @@ void tst_QNetworkReply::notFoundWithCompression() QCOMPARE(reply->readAll(), expected); } +#if QT_CONFIG(http) +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); +} +#endif + +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() { diff --git a/tests/auto/network/access/qnetworkreply_local/CMakeLists.txt b/tests/auto/network/access/qnetworkreply_local/CMakeLists.txt new file mode 100644 index 0000000000..13a60afb13 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply_local/CMakeLists.txt @@ -0,0 +1,9 @@ +qt_internal_add_test(tst_qnetworkreply_local + SOURCES + minihttpserver.h + tst_qnetworkreply_local.cpp + LIBRARIES + Qt::CorePrivate + Qt::NetworkPrivate + BUNDLE_ANDROID_OPENSSL_LIBS +) diff --git a/tests/auto/network/access/qnetworkreply_local/minihttpserver.h b/tests/auto/network/access/qnetworkreply_local/minihttpserver.h new file mode 100644 index 0000000000..eb0697a6f8 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply_local/minihttpserver.h @@ -0,0 +1,246 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MINIHTTPSERVER_H +#define MINIHTTPSERVER_H + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QtNetwork/qtcpserver.h> +#include <QtNetwork/qtcpsocket.h> +#include <QtNetwork/qlocalsocket.h> +#if QT_CONFIG(ssl) +# include <QtNetwork/qsslsocket.h> +#endif +#if QT_CONFIG(localserver) +# include <QtNetwork/qlocalserver.h> +#endif + +#include <QtCore/qpointer.h> +#include <QtCore/qhash.h> + +#include <utility> + +static inline QByteArray default200Response() +{ + return QByteArrayLiteral("HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 12\r\n" + "\r\n" + "Hello World!"); +} +class MiniHttpServerV2 : public QObject +{ + Q_OBJECT + +public: + struct State; + +#if QT_CONFIG(localserver) + void bind(QLocalServer *server) + { + Q_ASSERT(!localServer); + localServer = server; + connect(server, &QLocalServer::newConnection, this, + &MiniHttpServerV2::incomingLocalConnection); + } +#endif + + void bind(QTcpServer *server) + { + Q_ASSERT(!tcpServer); + tcpServer = server; + connect(server, &QTcpServer::pendingConnectionAvailable, this, + &MiniHttpServerV2::incomingConnection); + } + + void setDataToTransmit(QByteArray data) { dataToTransmit = std::move(data); } + + void clearServerState() + { + auto copy = std::exchange(clientStates, {}); + for (auto [socket, _] : copy.asKeyValueRange()) { + if (auto *tcpSocket = qobject_cast<QTcpSocket *>(socket)) + tcpSocket->disconnectFromHost(); + else if (auto *localSocket = qobject_cast<QLocalSocket *>(socket)) + localSocket->disconnectFromServer(); + else + Q_UNREACHABLE_RETURN(); + socket->deleteLater(); + } + } + + bool hasPendingConnections() const + { + return +#if QT_CONFIG(localserver) + (localServer && localServer->hasPendingConnections()) || +#endif + (tcpServer && tcpServer->hasPendingConnections()); + } + + QString addressForScheme(QStringView scheme) const + { + using namespace Qt::StringLiterals; + if (scheme.startsWith("unix"_L1) || scheme.startsWith("local"_L1)) { +#if QT_CONFIG(localserver) + if (localServer) + return localServer->serverName(); +#endif + } else if (scheme == "http"_L1) { + if (tcpServer) + return "%1:%2"_L1.arg(tcpServer->serverAddress().toString(), + QString::number(tcpServer->serverPort())); + } + return {}; + } + + QList<State> peerStates() const { return clientStates.values(); } + +protected: +#if QT_CONFIG(localserver) + void incomingLocalConnection() + { + auto *socket = localServer->nextPendingConnection(); + connectSocketSignals(socket); + } +#endif + + void incomingConnection() + { + auto *socket = tcpServer->nextPendingConnection(); + connectSocketSignals(socket); + } + + void reply(QIODevice *socket) + { + Q_ASSERT(socket); + if (dataToTransmit.isEmpty()) { + emit socket->bytesWritten(0); // emulate having written the data + return; + } + if (!stopTransfer) + socket->write(dataToTransmit); + } + +private: + void connectSocketSignals(QIODevice *socket) + { + connect(socket, &QIODevice::readyRead, this, [this, socket]() { readyReadSlot(socket); }); + connect(socket, &QIODevice::bytesWritten, this, + [this, socket]() { bytesWrittenSlot(socket); }); +#if QT_CONFIG(ssl) + if (auto *sslSocket = qobject_cast<QSslSocket *>(socket)) + connect(sslSocket, &QSslSocket::sslErrors, this, &MiniHttpServerV2::slotSslErrors); +#endif + + if (auto *tcpSocket = qobject_cast<QTcpSocket *>(socket)) { + connect(tcpSocket, &QAbstractSocket::errorOccurred, this, &MiniHttpServerV2::slotError); + } else if (auto *localSocket = qobject_cast<QLocalSocket *>(socket)) { + connect(localSocket, &QLocalSocket::errorOccurred, this, + [this](QLocalSocket::LocalSocketError error) { + slotError(QAbstractSocket::SocketError(error)); + }); + } else { + Q_UNREACHABLE_RETURN(); + } + } + + void parseContentLength(State &st, QByteArrayView header) + { + qsizetype index = header.indexOf("\r\ncontent-length:"); + if (index == -1) + return; + st.foundContentLength = true; + + index += sizeof("\r\ncontent-length:") - 1; + const auto *end = std::find(header.cbegin() + index, header.cend(), '\r'); + QByteArrayView num = header.mid(index, std::distance(header.cbegin() + index, end)); + bool ok = false; + st.contentLength = num.toInt(&ok); + if (!ok) + st.contentLength = -1; + } + +private slots: +#if QT_CONFIG(ssl) + void slotSslErrors(const QList<QSslError> &errors) + { + QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender()); + Q_ASSERT(currentClient); + qDebug() << "slotSslErrors" << currentClient->errorString() << errors; + } +#endif + void slotError(QAbstractSocket::SocketError err) + { + QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender()); + Q_ASSERT(currentClient); + qDebug() << "slotError" << err << currentClient->errorString(); + } + +public slots: + + void readyReadSlot(QIODevice *socket) + { + if (stopTransfer) + return; + State &st = clientStates[socket]; + st.receivedData += socket->readAll(); + const qsizetype doubleEndlPos = st.receivedData.indexOf("\r\n\r\n"); + + if (doubleEndlPos != -1) { + const qsizetype endOfHeader = doubleEndlPos + 4; + st.contentRead = st.receivedData.size() - endOfHeader; + + if (!st.checkedContentLength) { + parseContentLength(st, QByteArrayView(st.receivedData).first(endOfHeader)); + st.checkedContentLength = true; + } + + if (st.contentRead < st.contentLength) + return; + + // multiple requests incoming, remove the bytes of the current one + if (multiple) + st.receivedData.remove(0, endOfHeader); + + reply(socket); + } + } + + void bytesWrittenSlot(QIODevice *socket) + { + // Disconnect and delete in next cycle (else Windows clients will fail with + // RemoteHostClosedError). + if (doClose && socket->bytesToWrite() == 0) { + disconnect(socket, nullptr, this, nullptr); + socket->deleteLater(); + } + } + +private: + QByteArray dataToTransmit = default200Response(); + + QTcpServer *tcpServer = nullptr; +#if QT_CONFIG(localserver) + QLocalServer *localServer = nullptr; +#endif + + QHash<QIODevice *, State> clientStates; + +public: + struct State + { + QByteArray receivedData; + qsizetype contentLength = 0; + qsizetype contentRead = 0; + bool checkedContentLength = false; + bool foundContentLength = false; + }; + + bool doClose = true; + bool multiple = false; + bool stopTransfer = false; +}; + +#endif // MINIHTTPSERVER_H diff --git a/tests/auto/network/access/qnetworkreply_local/tst_qnetworkreply_local.cpp b/tests/auto/network/access/qnetworkreply_local/tst_qnetworkreply_local.cpp new file mode 100644 index 0000000000..6d78c81593 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply_local/tst_qnetworkreply_local.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QtTest/qtest.h> + +#include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/qnetworkaccessmanager.h> + +#include "minihttpserver.h" + +using namespace Qt::StringLiterals; + +/* + The tests here are meant to be self-contained, using servers in the same + process if needed. This enables externals to more easily run the tests too. +*/ +class tst_QNetworkReply_local : public QObject +{ + Q_OBJECT +private slots: + void initTestCase_data(); + + void get(); + void post(); + +#if QT_CONFIG(localserver) + void fullServerName_data(); + void fullServerName(); +#endif +}; + +void tst_QNetworkReply_local::initTestCase_data() +{ + QTest::addColumn<QString>("scheme"); + + QTest::newRow("http") << "http"; +#if QT_CONFIG(localserver) + QTest::newRow("unix") << "unix+http"; + QTest::newRow("local") << "local+http"; // equivalent to unix, but test that it works +#endif +} + +static std::unique_ptr<MiniHttpServerV2> getServerForCurrentScheme() +{ + auto server = std::make_unique<MiniHttpServerV2>(); + QFETCH_GLOBAL(QString, scheme); + if (scheme.startsWith("unix"_L1) || scheme.startsWith("local"_L1)) { +#if QT_CONFIG(localserver) + QLocalServer *localServer = new QLocalServer(server.get()); + localServer->listen(u"qt_networkreply_test_"_s + % QLatin1StringView(QTest::currentTestFunction()) + % QString::number(QCoreApplication::applicationPid())); + server->bind(localServer); +#endif + } else if (scheme == "http") { + QTcpServer *tcpServer = new QTcpServer(server.get()); + tcpServer->listen(QHostAddress::LocalHost, 0); + server->bind(tcpServer); + } + return server; +} + +static QUrl getUrlForCurrentScheme(MiniHttpServerV2 *server) +{ + QFETCH_GLOBAL(QString, scheme); + const QString address = server->addressForScheme(scheme); + const QString urlString = QLatin1StringView("%1://%2").arg(scheme, address); + return { urlString }; +} + +void tst_QNetworkReply_local::get() +{ + std::unique_ptr<MiniHttpServerV2> server = getServerForCurrentScheme(); + const QUrl url = getUrlForCurrentScheme(server.get()); + + QNetworkAccessManager manager; + std::unique_ptr<QNetworkReply> reply(manager.get(QNetworkRequest(url))); + + const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); }); + QVERIFY(res); + + QCOMPARE(reply->readAll(), QByteArray("Hello World!")); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); +} + +void tst_QNetworkReply_local::post() +{ + std::unique_ptr<MiniHttpServerV2> server = getServerForCurrentScheme(); + const QUrl url = getUrlForCurrentScheme(server.get()); + + QNetworkAccessManager manager; + const QByteArray payload = "Hello from the other side!"_ba; + QNetworkRequest req(url); + req.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + std::unique_ptr<QNetworkReply> reply(manager.post(req, payload)); + + const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); }); + QVERIFY(res); + + QCOMPARE(reply->readAll(), QByteArray("Hello World!")); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + auto states = server->peerStates(); + QCOMPARE(states.size(), 1); + + const auto &firstRequest = states.at(0); + + QVERIFY(firstRequest.checkedContentLength); + QCOMPARE(firstRequest.contentLength, payload.size()); + QCOMPARE_GT(firstRequest.receivedData.size(), payload.size() + 4); + QCOMPARE(firstRequest.receivedData.last(payload.size() + 4), "\r\n\r\n" + payload); +} + +#if QT_CONFIG(localserver) +void tst_QNetworkReply_local::fullServerName_data() +{ +#if defined(Q_OS_ANDROID) || defined(QT_PLATFORM_UIKIT) + QSKIP("While partially supported, the test as-is doesn't make sense on this platform."); +#else + + QTest::addColumn<QString>("hostAndPath"); + + QTest::newRow("dummy-host") << u"://irrelevant/test"_s; + QTest::newRow("no-host") << u":///test"_s; +#endif +} + +void tst_QNetworkReply_local::fullServerName() +{ + QFETCH_GLOBAL(QString, scheme); + if (!scheme.startsWith("unix"_L1) && !scheme.startsWith("local"_L1)) + return; // only relevant for local sockets + + MiniHttpServerV2 server; + QLocalServer localServer; + + QString path; +#ifdef Q_OS_WIN + path = uR"(\\.\pipe\qt_networkreply_test_fullServerName)"_s + % QString::number(QCoreApplication::applicationPid()); +#else + path = u"/tmp/qt_networkreply_test_fullServerName"_s + % QString::number(QCoreApplication::applicationPid()) % u".sock"_s; +#endif + + QVERIFY(localServer.listen(path)); + server.bind(&localServer); + + QFETCH(QString, hostAndPath); + QUrl url(scheme % hostAndPath); + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::FullLocalServerNameAttribute, path); + + QNetworkAccessManager manager; + std::unique_ptr<QNetworkReply> reply(manager.get(req)); + + const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); }); + QVERIFY(res); + + QCOMPARE(reply->readAll(), QByteArray("Hello World!")); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + const QByteArray receivedData = server.peerStates().at(0).receivedData; + const QByteArray expectedGet = "GET " % url.path().toUtf8() % " HTTP/1.1\r\n"; + QVERIFY(receivedData.startsWith(expectedGet)); + + const QByteArray expectedHost = "host: " % url.host().toUtf8() % "\r\n"; + QVERIFY(receivedData.contains(expectedHost)); +} +#endif + +QTEST_MAIN(tst_QNetworkReply_local) + +#include "tst_qnetworkreply_local.moc" +#include "moc_minihttpserver.cpp" diff --git a/tests/auto/network/access/qnetworkrequest/CMakeLists.txt b/tests/auto/network/access/qnetworkrequest/CMakeLists.txt index 7fa7aebacb..2c4a7dd7ca 100644 --- a/tests/auto/network/access/qnetworkrequest/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkrequest/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkrequest Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkrequest LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkrequest SOURCES tst_qnetworkrequest.cpp diff --git a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp index c04530d085..f0b02ae91d 100644 --- a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp +++ b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp @@ -1,8 +1,12 @@ // Copyright (C) 2022 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 <QTest> +#if QT_CONFIG(http) +#include <QtNetwork/QHttp1Configuration> +#include <QtNetwork/QHttp2Configuration> +#endif #include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkCookie> @@ -31,6 +35,10 @@ private slots: void rawHeaderParsing_data(); void rawHeaderParsing(); void originatingObject(); + void setHeaders_data(); + void setHeaders(); + void operatorEqual_data(); + void operatorEqual(); void removeHeader(); }; @@ -166,11 +174,11 @@ void tst_QNetworkRequest::rawHeaderList_data() void tst_QNetworkRequest::rawHeaderList() { - QFETCH(QList<QByteArray>, set); + QFETCH(const QList<QByteArray>, set); QFETCH(QList<QByteArray>, expected); QNetworkRequest request; - foreach (QByteArray header, set) + for (const QByteArray &header : set) request.setRawHeader(header, "a value"); QList<QByteArray> got = request.rawHeaderList(); @@ -347,7 +355,7 @@ void tst_QNetworkRequest::rawHeaderParsing_data() << true << "Content-Type" << "text/html"; QTest::newRow("Content-Length") << QNetworkRequest::ContentLengthHeader << QVariant(qint64(1)) - << true << "Content-Length" << " 1 "; + << true << "Content-Length" << "1"; QTest::newRow("Location") << QNetworkRequest::LocationHeader << QVariant(QUrl("http://foo/with space")) << true << "Location" << "http://foo/with%20space"; @@ -552,5 +560,155 @@ void tst_QNetworkRequest::originatingObject() QVERIFY(!request.originatingObject()); } +void tst_QNetworkRequest::setHeaders_data() +{ + QTest::addColumn<QHttpHeaders>("h"); + QTest::newRow("null") << QHttpHeaders(); + QHttpHeaders headers; + headers.append("name1", "value1"); + QTest::newRow("valid") << headers; +} + +void tst_QNetworkRequest::setHeaders() +{ + QFETCH(QHttpHeaders, h); + + QNetworkRequest r1; + + auto result = r1.headers(); + QVERIFY(result.isEmpty()); + + r1.setHeaders(h); + QCOMPARE(r1.headers().toListOfPairs(), h.toListOfPairs()); + + QNetworkRequest r2; + auto tmp = h; + r2.setHeaders(std::move(tmp)); + QCOMPARE(r2.headers().toListOfPairs(), h.toListOfPairs()); +} + +void tst_QNetworkRequest::operatorEqual_data() +{ + QTest::addColumn<QNetworkRequest>("a"); + QTest::addColumn<QNetworkRequest>("b"); + QTest::addColumn<bool>("expectedToMatch"); + QTest::newRow("null") << QNetworkRequest() << QNetworkRequest() << true; + + QNetworkRequest data1; + data1.setUrl(QUrl("http://qt-project.org")); + QTest::newRow("url-1-1") << data1 << QNetworkRequest() << false; + QTest::newRow("url-1-2") << data1 << data1 << true; + + QNetworkRequest data2; + QHttpHeaders headers; + headers.append("name1", "value1"); + data2.setHeaders(headers); + QTest::newRow("headers-2-1") << data2 << QNetworkRequest() << false; + QTest::newRow("headers-2-2") << data2 << data2 << true; + QTest::newRow("headers-2-3") << data2 << data1 << false; + + QNetworkRequest data3; + data3.setPeerVerifyName("peerName"); + QTest::newRow("peerName-3-1") << data3 << QNetworkRequest() << false; + QTest::newRow("peerName-3-2") << data3 << data3 << true; + QTest::newRow("peerName-3-3") << data3 << data1 << false; + QTest::newRow("peerName-3-4") << data3 << data2 << false; + + QNetworkRequest data4; + data4.setAttribute(QNetworkRequest::Http2AllowedAttribute, true); + QTest::newRow("attribute-4-1") << data4 << QNetworkRequest() << false; + QTest::newRow("attribute-4-2") << data4 << data4 << true; + QTest::newRow("attribute-4-3") << data4 << data1 << false; + QTest::newRow("attribute-4-4") << data4 << data2 << false; + QTest::newRow("attribute-4-5") << data4 << data3 << false; + + QNetworkRequest data5; + data5.setPriority(QNetworkRequest::Priority::HighPriority); + QTest::newRow("priority-5-1") << data5 << QNetworkRequest() << false; + QTest::newRow("priority-5-2") << data5 << data5 << true; + QTest::newRow("priority-5-3") << data5 << data1 << false; + QTest::newRow("priority-5-4") << data5 << data2 << false; + QTest::newRow("priority-5-5") << data5 << data3 << false; + QTest::newRow("priority-5-6") << data5 << data4 << false; + + QNetworkRequest data6; + data6.setMaximumRedirectsAllowed(3); + QTest::newRow("maxRedirects-6-1") << data6 << QNetworkRequest() << false; + QTest::newRow("maxRedirects-6-2") << data6 << data6 << true; + QTest::newRow("maxRedirects-6-3") << data6 << data1 << false; + QTest::newRow("maxRedirects-6-4") << data6 << data2 << false; + QTest::newRow("maxRedirects-6-5") << data6 << data3 << false; + QTest::newRow("maxRedirects-6-6") << data6 << data4 << false; + QTest::newRow("maxRedirects-6-7") << data6 << data5 << false; + +#if QT_CONFIG(http) + QNetworkRequest data7; + QHttp1Configuration http1Configuration; + http1Configuration.setNumberOfConnectionsPerHost(5); + data7.setHttp1Configuration(http1Configuration); + QTest::newRow("http1Config-7-1") << data7 << QNetworkRequest() << false; + QTest::newRow("http1Config-7-2") << data7 << data7 << true; + QTest::newRow("http1Config-7-3") << data7 << data1 << false; + QTest::newRow("http1Config-7-4") << data7 << data2 << false; + QTest::newRow("http1Config-7-5") << data7 << data3 << false; + QTest::newRow("http1Config-7-6") << data7 << data4 << false; + QTest::newRow("http1Config-7-7") << data7 << data5 << false; + QTest::newRow("http1Config-7-8") << data7 << data6 << false; + + QNetworkRequest data8; + QHttp2Configuration http2Configuration; + http2Configuration.setMaxFrameSize(16386); + data8.setHttp2Configuration(http2Configuration); + QTest::newRow("http2Config-8-1") << data8 << QNetworkRequest() << false; + QTest::newRow("http2Config-8-2") << data8 << data8 << true; + QTest::newRow("http2Config-8-3") << data8 << data1 << false; + QTest::newRow("http2Config-8-4") << data8 << data2 << false; + QTest::newRow("http2Config-8-5") << data8 << data3 << false; + QTest::newRow("http2Config-8-6") << data8 << data4 << false; + QTest::newRow("http2Config-8-7") << data8 << data5 << false; + QTest::newRow("http2Config-8-8") << data8 << data6 << false; + QTest::newRow("http2Config-8-9") << data8 << data7 << false; + + QNetworkRequest data9; + data9.setDecompressedSafetyCheckThreshold(-1); + QTest::newRow("threshold-9-1") << data9 << QNetworkRequest() << false; + QTest::newRow("threshold-9-2") << data9 << data9 << true; + QTest::newRow("threshold-9-3") << data9 << data1 << false; + QTest::newRow("threshold-9-4") << data9 << data2 << false; + QTest::newRow("threshold-9-5") << data9 << data3 << false; + QTest::newRow("threshold-9-6") << data9 << data4 << false; + QTest::newRow("threshold-9-7") << data9 << data5 << false; + QTest::newRow("threshold-9-8") << data9 << data6 << false; + QTest::newRow("threshold-9-9") << data9 << data7 << false; + QTest::newRow("threshold-9-10") << data9 << data8 << false; +#endif + +#if QT_CONFIG(http) || defined (Q_OS_WASM) + QNetworkRequest data10; + data10.setTransferTimeout(50000); + QTest::newRow("timeout-10-1") << data10 << QNetworkRequest() << false; + QTest::newRow("timeout-10-2") << data10 << data10 << true; + QTest::newRow("timeout-10-3") << data10 << data1 << false; + QTest::newRow("timeout-10-4") << data10 << data2 << false; + QTest::newRow("timeout-10-5") << data10 << data3 << false; + QTest::newRow("timeout-10-6") << data10 << data4 << false; + QTest::newRow("timeout-10-7") << data10 << data5 << false; + QTest::newRow("timeout-10-8") << data10 << data6 << false; + QTest::newRow("timeout-10-9") << data10 << data7 << false; + QTest::newRow("timeout-10-10") << data10 << data8 << false; + QTest::newRow("timeout-10-11") << data10 << data9 << false; +#endif +} + +// public bool operator==(const QNetworkRequest &other) const +void tst_QNetworkRequest::operatorEqual() +{ + QFETCH(QNetworkRequest, a); + QFETCH(QNetworkRequest, b); + QFETCH(bool, expectedToMatch); + + QCOMPARE(a == b, expectedToMatch); +} + QTEST_MAIN(tst_QNetworkRequest) #include "tst_qnetworkrequest.moc" diff --git a/tests/auto/network/access/qnetworkrequestfactory/CMakeLists.txt b/tests/auto/network/access/qnetworkrequestfactory/CMakeLists.txt new file mode 100644 index 0000000000..d2112de58f --- /dev/null +++ b/tests/auto/network/access/qnetworkrequestfactory/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2023 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_qnetworkrequestfactory LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qnetworkrequestfactory + SOURCES + tst_qnetworkrequestfactory.cpp + LIBRARIES + Qt::Core + Qt::Test + Qt::Network +) diff --git a/tests/auto/network/access/qnetworkrequestfactory/tst_qnetworkrequestfactory.cpp b/tests/auto/network/access/qnetworkrequestfactory/tst_qnetworkrequestfactory.cpp new file mode 100644 index 0000000000..d04a7ff3ec --- /dev/null +++ b/tests/auto/network/access/qnetworkrequestfactory/tst_qnetworkrequestfactory.cpp @@ -0,0 +1,423 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/qtest.h> +#include <QtNetwork/qnetworkrequestfactory.h> +#ifndef QT_NO_SSL +#include <QtNetwork/qsslconfiguration.h> +#endif +#include <QtCore/qurlquery.h> +#include <QtCore/qurl.h> + +using namespace Qt::StringLiterals; +using namespace std::chrono_literals; + +class tst_QNetworkRequestFactory : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void urlAndPath_data(); + void urlAndPath(); + void queryParameters(); + void sslConfiguration(); + void headers(); + void bearerToken(); + void operators(); + void timeout(); + void userInfo(); + void priority(); + void attributes(); + +private: + const QUrl url1{u"http://foo.io"_s}; + const QUrl url2{u"http://bar.io"_s}; + const QByteArray bearerToken1{"bearertoken1"}; + const QByteArray bearerToken2{"bearertoken2"}; +}; + +void tst_QNetworkRequestFactory::urlAndPath_data() +{ + QTest::addColumn<QUrl>("baseUrl"); + QTest::addColumn<QString>("requestPath"); + QTest::addColumn<QUrl>("expectedRequestUrl"); + + QUrl base{"http://xyz.io"}; + QUrl result{"http://xyz.io/path/to"}; + QTest::newRow("baseUrl_nopath_noslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_nopath_noslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_nopath_noslash_3") << base << u"path/to"_s << result; + + base.setUrl("http://xyz.io/"); + result.setUrl("http://xyz.io/path/to"); + QTest::newRow("baseUrl_nopath_withslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_nopath_withslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_nopath_withslash_3") << base << u"path/to"_s << result; + + base.setUrl("http://xyz.io/v1"); + result.setUrl("http://xyz.io/v1/path/to"); + QTest::newRow("baseUrl_withpath_noslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_withpath_noslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_withpath_noslash_3") << base << u"path/to"_s << result; + + base.setUrl("http://xyz.io/v1/"); + QTest::newRow("baseUrl_withpath_withslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_withpath_withslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_withpath_withslash_3") << base << u"path/to"_s << result; + + // Currently we keep any double '//', but not sure if there is a use case for it, or could + // it be corrected to a single '/' + base.setUrl("http://xyz.io/v1//"); + result.setUrl("http://xyz.io/v1//path/to"); + QTest::newRow("baseUrl_withpath_doubleslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_withpath_doubleslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_withpath_doubleslash_3") << base << u"path/to"_s << result; +} + +void tst_QNetworkRequestFactory::urlAndPath() +{ + QFETCH(QUrl, baseUrl); + QFETCH(QString, requestPath); + QFETCH(QUrl, expectedRequestUrl); + + // Set with constructor + QNetworkRequestFactory factory1{baseUrl}; + QCOMPARE(factory1.baseUrl(), baseUrl); + + // Set with setter calls + QNetworkRequestFactory factory2{}; + factory2.setBaseUrl(baseUrl); + QCOMPARE(factory2.baseUrl(), baseUrl); + + // Request path + QNetworkRequest request = factory1.createRequest(); + QCOMPARE(request.url(), baseUrl); // No path was provided for createRequest(), expect baseUrl + request = factory1.createRequest(requestPath); + QCOMPARE(request.url(), expectedRequestUrl); + + // Check the request path didn't change base url + QCOMPARE(factory1.baseUrl(), baseUrl); +} + +void tst_QNetworkRequestFactory::queryParameters() +{ + QNetworkRequestFactory factory({"http://example.com"}); + const QUrlQuery query1{{"q1k", "q1v"}}; + const QUrlQuery query2{{"q2k", "q2v"}}; + + // Set query parameters in createRequest() call + QCOMPARE(factory.createRequest(query1).url(), QUrl{"http://example.com?q1k=q1v"}); + QCOMPARE(factory.createRequest(query2).url(), QUrl{"http://example.com?q2k=q2v"}); + + // Set query parameters into the factory + factory.setQueryParameters(query1); + QUrlQuery resultQuery = factory.queryParameters(); + for (const auto &item: query1.queryItems()) { + QVERIFY(resultQuery.hasQueryItem(item.first)); + QCOMPARE(resultQuery.queryItemValue(item.first), item.second); + } + QCOMPARE(factory.createRequest().url(), QUrl{"http://example.com?q1k=q1v"}); + + // Set query parameters into both createRequest() and factory + QCOMPARE(factory.createRequest(query2).url(), QUrl{"http://example.com?q2k=q2v&q1k=q1v"}); + + // Clear query parameters + factory.clearQueryParameters(); + QVERIFY(factory.queryParameters().isEmpty()); + QCOMPARE(factory.createRequest().url(), QUrl{"http://example.com"}); + + const QString pathWithQuery{"content?raw=1"}; + // Set query parameters in per-request path + QCOMPARE(factory.createRequest(pathWithQuery).url(), + QUrl{"http://example.com/content?raw=1"}); + // Set query parameters in per-request path and the query parameter + QCOMPARE(factory.createRequest(pathWithQuery, query1).url(), + QUrl{"http://example.com/content?q1k=q1v&raw=1"}); + // Set query parameter in per-request path and into the factory + factory.setQueryParameters(query2); + QCOMPARE(factory.createRequest(pathWithQuery).url(), + QUrl{"http://example.com/content?raw=1&q2k=q2v"}); + // Set query parameters in per-request, as additional parameters, and into the factory + QCOMPARE(factory.createRequest(pathWithQuery, query1).url(), + QUrl{"http://example.com/content?q1k=q1v&raw=1&q2k=q2v"}); + + // Test that other than path and query items as part of path are ignored + factory.setQueryParameters(query1); + QRegularExpression re("The provided path*"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QCOMPARE(factory.createRequest("https://example2.com").url(), QUrl{"http://example.com?q1k=q1v"}); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QCOMPARE(factory.createRequest("https://example2.com?q3k=q3v").url(), + QUrl{"http://example.com?q3k=q3v&q1k=q1v"}); +} + +void tst_QNetworkRequestFactory::sslConfiguration() +{ +#ifdef QT_NO_SSL + QSKIP("Skipping SSL tests, not supported by build"); +#else + // Two initially equal factories + QNetworkRequestFactory factory1{url1}; + QNetworkRequestFactory factory2{url1}; + + // Make two differing SSL configurations (for this test it's irrelevant how they differ) + QSslConfiguration config1; + config1.setProtocol(QSsl::TlsV1_2); + QSslConfiguration config2; + config2.setProtocol(QSsl::DtlsV1_2); + + // Set configuration and verify that the same config is returned + factory1.setSslConfiguration(config1); + QCOMPARE(factory1.sslConfiguration(), config1); + factory2.setSslConfiguration(config2); + QCOMPARE(factory2.sslConfiguration(), config2); + + // Verify requests are set with appropriate SSL configs + QNetworkRequest request1 = factory1.createRequest(); + QCOMPARE(request1.sslConfiguration(), config1); + QNetworkRequest request2 = factory2.createRequest(); + QCOMPARE(request2.sslConfiguration(), config2); +#endif +} + +void tst_QNetworkRequestFactory::headers() +{ + const QByteArray name1{"headername1"}; + const QByteArray name2{"headername2"}; + const QByteArray value1{"headervalue1"}; + const QByteArray value2{"headervalue2"}; + const QByteArray value3{"headervalue3"}; + + QNetworkRequestFactory factory{url1}; + // Initial state when no headers are set + QVERIFY(factory.commonHeaders().isEmpty()); + QVERIFY(factory.commonHeaders().values(name1).isEmpty()); + QVERIFY(!factory.commonHeaders().contains(name1)); + + // Set headers + QHttpHeaders h1; + h1.append(name1, value1); + factory.setCommonHeaders(h1); + QVERIFY(factory.commonHeaders().contains(name1)); + QCOMPARE(factory.commonHeaders().combinedValue(name1), value1); + QCOMPARE(factory.commonHeaders().size(), 1); + QVERIFY(factory.commonHeaders().values("nonexistent").isEmpty()); + QNetworkRequest request = factory.createRequest(); + QVERIFY(request.hasRawHeader(name1)); + QCOMPARE(request.rawHeader(name1), value1); + + // Check that empty header does not match + QVERIFY(!factory.commonHeaders().contains(""_ba)); + QVERIFY(factory.commonHeaders().values(""_ba).isEmpty()); + + // Clear headers + factory.clearCommonHeaders(); + QVERIFY(factory.commonHeaders().isEmpty()); + request = factory.createRequest(); + QVERIFY(!request.hasRawHeader(name1)); + + // Set headers with more entries + h1.clear(); + h1.append(name1, value1); + h1.append(name2, value2); + factory.setCommonHeaders(h1); + QVERIFY(factory.commonHeaders().contains(name1)); + QVERIFY(factory.commonHeaders().contains(name2)); + QCOMPARE(factory.commonHeaders().combinedValue(name1), value1); + QCOMPARE(factory.commonHeaders().combinedValue(name2), value2); + QCOMPARE(factory.commonHeaders().size(), 2); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(name1)); + QVERIFY(request.hasRawHeader(name2)); + QCOMPARE(request.rawHeader(name1), value1); + QCOMPARE(request.rawHeader(name2), value2); + // Append more values to pre-existing header name2 + h1.clear(); + h1.append(name1, value1); + h1.append(name1, value2); + h1.append(name1, value3); + factory.setCommonHeaders(h1); + QVERIFY(factory.commonHeaders().contains(name1)); + QCOMPARE(factory.commonHeaders().combinedValue(name1), value1 + ", " + value2 + ", " + value3); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(name1)); + QCOMPARE(request.rawHeader(name1), value1 + ", " + value2 + ", " + value3); +} + +void tst_QNetworkRequestFactory::bearerToken() +{ + const auto authHeader = "Authorization"_ba; + QNetworkRequestFactory factory{url1}; + QVERIFY(factory.bearerToken().isEmpty()); + + factory.setBearerToken(bearerToken1); + QCOMPARE(factory.bearerToken(), bearerToken1); + QNetworkRequest request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + QCOMPARE(request.rawHeader(authHeader), "Bearer "_ba + bearerToken1); + + // Verify that bearerToken is not in debug output + QString debugOutput; + QDebug debug(&debugOutput); + debug << factory; + QVERIFY(debugOutput.contains("bearerToken = (is set)")); + QVERIFY(!debugOutput.contains(bearerToken1)); + + factory.setBearerToken(bearerToken2); + QCOMPARE(factory.bearerToken(), bearerToken2); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + QCOMPARE(request.rawHeader(authHeader), "Bearer "_ba + bearerToken2); + + // Set authorization header manually + const auto value = "headervalue"_ba; + QHttpHeaders h1; + h1.append(authHeader, value); + factory.setCommonHeaders(h1); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + // bearerToken has precedence over manually set header + QCOMPARE(request.rawHeader(authHeader), "Bearer "_ba + bearerToken2); + // clear bearer token, the manually set header is now used + factory.clearBearerToken(); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + QCOMPARE(request.rawHeader(authHeader), value); +} + +void tst_QNetworkRequestFactory::operators() +{ + QNetworkRequestFactory factory1(url1); + + // Copy ctor + QNetworkRequestFactory factory2(factory1); + QCOMPARE(factory2.baseUrl(), factory1.baseUrl()); + + // Copy assignment + QNetworkRequestFactory factory3; + factory3 = factory2; + QCOMPARE(factory3.baseUrl(), factory2.baseUrl()); + + // Move assignment + QNetworkRequestFactory factory4; + factory4 = std::move(factory3); + QCOMPARE(factory4.baseUrl(), factory2.baseUrl()); + + // Verify implicit sharing + factory1.setBaseUrl(url2); + QCOMPARE(factory1.baseUrl(), url2); // changed + QCOMPARE(factory2.baseUrl(), url1); // remains + + // Move ctor + QNetworkRequestFactory factory5{std::move(factory4)}; + QCOMPARE(factory5.baseUrl(), factory2.baseUrl()); // the moved factory4 originates from factory2 + QCOMPARE(factory5.baseUrl(), url1); +} + +void tst_QNetworkRequestFactory::timeout() +{ + constexpr auto defaultTimeout = 0ms; + constexpr auto timeout = 150ms; + + QNetworkRequestFactory factory; + QNetworkRequest request = factory.createRequest(); + QCOMPARE(factory.transferTimeout(), defaultTimeout); + QCOMPARE(request.transferTimeoutAsDuration(), defaultTimeout); + + factory.setTransferTimeout(timeout); + request = factory.createRequest(); + QCOMPARE(factory.transferTimeout(), timeout); + QCOMPARE(request.transferTimeoutAsDuration(), timeout); +} + +void tst_QNetworkRequestFactory::userInfo() +{ + QNetworkRequestFactory factory; + QVERIFY(factory.userName().isEmpty()); + QVERIFY(factory.password().isEmpty()); + + const auto uname = u"a_username"_s; + const auto password = u"a_password"_s; + factory.setUserName(uname); + QCOMPARE(factory.userName(), uname); + factory.setPassword(password); + QCOMPARE(factory.password(), password); + + // Verify that debug output does not contain password + QString debugOutput; + QDebug debug(&debugOutput); + debug << factory; + QVERIFY(debugOutput.contains("password = (is set)")); + QVERIFY(!debugOutput.contains(password)); + + factory.clearUserName(); + factory.clearPassword(); + QVERIFY(factory.userName().isEmpty()); + QVERIFY(factory.password().isEmpty()); +} + +void tst_QNetworkRequestFactory::priority() +{ + QNetworkRequestFactory factory(u"http://example.com"_s); + QCOMPARE(factory.priority(), QNetworkRequest::NormalPriority); + auto request = factory.createRequest("/index.html"); + QCOMPARE(request.priority(), QNetworkRequest::NormalPriority); + + factory.setPriority(QNetworkRequest::HighPriority); + QCOMPARE(factory.priority(), QNetworkRequest::HighPriority); + request = factory.createRequest("/index.html"); + QCOMPARE(request.priority(), QNetworkRequest::HighPriority); +} + +void tst_QNetworkRequestFactory::attributes() +{ + const auto attribute1 = QNetworkRequest::Attribute::BackgroundRequestAttribute; + const auto attribute2 = QNetworkRequest::User; + QNetworkRequestFactory factory; + QNetworkRequest request; + + // Empty factory + QVERIFY(!factory.attribute(attribute1).isValid()); + request = factory.createRequest(); + QVERIFY(!request.attribute(attribute1).isValid()); + + // (Re-)set and clear individual attribute + factory.setAttribute(attribute1, true); + QVERIFY(factory.attribute(attribute1).isValid()); + QCOMPARE(factory.attribute(attribute1).toBool(), true); + request = factory.createRequest(); + QVERIFY(request.attribute(attribute1).isValid()); + QCOMPARE(request.attribute(attribute1).toBool(), true); + // Replace previous value + factory.setAttribute(attribute1, false); + QVERIFY(factory.attribute(attribute1).isValid()); + QCOMPARE(factory.attribute(attribute1).toBool(), false); + request = factory.createRequest(); + QVERIFY(request.attribute(attribute1).isValid()); + QCOMPARE(request.attribute(attribute1).toBool(), false); + // Clear individual attribute + factory.clearAttribute(attribute1); + QVERIFY(!factory.attribute(attribute1).isValid()); + + // Getter default value + QCOMPARE(factory.attribute(attribute2, 111).toInt(), 111); // default value returned + factory.setAttribute(attribute2, 222); + QCOMPARE(factory.attribute(attribute2, 111).toInt(), 222); // actual value returned + factory.clearAttribute(attribute2); + QCOMPARE(factory.attribute(attribute2, 111).toInt(), 111); // default value returned + + // Clear attributes + factory.setAttribute(attribute1, true); + factory.setAttribute(attribute2, 333); + QVERIFY(factory.attribute(attribute1).isValid()); + QVERIFY(factory.attribute(attribute2).isValid()); + factory.clearAttributes(); + QVERIFY(!factory.attribute(attribute1).isValid()); + QVERIFY(!factory.attribute(attribute2).isValid()); + request = factory.createRequest(); + QVERIFY(!request.attribute(attribute1).isValid()); + QVERIFY(!request.attribute(attribute2).isValid()); +} + +QTEST_MAIN(tst_QNetworkRequestFactory) +#include "tst_qnetworkrequestfactory.moc" diff --git a/tests/auto/network/access/qrestaccessmanager/CMakeLists.txt b/tests/auto/network/access/qrestaccessmanager/CMakeLists.txt new file mode 100644 index 0000000000..614248be28 --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2023 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_qrestaccessmanager LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qrestaccessmanager + SOURCES + tst_qrestaccessmanager.cpp + httptestserver.cpp httptestserver_p.h + LIBRARIES + Qt::Network + Qt::CorePrivate +) diff --git a/tests/auto/network/access/qrestaccessmanager/httptestserver.cpp b/tests/auto/network/access/qrestaccessmanager/httptestserver.cpp new file mode 100644 index 0000000000..25869eb46b --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/httptestserver.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "httptestserver_p.h" + +#include <QtNetwork/qtcpsocket.h> + +#include <QtCore/qcoreapplication.h> + +#include <private/qlocale_p.h> + +using namespace Qt::StringLiterals; + +static constexpr char CRLF[] = "\r\n"; + +HttpTestServer::HttpTestServer(QObject *parent) : QTcpServer(parent) +{ + QObject::connect(this, &QTcpServer::newConnection, this, &HttpTestServer::handleConnected); + const auto ok = listen(QHostAddress::LocalHost); + Q_ASSERT(ok); +}; + +HttpTestServer::~HttpTestServer() +{ + if (isListening()) + close(); +} + +QUrl HttpTestServer::url() +{ + return QUrl(u"http://127.0.0.1:%1"_s.arg(serverPort())); +} + +void HttpTestServer::handleConnected() +{ + Q_ASSERT(!m_socket); // No socket must exist previously, this is a single-connection server + m_socket = nextPendingConnection(); + Q_ASSERT(m_socket); + QObject::connect(m_socket, &QTcpSocket::readyRead, + this, &HttpTestServer::handleDataAvailable); +} + +void HttpTestServer::handleDataAvailable() +{ + Q_ASSERT(m_socket); + bool ok = true; + + // Parse the incoming request data into the HttpData object + while (m_socket->bytesAvailable()) { + if (state == State::ReadingMethod && !(ok = readMethod(m_socket))) + qWarning("Invalid Method"); + if (ok && state == State::ReadingUrl && !(ok = readUrl(m_socket))) + qWarning("Invalid URL"); + if (ok && state == State::ReadingStatus && !(ok = readStatus(m_socket))) + qWarning("Invalid Status"); + if (ok && state == State::ReadingHeader && !(ok = readHeaders(m_socket))) + qWarning("Invalid Header"); + if (ok && state == State::ReadingBody && !(ok = readBody(m_socket))) + qWarning("Invalid Body"); + } // while bytes available + + Q_ASSERT(ok); + Q_ASSERT(m_handler); + Q_ASSERT(state == State::AllDone); + + if (auto values = m_request.headers.values( + QHttpHeaders::WellKnownHeader::Host); !values.empty()) { + const auto parts = values.first().split(':'); + m_request.url.setHost(parts.at(0)); + if (parts.size() == 2) + m_request.url.setPort(parts.at(1).toUInt()); + } + HttpData response; + ResponseControl control; + // Inform the testcase about request and ask for response data + m_handler(m_request, response, control); + + QByteArray responseMessage; + responseMessage += "HTTP/1.1 "; + responseMessage += QByteArray::number(response.status); + responseMessage += CRLF; + // Insert headers if any + for (const auto &[name,value] : response.headers.toListOfPairs()) { + responseMessage += name; + responseMessage += ": "; + responseMessage += value; + responseMessage += CRLF; + } + responseMessage += CRLF; + /* + qDebug() << "HTTPTestServer received request" + << "\nMethod:" << m_request.method + << "\nHeaders:" << m_request.headers + << "\nBody:" << m_request.body; + */ + if (control.respond) { + if (control.responseChunkSize <= 0) { + responseMessage += response.body; + // qDebug() << "HTTPTestServer response:" << responseMessage; + m_socket->write(responseMessage); + } else { + // Respond in chunks, first write the headers + // qDebug() << "HTTPTestServer response:" << responseMessage; + m_socket->write(responseMessage); + // Then write bodydata in chunks, while allowing the testcase to process as well + QByteArray chunk; + while (!response.body.isEmpty()) { + chunk = response.body.left(control.responseChunkSize); + response.body.remove(0, control.responseChunkSize); + // qDebug() << "SERVER writing chunk" << chunk; + m_socket->write(chunk); + m_socket->flush(); + m_socket->waitForBytesWritten(); + // Process events until testcase indicates it's ready for next chunk. + // This way we can control the bytes the testcase gets in each chunk + control.readyForNextChunk = false; + while (!control.readyForNextChunk) + QCoreApplication::processEvents(); + } + } + } + m_socket->disconnectFromHost(); + m_request = {}; + m_socket = nullptr; // deleted by QTcpServer during destruction + state = State::ReadingMethod; + fragment.clear(); +} + +bool HttpTestServer::readMethod(QTcpSocket *socket) +{ + bool finished = false; + while (socket->bytesAvailable() && !finished) { + const auto c = socket->read(1).at(0); + if (ascii_isspace(c)) + finished = true; + else if (std::isupper(c) && fragment.size() < 8) + fragment += c; + else + return false; + } + if (finished) { + if (fragment == "HEAD") + method = Method::Head; + else if (fragment == "GET") + method = Method::Get; + else if (fragment == "PUT") + method = Method::Put; + else if (fragment == "PATCH") + method = Method::Patch; + else if (fragment == "POST") + method = Method::Post; + else if (fragment == "DELETE") + method = Method::Delete; + else if (fragment == "FOOBAR") // used by custom verb/method tests + method = Method::Custom; + else + qWarning("Invalid operation %s", fragment.data()); + + state = State::ReadingUrl; + m_request.method = fragment; + fragment.clear(); + + return method != Method::Unknown; + } + return true; +} + +bool HttpTestServer::readUrl(QTcpSocket *socket) +{ + bool finished = false; + while (socket->bytesAvailable() && !finished) { + const auto c = socket->read(1).at(0); + if (std::isspace(c)) + finished = true; + else + fragment += c; + } + if (finished) { + if (!fragment.startsWith('/')) { + qWarning("Invalid URL path %s", fragment.constData()); + return false; + } + m_request.url = QStringLiteral("http://127.0.0.1:") + QString::number(m_request.port) + + QString::fromUtf8(fragment); + state = State::ReadingStatus; + if (!m_request.url.isValid()) { + qWarning("Invalid URL %s", fragment.constData()); + return false; + } + fragment.clear(); + } + return true; +} + +bool HttpTestServer::readStatus(QTcpSocket *socket) +{ + bool finished = false; + while (socket->bytesAvailable() && !finished) { + fragment += socket->read(1); + if (fragment.endsWith(CRLF)) { + finished = true; + fragment.resize(fragment.size() - 2); + } + } + if (finished) { + if (!std::isdigit(fragment.at(fragment.size() - 3)) || + fragment.at(fragment.size() - 2) != '.' || + !std::isdigit(fragment.at(fragment.size() - 1))) { + qWarning("Invalid version"); + return false; + } + m_request.version = std::pair(fragment.at(fragment.size() - 3) - '0', + fragment.at(fragment.size() - 1) - '0'); + state = State::ReadingHeader; + fragment.clear(); + } + return true; +} + +bool HttpTestServer::readHeaders(QTcpSocket *socket) +{ + while (socket->bytesAvailable()) { + fragment += socket->read(1); + if (fragment.endsWith(CRLF)) { + if (fragment == CRLF) { + state = State::ReadingBody; + fragment.clear(); + return true; + } else { + fragment.chop(2); + const int index = fragment.indexOf(':'); + if (index == -1) + return false; + + QByteArray key = fragment.sliced(0, index).trimmed(); + QByteArray value = fragment.sliced(index + 1).trimmed(); + m_request.headers.append(key, value); + fragment.clear(); + } + } + } + return true; +} + +bool HttpTestServer::readBody(QTcpSocket *socket) +{ + qint64 bytesLeft = 0; + if (auto values = m_request.headers.values( + QHttpHeaders::WellKnownHeader::ContentLength); !values.empty()) { + bool conversionResult; + bytesLeft = values.first().toInt(&conversionResult); + if (!conversionResult) + return false; + fragment.resize(bytesLeft); + } + while (bytesLeft) { + qint64 got = socket->read(&fragment.data()[fragment.size() - bytesLeft], bytesLeft); + if (got < 0) + return false; // error + bytesLeft -= got; + if (bytesLeft) + qApp->processEvents(); + } + fragment.swap(m_request.body); + state = State::AllDone; + return true; +} + diff --git a/tests/auto/network/access/qrestaccessmanager/httptestserver_p.h b/tests/auto/network/access/qrestaccessmanager/httptestserver_p.h new file mode 100644 index 0000000000..0a94b2c8a6 --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/httptestserver_p.h @@ -0,0 +1,90 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QRESTACCESSSMANAGER_HTTPTESTSERVER_P_H +#define QRESTACCESSSMANAGER_HTTPTESTSERVER_P_H + +#include <QtNetwork/qtcpserver.h> +#include <QtNetwork/qhttpheaders.h> + +#include <QtCore/qmap.h> +#include <QtCore/qurl.h> + +#include <functional> + +// This struct is used for parsing the incoming network request data into, as well +// as getting the response data from the testcase +struct HttpData { + QUrl url; + int status = 0; + QByteArray body; + QByteArray method; + quint16 port = 0; + QPair<quint8, quint8> version; + QHttpHeaders headers; +}; + +struct ResponseControl +{ + bool respond = true; + qsizetype responseChunkSize = -1; + bool readyForNextChunk = true; +}; + +// Simple HTTP server. Currently supports only one concurrent connection +class HttpTestServer : public QTcpServer +{ + Q_OBJECT + +public: + explicit HttpTestServer(QObject *parent = nullptr); + ~HttpTestServer() override; + + // Returns this server's URL for the testcase to send requests to + QUrl url(); + + enum class State { + ReadingMethod, + ReadingUrl, + ReadingStatus, + ReadingHeader, + ReadingBody, + AllDone + } state = State::ReadingMethod; + + enum class Method { + Unknown, + Head, + Get, + Put, + Patch, + Post, + Delete, + Custom, + } method = Method::Unknown; + + // Parsing helpers for incoming data => HttpData + bool readMethod(QTcpSocket *socket); + bool readUrl(QTcpSocket *socket); + bool readStatus(QTcpSocket *socket); + bool readHeaders(QTcpSocket *socket); + bool readBody(QTcpSocket *socket); + // Parsing-time buffer in case data is received a small chunk at a time (readyRead()) + QByteArray fragment; + + // Settable callback for testcase. Gives the received request data, and takes in response data + using Handler = std::function<void(const HttpData &request, HttpData &response, + ResponseControl &control)>; + void setHandler(Handler handler) { m_handler = std::move(handler); } + +private slots: + void handleConnected(); + void handleDataAvailable(); + +private: + QTcpSocket *m_socket = nullptr; + HttpData m_request; + Handler m_handler = nullptr; +}; + +#endif // QRESTACCESSSMANAGER_HTTPTESTSERVER_P_H diff --git a/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp new file mode 100644 index 0000000000..d6bdda76ca --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp @@ -0,0 +1,876 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "httptestserver_p.h" + +#if QT_CONFIG(http) +#include <QtNetwork/qhttpmultipart.h> +#endif +#include <QtNetwork/qrestaccessmanager.h> +#include <QtNetwork/qauthenticator.h> +#include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/qnetworkrequestfactory.h> +#include <QtNetwork/qrestreply.h> + +#include <QTest> +#include <QtTest/qsignalspy.h> + +#include <QtCore/private/qglobal_p.h> // for access to Qt's feature system +#include <QtCore/qbuffer.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qstringconverter.h> + +using namespace Qt::StringLiterals; +using namespace std::chrono_literals; + +using Header = QHttpHeaders::WellKnownHeader; + +class tst_QRestAccessManager : public QObject +{ + Q_OBJECT + +private slots: + void initialization(); + void destruction(); + void callbacks(); +#if QT_CONFIG(http) + void requests(); +#endif + void reply(); + void errors(); + void body(); + void json(); + void text(); + void textStreaming(); + +private: + void memberHandler(QRestReply &reply); + + friend class Transient; + QList<QNetworkReply*> m_expectedReplies; + QList<QNetworkReply*> m_actualReplies; +}; + +void tst_QRestAccessManager::initialization() +{ + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestAccessManager: QNetworkAccesManager is nullptr"); + QRestAccessManager manager1(nullptr); + QVERIFY(!manager1.networkAccessManager()); + + QNetworkAccessManager qnam; + QRestAccessManager manager2(&qnam); + QVERIFY(manager2.networkAccessManager()); +} + +void tst_QRestAccessManager::reply() +{ + QNetworkAccessManager qnam; + + QNetworkReply *nr = qnam.get(QNetworkRequest(QUrl{"someurl"})); + QRestReply rr1(nr); + QCOMPARE(rr1.networkReply(), nr); + + // Move-construct + QRestReply rr2(std::move(rr1)); + QCOMPARE(rr2.networkReply(), nr); + + // Move-assign + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestReply: QNetworkReply is nullptr"); + QRestReply rr3(nullptr); + rr3 = std::move(rr2); + QCOMPARE(rr3.networkReply(), nr); +} + +#define VERIFY_REPLY_OK(METHOD) \ +{ \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + QCOMPARE(serverSideRequest.method, METHOD); \ + QVERIFY(restReply.isSuccess()); \ + QVERIFY(!restReply.hasError()); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} + +#if QT_CONFIG(http) +void tst_QRestAccessManager::requests() +{ + // A basic test for each HTTP method against the local testserver. + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + request.setRawHeader("Content-Type"_ba, "text/plain"); // To silence missing content-type warn + QNetworkReply *networkReply = nullptr; + std::unique_ptr<QHttpMultiPart> multiPart; + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\"")); + part.setBody("multipart_text"); + QByteArray ioDeviceData{"io_device_data"_ba}; + QBuffer bufferIoDevice(&ioDeviceData); + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.status = 200; + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + + }); + auto callback = [&](QRestReply &reply) { networkReply = reply.networkReply(); }; + const QByteArray byteArrayData{"some_data"_ba}; + const QJsonObject jsonObjectData{{"key1", "value1"}, {"key2", "value2"}}; + const QJsonArray jsonArrayData{{"arrvalue1", "arrvalue2", QJsonObject{{"key1", "value1"}}}}; + const QVariantMap variantMapData{{"key1", "value1"}, {"key2", "value2"}}; + const QByteArray methodDELETE{"DELETE"_ba}; + const QByteArray methodHEAD{"HEAD"_ba}; + const QByteArray methodPOST{"POST"_ba}; + const QByteArray methodGET{"GET"_ba}; + const QByteArray methodPUT{"PUT"_ba}; + const QByteArray methodPATCH{"PATCH"_ba}; + const QByteArray methodCUSTOM{"FOOBAR"_ba}; + + // DELETE + manager.deleteResource(request, this, callback); + VERIFY_REPLY_OK(methodDELETE); + QCOMPARE(serverSideRequest.body, ""_ba); + + // HEAD + manager.head(request, this, callback); + VERIFY_REPLY_OK(methodHEAD); + QCOMPARE(serverSideRequest.body, ""_ba); + + // GET + manager.get(request, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(serverSideRequest.body, ""_ba); + + manager.get(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.get(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.get(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + // CUSTOM + manager.sendCustomRequest(request, methodCUSTOM, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodCUSTOM); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.sendCustomRequest(request, methodCUSTOM, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodCUSTOM); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + multiPart.reset(new QHttpMultiPart(QHttpMultiPart::FormDataType)); + multiPart->append(part); + manager.sendCustomRequest(request, methodCUSTOM, multiPart.get(), this, callback); + VERIFY_REPLY_OK(methodCUSTOM); + QVERIFY(serverSideRequest.body.contains("--boundary"_ba)); + QVERIFY(serverSideRequest.body.contains("multipart_text"_ba)); + + // POST + manager.post(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.post(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.post(request, QJsonDocument{jsonArrayData}, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).array(), jsonArrayData); + + manager.post(request, variantMapData, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType); + multiPart->append(part); + manager.post(request, multiPart.get(), this, callback); + VERIFY_REPLY_OK(methodPOST); + QVERIFY(serverSideRequest.body.contains("--boundary"_ba)); + QVERIFY(serverSideRequest.body.contains("multipart_text"_ba)); + + manager.post(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + // PUT + manager.put(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.put(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.put(request, QJsonDocument{jsonArrayData}, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).array(), jsonArrayData); + + manager.put(request, variantMapData, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType); + multiPart->append(part); + manager.put(request, multiPart.get(), this, callback); + VERIFY_REPLY_OK(methodPUT); + QVERIFY(serverSideRequest.body.contains("--boundary"_ba)); + QVERIFY(serverSideRequest.body.contains("multipart_text"_ba)); + + manager.put(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + // PATCH + manager.patch(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.patch(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.patch(request, QJsonDocument{jsonArrayData}, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).array(), jsonArrayData); + + manager.patch(request, variantMapData, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.patch(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + //These must NOT compile + //manager.get(request, [](){}); // callback without context object + //manager.get(request, ""_ba, [](){}); // callback without context object + //manager.get(request, QString()); // wrong datatype + //manager.get(request, 123); // wrong datatype + //manager.post(request, QString()); // wrong datatype + //manager.put(request, 123); // wrong datatype + //manager.post(request); // data is required + //manager.put(request, QString()); // wrong datatype + //manager.put(request); // data is required + //manager.patch(request, 123); // wrong datatype + //manager.patch(request, QString()); // wrong datatype + //manager.patch(request); // data is required + //manager.deleteResource(request, "f"_ba); // data not allowed + //manager.head(request, "f"_ba); // data not allowed + //manager.post(request, ""_ba, this, [](int param){}); // Wrong callback signature + //manager.get(request, this, [](int param){}); // Wrong callback signature + //manager.sendCustomRequest(request, this, [](){}); // No verb && no data + //manager.sendCustomRequest(request, "FOOBAR", this, [](){}); // No verb || no data +} +#endif + +void tst_QRestAccessManager::memberHandler(QRestReply &reply) +{ + m_actualReplies.append(reply.networkReply()); +} + +// Class that is destroyed during an active request. +// Used to test that the callbacks won't be called in these cases +class Transient : public QObject +{ + Q_OBJECT +public: + explicit Transient(tst_QRestAccessManager *test) : QObject(test), m_test(test) {} + + void memberHandler(QRestReply &reply) + { + m_test->m_actualReplies.append(reply.networkReply()); + } + +private: + tst_QRestAccessManager *m_test = nullptr; +}; + +template <typename Functor, std::enable_if_t< + QtPrivate::AreFunctionsCompatible<void(*)(QRestReply&), Functor>::value, bool> = true> +inline constexpr bool isCompatibleCallback(Functor &&) { return true; } + +template <typename Functor, std::enable_if_t< + !QtPrivate::AreFunctionsCompatible<void(*)(QRestReply&), Functor>::value, bool> = true, + typename = void> +inline constexpr bool isCompatibleCallback(Functor &&) { return false; } + +void tst_QRestAccessManager::callbacks() +{ + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + + QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error + + auto lambdaHandler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; + Transient *transient = nullptr; + QByteArray data{"some_data"}; + + // Compile-time tests for callback signatures + static_assert(isCompatibleCallback([](QRestReply&){})); // Correct signature + static_assert(isCompatibleCallback(lambdaHandler)); + static_assert(isCompatibleCallback(&Transient::memberHandler)); + static_assert(isCompatibleCallback([](){})); // Less parameters are allowed + + static_assert(!isCompatibleCallback([](QString){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply*){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](const QString &){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply&, QString){})); // Too many parameters + + // -- Test without data + // Without callback using signals and slot + QNetworkReply* reply = manager.get(request); + m_expectedReplies.append(reply); + QObject::connect(reply, &QNetworkReply::finished, this, + [this, reply](){m_actualReplies.append(reply);}); + + // With lambda callback, without context object + m_expectedReplies.append(manager.get(request, nullptr, lambdaHandler)); + m_expectedReplies.append(manager.get(request, nullptr, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With lambda callback and context object + m_expectedReplies.append(manager.get(request, this, lambdaHandler)); + m_expectedReplies.append(manager.get(request, this, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With member callback and context object + m_expectedReplies.append(manager.get(request, this, &tst_QRestAccessManager::memberHandler)); + // With context object that is destroyed, there should be no callback or eg. crash. + transient = new Transient(this); + manager.get(request, transient, &Transient::memberHandler); // Reply not added to expecteds + delete transient; + + // Let requests finish + QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); + for (auto reply: m_actualReplies) { + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); + } + m_actualReplies.clear(); + m_expectedReplies.clear(); + + // -- Test with data + // With lambda callback, without context object + m_expectedReplies.append(manager.post(request, data, nullptr, lambdaHandler)); + m_expectedReplies.append(manager.post(request, data, nullptr, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With lambda callback and context object + m_expectedReplies.append(manager.post(request, data, this, lambdaHandler)); + m_expectedReplies.append(manager.post(request, data, this, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With member callback and context object + m_expectedReplies.append(manager.post(request, data, + this, &tst_QRestAccessManager::memberHandler)); + // With context object that is destroyed, there should be no callback or eg. crash + transient = new Transient(this); + manager.post(request, data, transient, &Transient::memberHandler); // Note: reply not expected + delete transient; + + // Let requests finish + QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); + for (auto reply: m_actualReplies) { + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + reply->deleteLater(); + } + m_actualReplies.clear(); + m_expectedReplies.clear(); + + // -- Test GET with data separately, as GET provides methods that are usable with and + // without data, and fairly easy to get the qrestaccessmanager.h template SFINAE subtly wrong. + // With lambda callback, without context object + m_expectedReplies.append(manager.get(request, data, nullptr, lambdaHandler)); + m_expectedReplies.append(manager.get(request, data, nullptr, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With lambda callback and context object + m_expectedReplies.append(manager.get(request, data, this, lambdaHandler)); + m_expectedReplies.append(manager.get(request, data, this, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With member callback and context object + m_expectedReplies.append(manager.get(request, data, + this, &tst_QRestAccessManager::memberHandler)); + // With context object that is destroyed, there should be no callback or eg. crash + transient = new Transient(this); + manager.get(request, data, transient, &Transient::memberHandler); // Reply not added + delete transient; + + // Let requests finish + QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); + for (auto reply: m_actualReplies) { + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); + } + m_actualReplies.clear(); + m_expectedReplies.clear(); +} + +void tst_QRestAccessManager::destruction() +{ + std::unique_ptr<QNetworkAccessManager> qnam = std::make_unique<QNetworkAccessManager>(); + std::unique_ptr<QRestAccessManager> manager = std::make_unique<QRestAccessManager>(qnam.get()); + QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error + m_expectedReplies.clear(); + m_actualReplies.clear(); + auto handler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; + + // Delete reply immediately, make sure nothing bad happens and that there is no callback + QNetworkReply *networkReply = manager->get(request, this, handler); + delete networkReply; + QTest::qWait(20); // allow some time for the callback to arrive (it shouldn't) + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 + + // Delete access manager immediately after request, make sure nothing bad happens + manager->get(request, this, handler); + manager->post(request, "data"_ba, this, handler); + QTest::ignoreMessage(QtWarningMsg, "Access manager destroyed while 2 requests were still" + " in progress"); + manager.reset(); + QTest::qWait(20); + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 + + // Destroy the underlying QNAM while requests in progress + manager = std::make_unique<QRestAccessManager>(qnam.get()); + manager->get(request, this, handler); + manager->post(request, "data"_ba, this, handler); + qnam.reset(); + QTest::qWait(20); + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 +} + +#define VERIFY_HTTP_ERROR_STATUS(STATUS) \ +{ \ + serverSideResponse.status = STATUS; \ + QRestReply restReply(manager.get(request)); \ + QTRY_VERIFY(restReply.networkReply()->isFinished()); \ + QVERIFY(!restReply.hasError()); \ + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); \ + QCOMPARE(restReply.error(), QNetworkReply::NetworkError::NoError); \ + QVERIFY(!restReply.isSuccess()); \ + restReply.networkReply()->deleteLater(); \ +} \ + +void tst_QRestAccessManager::errors() +{ + // Tests the distinction between HTTP and other (network/protocol) errors + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + + HttpData serverSideResponse; // The response data the server responds with + server.setHandler([&](const HttpData &, HttpData &response, ResponseControl &) { + response = serverSideResponse; + }); + + // Test few HTTP statuses in different categories + VERIFY_HTTP_ERROR_STATUS(301) // QNetworkReply::ProtocolUnknownError + VERIFY_HTTP_ERROR_STATUS(302) // QNetworkReply::ProtocolUnknownError + VERIFY_HTTP_ERROR_STATUS(400) // QNetworkReply::ProtocolInvalidOperationError + VERIFY_HTTP_ERROR_STATUS(401) // QNetworkReply::AuthenticationRequiredEror + VERIFY_HTTP_ERROR_STATUS(402) // QNetworkReply::UnknownContentError + VERIFY_HTTP_ERROR_STATUS(403) // QNetworkReply::ContentAccessDenied + VERIFY_HTTP_ERROR_STATUS(404) // QNetworkReply::ContentNotFoundError + VERIFY_HTTP_ERROR_STATUS(405) // QNetworkReply::ContentOperationNotPermittedError + VERIFY_HTTP_ERROR_STATUS(406) // QNetworkReply::UnknownContentError + VERIFY_HTTP_ERROR_STATUS(407) // QNetworkReply::ProxyAuthenticationRequiredError + VERIFY_HTTP_ERROR_STATUS(408) // QNetworkReply::UnknownContentError + VERIFY_HTTP_ERROR_STATUS(409) // QNetworkReply::ContentConflictError + VERIFY_HTTP_ERROR_STATUS(410) // QNetworkReply::ContentGoneError + VERIFY_HTTP_ERROR_STATUS(500) // QNetworkReply::InternalServerError + VERIFY_HTTP_ERROR_STATUS(501) // QNetworkReply::OperationNotImplementedError + VERIFY_HTTP_ERROR_STATUS(502) // QNetworkReply::UnknownServerError + VERIFY_HTTP_ERROR_STATUS(503) // QNetworkReply::ServiceUnavailableError + VERIFY_HTTP_ERROR_STATUS(504) // QNetworkReply::UnknownServerError + VERIFY_HTTP_ERROR_STATUS(505) // QNetworkReply::UnknownServerError + + { + // Test that actual network/protocol errors come through + QRestReply restReply(manager.get({})); // Empty url + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + restReply.networkReply()->deleteLater(); + } + + { + QRestReply restReply(manager.get(QNetworkRequest{{"http://non-existent.foo.bar.test"}})); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::HostNotFoundError); + restReply.networkReply()->deleteLater(); + } + + { + QRestReply restReply(manager.get(request)); + restReply.networkReply()->abort(); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::OperationCanceledError); + restReply.networkReply()->deleteLater(); + } +} + +void tst_QRestAccessManager::body() +{ + // Test using QRestReply::body() data accessor + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + QNetworkReply *networkReply = nullptr; + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + }); + + { + serverSideResponse.status = 200; + serverSideResponse.body = "some_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.readBody(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + serverSideResponse.status = 200; + serverSideResponse.body = ""_ba; // Empty + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.readBody(), serverSideResponse.body); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + serverSideResponse.status = 500; + serverSideResponse.body = "some_other_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.readBody(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } +} + +void tst_QRestAccessManager::json() +{ + // Tests using QRestReply::readJson() + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + QNetworkReply *networkReply = nullptr; + QJsonDocument responseJsonDocument; + std::optional<QJsonDocument> json; + QJsonParseError parseError; + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.status = 200; + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + }); + + { + // Test receiving valid json object + serverSideResponse.body = "{\"key1\":\"value1\",""\"key2\":\"value2\"}\n"_ba; + networkReply = manager.get(request); + // Read unfinished reply + QVERIFY(!networkReply->isFinished()); + QTest::ignoreMessage(QtWarningMsg, "readJson() called on an unfinished reply, ignoring"); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; // Reset to impossible value + QRestReply restReply(networkReply); + QVERIFY(!restReply.readJson(&parseError)); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + // Read finished reply + QTRY_VERIFY(networkReply->isFinished()); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.readJson(&parseError); + QVERIFY(json); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isObject()); + QCOMPARE(responseJsonDocument["key1"], "value1"); + QCOMPARE(responseJsonDocument["key2"], "value2"); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + // Test receiving an invalid json object + serverSideResponse.body = "foobar"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + const auto json = restReply.readJson(&parseError); + networkReply->deleteLater(); + networkReply = nullptr; + QCOMPARE_EQ(json, std::nullopt); + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); + QCOMPARE_GT(parseError.offset, 0); + } + + { + // Test receiving valid json array + serverSideResponse.body = "[\"foo\", \"bar\"]\n"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.readJson(&parseError); + networkReply->deleteLater(); + networkReply = nullptr; + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + QVERIFY(json); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isArray()); + QCOMPARE(responseJsonDocument.array().size(), 2); + QCOMPARE(responseJsonDocument[0].toString(), "foo"_L1); + QCOMPARE(responseJsonDocument[1].toString(), "bar"_L1); + } +} + +#define VERIFY_TEXT_REPLY_OK \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + responseString = restReply.readText(); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ + QCOMPARE(responseString, sourceString); \ +} + +#define VERIFY_TEXT_REPLY_ERROR(WARNING_MESSAGE) \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ + QTest::ignoreMessage(QtWarningMsg, WARNING_MESSAGE); \ + QRestReply restReply(networkReply); \ + responseString = restReply.readText(); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ + QVERIFY(responseString.isEmpty()); \ +} + +void tst_QRestAccessManager::text() +{ + // Test using QRestReply::text() data accessor with various text encodings + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + QNetworkReply *networkReply = nullptr; + QJsonObject responseJsonObject; + + QStringEncoder encUTF8("UTF-8"); + QStringEncoder encUTF16("UTF-16"); + QStringEncoder encUTF32("UTF-32"); + QString responseString; + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.status = 200; + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + }); + + const QString sourceString("this is a string"_L1); + + // Charset parameter of Content-Type header may specify non-UTF-8 character encoding. + // + // QString is UTF-16, and in the tests below we encode the response data to various + // charset encodings (into byte arrays). When we get the response data, the text() + // should consider the indicated charset and convert it to an UTF-16 QString => the returned + // QString from text() should match with the original (UTF-16) QString. + + // Successful UTF-8 (explicit) + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-8"_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-8 (obfuscated) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=\"UT\\F-8\""_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-8 (empty charset) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=\"\""_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-8 (implicit) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain"_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-16 + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-16"_ba); + serverSideResponse.body = encUTF16(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-16, parameter case insensitivity + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; chARset=uTf-16"_ba); + serverSideResponse.body = encUTF16(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-32 + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-32"_ba); + serverSideResponse.body = encUTF32(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-32 with spec-wise allowed extra trailing content in the Content-Type header value + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, + "text(this is a \\)comment)/ (this (too)) plain; charset = \"UTF-32\";extraparameter=bar"_ba); + serverSideResponse.body = encUTF32(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-32 with spec-wise allowed extra leading content in the Content-Type header value + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, + "text/plain; extraparameter=bar;charset = \"UT\\F-32\""_ba); + serverSideResponse.body = encUTF32(sourceString); + VERIFY_TEXT_REPLY_OK; + + { + // Unsuccessful UTF-32, wrong encoding indicated (indicated UTF-32 but data is UTF-8) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-32"_ba); + serverSideResponse.body = encUTF8(sourceString); + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + responseString = restReply.readText(); + QCOMPARE_NE(responseString, sourceString); + networkReply->deleteLater(); + networkReply = nullptr; + } + + // Unsupported encoding + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=foo"_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_ERROR("readText(): Charset \"foo\" is not supported") + + // Broken UTF-8 + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-8"_ba); + serverSideResponse.body = "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0"; // invalid characters + VERIFY_TEXT_REPLY_ERROR("readText(): Decoding error occurred"); +} + +void tst_QRestAccessManager::textStreaming() +{ + // Tests textual data received in chunks + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + + // Create long text data + const QString expectedData = u"사랑abcd€fghiklmnΩpqrstuvwx愛사랑A사랑BCD€FGHIJKLMNΩPQRsTUVWXYZ愛"_s; + QString cumulativeReceivedText; + QStringEncoder encUTF8("UTF-8"); + ResponseControl *responseControl = nullptr; + + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-8"_ba); + serverSideResponse.body = encUTF8(expectedData); + serverSideResponse.status = 200; + + server.setHandler([&](const HttpData &, HttpData &response, ResponseControl &control) { + response = serverSideResponse; + responseControl = &control; // store for later + control.responseChunkSize = 5; // tell testserver to send data in chunks of this size + }); + + { + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + cumulativeReceivedText += restReply.readText(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, expectedData); + restReply.networkReply()->deleteLater(); + } + + { + cumulativeReceivedText.clear(); + // Broken UTF-8 characters after first five ok characters + serverSideResponse.body = + "12345"_ba + "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0" + "abcde"_ba; + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + static bool firstTime = true; + if (!firstTime) // First text part is without warnings + QTest::ignoreMessage(QtWarningMsg, "readText(): Decoding error occurred"); + firstTime = false; + cumulativeReceivedText += restReply.readText(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, "12345"_ba); + restReply.networkReply()->deleteLater(); + } +} + +QTEST_MAIN(tst_QRestAccessManager) +#include "tst_qrestaccessmanager.moc" diff --git a/tests/auto/network/kernel/CMakeLists.txt b/tests/auto/network/kernel/CMakeLists.txt index 52928e7aa1..df87e9d58e 100644 --- a/tests/auto/network/kernel/CMakeLists.txt +++ b/tests/auto/network/kernel/CMakeLists.txt @@ -1,17 +1,21 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -if(QT_FEATURE_qdnslookup) +if(QT_FEATURE_dnslookup AND (QT_FEATURE_libresolv OR WIN32)) add_subdirectory(qdnslookup) add_subdirectory(qdnslookup_appless) endif() if(QT_FEATURE_networkinterface) + add_subdirectory(qnetworkaddressentry) add_subdirectory(qnetworkproxyfactory) add_subdirectory(qnetworkinterface) endif() -add_subdirectory(qnetworkproxy) -add_subdirectory(qnetworkdatagram) -add_subdirectory(qnetworkaddressentry) +if(QT_FEATURE_networkproxy) + add_subdirectory(qnetworkproxy) +endif() +if(QT_FEATURE_udpsocket) + add_subdirectory(qnetworkdatagram) +endif() add_subdirectory(qhostaddress) if(QT_FEATURE_private_tests AND NOT MACOS AND NOT INTEGRITY) add_subdirectory(qhostinfo) diff --git a/tests/auto/network/kernel/qauthenticator/CMakeLists.txt b/tests/auto/network/kernel/qauthenticator/CMakeLists.txt index 0989bd7395..552e9065ed 100644 --- a/tests/auto/network/kernel/qauthenticator/CMakeLists.txt +++ b/tests/auto/network/kernel/qauthenticator/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qauthenticator LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/kernel/qauthenticator/tst_qauthenticator.cpp b/tests/auto/network/kernel/qauthenticator/tst_qauthenticator.cpp index 744dabd3da..1cd1b6a63b 100644 --- a/tests/auto/network/kernel/qauthenticator/tst_qauthenticator.cpp +++ b/tests/auto/network/kernel/qauthenticator/tst_qauthenticator.cpp @@ -1,11 +1,12 @@ // 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/QString> #include <QTest> #include <QtCore/QCoreApplication> #include <QtNetwork/QAuthenticator> +#include <QtNetwork/QHttpHeaders> #include <private/qauthenticator_p.h> @@ -60,8 +61,8 @@ void tst_QAuthenticator::basicAuth() QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(auth); QCOMPARE(priv->phase, QAuthenticatorPrivate::Start); - QList<QPair<QByteArray, QByteArray> > headers; - headers << qMakePair(QByteArray("WWW-Authenticate"), "Basic " + data.toUtf8()); + QHttpHeaders headers; + headers.append(QByteArray("WWW-Authenticate"), "Basic " + data.toUtf8()); priv->parseHttpResponse(headers, /*isProxy = */ false, {}); QCOMPARE(auth.realm(), realm); @@ -103,13 +104,13 @@ void tst_QAuthenticator::ntlmAuth() QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(auth); QCOMPARE(priv->phase, QAuthenticatorPrivate::Start); - QList<QPair<QByteArray, QByteArray> > headers; + QHttpHeaders headers; // NTLM phase 1: negotiate // This phase of NTLM contains no information, other than what we're willing to negotiate // Current implementation uses flags: // NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET - headers << qMakePair(QByteArrayLiteral("WWW-Authenticate"), QByteArrayLiteral("NTLM")); + headers.append(QByteArrayLiteral("WWW-Authenticate"), QByteArrayLiteral("NTLM")); priv->parseHttpResponse(headers, /*isProxy = */ false, {}); if (sso) QVERIFY(priv->calculateResponse("GET", "/", u"").startsWith("NTLM ")); @@ -118,7 +119,7 @@ void tst_QAuthenticator::ntlmAuth() // NTLM phase 2: challenge headers.clear(); - headers << qMakePair(QByteArray("WWW-Authenticate"), "NTLM " + data.toUtf8()); + headers.append(QByteArray("WWW-Authenticate"), "NTLM " + data.toUtf8()); priv->parseHttpResponse(headers, /*isProxy = */ false, {}); QEXPECT_FAIL("with-realm", "NTLM authentication code doesn't extract the realm", Continue); @@ -143,10 +144,10 @@ void tst_QAuthenticator::sha256AndMd5Digest() QVERIFY(priv->isMethodSupported("digest")); // sanity check QCOMPARE(priv->phase, QAuthenticatorPrivate::Start); - QList<QPair<QByteArray, QByteArray>> headers; + QHttpHeaders headers; // Put sha256 first, so that its parsed first... - headers.emplace_back("WWW-Authenticate", sha256); - headers.emplace_back("WWW-Authenticate", md5); + headers.append("WWW-Authenticate", sha256); + headers.append("WWW-Authenticate", md5); priv->parseHttpResponse(headers, false, QString()); QByteArray response = priv->calculateResponse("GET", "/index", {}); diff --git a/tests/auto/network/kernel/qdnslookup/CMakeLists.txt b/tests/auto/network/kernel/qdnslookup/CMakeLists.txt index b069ba3ddc..ea539ecbe0 100644 --- a/tests/auto/network/kernel/qdnslookup/CMakeLists.txt +++ b/tests/auto/network/kernel/qdnslookup/CMakeLists.txt @@ -5,11 +5,18 @@ ## tst_qdnslookup Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdnslookup LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdnslookup SOURCES tst_qdnslookup.cpp LIBRARIES Qt::Network + Qt::TestPrivate ) qt_internal_extend_target(tst_qdnslookup CONDITION WIN32 diff --git a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp index 1b7e8db7b1..af3a74a498 100644 --- a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp +++ b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp @@ -1,10 +1,10 @@ // Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> // Copyright (C) 2016 Intel Corporation. -// 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 <QTest> #include <QSignalSpy> +#include <QtTest/private/qpropertytesthelper_p.h> #include <QtNetwork/QDnsLookup> @@ -13,6 +13,13 @@ #include <QtNetwork/QNetworkDatagram> #include <QtNetwork/QUdpSocket> +#if QT_CONFIG(networkproxy) +# include <QtNetwork/QNetworkProxyFactory> +#endif +#if QT_CONFIG(ssl) +# include <QtNetwork/QSslSocket> +#endif + #ifdef Q_OS_UNIX # include <QtCore/QFile> #else @@ -35,12 +42,21 @@ class tst_QDnsLookup: public QObject QString domainName(const QString &input); QString domainNameList(const QString &input); QStringList domainNameListAlternatives(const QString &input); + + std::unique_ptr<QDnsLookup> lookupCommon(QDnsLookup::Type type, const QString &domain, + const QHostAddress &server = {}, quint16 port = 0, + QDnsLookup::Protocol protocol = QDnsLookup::Standard); + QStringList formatReply(const QDnsLookup *lookup) const; + + void setNameserver_helper(QDnsLookup::Protocol protocol); public slots: void initTestCase(); private slots: void lookupLocalhost(); void lookupRoot(); + void lookupNxDomain_data(); + void lookupNxDomain(); void lookup_data(); void lookup(); void lookupIdn_data() { lookup_data(); } @@ -51,7 +67,10 @@ private slots: void setNameserverLoopback(); void setNameserver_data(); void setNameserver(); + void dnsOverTls_data(); + void dnsOverTls(); void bindingsAndProperties(); + void automatedBindings(); }; static constexpr qsizetype HeaderSize = 6 * sizeof(quint16); @@ -67,9 +86,11 @@ static const char preparedDnsQuery[] = "\x00\x00\x06\x00\x01" // <root domain> IN SOA ; -static QList<QHostAddress> systemNameservers() +static QList<QHostAddress> systemNameservers(QDnsLookup::Protocol protocol) { QList<QHostAddress> result; + if (protocol != QDnsLookup::Standard) + return result; #ifdef Q_OS_WIN ULONG infosize = 0; @@ -84,25 +105,29 @@ static QList<QHostAddress> systemNameservers() } } #else - QFile f("/etc/resolv.conf"); - if (!f.open(QIODevice::ReadOnly)) - return result; - - while (!f.atEnd()) { - static const char command[] = "nameserver"; - QByteArray line = f.readLine().simplified(); - if (!line.startsWith(command)) - continue; - - QString addr = QLatin1StringView(line).mid(sizeof(command)); - result.emplaceBack(addr); - } + auto parseFile = [&](QLatin1StringView path) { + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) + return; + + while (!f.atEnd()) { + static const char command[] = "nameserver"; + QByteArray line = f.readLine().simplified(); + if (!line.startsWith(command)) + continue; + + QString addr = QLatin1StringView(line).mid(sizeof(command)); + result.emplaceBack(addr); + } + }; + parseFile("/etc/resolv.conf"_L1); + parseFile("/run/systemd/resolve/resolv.conf"_L1); #endif return result; } -static QList<QHostAddress> globalPublicNameservers() +static QList<QHostAddress> globalPublicNameservers(QDnsLookup::Protocol proto) { const char *const candidates[] = { // Google's dns.google @@ -117,6 +142,56 @@ static QList<QHostAddress> globalPublicNameservers() //"9.9.9.9", "2620:fe::9", }; + auto udpSendAndReceive = [](const QHostAddress &addr, QByteArray &data) { + QUdpSocket socket; + socket.connectToHost(addr, 53); + if (socket.waitForConnected(1)) + socket.write(data); + + if (!socket.waitForReadyRead(1000)) + return socket.errorString(); + + QNetworkDatagram dgram = socket.receiveDatagram(); + if (!dgram.isValid()) + return socket.errorString(); + + data = dgram.data(); + return QString(); + }; + + auto tlsSendAndReceive = [](const QHostAddress &addr, QByteArray &data) { +#if QT_CONFIG(ssl) + QSslSocket socket; + QDeadlineTimer timeout(2000); + socket.connectToHostEncrypted(addr.toString(), 853); + if (!socket.waitForEncrypted(2000)) + return socket.errorString(); + + quint16 size = qToBigEndian<quint16>(data.size()); + socket.write(reinterpret_cast<char *>(&size), sizeof(size)); + socket.write(data); + + if (!socket.waitForReadyRead(timeout.remainingTime())) + return socket.errorString(); + if (socket.bytesAvailable() < 2) + return u"protocol error"_s; + + socket.read(reinterpret_cast<char *>(&size), sizeof(size)); + size = qFromBigEndian(size); + + while (socket.bytesAvailable() < size) { + int remaining = timeout.remainingTime(); + if (remaining < 0 || !socket.waitForReadyRead(remaining)) + return socket.errorString(); + } + + data = socket.readAll(); + return QString(); +#else + return u"SSL/TLS support not compiled in"_s; +#endif + }; + QList<QHostAddress> result; QRandomGenerator &rng = *QRandomGenerator::system(); for (auto name : candidates) { @@ -127,23 +202,18 @@ static QList<QHostAddress> globalPublicNameservers() char *ptr = data.data(); qToBigEndian(id, ptr); - QUdpSocket socket; - socket.connectToHost(addr, 53); - if (socket.waitForConnected(1)) - socket.write(data); - - if (!socket.waitForReadyRead(1000)) { - qDebug() << addr << "discarded:" << socket.errorString(); + QString errorString = [&] { + switch (proto) { + case QDnsLookup::Standard: return udpSendAndReceive(addr, data); + case QDnsLookup::DnsOverTls: return tlsSendAndReceive(addr, data); + } + Q_UNREACHABLE(); + }(); + if (!errorString.isEmpty()) { + qDebug() << addr << "discarded:" << errorString; continue; } - QNetworkDatagram dgram = socket.receiveDatagram(); - if (!dgram.isValid()) { - qDebug() << addr << "discarded:" << socket.errorString(); - continue; - } - - data = dgram.data(); ptr = data.data(); if (data.size() < HeaderSize) { qDebug() << addr << "discarded: reply too small"; @@ -170,6 +240,11 @@ void tst_QDnsLookup::initTestCase() { if (qgetenv("QTEST_ENVIRONMENT") == "ci") dnsServersMustWork = true; + +#if QT_CONFIG(networkproxy) + // for DNS-over-TLS + QNetworkProxyFactory::setUseSystemConfiguration(true); +#endif } QString tst_QDnsLookup::domainName(const QString &input) @@ -190,9 +265,9 @@ QString tst_QDnsLookup::domainName(const QString &input) QString tst_QDnsLookup::domainNameList(const QString &input) { - QStringList list = input.split(QLatin1Char(';')); + const QStringList list = input.split(QLatin1Char(';')); QString result; - foreach (const QString &s, list) { + for (const QString &s : list) { if (!result.isEmpty()) result += ';'; result += domainName(s); @@ -208,238 +283,277 @@ QStringList tst_QDnsLookup::domainNameListAlternatives(const QString &input) return alternatives; } +std::unique_ptr<QDnsLookup> +tst_QDnsLookup::lookupCommon(QDnsLookup::Type type, const QString &domain, + const QHostAddress &server, quint16 port, + QDnsLookup::Protocol protocol) +{ + auto lookup = std::make_unique<QDnsLookup>(type, domainName(domain), protocol, server, port); + QObject::connect(lookup.get(), &QDnsLookup::finished, + &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); + lookup->lookup(); + QTestEventLoop::instance().enterLoopMSecs(Timeout); + + QDnsLookup::Error error = lookup->error(); + if (QTestEventLoop::instance().timeout()) + error = QDnsLookup::TimeoutError; + + if (!dnsServersMustWork && (error == QDnsLookup::ServerFailureError + || error == QDnsLookup::ServerRefusedError + || error == QDnsLookup::TimeoutError)) { + // It's not a QDnsLookup problem if the server refuses to answer the query. + // This happens for queries of type ANY through Dnsmasq, for example. + [&] { + auto me = QMetaEnum::fromType<QDnsLookup::Type>(); + QString msg = u"Server refused or was unable to answer query; %1 type %3: %2"_s + .arg(domain, lookup->errorString(), QString(me.valueToKey(int(type)))); + QSKIP(msg.toLocal8Bit()); + }(); + return {}; + } + + return lookup; +} + +QStringList tst_QDnsLookup::formatReply(const QDnsLookup *lookup) const +{ + QStringList result; + QString domain = lookup->name(); + + auto shorter = [this](QString value) { + const QString &ending = usingIdnDomain ? idnDomain : normalDomain; + if (value.endsWith(ending)) + value.chop(ending.size()); + else + value += u'.'; + return value; + }; + + for (const QDnsMailExchangeRecord &rr : lookup->mailExchangeRecords()) { + QString entry = u"MX %1 %2"_s.arg(rr.preference(), 5).arg(shorter(rr.exchange())); + if (rr.name() != domain) + entry = "MX unexpected label to "_L1 + rr.name(); + result.append(std::move(entry)); + } + + for (const QDnsServiceRecord &rr : lookup->serviceRecords()) { + QString entry = u"SRV %1 %2 %3 %4"_s.arg(rr.priority(), 5).arg(rr.weight()) + .arg(rr.port()).arg(shorter(rr.target())); + if (rr.name() != domain) + entry = "SRV unexpected label to "_L1 + rr.name(); + result.append(std::move(entry)); + } + + auto addNameRecords = [&](QLatin1StringView rrtype, const QList<QDnsDomainNameRecord> &rrset) { + for (const QDnsDomainNameRecord &rr : rrset) { + QString entry = u"%1 %2"_s.arg(rrtype, shorter(rr.value())); + if (rr.name() != domain) + entry = rrtype + " unexpected label to "_L1 + rr.name(); + result.append(std::move(entry)); + } + }; + addNameRecords("NS"_L1, lookup->nameServerRecords()); + addNameRecords("PTR"_L1, lookup->pointerRecords()); + addNameRecords("CNAME"_L1, lookup->canonicalNameRecords()); + + for (const QDnsHostAddressRecord &rr : lookup->hostAddressRecords()) { + if (rr.name() != domain) + continue; // A and AAAA may appear as extra records in the answer section + QHostAddress addr = rr.value(); + result.append(u"%1 %2"_s + .arg(addr.protocol() == QHostAddress::IPv6Protocol ? "AAAA" : "A", + addr.toString())); + } + + for (const QDnsTextRecord &rr : lookup->textRecords()) { + QString entry = "TXT"_L1; + for (const QByteArray &data : rr.values()) { + entry += u' '; + entry += QDebug::toString(data); + } + result.append(std::move(entry)); + } + + for (const QDnsTlsAssociationRecord &rr : lookup->tlsAssociationRecords()) { + QString entry = u"TLSA %1 %2 %3 %4"_s.arg(int(rr.usage())).arg(int(rr.selector())) + .arg(int(rr.matchType())).arg(rr.value().toHex().toUpper()); + if (rr.name() != domain) + entry = "TLSA unexpected label to "_L1 + rr.name(); + result.append(std::move(entry)); + } + + result.sort(); + return result; +} + void tst_QDnsLookup::lookupLocalhost() { - QDnsLookup lookup(QDnsLookup::Type::A, u"localhost"_s); - lookup.lookup(); - QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); - QCOMPARE(lookup.error(), QDnsLookup::NoError); + auto lookup = lookupCommon(QDnsLookup::Type::A, u"localhost."_s); + QVERIFY(lookup); + QCOMPARE(lookup->error(), QDnsLookup::NoError); - QList<QDnsHostAddressRecord> hosts = lookup.hostAddressRecords(); + QList<QDnsHostAddressRecord> hosts = lookup->hostAddressRecords(); QCOMPARE(hosts.size(), 1); QCOMPARE(hosts.at(0).value(), QHostAddress::LocalHost); - QVERIFY2(hosts.at(0).name().startsWith(lookup.name()), + QVERIFY2(hosts.at(0).name().startsWith(lookup->name()), qPrintable(hosts.at(0).name())); } void tst_QDnsLookup::lookupRoot() { - QDnsLookup lookup(QDnsLookup::Type::NS, u""_s); - lookup.lookup(); - QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); - QCOMPARE(lookup.error(), QDnsLookup::NoError); +#ifdef Q_OS_WIN + QSKIP("This test fails on Windows as it seems to treat the lookup as a local one."); +#else + auto lookup = lookupCommon(QDnsLookup::Type::NS, u""_s); + if (!lookup) + return; + QCOMPARE(lookup->error(), QDnsLookup::NoError); - const QList<QDnsDomainNameRecord> servers = lookup.nameServerRecords(); + const QList<QDnsDomainNameRecord> servers = lookup->nameServerRecords(); QVERIFY(!servers.isEmpty()); for (const QDnsDomainNameRecord &ns : servers) { QCOMPARE(ns.name(), QString()); QVERIFY(ns.value().endsWith(".root-servers.net")); } +#endif +} + +void tst_QDnsLookup::lookupNxDomain_data() +{ + QTest::addColumn<QDnsLookup::Type>("type"); + QTest::addColumn<QString>("domain"); + + QTest::newRow("a") << QDnsLookup::A << "invalid.invalid"; + QTest::newRow("aaaa") << QDnsLookup::AAAA << "invalid.invalid"; + QTest::newRow("any") << QDnsLookup::ANY << "invalid.invalid"; + QTest::newRow("mx") << QDnsLookup::MX << "invalid.invalid"; + QTest::newRow("ns") << QDnsLookup::NS << "invalid.invalid"; + QTest::newRow("ptr") << QDnsLookup::PTR << "invalid.invalid"; + QTest::newRow("srv") << QDnsLookup::SRV << "invalid.invalid"; + QTest::newRow("txt") << QDnsLookup::TXT << "invalid.invalid"; +} + +void tst_QDnsLookup::lookupNxDomain() +{ + QFETCH(QDnsLookup::Type, type); + QFETCH(QString, domain); + + auto lookup = lookupCommon(type, domain); + if (!lookup) + return; + QCOMPARE(lookup->name(), domainName(domain)); + QCOMPARE(lookup->type(), type); + QCOMPARE(lookup->error(), QDnsLookup::NotFoundError); } void tst_QDnsLookup::lookup_data() { - QTest::addColumn<int>("type"); + QTest::addColumn<QDnsLookup::Type>("type"); QTest::addColumn<QString>("domain"); - QTest::addColumn<int>("error"); - QTest::addColumn<QString>("cname"); - QTest::addColumn<QString>("host"); - QTest::addColumn<QString>("mx"); - QTest::addColumn<QString>("ns"); - QTest::addColumn<QString>("ptr"); - QTest::addColumn<QString>("srv"); - QTest::addColumn<QString>("txt"); - - QTest::newRow("a-notfound") << int(QDnsLookup::A) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("a-single") << int(QDnsLookup::A) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << ""; - QTest::newRow("a-multi") << int(QDnsLookup::A) << "a-multi" << int(QDnsLookup::NoError) << "" << "192.0.2.1;192.0.2.2;192.0.2.3" << "" << "" << "" << "" << ""; - QTest::newRow("aaaa-notfound") << int(QDnsLookup::AAAA) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("aaaa-single") << int(QDnsLookup::AAAA) << "aaaa-single" << int(QDnsLookup::NoError) << "" << "2001:db8::1" << "" << "" << "" << "" << ""; - QTest::newRow("aaaa-multi") << int(QDnsLookup::AAAA) << "aaaa-multi" << int(QDnsLookup::NoError) << "" << "2001:db8::1;2001:db8::2;2001:db8::3" << "" << "" << "" << "" << ""; - - QTest::newRow("any-notfound") << int(QDnsLookup::ANY) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("any-a-single") << int(QDnsLookup::ANY) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << ""; - QTest::newRow("any-a-plus-aaaa") << int(QDnsLookup::ANY) << "a-plus-aaaa" << int(QDnsLookup::NoError) << "" << "198.51.100.1;2001:db8::1:1" << "" << "" << "" << "" << ""; - QTest::newRow("any-multi") << int(QDnsLookup::ANY) << "multi" << int(QDnsLookup::NoError) << "" << "198.51.100.1;198.51.100.2;198.51.100.3;2001:db8::1:1;2001:db8::1:2" << "" << "" << "" << "" << ""; - - QTest::newRow("mx-notfound") << int(QDnsLookup::MX) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("mx-single") << int(QDnsLookup::MX) << "mx-single" << int(QDnsLookup::NoError) << "" << "" << "10 multi" << "" << "" << "" << ""; - QTest::newRow("mx-single-cname") << int(QDnsLookup::MX) << "mx-single-cname" << int(QDnsLookup::NoError) << "" << "" << "10 cname" << "" << "" << "" << ""; - QTest::newRow("mx-multi") << int(QDnsLookup::MX) << "mx-multi" << int(QDnsLookup::NoError) << "" << "" << "10 multi;20 a-single" << "" << "" << "" << ""; - QTest::newRow("mx-multi-sameprio") << int(QDnsLookup::MX) << "mx-multi-sameprio" << int(QDnsLookup::NoError) << "" << "" - << "10 multi;10 a-single|" - "10 a-single;10 multi" << "" << "" << "" << ""; - - QTest::newRow("ns-notfound") << int(QDnsLookup::NS) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("ns-single") << int(QDnsLookup::NS) << "ns-single" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net." << "" << "" << ""; - QTest::newRow("ns-multi") << int(QDnsLookup::NS) << "ns-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net.;ns12.cloudns.net." << "" << "" << ""; - - QTest::newRow("ptr-notfound") << int(QDnsLookup::PTR) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; + QTest::addColumn<QString>("expected"); + + QTest::newRow("a-single") << QDnsLookup::A << "a-single" + << "A 192.0.2.1"; + QTest::newRow("a-multi") << QDnsLookup::A << "a-multi" + << "A 192.0.2.1;A 192.0.2.2;A 192.0.2.3"; + QTest::newRow("aaaa-single") << QDnsLookup::AAAA << "aaaa-single" + << "AAAA 2001:db8::1"; + QTest::newRow("aaaa-multi") << QDnsLookup::AAAA << "aaaa-multi" + << "AAAA 2001:db8::1;AAAA 2001:db8::2;AAAA 2001:db8::3"; + + QTest::newRow("any-a-single") << QDnsLookup::ANY << "a-single" + << "A 192.0.2.1"; + QTest::newRow("any-a-plus-aaaa") << QDnsLookup::ANY << "a-plus-aaaa" + << "A 198.51.100.1;AAAA 2001:db8::1:1"; + QTest::newRow("any-multi") << QDnsLookup::ANY << "multi" + << "A 198.51.100.1;A 198.51.100.2;A 198.51.100.3;" + "AAAA 2001:db8::1:1;AAAA 2001:db8::1:2" ; + + QTest::newRow("mx-single") << QDnsLookup::MX << "mx-single" + << "MX 10 multi"; + QTest::newRow("mx-single-cname") << QDnsLookup::MX << "mx-single-cname" + << "MX 10 cname"; + QTest::newRow("mx-multi") << QDnsLookup::MX << "mx-multi" + << "MX 10 multi;MX 20 a-single"; + QTest::newRow("mx-multi-sameprio") << QDnsLookup::MX << "mx-multi-sameprio" + << "MX 10 a-single;MX 10 multi"; + + QTest::newRow("ns-single") << QDnsLookup::NS << "ns-single" + << "NS ns11.cloudns.net."; + QTest::newRow("ns-multi") << QDnsLookup::NS << "ns-multi" + << "NS ns11.cloudns.net.;NS ns12.cloudns.net."; + #if 0 // temporarily disabled since the new hosting provider can't insert // PTR records outside of the in-addr.arpa zone - QTest::newRow("ptr-single") << int(QDnsLookup::PTR) << "ptr-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "a-single" << "" << ""; + QTest::newRow("ptr-single") << QDnsLookup::PTR << "ptr-single" + << "PTR a-single"; #endif - - QTest::newRow("srv-notfound") << int(QDnsLookup::SRV) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("srv-single") << int(QDnsLookup::SRV) << "_echo._tcp.srv-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "5 0 7 multi" << ""; - QTest::newRow("srv-prio") << int(QDnsLookup::SRV) << "_echo._tcp.srv-prio" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "1 0 7 multi;2 0 7 a-plus-aaaa" << ""; - QTest::newRow("srv-weighted") << int(QDnsLookup::SRV) << "_echo._tcp.srv-weighted" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" - << "5 75 7 multi;5 25 7 a-plus-aaaa|" - "5 25 7 a-plus-aaaa;5 75 7 multi" << ""; - QTest::newRow("srv-multi") << int(QDnsLookup::SRV) << "_echo._tcp.srv-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" - << "1 50 7 multi;2 50 7 a-single;2 50 7 aaaa-single;3 50 7 a-multi|" - "1 50 7 multi;2 50 7 aaaa-single;2 50 7 a-single;3 50 7 a-multi" << ""; - - QTest::newRow("txt-notfound") << int(QDnsLookup::TXT) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << ""; - QTest::newRow("txt-single") << int(QDnsLookup::TXT) << "txt-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello"; - QTest::newRow("txt-multi-onerr") << int(QDnsLookup::TXT) << "txt-multi-onerr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" - << QString::fromLatin1("Hello\0World", sizeof("Hello\0World") - 1); - QTest::newRow("txt-multi-multirr") << int(QDnsLookup::TXT) << "txt-multi-multirr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello;World"; + QTest::newRow("ptr-1.1.1.1") << QDnsLookup::PTR << "1.1.1.1.in-addr.arpa." + << "PTR one.one.one.one."; + QTest::newRow("ptr-8.8.8.8") << QDnsLookup::PTR << "8.8.8.8.in-addr.arpa." + << "PTR dns.google."; + QTest::newRow("ptr-2001:4860:4860::8888") + << QDnsLookup::PTR << "8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa." + << "PTR dns.google."; + QTest::newRow("ptr-2606:4700:4700::1111") + << QDnsLookup::PTR << "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa." + << "PTR one.one.one.one."; + + QTest::newRow("srv-single") << QDnsLookup::SRV << "_echo._tcp.srv-single" + << "SRV 5 0 7 multi"; + QTest::newRow("srv-prio") << QDnsLookup::SRV << "_echo._tcp.srv-prio" + << "SRV 1 0 7 multi;SRV 2 0 7 a-plus-aaaa"; + QTest::newRow("srv-weighted") << QDnsLookup::SRV << "_echo._tcp.srv-weighted" + << "SRV 5 25 7 a-plus-aaaa;SRV 5 75 7 multi"; + QTest::newRow("srv-multi") << QDnsLookup::SRV << "_echo._tcp.srv-multi" + << "SRV 1 50 7 multi;" + "SRV 2 50 7 a-single;" + "SRV 2 50 7 aaaa-single;" + "SRV 3 50 7 a-multi"; + + QTest::newRow("tlsa") << QDnsLookup::Type::TLSA << "_25._tcp.multi" + << "TLSA 3 1 1 0123456789ABCDEFFEDCBA9876543210" + "0123456789ABCDEFFEDCBA9876543210"; + + QTest::newRow("txt-single") << QDnsLookup::TXT << "txt-single" + << "TXT \"Hello\""; + QTest::newRow("txt-multi-onerr") << QDnsLookup::TXT << "txt-multi-onerr" + << "TXT \"Hello\" \"World\""; + QTest::newRow("txt-multi-multirr") << QDnsLookup::TXT << "txt-multi-multirr" + << "TXT \"Hello\";TXT \"World\""; } void tst_QDnsLookup::lookup() { - QFETCH(int, type); + QFETCH(QDnsLookup::Type, type); QFETCH(QString, domain); - QFETCH(int, error); - QFETCH(QString, cname); - QFETCH(QString, host); - QFETCH(QString, mx); - QFETCH(QString, ns); - QFETCH(QString, ptr); - QFETCH(QString, srv); - QFETCH(QString, txt); - - // transform the inputs - domain = domainName(domain); - cname = domainName(cname); - ns = domainNameList(ns); - ptr = domainNameList(ptr); - - // SRV and MX have reply entries that can change order - // and we can't sort - QStringList mx_alternatives = domainNameListAlternatives(mx); - QStringList srv_alternatives = domainNameListAlternatives(srv); + QFETCH(QString, expected); - QDnsLookup lookup; - lookup.setType(static_cast<QDnsLookup::Type>(type)); - lookup.setName(domain); - lookup.lookup(); - QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); - - auto extraErrorMsg = [&] () { - QString result; - QTextStream str(&result); - str << "Actual error: " << lookup.error(); - if (QString errorString = lookup.errorString(); !errorString.isEmpty()) - str << " (" << errorString << ')'; - str << ", expected: " << error; - str << ", domain: " << domain; - if (!cname.isEmpty()) - str << ", cname: " << cname; - str << ", host: " << host; - if (!srv.isEmpty()) - str << " server: " << srv; - if (!mx.isEmpty()) - str << " mx: " << mx; - if (!ns.isEmpty()) - str << " ns: " << ns; - if (!ptr.isEmpty()) - str << " ptr: " << ptr; - return result.toLocal8Bit(); - }; - - if (!dnsServersMustWork && (lookup.error() == QDnsLookup::ServerFailureError - || lookup.error() == QDnsLookup::ServerRefusedError - || lookup.error() == QDnsLookup::TimeoutError)) { - // It's not a QDnsLookup problem if the server refuses to answer the query. - // This happens for queries of type ANY through Dnsmasq, for example. - qWarning("Server refused or was unable to answer query; %s", extraErrorMsg().constData()); + std::unique_ptr<QDnsLookup> lookup = lookupCommon(type, domain); + if (!lookup) return; - } - - QVERIFY2(int(lookup.error()) == error, extraErrorMsg()); - if (error == QDnsLookup::NoError) - QVERIFY(lookup.errorString().isEmpty()); - QCOMPARE(int(lookup.type()), type); - QCOMPARE(lookup.name(), domain); - - // canonical names - if (!cname.isEmpty()) { - QVERIFY(!lookup.canonicalNameRecords().isEmpty()); - const QDnsDomainNameRecord cnameRecord = lookup.canonicalNameRecords().first(); - QCOMPARE(cnameRecord.name(), domain); - QCOMPARE(cnameRecord.value(), cname); - } else { - QVERIFY(lookup.canonicalNameRecords().isEmpty()); - } - // host addresses - const QString hostName = cname.isEmpty() ? domain : cname; - QStringList addresses; - foreach (const QDnsHostAddressRecord &record, lookup.hostAddressRecords()) { - //reply may include A & AAAA records for nameservers, ignore them and only look at records matching the query - if (record.name() == hostName) - addresses << record.value().toString().toLower(); - } - addresses.sort(); - QCOMPARE(addresses.join(';'), host); - - // mail exchanges - QStringList mailExchanges; - foreach (const QDnsMailExchangeRecord &record, lookup.mailExchangeRecords()) { - QCOMPARE(record.name(), domain); - mailExchanges << QString::number(record.preference()) + QLatin1Char(' ') + record.exchange(); - } - QVERIFY2(mx_alternatives.contains(mailExchanges.join(';')), - qPrintable("Actual: " + mailExchanges.join(';') + "\nExpected one of:\n" + mx_alternatives.join('\n'))); - - // name servers - QStringList nameServers; - foreach (const QDnsDomainNameRecord &record, lookup.nameServerRecords()) { - //reply may include NS records for authoritative nameservers, ignore them and only look at records matching the query - if (record.name() == domain) - nameServers << record.value(); - } - nameServers.sort(); - QCOMPARE(nameServers.join(';'), ns); - - // pointers - if (!ptr.isEmpty()) { - QVERIFY(!lookup.pointerRecords().isEmpty()); - const QDnsDomainNameRecord ptrRecord = lookup.pointerRecords().first(); - QCOMPARE(ptrRecord.name(), domain); - QCOMPARE(ptrRecord.value(), ptr); - } else { - QVERIFY(lookup.pointerRecords().isEmpty()); - } - - // services - QStringList services; - foreach (const QDnsServiceRecord &record, lookup.serviceRecords()) { - QCOMPARE(record.name(), domain); - services << (QString::number(record.priority()) + QLatin1Char(' ') - + QString::number(record.weight()) + QLatin1Char(' ') - + QString::number(record.port()) + QLatin1Char(' ') + record.target()); - } - QVERIFY2(srv_alternatives.contains(services.join(';')), - qPrintable("Actual: " + services.join(';') + "\nExpected one of:\n" + srv_alternatives.join('\n'))); - - // text - QStringList texts; - foreach (const QDnsTextRecord &record, lookup.textRecords()) { - QCOMPARE(record.name(), domain); - QString text; - foreach (const QByteArray &ba, record.values()) { - if (!text.isEmpty()) - text += '\0'; - text += QString::fromLatin1(ba); - } - texts << text; - } - texts.sort(); - QCOMPARE(texts.join(';'), txt); +#ifdef Q_OS_WIN + if (QTest::currentDataTag() == "tlsa"_L1) + QSKIP("WinDNS doesn't work properly with TLSA records and we don't know why"); +#endif + QCOMPARE(lookup->errorString(), QString()); + QCOMPARE(lookup->error(), QDnsLookup::NoError); + QCOMPARE(lookup->type(), type); + QCOMPARE(lookup->name(), domainName(domain)); + + QString result = formatReply(lookup.get()).join(u';'); + QCOMPARE(result, expected); + + // confirm that MX and SRV records are properly sorted + const QList<QDnsMailExchangeRecord> mx = lookup->mailExchangeRecords(); + for (qsizetype i = 1; i < mx.size(); ++i) + QCOMPARE_GE(mx[i].preference(), mx[i - 1].preference()); + + const QList<QDnsServiceRecord> srv = lookup->serviceRecords(); + for (qsizetype i = 1; i < srv.size(); ++i) + QCOMPARE_GE(srv[i].priority(), srv[i - 1].priority()); } void tst_QDnsLookup::lookupIdn() @@ -540,14 +654,15 @@ void tst_QDnsLookup::setNameserverLoopback() QByteArray data = dgram.data(); QCOMPARE_GT(data.size(), HeaderSize); - quint8 opcode = (quint8(data.at(3)) >> 4) & 0xF; + quint8 opcode = (quint8(data.at(2)) >> 3) & 0xF; QCOMPARE(opcode, 0); // standard query // send an NXDOMAIN reply to release the lookup thread QByteArray reply = data; - reply[2] = 0x80; // header->qr = true; + reply[2] = 0x80U; // header->qr = true; reply[3] = 3; // header->rcode = NXDOMAIN; - server.writeDatagram(dgram.makeReply(reply)); + server.writeDatagram(reply.constData(), reply.size(), dgram.senderAddress(), + dgram.senderPort()); server.close(); // now check that the QDnsLookup finished @@ -557,34 +672,57 @@ void tst_QDnsLookup::setNameserverLoopback() QCOMPARE(lookup.error(), QDnsLookup::NotFoundError); } -void tst_QDnsLookup::setNameserver_data() +template <QDnsLookup::Protocol Protocol> +static void setNameserver_data_helper(const QByteArray &protoName) { - static QList<QHostAddress> servers = systemNameservers() + globalPublicNameservers(); + if (!QDnsLookup::isProtocolSupported(Protocol)) + QSKIP(protoName + " not supported"); + + static QList<QHostAddress> servers = systemNameservers(Protocol) + + globalPublicNameservers(Protocol); QTest::addColumn<QHostAddress>("server"); if (servers.isEmpty()) { - QSKIP("No reachable DNS servers were found"); + QSKIP("No reachable " + protoName + " servers were found"); } else { for (const QHostAddress &h : std::as_const(servers)) QTest::addRow("%s", qUtf8Printable(h.toString())) << h; } } -void tst_QDnsLookup::setNameserver() +void tst_QDnsLookup::setNameserver_data() +{ + setNameserver_data_helper<QDnsLookup::Standard>("DNS"); +} + +void tst_QDnsLookup::setNameserver_helper(QDnsLookup::Protocol protocol) { QFETCH(QHostAddress, server); - QDnsLookup lookup; - lookup.setNameserver(server); + QElapsedTimer timer; + timer.start(); + std::unique_ptr<QDnsLookup> lookup = + lookupCommon(QDnsLookup::Type::A, "a-single", server, 0, protocol); + if (!lookup) + return; + qDebug() << "Lookup took" << timer.elapsed() << "ms"; + QCOMPARE(lookup->error(), QDnsLookup::NoError); + QString result = formatReply(lookup.get()).join(';'); + QCOMPARE(result, "A 192.0.2.1"); +} - lookup.setType(QDnsLookup::Type::A); - lookup.setName(domainName("a-single")); - lookup.lookup(); +void tst_QDnsLookup::setNameserver() +{ + setNameserver_helper(QDnsLookup::Standard); +} - QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); - QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); - QVERIFY(!lookup.hostAddressRecords().isEmpty()); - QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single")); - QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("192.0.2.1")); +void tst_QDnsLookup::dnsOverTls_data() +{ + setNameserver_data_helper<QDnsLookup::DnsOverTls>("DNS-over-TLS"); +} + +void tst_QDnsLookup::dnsOverTls() +{ + setNameserver_helper(QDnsLookup::DnsOverTls); } void tst_QDnsLookup::bindingsAndProperties() @@ -638,5 +776,37 @@ void tst_QDnsLookup::bindingsAndProperties() QCOMPARE(nameserverPortChangeSpy.size(), 1); } +void tst_QDnsLookup::automatedBindings() +{ + QDnsLookup lookup; + + QTestPrivate::testReadWritePropertyBasics(lookup, u"aaaa"_s, u"txt"_s, "name"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QDnsLookup::name"); + return; + } + + QTestPrivate::testReadWritePropertyBasics(lookup, QDnsLookup::AAAA, QDnsLookup::TXT, "type"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QDnsLookup::type"); + return; + } + + QTestPrivate::testReadWritePropertyBasics(lookup, QHostAddress{QHostAddress::Any}, + QHostAddress{QHostAddress::LocalHost}, + "nameserver"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QDnsLookup::nameserver"); + return; + } + + QTestPrivate::testReadWritePropertyBasics(lookup, quint16(123), quint16(456), + "nameserverPort"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QDnsLookup::nameserverPort"); + return; + } +} + QTEST_MAIN(tst_QDnsLookup) #include "tst_qdnslookup.moc" diff --git a/tests/auto/network/kernel/qdnslookup_appless/CMakeLists.txt b/tests/auto/network/kernel/qdnslookup_appless/CMakeLists.txt index a699ce43c0..41cf19753f 100644 --- a/tests/auto/network/kernel/qdnslookup_appless/CMakeLists.txt +++ b/tests/auto/network/kernel/qdnslookup_appless/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qdnslookup_appless Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdnslookup_appless LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdnslookup_appless SOURCES tst_qdnslookup_appless.cpp diff --git a/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp b/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp index 52a53ea171..21393ee628 100644 --- a/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp +++ b/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> -// 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/QCoreApplication> #include <QtNetwork/QDnsLookup> diff --git a/tests/auto/network/kernel/qhostaddress/CMakeLists.txt b/tests/auto/network/kernel/qhostaddress/CMakeLists.txt index 052acb7435..e11a600b60 100644 --- a/tests/auto/network/kernel/qhostaddress/CMakeLists.txt +++ b/tests/auto/network/kernel/qhostaddress/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qhostaddress Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qhostaddress LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qhostaddress SOURCES tst_qhostaddress.cpp diff --git a/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp b/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp index 1135420c47..18d1c04a85 100644 --- a/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp +++ b/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. -// 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 <qhostaddress.h> #include <private/qhostaddress_p.h> @@ -15,7 +15,7 @@ # include <qt_windows.h> #endif -#if defined(Q_OS_ANDROID) || defined(Q_OS_WASM) +#if defined(Q_OS_ANDROID) || defined(Q_OS_WASM) || defined(Q_OS_VXWORKS) # include <netinet/in.h> #endif diff --git a/tests/auto/network/kernel/qhostinfo/CMakeLists.txt b/tests/auto/network/kernel/qhostinfo/CMakeLists.txt index edb18acdeb..dc7ab3b221 100644 --- a/tests/auto/network/kernel/qhostinfo/CMakeLists.txt +++ b/tests/auto/network/kernel/qhostinfo/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qhostinfo LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp b/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp index 273bf3a035..bd4bb7ef81 100644 --- a/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp +++ b/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp @@ -1,51 +1,52 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. -// 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 // When using WinSock2 on Windows, it's the first thing that can be included // (except qglobal.h), or else you'll get tons of compile errors #include <qglobal.h> -// To prevent windows system header files from re-defining min/max -#define NOMINMAX 1 - #if defined(Q_OS_WIN) # include <winsock2.h> # include <ws2tcpip.h> #endif -#include <QTest> -#include <QTestEventLoop> -#include <QProcess> +#include <qhostinfo.h> +#include "private/qhostinfo_p.h" +#include "private/qnativesocketengine_p.h" + #include <QCoreApplication> #include <QDebug> -#include <QTcpSocket> #include <QTcpServer> +#include <QTcpSocket> +#include <QTest> +#include <QTestEventLoop> #include <private/qthread_p.h> -#include <time.h> +#include <sys/types.h> + #if defined(Q_OS_WIN) -#include <qt_windows.h> +# include <qt_windows.h> +# ifdef gai_strerror +# undef gai_strerror +# define gai_strerror gai_strerrorA +# endif #else -#include <unistd.h> -#include <signal.h> -#endif - -#include <qhostinfo.h> -#include "private/qhostinfo_p.h" - -#include <sys/types.h> -#if defined(Q_OS_UNIX) -# include <sys/socket.h> # include <netdb.h> +# include <sys/socket.h> +# include <unistd.h> +#endif +#ifndef NI_MAXHOST +# define NI_MAXHOST 1025 #endif #include "../../../network-settings.h" #define TEST_DOMAIN ".test.qt-project.org" +using namespace std::chrono_literals; class tst_QHostInfo : public QObject { @@ -84,15 +85,55 @@ private slots: void cache(); void abortHostLookup(); -protected slots: - void resultsReady(const QHostInfo &); private: bool ipv6LookupsAvailable; bool ipv6Available; - bool lookupDone; - int lookupsDoneCounter; +}; + +class tst_QHostInfo_Helper : public QObject +{ + Q_OBJECT +protected slots: + void resultsReady(const QHostInfo &); +public: + tst_QHostInfo_Helper(const QString &hostname) + : hostname(hostname) + {} + + QString hostname; + bool lookupDone = false; + int lookupsDoneCounter = 0; QHostInfo lookupResults; + + void blockingLookup() + { + lookupResults = QHostInfo::fromName(hostname); + lookupDone = true; + ++lookupsDoneCounter; + } + void lookupHostOldStyle() + { + QHostInfo::lookupHost(hostname, this, SLOT(resultsReady(QHostInfo))); + } + void lookupHostNewStyle() + { + QHostInfo::lookupHost(hostname, this, &tst_QHostInfo_Helper::resultsReady); + } + void lookupHostLambda() + { + QHostInfo::lookupHost(hostname, this, [this](const QHostInfo &hostInfo) { + resultsReady(hostInfo); + }); + } + + bool waitForResults(std::chrono::milliseconds timeout = 15s) + { + QTestEventLoop::instance().enterLoop(timeout); + return !QTestEventLoop::instance().timeout() && lookupDone; + } + + void checkResults(QHostInfo::HostInfoError err, const QString &addresses); }; void tst_QHostInfo::swapFunction() @@ -208,23 +249,12 @@ void tst_QHostInfo::lookupIPv4_data() QTest::newRow("idn-unicode") << QString::fromLatin1("a-single.alqualond\353" TEST_DOMAIN) << "192.0.2.1" << int(QHostInfo::NoError); } -void tst_QHostInfo::lookupIPv4() +void tst_QHostInfo_Helper::checkResults(QHostInfo::HostInfoError err, const QString &addresses) { - QFETCH(QString, hostname); - QFETCH(int, err); - QFETCH(QString, addresses); - - lookupDone = false; - QHostInfo::lookupHost(hostname, this, SLOT(resultsReady(QHostInfo))); - - QTestEventLoop::instance().enterLoop(10); - QVERIFY(!QTestEventLoop::instance().timeout()); - QVERIFY(lookupDone); - if ((int)lookupResults.error() != (int)err) { qWarning() << hostname << "=>" << lookupResults.errorString(); } - QCOMPARE((int)lookupResults.error(), (int)err); + QCOMPARE(lookupResults.error(), err); QStringList tmp; for (int i = 0; i < lookupResults.addresses().size(); ++i) @@ -237,6 +267,18 @@ void tst_QHostInfo::lookupIPv4() QCOMPARE(tmp.join(' '), expected.join(' ')); } +void tst_QHostInfo::lookupIPv4() +{ + QFETCH(QString, hostname); + QFETCH(int, err); + QFETCH(QString, addresses); + + tst_QHostInfo_Helper helper(hostname); + helper.lookupHostOldStyle(); + QVERIFY(helper.waitForResults()); + helper.checkResults(QHostInfo::HostInfoError(err), addresses); +} + void tst_QHostInfo::lookupIPv6_data() { QTest::addColumn<QString>("hostname"); @@ -262,24 +304,10 @@ void tst_QHostInfo::lookupIPv6() if (!ipv6LookupsAvailable) QSKIP("This platform does not support IPv6 lookups"); - lookupDone = false; - QHostInfo::lookupHost(hostname, this, SLOT(resultsReady(QHostInfo))); - - QTestEventLoop::instance().enterLoop(10); - QVERIFY(!QTestEventLoop::instance().timeout()); - QVERIFY(lookupDone); - - QCOMPARE((int)lookupResults.error(), (int)err); - - QStringList tmp; - for (int i = 0; i < lookupResults.addresses().size(); ++i) - tmp.append(lookupResults.addresses().at(i).toString()); - tmp.sort(); - - QStringList expected = addresses.split(' '); - expected.sort(); - - QCOMPARE(tmp.join(' ').toLower(), expected.join(' ').toLower()); + tst_QHostInfo_Helper helper(hostname); + helper.lookupHostOldStyle(); + QVERIFY(helper.waitForResults()); + helper.checkResults(QHostInfo::HostInfoError(err), addresses); } void tst_QHostInfo::lookupConnectToFunctionPointer_data() @@ -293,26 +321,10 @@ void tst_QHostInfo::lookupConnectToFunctionPointer() QFETCH(int, err); QFETCH(QString, addresses); - lookupDone = false; - QHostInfo::lookupHost(hostname, this, &tst_QHostInfo::resultsReady); - - QTestEventLoop::instance().enterLoop(10); - QVERIFY(!QTestEventLoop::instance().timeout()); - QVERIFY(lookupDone); - - if (int(lookupResults.error()) != int(err)) - qWarning() << hostname << "=>" << lookupResults.errorString(); - QCOMPARE(int(lookupResults.error()), int(err)); - - QStringList tmp; - for (const auto &result : lookupResults.addresses()) - tmp.append(result.toString()); - tmp.sort(); - - QStringList expected = addresses.split(' '); - expected.sort(); - - QCOMPARE(tmp.join(' '), expected.join(' ')); + tst_QHostInfo_Helper helper(hostname); + helper.lookupHostNewStyle(); + QVERIFY(helper.waitForResults()); + helper.checkResults(QHostInfo::HostInfoError(err), addresses); } void tst_QHostInfo::lookupConnectToFunctionPointerDeleted() @@ -337,89 +349,38 @@ void tst_QHostInfo::lookupConnectToLambda() QFETCH(int, err); QFETCH(QString, addresses); - lookupDone = false; - QHostInfo::lookupHost(hostname, [this](const QHostInfo &hostInfo) { - resultsReady(hostInfo); - }); - - QTestEventLoop::instance().enterLoop(10); - QVERIFY(!QTestEventLoop::instance().timeout()); - QVERIFY(lookupDone); - - if (int(lookupResults.error()) != int(err)) - qWarning() << hostname << "=>" << lookupResults.errorString(); - QCOMPARE(int(lookupResults.error()), int(err)); - - QStringList tmp; - for (int i = 0; i < lookupResults.addresses().size(); ++i) - tmp.append(lookupResults.addresses().at(i).toString()); - tmp.sort(); - - QStringList expected = addresses.split(' '); - expected.sort(); - - QCOMPARE(tmp.join(' '), expected.join(' ')); + tst_QHostInfo_Helper helper(hostname); + helper.lookupHostLambda(); + QVERIFY(helper.waitForResults()); + helper.checkResults(QHostInfo::HostInfoError(err), addresses); } static QStringList reverseLookupHelper(const QString &ip) { QStringList results; - - const QString pythonCode = - "import socket;" - "import sys;" - "print (socket.getnameinfo((sys.argv[1], 0), 0)[0]);"; - - QList<QByteArray> lines; - QProcess python; - python.setProcessChannelMode(QProcess::ForwardedErrorChannel); - python.start("python3", QStringList() << QString("-c") << pythonCode << ip); - if (python.waitForFinished()) { - if (python.exitStatus() == QProcess::NormalExit && python.exitCode() == 0) - lines = python.readAllStandardOutput().split('\n'); - for (QByteArray line : lines) { - if (!line.isEmpty()) - results << line.trimmed(); - } - if (!results.isEmpty()) - return results; - } - - qDebug() << "Python failed, falling back to nslookup"; - QProcess lookup; - lookup.setProcessChannelMode(QProcess::ForwardedErrorChannel); - lookup.start("nslookup", QStringList(ip)); - if (!lookup.waitForFinished()) { - results << "nslookup failure"; - qDebug() << "nslookup failure"; + union qt_sockaddr { + sockaddr a; + sockaddr_in a4; + sockaddr_in6 a6; + } sa = {}; + + QHostAddress addr(ip); + if (addr.isNull()) { + qWarning("Could not parse IP address: %ls", qUtf16Printable(ip)); return results; } - lines = lookup.readAllStandardOutput().split('\n'); - - QByteArray name; - - const QByteArray nameMarkerNix("name ="); - const QByteArray nameMarkerWin("Name:"); - const QByteArray addressMarkerWin("Address:"); - - for (QByteArray line : lines) { - int index = -1; - if ((index = line.indexOf(nameMarkerNix)) != -1) { // Linux and macOS - name = line.mid(index + nameMarkerNix.size()).chopped(1).trimmed(); - results << name; - } else if (line.startsWith(nameMarkerWin)) { // Windows formatting - name = line.mid(line.lastIndexOf(" ")).trimmed(); - } else if (line.startsWith(addressMarkerWin)) { - QByteArray address = line.mid(addressMarkerWin.size()).trimmed(); - if (address == ip.toUtf8()) { - results << name; - } - } - } - if (results.isEmpty()) { - qDebug() << "Failure to parse nslookup output: " << lines; + // from qnativesocketengine_p.h: + QT_SOCKLEN_T len = setSockaddr(&sa.a, addr, /*port = */ 0); + + QByteArray name(NI_MAXHOST, Qt::Uninitialized); + int ni_flags = NI_NAMEREQD | NI_NUMERICSERV; + if (int r = getnameinfo(&sa.a, len, name.data(), name.size(), nullptr, 0, ni_flags)) { + qWarning("Failed to reverse look up '%ls': %s", qUtf16Printable(ip), gai_strerror(r)); + } else { + results << QString::fromLatin1(name, qstrnlen(name, name.size())); } + return results; } @@ -432,8 +393,10 @@ void tst_QHostInfo::reverseLookup_data() QTest::newRow("dns.google") << QString("8.8.8.8") << reverseLookupHelper("8.8.8.8") << 0 << false; QTest::newRow("one.one.one.one") << QString("1.1.1.1") << reverseLookupHelper("1.1.1.1") << 0 << false; - QTest::newRow("dns.google IPv6") << QString("2001:4860:4860::8888") << reverseLookupHelper("2001:4860:4860::8888") << 0 << true; - QTest::newRow("cloudflare IPv6") << QString("2606:4700:4700::1111") << reverseLookupHelper("2606:4700:4700::1111") << 0 << true; + if (QStringList hostNames = reverseLookupHelper("2001:4860:4860::8888"); !hostNames.isEmpty()) + QTest::newRow("dns.google IPv6") << QString("2001:4860:4860::8888") << std::move(hostNames) << 0 << true; + if (QStringList hostNames = reverseLookupHelper("2606:4700:4700::1111"); !hostNames.isEmpty()) + QTest::newRow("cloudflare IPv6") << QString("2606:4700:4700::1111") << std::move(hostNames) << 0 << true; QTest::newRow("bogus-name IPv6") << QString("1::2::3::4") << QStringList() << 1 << true; } @@ -442,11 +405,6 @@ void tst_QHostInfo::reverseLookup() QFETCH(QString, address); QFETCH(QStringList, hostNames); QFETCH(int, err); - QFETCH(bool, ipv6); - - if (ipv6 && !ipv6LookupsAvailable) { - QSKIP("IPv6 reverse lookups are not supported on this platform"); - } QHostInfo info = QHostInfo::fromName(address); @@ -454,7 +412,7 @@ void tst_QHostInfo::reverseLookup() if (!hostNames.contains(info.hostName())) qDebug() << "Failure: expecting" << hostNames << ",got " << info.hostName(); QVERIFY(hostNames.contains(info.hostName())); - QCOMPARE(info.addresses().first(), QHostAddress(address)); + QCOMPARE(info.addresses().constFirst(), QHostAddress(address)); } else { QCOMPARE(info.hostName(), address); QCOMPARE(info.error(), QHostInfo::HostNotFound); @@ -475,21 +433,9 @@ void tst_QHostInfo::blockingLookup() QFETCH(int, err); QFETCH(QString, addresses); - QHostInfo hostInfo = QHostInfo::fromName(hostname); - QStringList tmp; - for (int i = 0; i < hostInfo.addresses().size(); ++i) - tmp.append(hostInfo.addresses().at(i).toString()); - tmp.sort(); - - if ((int)hostInfo.error() != (int)err) { - qWarning() << hostname << "=>" << lookupResults.errorString(); - } - QCOMPARE((int)hostInfo.error(), (int)err); - - QStringList expected = addresses.split(' '); - expected.sort(); - - QCOMPARE(tmp.join(' ').toUpper(), expected.join(' ').toUpper()); + tst_QHostInfo_Helper helper(hostname); + helper.blockingLookup(); + helper.checkResults(QHostInfo::HostInfoError(err), addresses); } void tst_QHostInfo::raceCondition() @@ -555,24 +501,22 @@ void tst_QHostInfo::threadSafetyAsynchronousAPI() { const int nattempts = 10; const int lookupsperthread = 10; - QList<QThread*> threads; - QList<LookupReceiver*> receivers; + QThread threads[nattempts]; + LookupReceiver receivers[nattempts]; for (int i = 0; i < nattempts; ++i) { - QThread* thread = new QThread; - LookupReceiver* receiver = new LookupReceiver; + QThread *thread = &threads[i]; + LookupReceiver *receiver = &receivers[i]; receiver->numrequests = lookupsperthread; - receivers.append(receiver); receiver->moveToThread(thread); connect(thread, SIGNAL(started()), receiver, SLOT(start())); thread->start(); - threads.append(thread); } - for (int k = threads.size() - 1; k >= 0; --k) - QVERIFY(threads.at(k)->wait(60000)); - foreach (LookupReceiver* receiver, receivers) { - QCOMPARE(receiver->result.error(), QHostInfo::NoError); - QCOMPARE(receiver->result.addresses().at(0).toString(), QString("192.0.2.1")); - QCOMPARE(receiver->numrequests, 0); + for (int k = nattempts - 1; k >= 0; --k) + QVERIFY(threads[k].wait(60000)); + for (LookupReceiver &receiver : receivers) { + QCOMPARE(receiver.result.error(), QHostInfo::NoError); + QCOMPARE(receiver.result.addresses().at(0).toString(), QString("192.0.2.1")); + QCOMPARE(receiver.numrequests, 0); } } @@ -581,17 +525,11 @@ void tst_QHostInfo::threadSafetyAsynchronousAPI() void tst_QHostInfo::multipleSameLookups() { const int COUNT = 10; - lookupsDoneCounter = 0; - + tst_QHostInfo_Helper helper("localhost"); for (int i = 0; i < COUNT; i++) - QHostInfo::lookupHost("localhost", this, SLOT(resultsReady(QHostInfo))); + helper.lookupHostOldStyle(); - QElapsedTimer timer; - timer.start(); - while (timer.elapsed() < 10000 && lookupsDoneCounter < COUNT) { - QTestEventLoop::instance().enterLoop(2); - } - QCOMPARE(lookupsDoneCounter, COUNT); + QTRY_COMPARE_WITH_TIMEOUT(helper.lookupsDoneCounter, COUNT, 10s); } // this test is for the multi-threaded QHostInfo rewrite. It is about getting results at all, @@ -620,19 +558,15 @@ void tst_QHostInfo::multipleDifferentLookups() QFETCH(int, repeats); const int COUNT = hostnameList.size(); - lookupsDoneCounter = 0; + tst_QHostInfo_Helper helper(QString{}); for (int i = 0; i < hostnameList.size(); i++) - for (int j = 0; j < repeats; ++j) - QHostInfo::lookupHost(hostnameList.at(i), this, SLOT(resultsReady(QHostInfo))); - - QElapsedTimer timer; - timer.start(); - while (timer.elapsed() < 60000 && lookupsDoneCounter < repeats*COUNT) { - QTestEventLoop::instance().enterLoop(2); - //qDebug() << "t:" << timer.elapsed(); - } - QCOMPARE(lookupsDoneCounter, repeats*COUNT); + for (int j = 0; j < repeats; ++j) { + helper.hostname = hostnameList.at(i); + helper.lookupHostOldStyle(); + } + + QTRY_COMPARE_WITH_TIMEOUT(helper.lookupsDoneCounter, repeats*COUNT, 60s); } void tst_QHostInfo::cache() @@ -641,13 +575,12 @@ void tst_QHostInfo::cache() if (!cache) return; // test makes only sense when cache enabled - // reset slot counter - lookupsDoneCounter = 0; + tst_QHostInfo_Helper helper("localhost"); // lookup once, wait in event loop, result should not come directly. bool valid = true; int id = -1; - QHostInfo result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id); + QHostInfo result = qt_qhostinfo_lookup(helper.hostname, &helper, SLOT(resultsReady(QHostInfo)), &valid, &id); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(!valid); @@ -655,7 +588,7 @@ void tst_QHostInfo::cache() // loopkup second time, result should come directly valid = false; - result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id); + result = qt_qhostinfo_lookup(helper.hostname, &helper, SLOT(resultsReady(QHostInfo)), &valid, &id); QVERIFY(valid); QVERIFY(!result.addresses().isEmpty()); @@ -664,17 +597,17 @@ void tst_QHostInfo::cache() // lookup third time, result should not come directly. valid = true; - result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id); + result = qt_qhostinfo_lookup(helper.hostname, &helper, SLOT(resultsReady(QHostInfo)), &valid, &id); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(!valid); QVERIFY(result.addresses().isEmpty()); // the slot should have been called 2 times. - QCOMPARE(lookupsDoneCounter, 2); + QCOMPARE(helper.lookupsDoneCounter, 2); } -void tst_QHostInfo::resultsReady(const QHostInfo &hi) +void tst_QHostInfo_Helper::resultsReady(const QHostInfo &hi) { QVERIFY(QThread::currentThread() == thread()); lookupDone = true; @@ -685,16 +618,15 @@ void tst_QHostInfo::resultsReady(const QHostInfo &hi) void tst_QHostInfo::abortHostLookup() { - //reset counter - lookupsDoneCounter = 0; + tst_QHostInfo_Helper helper("a-single" TEST_DOMAIN); bool valid = false; int id = -1; - QHostInfo result = qt_qhostinfo_lookup("a-single" TEST_DOMAIN, this, SLOT(resultsReady(QHostInfo)), &valid, &id); + QHostInfo result = qt_qhostinfo_lookup(helper.hostname, &helper, SLOT(resultsReady(QHostInfo)), &valid, &id); QVERIFY(!valid); //it is assumed that the DNS request/response in the backend is slower than it takes to call abort QHostInfo::abortHostLookup(id); QTestEventLoop::instance().enterLoop(5); - QCOMPARE(lookupsDoneCounter, 0); + QCOMPARE(helper.lookupsDoneCounter, 0); } class LookupAborter : public QObject diff --git a/tests/auto/network/kernel/qnetworkaddressentry/CMakeLists.txt b/tests/auto/network/kernel/qnetworkaddressentry/CMakeLists.txt index 023819be9d..02bf37880a 100644 --- a/tests/auto/network/kernel/qnetworkaddressentry/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkaddressentry/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkaddressentry Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkaddressentry LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkaddressentry SOURCES tst_qnetworkaddressentry.cpp diff --git a/tests/auto/network/kernel/qnetworkaddressentry/tst_qnetworkaddressentry.cpp b/tests/auto/network/kernel/qnetworkaddressentry/tst_qnetworkaddressentry.cpp index 13d957dbcf..801eb58931 100644 --- a/tests/auto/network/kernel/qnetworkaddressentry/tst_qnetworkaddressentry.cpp +++ b/tests/auto/network/kernel/qnetworkaddressentry/tst_qnetworkaddressentry.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 <QTest> diff --git a/tests/auto/network/kernel/qnetworkdatagram/CMakeLists.txt b/tests/auto/network/kernel/qnetworkdatagram/CMakeLists.txt index 38dbc5c4a7..5bfc2edc6a 100644 --- a/tests/auto/network/kernel/qnetworkdatagram/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkdatagram/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkdatagram Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkdatagram LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkdatagram SOURCES tst_qnetworkdatagram.cpp diff --git a/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp b/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp index 6339e5dcd8..df473133a6 100644 --- a/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp +++ b/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 Intel Corporation. -// 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 <QNetworkDatagram> #include <QTest> diff --git a/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt b/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt index 4c473cd4b3..354cefc993 100644 --- a/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qnetworkinformation LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkinformation SOURCES tst_qnetworkinformation.cpp diff --git a/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.cpp b/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.cpp index 720388c53f..daf81823e8 100644 --- a/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.cpp +++ b/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.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/private/qnetworkinformation_p.h> #include <QtNetwork/qnetworkinformation.h> diff --git a/tests/auto/network/kernel/qnetworkinformation_appless/CMakeLists.txt b/tests/auto/network/kernel/qnetworkinformation_appless/CMakeLists.txt index 1a7cf41079..d4a2e486bd 100644 --- a/tests/auto/network/kernel/qnetworkinformation_appless/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkinformation_appless/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qnetworkinformation_appless LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkinformation_appless SOURCES tst_qnetworkinformation_appless.cpp diff --git a/tests/auto/network/kernel/qnetworkinformation_appless/tst_qnetworkinformation_appless.cpp b/tests/auto/network/kernel/qnetworkinformation_appless/tst_qnetworkinformation_appless.cpp index ad67dbcdb5..b08843314a 100644 --- a/tests/auto/network/kernel/qnetworkinformation_appless/tst_qnetworkinformation_appless.cpp +++ b/tests/auto/network/kernel/qnetworkinformation_appless/tst_qnetworkinformation_appless.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 <QtCore/qcoreapplication.h> #include <QtNetwork/qnetworkinformation.h> diff --git a/tests/auto/network/kernel/qnetworkinterface/CMakeLists.txt b/tests/auto/network/kernel/qnetworkinterface/CMakeLists.txt index b06ef5c3d1..3e5dab63e0 100644 --- a/tests/auto/network/kernel/qnetworkinterface/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkinterface/CMakeLists.txt @@ -5,10 +5,16 @@ ## tst_qnetworkinterface Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkinterface LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkinterface SOURCES tst_qnetworkinterface.cpp LIBRARIES - Qt::Network + Qt::NetworkPrivate QT_TEST_SERVER_LIST "apache2" ) diff --git a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp index 36027d89da..e5bbf3467c 100644 --- a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp +++ b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. -// 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 <QTest> #include <QtEndian> @@ -12,6 +11,8 @@ #include <qudpsocket.h> #include "../../../network-settings.h" +#include <private/qtnetwork-config_p.h> + Q_DECLARE_METATYPE(QHostAddress) class tst_QNetworkInterface : public QObject @@ -49,14 +50,13 @@ tst_QNetworkInterface::~tst_QNetworkInterface() bool tst_QNetworkInterface::isIPv6Working() { - // Version without following cannot get IPV6 information - #if !defined(QT_NO_GETIFADDRS) && !defined(QT_NO_IPV6IFNAME) - QUdpSocket socket; - socket.connectToHost(QHostAddress::LocalHostIPv6, 1234); - return socket.state() == QAbstractSocket::ConnectedState || socket.waitForConnected(100); - #else - return false; - #endif + // QNetworkInterface may be unable to detect IPv6 addresses even if they + // are there, due to limitations of the implementation. + if (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows || + QT_CONFIG(linux_netlink) || (QT_CONFIG(getifaddrs) && QT_CONFIG(ipv6ifname))) { + return QtNetworkSettings::hasIPv6(); + } + return false; } void tst_QNetworkInterface::initTestCase() @@ -71,8 +71,8 @@ void tst_QNetworkInterface::initTestCase() void tst_QNetworkInterface::dump() { // This is for manual testing: - QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces(); - foreach (const QNetworkInterface &i, allInterfaces) { + const QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &i : allInterfaces) { QString flags; if (i.flags() & QNetworkInterface::IsUp) flags += "Up,"; if (i.flags() & QNetworkInterface::IsRunning) flags += "Running,"; @@ -99,7 +99,8 @@ void tst_QNetworkInterface::dump() qDebug() << " MTU: " << i.maximumTransmissionUnit(); int count = 0; - foreach (const QNetworkAddressEntry &e, i.addressEntries()) { + const auto entries = i.addressEntries(); + for (const QNetworkAddressEntry &e : entries) { QDebug s = qDebug(); s.nospace() << " address " << qSetFieldWidth(2) << count++ << qSetFieldWidth(0); @@ -125,11 +126,11 @@ void tst_QNetworkInterface::dump() void tst_QNetworkInterface::consistencyCheck() { - QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); + const QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); QSet<QString> interfaceNames; QList<int> interfaceIndexes; - foreach (const QNetworkInterface &iface, ifaces) { + for (const QNetworkInterface &iface : ifaces) { QVERIFY(iface.isValid()); QVERIFY2(!interfaceNames.contains(iface.name()), "duplicate name = " + iface.name().toLocal8Bit()); @@ -196,6 +197,14 @@ void tst_QNetworkInterface::localAddress_data() } else if (!ipv6 || entry.prefixLength() != 64) { continue; } else { +#ifdef Q_OS_ANDROID + // Android seem to not allow IPv6 connection from interfaces other than wlan, + // if it's connected, and wlan is connected by default on Android emulators, + // so prefer selecting wlan in this test. + const QString scopeId = addr.scopeId(); + if (!scopeId.isEmpty() && !scopeId.startsWith("wlan")) + continue; +#endif // add a random node in this IPv6 network quint64 randomid = qFromBigEndian(Q_UINT64_C(0x8f41f072e5733caa)); QIPv6Address ip6 = addr.toIPv6Address(); @@ -250,10 +259,10 @@ void tst_QNetworkInterface::interfaceFromXXX_data() { QTest::addColumn<QNetworkInterface>("iface"); - QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces(); + const QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces(); if (allInterfaces.size() == 0) QSKIP("No interfaces to test!"); - foreach (QNetworkInterface iface, allInterfaces) + for (const QNetworkInterface &iface : allInterfaces) QTest::newRow(iface.name().toLocal8Bit()) << iface; } @@ -267,7 +276,8 @@ void tst_QNetworkInterface::interfaceFromXXX() QCOMPARE(QNetworkInterface::interfaceNameFromIndex(idx), iface.name()); QCOMPARE(QNetworkInterface::interfaceIndexFromName(iface.name()), idx); } - foreach (QNetworkAddressEntry entry, iface.addressEntries()) { + const auto entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { QVERIFY(!entry.ip().isNull()); if (!entry.netmask().isNull()) { diff --git a/tests/auto/network/kernel/qnetworkproxy/CMakeLists.txt b/tests/auto/network/kernel/qnetworkproxy/CMakeLists.txt index 4669a3969a..629bfb0a8a 100644 --- a/tests/auto/network/kernel/qnetworkproxy/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkproxy/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkproxy Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkproxy LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkproxy SOURCES tst_qnetworkproxy.cpp diff --git a/tests/auto/network/kernel/qnetworkproxy/tst_qnetworkproxy.cpp b/tests/auto/network/kernel/qnetworkproxy/tst_qnetworkproxy.cpp index 56d61db78c..1f00f8d054 100644 --- a/tests/auto/network/kernel/qnetworkproxy/tst_qnetworkproxy.cpp +++ b/tests/auto/network/kernel/qnetworkproxy/tst_qnetworkproxy.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 <QTest> diff --git a/tests/auto/network/kernel/qnetworkproxyfactory/CMakeLists.txt b/tests/auto/network/kernel/qnetworkproxyfactory/CMakeLists.txt index 95db1f7dad..41dae6a02a 100644 --- a/tests/auto/network/kernel/qnetworkproxyfactory/CMakeLists.txt +++ b/tests/auto/network/kernel/qnetworkproxyfactory/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qnetworkproxyfactory Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkproxyfactory LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkproxyfactory SOURCES tst_qnetworkproxyfactory.cpp diff --git a/tests/auto/network/kernel/qnetworkproxyfactory/tst_qnetworkproxyfactory.cpp b/tests/auto/network/kernel/qnetworkproxyfactory/tst_qnetworkproxyfactory.cpp index 1350dee150..c38a480766 100644 --- a/tests/auto/network/kernel/qnetworkproxyfactory/tst_qnetworkproxyfactory.cpp +++ b/tests/auto/network/kernel/qnetworkproxyfactory/tst_qnetworkproxyfactory.cpp @@ -1,6 +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 <QtTest/QTest> #include <QtTest/QTestEventLoop> @@ -94,7 +93,8 @@ void tst_QNetworkProxyFactory::systemProxyForQuery_data() QTest::newRow("autobind-server") << (int)QNetworkProxyQuery::TcpServer << QUrl() << QString() << QString() << 0 << (int)QNetworkProxy::ListeningCapability; QTest::newRow("web-server") << (int)QNetworkProxyQuery::TcpServer << QUrl() << QString() << QString() << 80 << (int)QNetworkProxy::ListeningCapability; //windows: these should be bypassed if "bypass proxy server for local addresses" is ticked - foreach (QHostAddress address, QNetworkInterface::allAddresses()) { + const auto addresses = QNetworkInterface::allAddresses(); + for (const QHostAddress &address : addresses) { QTest::newRow(qPrintable(address.toString())) << (int)QNetworkProxyQuery::TcpSocket << QUrl() << QString() << address.toString() << 0 << 0; } @@ -147,16 +147,15 @@ void tst_QNetworkProxyFactory::systemProxyForQuery() const QElapsedTimer sw; sw.start(); - QList<QNetworkProxy> systemProxyList = QNetworkProxyFactory::systemProxyForQuery(query); + const QList<QNetworkProxy> systemProxyList = QNetworkProxyFactory::systemProxyForQuery(query); qDebug() << sw.elapsed() << "ms"; QVERIFY(!systemProxyList.isEmpty()); // for manual comparison with system qDebug() << systemProxyList; - foreach (const QNetworkProxy &proxy, systemProxyList) { + for (const QNetworkProxy &proxy : systemProxyList) QVERIFY((requiredCapabilities == 0) || (proxy.capabilities() & requiredCapabilities)); - } } void tst_QNetworkProxyFactory::systemProxyForQuery_local() diff --git a/tests/auto/network/selftest/tst_networkselftest.cpp b/tests/auto/network/selftest/tst_networkselftest.cpp index 11a5a6b880..4288a65a37 100644 --- a/tests/auto/network/selftest/tst_networkselftest.cpp +++ b/tests/auto/network/selftest/tst_networkselftest.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 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 <QTest> diff --git a/tests/auto/network/socket/CMakeLists.txt b/tests/auto/network/socket/CMakeLists.txt index c3f8e8f87f..7136017f39 100644 --- a/tests/auto/network/socket/CMakeLists.txt +++ b/tests/auto/network/socket/CMakeLists.txt @@ -7,14 +7,20 @@ if(QT_FEATURE_private_tests) add_subdirectory(qsocks5socketengine) add_subdirectory(platformsocketengine) endif() -add_subdirectory(qudpsocket) +if(QT_FEATURE_udpsocket) + add_subdirectory(qudpsocket) +endif() add_subdirectory(qabstractsocket) -if(NOT ANDROID) +if(QT_FEATURE_localserver AND NOT ANDROID) # QTBUG-87387 add_subdirectory(qlocalsocket) +endif() + +if(QT_FEATURE_networkinterface AND NOT ANDROID) # QTBUG-87388 add_subdirectory(qtcpserver) endif() + if(QT_FEATURE_sctp) add_subdirectory(qsctpsocket) endif() diff --git a/tests/auto/network/socket/platformsocketengine/BLACKLIST b/tests/auto/network/socket/platformsocketengine/BLACKLIST index f1f88d26d1..796f81a02a 100644 --- a/tests/auto/network/socket/platformsocketengine/BLACKLIST +++ b/tests/auto/network/socket/platformsocketengine/BLACKLIST @@ -1,11 +1,2 @@ [tcpLoopbackPerformance] -windows-10 msvc-2015 -windows-7sp1 -[receiveUrgentData] -windows-10 msvc-2015 -windows-7sp1 -[serverTest] -windows-10 msvc-2015 -windows-7sp1 -[tcpLoopbackPerformance] windows diff --git a/tests/auto/network/socket/platformsocketengine/CMakeLists.txt b/tests/auto/network/socket/platformsocketengine/CMakeLists.txt index d94c700770..891041df04 100644 --- a/tests/auto/network/socket/platformsocketengine/CMakeLists.txt +++ b/tests/auto/network/socket/platformsocketengine/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_platformsocketengine LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp b/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp index 7b1b0ad666..1f6ecf9200 100644 --- a/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp +++ b/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. -// 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 <QtTest/QTest> @@ -255,7 +255,7 @@ void tst_PlatformSocketEngine::broadcastTest() PLATFORMSOCKETENGINE broadcastSocket; // Initialize a regular Udp socket - QVERIFY(broadcastSocket.initialize(QAbstractSocket::UdpSocket, QAbstractSocket::AnyIPProtocol)); + QVERIFY(broadcastSocket.initialize(QAbstractSocket::UdpSocket, QAbstractSocket::IPv4Protocol)); // Bind to any port on all interfaces QVERIFY(broadcastSocket.bind(QHostAddress::Any, 0)); diff --git a/tests/auto/network/socket/qabstractsocket/CMakeLists.txt b/tests/auto/network/socket/qabstractsocket/CMakeLists.txt index 24e8af2bdc..3ca18aceef 100644 --- a/tests/auto/network/socket/qabstractsocket/CMakeLists.txt +++ b/tests/auto/network/socket/qabstractsocket/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qabstractsocket Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qabstractsocket LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qabstractsocket SOURCES tst_qabstractsocket.cpp diff --git a/tests/auto/network/socket/qabstractsocket/tst_qabstractsocket.cpp b/tests/auto/network/socket/qabstractsocket/tst_qabstractsocket.cpp index 984f411550..6b038acdfa 100644 --- a/tests/auto/network/socket/qabstractsocket/tst_qabstractsocket.cpp +++ b/tests/auto/network/socket/qabstractsocket/tst_qabstractsocket.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 <QTest> diff --git a/tests/auto/network/socket/qhttpsocketengine/BLACKLIST b/tests/auto/network/socket/qhttpsocketengine/BLACKLIST index ceb3b7862e..e0cd701636 100644 --- a/tests/auto/network/socket/qhttpsocketengine/BLACKLIST +++ b/tests/auto/network/socket/qhttpsocketengine/BLACKLIST @@ -1,5 +1,2 @@ -[downloadBigFile] -windows-10 msvc-2015 -windows-7sp1 [ensureEofTriggersNotification] windows diff --git a/tests/auto/network/socket/qhttpsocketengine/CMakeLists.txt b/tests/auto/network/socket/qhttpsocketengine/CMakeLists.txt index 7dfae9059d..6b4f904725 100644 --- a/tests/auto/network/socket/qhttpsocketengine/CMakeLists.txt +++ b/tests/auto/network/socket/qhttpsocketengine/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qhttpsocketengine LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp b/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp index c9bf8b92f3..a52099b5b4 100644 --- a/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp +++ b/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.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 <QtTest/QTest> @@ -18,6 +18,8 @@ #include "../../../network-settings.h" +using namespace std::chrono_literals; + class tst_QHttpSocketEngine : public QObject { Q_OBJECT @@ -323,7 +325,7 @@ void tst_QHttpSocketEngine::simpleErrorsAndStates() QCOMPARE(socketDevice.state(), QAbstractSocket::UnconnectedState); QVERIFY(!socketDevice.connectToHost(QHostAddress(QtNetworkSettings::socksProxyServerName()), 8088)); QCOMPARE(socketDevice.state(), QAbstractSocket::ConnectingState); - if (socketDevice.waitForWrite(30000)) { + if (socketDevice.waitForWrite(30s)) { QVERIFY(socketDevice.state() == QAbstractSocket::ConnectedState || socketDevice.state() == QAbstractSocket::UnconnectedState); } else { diff --git a/tests/auto/network/socket/qlocalsocket/CMakeLists.txt b/tests/auto/network/socket/qlocalsocket/CMakeLists.txt index 975141ceb2..4b886a02ee 100644 --- a/tests/auto/network/socket/qlocalsocket/CMakeLists.txt +++ b/tests/auto/network/socket/qlocalsocket/CMakeLists.txt @@ -1,7 +1,14 @@ # 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_qlocalsocket LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + add_subdirectory(socketprocess) + qt_internal_add_test(tst_qlocalsocket SOURCES tst_qlocalsocket.cpp @@ -10,5 +17,6 @@ qt_internal_add_test(tst_qlocalsocket QLOCALSOCKET_DEBUG LIBRARIES Qt::Network + Qt::TestPrivate ) add_dependencies(tst_qlocalsocket socketprocess) diff --git a/tests/auto/network/socket/qlocalsocket/socketprocess/main.cpp b/tests/auto/network/socket/qlocalsocket/socketprocess/main.cpp index c1a78fae23..d254253aca 100644 --- a/tests/auto/network/socket/qlocalsocket/socketprocess/main.cpp +++ b/tests/auto/network/socket/qlocalsocket/socketprocess/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 <qcoreapplication.h> diff --git a/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp b/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp index f240f3e832..30ffb50d23 100644 --- a/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp +++ b/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp @@ -1,10 +1,11 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. -// 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 <QTest> #include <QSignalSpy> +#include <QtTest/private/qpropertytesthelper_p.h> #if QT_CONFIG(process) #include <QProcess> #endif @@ -123,6 +124,7 @@ private slots: void verifyListenWithDescriptor_data(); void serverBindingsAndProperties(); + void socketBindings(); protected slots: void socketClosedSlot(); @@ -1662,6 +1664,9 @@ void tst_QLocalSocket::asyncDisconnectNotify() void tst_QLocalSocket::verifySocketOptions_data() { #ifdef Q_OS_LINUX + if (::geteuid() == 0) + QSKIP("Running this test as root doesn't make sense"); + QTest::addColumn<QString>("service"); QTest::addColumn<QLocalServer::SocketOption>("opts"); QTest::addColumn<QFile::Permissions>("perms"); @@ -1707,7 +1712,7 @@ void tst_QLocalSocket::verifySocketOptions() void tst_QLocalSocket::verifyListenWithDescriptor() { -#ifdef Q_OS_UNIX +#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) QFETCH(QString, path); QFETCH(bool, abstract); QFETCH(bool, bound); @@ -1807,14 +1812,18 @@ void tst_QLocalSocket::serverBindingsAndProperties() { CrashSafeLocalServer server; - QProperty<QLocalServer::SocketOptions> sockOpts; - server.bindableSocketOptions().setBinding(Qt::makePropertyBinding(sockOpts)); - sockOpts = QLocalServer::GroupAccessOption | QLocalServer::UserAccessOption; - QCOMPARE(server.socketOptions(), sockOpts.value()); + QTestPrivate::testReadWritePropertyBasics( + server, QLocalServer::SocketOptions{QLocalServer::GroupAccessOption}, + QLocalServer::SocketOptions{QLocalServer::OtherAccessOption}, "socketOptions"); +} + +void tst_QLocalSocket::socketBindings() +{ + QLocalSocket socket; - sockOpts.setBinding(server.bindableSocketOptions().makeBinding()); - server.setSocketOptions(QLocalServer::OtherAccessOption); - QCOMPARE(sockOpts.value(), QLocalServer::OtherAccessOption); + QTestPrivate::testReadWritePropertyBasics( + socket, QLocalSocket::SocketOptions{QLocalSocket::AbstractNamespaceOption}, + QLocalSocket::SocketOptions{QLocalSocket::NoOptions}, "socketOptions"); } QTEST_MAIN(tst_QLocalSocket) diff --git a/tests/auto/network/socket/qsctpsocket/CMakeLists.txt b/tests/auto/network/socket/qsctpsocket/CMakeLists.txt index fb08d03aad..4bf5438841 100644 --- a/tests/auto/network/socket/qsctpsocket/CMakeLists.txt +++ b/tests/auto/network/socket/qsctpsocket/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qsctpsocket Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsctpsocket LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qsctpsocket SOURCES tst_qsctpsocket.cpp diff --git a/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp b/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp index d661ad5405..2893053158 100644 --- a/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp +++ b/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com> -// 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 <QTest> #include <QDebug> @@ -127,12 +127,13 @@ void tst_QSctpSocket::bind_data() // these ranges are guaranteed to be reserved for 'documentation purposes', // and thus, should be unused in the real world. Not that I'm assuming the // world is full of competent administrators, or anything. - QStringList knownBad; - knownBad << "198.51.100.1"; - knownBad << "2001:0DB8::1"; - foreach (const QString &badAddress, knownBad) { + const QString knownBad[] = { + "198.51.100.1", + "2001:0DB8::1", + }; + + for (const QString &badAddress : knownBad) QTest::newRow(badAddress.toLatin1().constData()) << badAddress << false << QString(); - } } // Testing bind function diff --git a/tests/auto/network/socket/qsocks5socketengine/BLACKLIST b/tests/auto/network/socket/qsocks5socketengine/BLACKLIST index 479d0f878f..6b3a39ab5e 100644 --- a/tests/auto/network/socket/qsocks5socketengine/BLACKLIST +++ b/tests/auto/network/socket/qsocks5socketengine/BLACKLIST @@ -7,7 +7,4 @@ qnx ci # QTBUG-74162 [passwordAuth2] ubuntu -[downloadBigFile] -windows-10 msvc-2015 -windows-7sp1 diff --git a/tests/auto/network/socket/qsocks5socketengine/CMakeLists.txt b/tests/auto/network/socket/qsocks5socketengine/CMakeLists.txt index 9954084f39..dc0b87cd94 100644 --- a/tests/auto/network/socket/qsocks5socketengine/CMakeLists.txt +++ b/tests/auto/network/socket/qsocks5socketengine/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qsocks5socketengine LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp b/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp index aa860893bb..cc77ba2da3 100644 --- a/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp +++ b/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.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 <QtTest/QTest> @@ -23,6 +23,8 @@ #include "../../../network-settings.h" +using namespace std::chrono_literals; + class tst_QSocks5SocketEngine : public QObject, public QAbstractSocketEngineReceiver { Q_OBJECT @@ -341,7 +343,7 @@ void tst_QSocks5SocketEngine::simpleErrorsAndStates() QCOMPARE(socketDevice.state(), QAbstractSocket::UnconnectedState); QVERIFY(!socketDevice.connectToHost(QHostInfo::fromName(QtNetworkSettings::socksProxyServerName()).addresses().first(), 8088)); QCOMPARE(socketDevice.state(), QAbstractSocket::ConnectingState); - if (socketDevice.waitForWrite(15000)) { + if (socketDevice.waitForWrite(15s)) { QVERIFY(socketDevice.state() == QAbstractSocket::UnconnectedState || socketDevice.state() == QAbstractSocket::ConnectedState); } else { diff --git a/tests/auto/network/socket/qtcpserver/BLACKLIST b/tests/auto/network/socket/qtcpserver/BLACKLIST index 339987046c..a8b5f5d137 100644 --- a/tests/auto/network/socket/qtcpserver/BLACKLIST +++ b/tests/auto/network/socket/qtcpserver/BLACKLIST @@ -1,16 +1,12 @@ [listenWhileListening:WithSocks5Proxy] linux windows -[ipv6Server] -windows-7sp1 -windows-10 msvc-2017 [ipv6Server:WithoutProxy] windows osx [serverAddress] # QTBUG-103056 qnx -windows-7sp1 windows-10 [linkLocal] diff --git a/tests/auto/network/socket/qtcpserver/CMakeLists.txt b/tests/auto/network/socket/qtcpserver/CMakeLists.txt index d87f18c4f0..b01a164302 100644 --- a/tests/auto/network/socket/qtcpserver/CMakeLists.txt +++ b/tests/auto/network/socket/qtcpserver/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_qtcpserver LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + add_subdirectory(crashingServer) add_subdirectory(test) diff --git a/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp b/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp index c14c429520..1c41552eb5 100644 --- a/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp +++ b/tests/auto/network/socket/qtcpserver/crashingServer/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> diff --git a/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp b/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp index 90e6f3da8e..c03076d98e 100644 --- a/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp +++ b/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp @@ -1,9 +1,7 @@ // 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 <qglobal.h> -// To prevent windows system header files from re-defining min/max -#define NOMINMAX 1 #if defined(_WIN32) #include <winsock2.h> #else @@ -867,10 +865,12 @@ void tst_QTcpServer::serverAddress_data() QTest::newRow("AnyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::AnyIPv4); if (QtNetworkSettings::hasIPv6()) QTest::newRow("AnyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv6); - foreach (const QNetworkInterface &iface, QNetworkInterface::allInterfaces()) { + const auto ifaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &iface : ifaces) { if ((iface.flags() & QNetworkInterface::IsUp) == 0) continue; - foreach (const QNetworkAddressEntry &entry, iface.addressEntries()) { + const auto entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { QTest::newRow(qPrintable(entry.ip().toString())) << entry.ip() << entry.ip(); } } @@ -924,7 +924,8 @@ void tst_QTcpServer::linkLocal() QSet <QString> scopes; QHostAddress localMaskv4("169.254.0.0"); QHostAddress localMaskv6("fe80::"); - foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) { + const auto ifaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &iface : ifaces) { //Windows preallocates link local addresses to interfaces that are down. //These may or may not work depending on network driver (they do not work for the Bluetooth PAN driver) if (iface.flags() & QNetworkInterface::IsUp) { @@ -944,7 +945,8 @@ void tst_QTcpServer::linkLocal() if (iface.name().startsWith("awdl")) continue; #endif - foreach (QNetworkAddressEntry addressEntry, iface.addressEntries()) { + const auto entries = iface.addressEntries(); + for (const QNetworkAddressEntry &addressEntry : entries) { QHostAddress addr = addressEntry.ip(); if (addr.isInSubnet(localMaskv4, 16)) { addresses << addr; @@ -963,7 +965,7 @@ void tst_QTcpServer::linkLocal() QList<QTcpServer*> servers; quint16 port = 0; - foreach (const QHostAddress& addr, addresses) { + for (const QHostAddress &addr : std::as_const(addresses)) { QTcpServer *server = new QTcpServer; QVERIFY(server->listen(addr, port)); port = server->serverPort(); //listen to same port on different interfaces @@ -971,7 +973,7 @@ void tst_QTcpServer::linkLocal() } QList<QTcpSocket*> clients; - foreach (const QHostAddress& addr, addresses) { + for (const QHostAddress &addr : std::as_const(addresses)) { //unbound socket QTcpSocket *socket = new QTcpSocket; socket->connectToHost(addr, port); @@ -986,7 +988,7 @@ void tst_QTcpServer::linkLocal() } //each server should have two connections - foreach (QTcpServer* server, servers) { + for (QTcpServer *server : std::as_const(servers)) { //qDebug() << "checking for connections" << server->serverAddress() << ":" << server->serverPort(); QVERIFY(server->waitForNewConnection(5000)); QTcpSocket* remote = server->nextPendingConnection(); diff --git a/tests/auto/network/socket/qtcpsocket/CMakeLists.txt b/tests/auto/network/socket/qtcpsocket/CMakeLists.txt index 051e58f3ab..7b6bb4d881 100644 --- a/tests/auto/network/socket/qtcpsocket/CMakeLists.txt +++ b/tests/auto/network/socket/qtcpsocket/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qtcpsocket LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() diff --git a/tests/auto/network/socket/qtcpsocket/stressTest/Test.cpp b/tests/auto/network/socket/qtcpsocket/stressTest/Test.cpp index 4a7c36d1c1..8d6e470220 100644 --- a/tests/auto/network/socket/qtcpsocket/stressTest/Test.cpp +++ b/tests/auto/network/socket/qtcpsocket/stressTest/Test.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 // Qt #include <QByteArray> #include <QCoreApplication> diff --git a/tests/auto/network/socket/qtcpsocket/stressTest/Test.h b/tests/auto/network/socket/qtcpsocket/stressTest/Test.h index f5a19ae16a..495b90d733 100644 --- a/tests/auto/network/socket/qtcpsocket/stressTest/Test.h +++ b/tests/auto/network/socket/qtcpsocket/stressTest/Test.h @@ -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 #ifndef TEST_H #define TEST_H diff --git a/tests/auto/network/socket/qtcpsocket/stressTest/main.cpp b/tests/auto/network/socket/qtcpsocket/stressTest/main.cpp index 57fec691d6..f989a4de34 100644 --- a/tests/auto/network/socket/qtcpsocket/stressTest/main.cpp +++ b/tests/auto/network/socket/qtcpsocket/stressTest/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 "Test.h" #include <QCoreApplication> diff --git a/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp b/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp index 5dc12b924f..4ec01a9d94 100644 --- a/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp +++ b/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp @@ -1,11 +1,9 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2017 Intel Corporation. -// 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 <qglobal.h> -// To prevent windows system header files from re-defining min/max -#define NOMINMAX 1 #if defined(_WIN32) #include <winsock2.h> #else @@ -62,6 +60,8 @@ #include "../../../network-settings.h" +using namespace Qt::StringLiterals; + QT_FORWARD_DECLARE_CLASS(QTcpSocket) class SocketPair; @@ -283,7 +283,7 @@ tst_QTcpSocket::tst_QTcpSocket() tmpSocket = 0; //This code relates to the socketsConstructedBeforeEventLoop test case - earlyConstructedSockets = new SocketPair; + earlyConstructedSockets = new SocketPair(this); QVERIFY(earlyConstructedSockets->create()); earlyBytesWrittenCount = 0; earlyReadyReadCount = 0; @@ -489,12 +489,13 @@ void tst_QTcpSocket::bind_data() bool testIpv6 = false; // iterate all interfaces, add all addresses on them as test data - QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); - foreach (const QNetworkInterface &netinterface, interfaces) { + const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &netinterface : interfaces) { if (!netinterface.isValid()) continue; - foreach (const QNetworkAddressEntry &entry, netinterface.addressEntries()) { + const auto entries = netinterface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { if (entry.ip().isInSubnet(QHostAddress::parseSubnet("fe80::/10")) || entry.ip().isInSubnet(QHostAddress::parseSubnet("169.254/16"))) continue; // link-local bind will fail, at least on Linux, so skip it. @@ -524,12 +525,12 @@ void tst_QTcpSocket::bind_data() // these ranges are guaranteed to be reserved for 'documentation purposes', // and thus, should be unused in the real world. Not that I'm assuming the // world is full of competent administrators, or anything. - QStringList knownBad; - knownBad << "198.51.100.1"; - knownBad << "2001:0DB8::1"; - foreach (const QString &badAddress, knownBad) { + const QString knownBad[] = { + u"198.51.100.1"_s, + u"2001:0DB8::1"_s + }; + for (const QString &badAddress : knownBad) QTest::addRow("%s:0", badAddress.toLatin1().constData()) << badAddress << 0 << false << QString(); - } // try to bind to a privileged ports // we should fail if we're not root (unless the ports are in use!) @@ -2582,7 +2583,7 @@ void tst_QTcpSocket::moveToThread0() { // Case 1: Moved after connecting, before waiting for connection. - QTcpSocket *socket = newSocket();; + QTcpSocket *socket = newSocket(); socket->connectToHost(QtNetworkSettings::imapServerName(), 143); socket->moveToThread(0); QVERIFY(socket->waitForConnected(5000)); diff --git a/tests/auto/network/socket/qudpsocket/BLACKLIST b/tests/auto/network/socket/qudpsocket/BLACKLIST index 704d83509d..f5f6a8e156 100644 --- a/tests/auto/network/socket/qudpsocket/BLACKLIST +++ b/tests/auto/network/socket/qudpsocket/BLACKLIST @@ -1,11 +1,7 @@ [writeDatagramToNonExistingPeer] windows -# QTBUG-85364 -windows-10 gcc cmake [readyReadForEmptyDatagram] opensuse-leap -[echo] -opensuse-42.3 [multicast] centos macos arm diff --git a/tests/auto/network/socket/qudpsocket/CMakeLists.txt b/tests/auto/network/socket/qudpsocket/CMakeLists.txt index 4d81f852d7..a7a2659340 100644 --- a/tests/auto/network/socket/qudpsocket/CMakeLists.txt +++ b/tests/auto/network/socket/qudpsocket/CMakeLists.txt @@ -1,5 +1,11 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -add_subdirectory(test) +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qudpsocket LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + add_subdirectory(clientserver) +add_subdirectory(test) diff --git a/tests/auto/network/socket/qudpsocket/clientserver/main.cpp b/tests/auto/network/socket/qudpsocket/clientserver/main.cpp index a83aa6d420..76bdf3aada 100644 --- a/tests/auto/network/socket/qudpsocket/clientserver/main.cpp +++ b/tests/auto/network/socket/qudpsocket/clientserver/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 <QtNetwork> class ClientServer : public QUdpSocket diff --git a/tests/auto/network/socket/qudpsocket/test/CMakeLists.txt b/tests/auto/network/socket/qudpsocket/test/CMakeLists.txt index 550ea53438..69b62c2f9f 100644 --- a/tests/auto/network/socket/qudpsocket/test/CMakeLists.txt +++ b/tests/auto/network/socket/qudpsocket/test/CMakeLists.txt @@ -14,3 +14,7 @@ qt_internal_add_test(tst_qudpsocket Qt::TestPrivate QT_TEST_SERVER_LIST "danted" "echo" ) + +if(QT_FEATURE_process) + add_dependencies(tst_qudpsocket clientserver) +endif() diff --git a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp index 4380433899..689ff452f9 100644 --- a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp +++ b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2017 Intel Corporation. -// 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 <QTest> #include <QSignalSpy> @@ -11,6 +10,7 @@ #endif #include <QScopeGuard> #include <QVersionNumber> +#include <QSemaphore> #include <qcoreapplication.h> #include <qfileinfo.h> @@ -46,6 +46,8 @@ # define RELIABLE_BYTES_AVAILABLE #endif +using namespace Qt::StringLiterals; + Q_DECLARE_METATYPE(QHostAddress) QT_FORWARD_DECLARE_CLASS(QUdpSocket) @@ -103,6 +105,8 @@ private slots: void asyncReadDatagram(); void writeInHostLookupState(); + void readyReadConnectionThrottling(); + protected slots: void empty_readyReadSlot(); void empty_connectedSlot(); @@ -121,6 +125,7 @@ private: QList<QHostAddress> allAddresses; QHostAddress multicastGroup4, multicastGroup6; QList<QHostAddress> linklocalMulticastGroups; + QNetworkInterface ifaceWithIPv6; QUdpSocket *m_asyncSender; QUdpSocket *m_asyncReceiver; }; @@ -171,26 +176,7 @@ QNetworkInterface tst_QUdpSocket::interfaceForGroup(const QHostAddress &multicas if (!scope.isEmpty()) return QNetworkInterface::interfaceFromName(scope); - static QNetworkInterface ipv6if = [&]() { - // find any link local address in the allAddress list - for (const QHostAddress &addr: std::as_const(allAddresses)) { - if (addr.isLoopback()) - continue; - - QString scope = addr.scopeId(); - if (!scope.isEmpty()) { - QNetworkInterface iface = QNetworkInterface::interfaceFromName(scope); - qDebug() << "Will bind IPv6 sockets to" << iface; - return iface; - } - } - - qWarning("interfaceForGroup(%s) could not find any link-local IPv6 address! " - "Make sure this test is behind a check of QtNetworkSettings::hasIPv6().", - qUtf8Printable(multicastGroup.toString())); - return QNetworkInterface(); - }(); - return ipv6if; + return ifaceWithIPv6; } bool tst_QUdpSocket::shouldWorkaroundLinuxKernelBug() @@ -275,9 +261,16 @@ void tst_QUdpSocket::initTestCase() continue; llbase.setScopeId(scope); linklocalMulticastGroups << llbase; + if (!ifaceWithIPv6.isValid()) { + // Remember the first interface we've found that has IPv6 so we can + // bind non-link-local sockets to it (the first is least likely to + // be some weird virtual interface). + ifaceWithIPv6 = QNetworkInterface::interfaceFromName(scope); + } } qDebug() << "Will use multicast groups" << multicastGroup4 << multicastGroup6 << linklocalMulticastGroups; + qDebug() << "Will bind IPv6 sockets to" << ifaceWithIPv6; m_workaroundLinuxKernelBug = shouldWorkaroundLinuxKernelBug(); if (QTestPrivate::isRunningArmOnX86()) @@ -383,7 +376,8 @@ void tst_QUdpSocket::broadcasting() const char *message[] = {"Yo mista", "", "Yo", "Wassap"}; QList<QHostAddress> broadcastAddresses; - foreach (QNetworkInterface iface, QNetworkInterface::allInterfaces()) { + const auto ifaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &iface : ifaces) { if ((iface.flags() & QNetworkInterface::CanBroadcast) && iface.flags() & QNetworkInterface::IsUp) { for (int i=0;i<iface.addressEntries().size();i++) { @@ -411,7 +405,7 @@ void tst_QUdpSocket::broadcasting() for (int k = 0; k < 4; k++) { broadcastSocket.writeDatagram(message[i], strlen(message[i]), QHostAddress::Broadcast, serverPort); - foreach (QHostAddress addr, broadcastAddresses) + for (const QHostAddress &addr : std::as_const(broadcastAddresses)) broadcastSocket.writeDatagram(message[i], strlen(message[i]), addr, serverPort); } QTestEventLoop::instance().enterLoop(15); @@ -1241,11 +1235,12 @@ void tst_QUdpSocket::multicastTtlOption_data() QTest::addColumn<int>("ttl"); QTest::addColumn<int>("expected"); - QList<QHostAddress> addresses; - addresses += QHostAddress(QHostAddress::AnyIPv4); - addresses += QHostAddress(QHostAddress::AnyIPv6); + const QHostAddress addresses[] = { + QHostAddress(QHostAddress::AnyIPv4), + QHostAddress(QHostAddress::AnyIPv6), + }; - foreach (const QHostAddress &address, addresses) { + for (const QHostAddress &address : addresses) { const QByteArray addressB = address.toString().toLatin1(); QTest::newRow((addressB + " 0").constData()) << address << 0 << 0; QTest::newRow((addressB + " 1").constData()) << address << 1 << 1; @@ -1287,11 +1282,12 @@ void tst_QUdpSocket::multicastLoopbackOption_data() QTest::addColumn<int>("loopback"); QTest::addColumn<int>("expected"); - QList<QHostAddress> addresses; - addresses += QHostAddress(QHostAddress::AnyIPv4); - addresses += QHostAddress(QHostAddress::AnyIPv6); + const QHostAddress addresses[] = { + QHostAddress(QHostAddress::AnyIPv4), + QHostAddress(QHostAddress::AnyIPv6), + }; - foreach (const QHostAddress &address, addresses) { + for (const QHostAddress &address : addresses) { const QByteArray addressB = address.toString().toLatin1(); QTest::newRow((addressB + " 0").constData()) << address << 0 << 0; QTest::newRow((addressB + " 1").constData()) << address << 1 << 1; @@ -1391,11 +1387,12 @@ void tst_QUdpSocket::setMulticastInterface_data() { QTest::addColumn<QNetworkInterface>("iface"); QTest::addColumn<QHostAddress>("address"); - QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); - foreach (const QNetworkInterface &iface, interfaces) { + const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &iface : interfaces) { if ((iface.flags() & QNetworkInterface::IsUp) == 0) continue; - foreach (const QNetworkAddressEntry &entry, iface.addressEntries()) { + const auto entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { const QByteArray testName = iface.name().toLatin1() + ':' + entry.ip().toString().toLatin1(); QTest::newRow(testName.constData()) << iface << entry.ip(); } @@ -1499,15 +1496,16 @@ void tst_QUdpSocket::multicast() if (!joinResult) return; - QList<QByteArray> datagrams = QList<QByteArray>() - << QByteArray("0123") - << QByteArray("4567") - << QByteArray("89ab") - << QByteArray("cdef"); + const QByteArray datagrams[] = { + "0123"_ba, + "4567"_ba, + "89ab"_ba, + "cdef"_ba, + }; QUdpSocket sender; sender.bind(); - foreach (const QByteArray &datagram, datagrams) { + for (const QByteArray &datagram : datagrams) { QNetworkDatagram dgram(datagram, groupAddress, receiver.localPort()); dgram.setInterfaceIndex(interfaceForGroup(groupAddress).index()); QCOMPARE(int(sender.writeDatagram(dgram)), @@ -1600,7 +1598,8 @@ void tst_QUdpSocket::linkLocalIPv6() QList <QHostAddress> addresses; QSet <QString> scopes; QHostAddress localMask("fe80::"); - foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) { + const auto ifaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &iface : ifaces) { //Windows preallocates link local addresses to interfaces that are down. //These may or may not work depending on network driver if (iface.flags() & QNetworkInterface::IsUp) { @@ -1615,7 +1614,8 @@ void tst_QUdpSocket::linkLocalIPv6() continue; #endif - foreach (QNetworkAddressEntry addressEntry, iface.addressEntries()) { + const auto entries = iface.addressEntries(); + for (const QNetworkAddressEntry &addressEntry : entries) { QHostAddress addr(addressEntry.ip()); if (!addr.scopeId().isEmpty() && addr.isInSubnet(localMask, 64)) { scopes << addr.scopeId(); @@ -1630,7 +1630,7 @@ void tst_QUdpSocket::linkLocalIPv6() QList <QUdpSocket*> sockets; quint16 port = 0; - foreach (const QHostAddress& addr, addresses) { + for (const QHostAddress &addr : std::as_const(addresses)) { QUdpSocket *s = new QUdpSocket; QVERIFY2(s->bind(addr, port), addr.toString().toLatin1() + '/' + QByteArray::number(port) + ": " + qPrintable(s->errorString())); @@ -1639,7 +1639,7 @@ void tst_QUdpSocket::linkLocalIPv6() } QByteArray testData("hello"); - foreach (QUdpSocket *s, sockets) { + for (QUdpSocket *s : std::as_const(sockets)) { QUdpSocket neutral; QVERIFY(neutral.bind(QHostAddress(QHostAddress::AnyIPv6))); QSignalSpy neutralReadSpy(&neutral, SIGNAL(readyRead())); @@ -1665,9 +1665,8 @@ void tst_QUdpSocket::linkLocalIPv6() QCOMPARE(dgram.data(), testData); //sockets bound to other interfaces shouldn't have received anything - foreach (QUdpSocket *s2, sockets) { + for (QUdpSocket *s2 : std::as_const(sockets)) QCOMPARE((int)s2->bytesAvailable(), 0); - } //Sending to the same address with different scope should normally fail //However it will pass if there is a route between two interfaces, @@ -1686,7 +1685,8 @@ void tst_QUdpSocket::linkLocalIPv4() QList <QHostAddress> addresses; QHostAddress localMask("169.254.0.0"); - foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) { + const auto ifaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &iface : ifaces) { //Windows preallocates link local addresses to interfaces that are down. //These may or may not work depending on network driver (they do not work for the Bluetooth PAN driver) if (iface.flags() & QNetworkInterface::IsUp) { @@ -1700,7 +1700,8 @@ void tst_QUdpSocket::linkLocalIPv4() if (iface.name().startsWith("utun")) continue; #endif - foreach (QNetworkAddressEntry addr, iface.addressEntries()) { + const auto entries = iface.addressEntries(); + for (const QNetworkAddressEntry &addr : entries) { if (addr.ip().isInSubnet(localMask, 16)) { addresses << addr.ip(); qDebug() << "Found IPv4 link local address" << addr.ip(); @@ -1713,7 +1714,7 @@ void tst_QUdpSocket::linkLocalIPv4() QList <QUdpSocket*> sockets; quint16 port = 0; - foreach (const QHostAddress& addr, addresses) { + for (const QHostAddress &addr : std::as_const(addresses)) { QUdpSocket *s = new QUdpSocket; QVERIFY2(s->bind(addr, port), qPrintable(s->errorString())); port = s->localPort(); //bind same port, different networks @@ -1724,7 +1725,7 @@ void tst_QUdpSocket::linkLocalIPv4() QVERIFY(neutral.bind(QHostAddress(QHostAddress::AnyIPv4))); QByteArray testData("hello"); - foreach (QUdpSocket *s, sockets) { + for (QUdpSocket *s : std::as_const(sockets)) { QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort())); QVERIFY2(neutral.waitForReadyRead(10000), QtNetworkSettings::msgSocketError(neutral).constData()); @@ -1756,9 +1757,8 @@ void tst_QUdpSocket::linkLocalIPv4() QCOMPARE(dgram.data(), testData); //sockets bound to other interfaces shouldn't have received anything - foreach (QUdpSocket *s2, sockets) { + for (QUdpSocket *s2 : std::as_const(sockets)) QCOMPARE((int)s2->bytesAvailable(), 0); - } } qDeleteAll(sockets); } @@ -1908,5 +1908,78 @@ void tst_QUdpSocket::writeInHostLookupState() QVERIFY(!socket.putChar('0')); } +void tst_QUdpSocket::readyReadConnectionThrottling() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + using namespace std::chrono_literals; + + // QTBUG-105871: + // We have some signal/slot connection throttling in QAbstractSocket, but it + // was caring about the bytes, not about the datagrams. + // Test that we don't disable read notifications until we have at least one + // datagram available. Otherwise our good users who use the datagram APIs + // can get into scenarios where they no longer get the readyRead signal + // unless they call a read function once in a while. + + QUdpSocket receiver; + QVERIFY(receiver.bind(QHostAddress(QHostAddress::LocalHost), 0)); + + QSemaphore semaphore; + + // Repro-ing deterministically eludes me, so we are bruteforcing it: + // The thread acts as a remote sender, flooding the receiver with datagrams, + // and at some point the receiver would get into the broken state mentioned + // earlier. + std::unique_ptr<QThread> thread(QThread::create([&semaphore, port = receiver.localPort()]() { + QUdpSocket sender; + sender.connectToHost(QHostAddress(QHostAddress::LocalHost), port); + QCOMPARE(sender.state(), QUdpSocket::ConnectedState); + + constexpr qsizetype PayloadSize = 242; + const QByteArray payload(PayloadSize, 'a'); + + semaphore.acquire(); // Wait for main thread to be ready + while (true) { + // We send 100 datagrams at a time, then sleep. + // This is mostly to let the main thread catch up between bursts so + // it doesn't get stuck in the loop. + for (int i = 0; i < 100; ++i) { + [[maybe_unused]] + qsizetype sent = sender.write(payload); + Q_ASSERT(sent > 0); + } + if (QThread::currentThread()->isInterruptionRequested()) + break; + QThread::sleep(20ms); + } + })); + thread->start(); + auto threadStopAndWaitGuard = qScopeGuard([&thread] { + thread->requestInterruption(); + thread->quit(); + thread->wait(); + }); + + qsizetype count = 0; + QObject::connect(&receiver, &QUdpSocket::readyRead, &receiver, + [&] { + while (receiver.hasPendingDatagrams()) { + receiver.readDatagram(nullptr, 0); + ++count; + } + // If this prints `false, xxxx` we were pretty much guaranteed + // that we would not get called again: + // qDebug() << receiver.hasPendingDatagrams() << receiver.bytesAvailable(); + }, + Qt::QueuedConnection); + + semaphore.release(); + constexpr qsizetype MaxCount = 500; + QVERIFY2(QTest::qWaitFor([&] { return count >= MaxCount; }, 10s), + QByteArray::number(count).constData()); +} + QTEST_MAIN(tst_QUdpSocket) #include "tst_qudpsocket.moc" diff --git a/tests/auto/network/socket/qudpsocket/udpServer/main.cpp b/tests/auto/network/socket/qudpsocket/udpServer/main.cpp index 72bbcfa71e..ba5bb3c0d0 100644 --- a/tests/auto/network/socket/qudpsocket/udpServer/main.cpp +++ b/tests/auto/network/socket/qudpsocket/udpServer/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 <QtNetwork> class Server : public QObject diff --git a/tests/auto/network/ssl/CMakeLists.txt b/tests/auto/network/ssl/CMakeLists.txt index 34b3a0aef1..b11b15b6ba 100644 --- a/tests/auto/network/ssl/CMakeLists.txt +++ b/tests/auto/network/ssl/CMakeLists.txt @@ -13,7 +13,7 @@ if(QT_FEATURE_private_tests AND QT_FEATURE_ssl) add_subdirectory(qsslsocket) add_subdirectory(qsslsocket_onDemandCertificates_member) add_subdirectory(qsslsocket_onDemandCertificates_static) -# add_subdirectory(qasn1element) + add_subdirectory(qasn1element) add_subdirectory(qssldiffiehellmanparameters) add_subdirectory(qsslserver) endif() diff --git a/tests/auto/network/ssl/qasn1element/CMakeLists.txt b/tests/auto/network/ssl/qasn1element/CMakeLists.txt index 1175802035..7b01a0b22d 100644 --- a/tests/auto/network/ssl/qasn1element/CMakeLists.txt +++ b/tests/auto/network/ssl/qasn1element/CMakeLists.txt @@ -5,10 +5,22 @@ ## tst_qasn1element Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qasn1element LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qasn1element SOURCES tst_qasn1element.cpp + ../../../../../src/plugins/tls/shared/qasn1element_p.h + ../../../../../src/plugins/tls/shared/qasn1element.cpp + INCLUDE_DIRECTORIES + ../../../../../src/plugins/tls/shared LIBRARIES + Qt::Core Qt::Network Qt::NetworkPrivate + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qasn1element/tst_qasn1element.cpp b/tests/auto/network/ssl/qasn1element/tst_qasn1element.cpp index eb614551a9..a54f0bd9d8 100644 --- a/tests/auto/network/ssl/qasn1element/tst_qasn1element.cpp +++ b/tests/auto/network/ssl/qasn1element/tst_qasn1element.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> -// 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 "private/qasn1element_p.h" +#include "qasn1element_p.h" #include <QTest> @@ -121,6 +121,9 @@ void tst_QAsn1Element::dateTime_data() QTest::newRow("UTCTime - year 2000") << QByteArray::fromHex("170d3030313232343035353530305a") << QDateTime(QDate(2000, 12, 24), QTime(5, 55), QTimeZone::UTC); + QTest::newRow("UTCTime - leap day year 2000") + << QByteArray::fromHex("170d3030303232393035353530305a") + << QDateTime(QDate(2000, 2, 29), QTime(5, 55), QTimeZone::UTC); QTest::newRow("UTCTime - year 2049") << QByteArray::fromHex("170d3439313232343035353530305a") << QDateTime(QDate(2049, 12, 24), QTime(5, 55), QTimeZone::UTC); diff --git a/tests/auto/network/ssl/qdtls/CMakeLists.txt b/tests/auto/network/ssl/qdtls/CMakeLists.txt index a3e2237dec..1d5eef094c 100644 --- a/tests/auto/network/ssl/qdtls/CMakeLists.txt +++ b/tests/auto/network/ssl/qdtls/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qdtls Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdtls LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data list(APPEND test_data "certs") @@ -14,4 +20,5 @@ qt_internal_add_test(tst_qdtls LIBRARIES Qt::NetworkPrivate TESTDATA ${test_data} + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qdtls/tst_qdtls.cpp b/tests/auto/network/ssl/qdtls/tst_qdtls.cpp index 5e96c055e3..372ee3a181 100644 --- a/tests/auto/network/ssl/qdtls/tst_qdtls.cpp +++ b/tests/auto/network/ssl/qdtls/tst_qdtls.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 <QTest> #include <QTestEventLoop> @@ -581,28 +581,36 @@ void tst_QDtls::protocolVersionMatching_data() QTest::addColumn<QSsl::SslProtocol>("clientProtocol"); QTest::addColumn<bool>("works"); + //OPENSSL_VERSION_NUMBER : + //(OPENSSL_VERSION_MAJOR<<28) | (OPENSSL_VERSION_MINOR<<20) | (OPENSSL_VERSION_PATCH<<4) + const long ossl311 = 0x30100010; + + if (QSslSocket::sslLibraryVersionNumber() < ossl311) { #if QT_DEPRECATED_SINCE(6, 3) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED - QTest::addRow("DtlsV1_0 <-> DtlsV1_0") << QSsl::DtlsV1_0 << QSsl::DtlsV1_0 << true; - QTest::addRow("DtlsV1_0OrLater <-> DtlsV1_0") << QSsl::DtlsV1_0OrLater << QSsl::DtlsV1_0 << true; - QTest::addRow("DtlsV1_0 <-> DtlsV1_0OrLater") << QSsl::DtlsV1_0 << QSsl::DtlsV1_0OrLater << true; - QTest::addRow("DtlsV1_0OrLater <-> DtlsV1_0OrLater") << QSsl::DtlsV1_0OrLater << QSsl::DtlsV1_0OrLater << true; + QTest::addRow("DtlsV1_0 <-> DtlsV1_0") << QSsl::DtlsV1_0 << QSsl::DtlsV1_0 << true; + QTest::addRow("DtlsV1_0OrLater <-> DtlsV1_0") << QSsl::DtlsV1_0OrLater << QSsl::DtlsV1_0 << true; + QTest::addRow("DtlsV1_0 <-> DtlsV1_0OrLater") << QSsl::DtlsV1_0 << QSsl::DtlsV1_0OrLater << true; + QTest::addRow("DtlsV1_0OrLater <-> DtlsV1_0OrLater") << QSsl::DtlsV1_0OrLater << QSsl::DtlsV1_0OrLater << true; QT_WARNING_POP #endif // QT_DEPRECATED_SINCE(6, 3) + } QTest::addRow("DtlsV1_2 <-> DtlsV1_2") << QSsl::DtlsV1_2 << QSsl::DtlsV1_2 << true; QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_2") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_2 << true; QTest::addRow("DtlsV1_2 <-> DtlsV1_2OrLater") << QSsl::DtlsV1_2 << QSsl::DtlsV1_2OrLater << true; QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_2OrLater") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_2OrLater << true; + if (QSslSocket::sslLibraryVersionNumber() < ossl311) { #if QT_DEPRECATED_SINCE(6, 3) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED - QTest::addRow("DtlsV1_0 <-> DtlsV1_2") << QSsl::DtlsV1_0 << QSsl::DtlsV1_2 << false; - QTest::addRow("DtlsV1_0 <-> DtlsV1_2OrLater") << QSsl::DtlsV1_0 << QSsl::DtlsV1_2OrLater << false; - QTest::addRow("DtlsV1_2 <-> DtlsV1_0") << QSsl::DtlsV1_2 << QSsl::DtlsV1_0 << false; - QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_0") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_0 << false; + QTest::addRow("DtlsV1_0 <-> DtlsV1_2") << QSsl::DtlsV1_0 << QSsl::DtlsV1_2 << false; + QTest::addRow("DtlsV1_0 <-> DtlsV1_2OrLater") << QSsl::DtlsV1_0 << QSsl::DtlsV1_2OrLater << false; + QTest::addRow("DtlsV1_2 <-> DtlsV1_0") << QSsl::DtlsV1_2 << QSsl::DtlsV1_0 << false; + QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_0") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_0 << false; QT_WARNING_POP -#endif // QT_DEPRECATED_SINCE(6, 3) +#endif // QT_DEPRECATED_SINCE(6, 3 + } } void tst_QDtls::protocolVersionMatching() diff --git a/tests/auto/network/ssl/qdtlscookie/CMakeLists.txt b/tests/auto/network/ssl/qdtlscookie/CMakeLists.txt index 8f55a0de20..d965c7efb6 100644 --- a/tests/auto/network/ssl/qdtlscookie/CMakeLists.txt +++ b/tests/auto/network/ssl/qdtlscookie/CMakeLists.txt @@ -5,9 +5,16 @@ ## tst_qdtlscookie Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdtlscookie LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdtlscookie SOURCES tst_qdtlscookie.cpp LIBRARIES Qt::NetworkPrivate + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp b/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp index 5690a5e04a..167a196104 100644 --- a/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp +++ b/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.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 <QTest> #include <QTestEventLoop> diff --git a/tests/auto/network/ssl/qocsp/CMakeLists.txt b/tests/auto/network/ssl/qocsp/CMakeLists.txt index 337da9a88c..98d38ec1c0 100644 --- a/tests/auto/network/ssl/qocsp/CMakeLists.txt +++ b/tests/auto/network/ssl/qocsp/CMakeLists.txt @@ -5,12 +5,19 @@ ## tst_qocsp Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qocsp LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qocsp SOURCES tst_qocsp.cpp LIBRARIES Qt::Network Qt::NetworkPrivate + BUNDLE_ANDROID_OPENSSL_LIBS ) qt_internal_extend_target(tst_qocsp CONDITION QT_FEATURE_openssl_linked diff --git a/tests/auto/network/ssl/qocsp/tst_qocsp.cpp b/tests/auto/network/ssl/qocsp/tst_qocsp.cpp index 2d64f00de5..81ce030be0 100644 --- a/tests/auto/network/ssl/qocsp/tst_qocsp.cpp +++ b/tests/auto/network/ssl/qocsp/tst_qocsp.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2018 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 <QTest> #include <QTestEventLoop> diff --git a/tests/auto/network/ssl/qpassworddigestor/CMakeLists.txt b/tests/auto/network/ssl/qpassworddigestor/CMakeLists.txt index a2a065496f..96491eada9 100644 --- a/tests/auto/network/ssl/qpassworddigestor/CMakeLists.txt +++ b/tests/auto/network/ssl/qpassworddigestor/CMakeLists.txt @@ -5,9 +5,16 @@ ## tst_qpassworddigestor Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qpassworddigestor LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qpassworddigestor SOURCES tst_qpassworddigestor.cpp LIBRARIES Qt::Network + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp b/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp index 5047fac2dd..1876c955af 100644 --- a/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp +++ b/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2018 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 <QTest> #include <QtNetwork/qpassworddigestor.h> diff --git a/tests/auto/network/ssl/qsslcertificate/CMakeLists.txt b/tests/auto/network/ssl/qsslcertificate/CMakeLists.txt index 633b7d5e39..6f81a5e030 100644 --- a/tests/auto/network/ssl/qsslcertificate/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslcertificate/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qsslcertificate Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsslcertificate LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # This test requires private tests to properly build if (NOT QT_FEATURE_private_tests) return() @@ -34,4 +40,5 @@ qt_internal_add_test(tst_qsslcertificate LIBRARIES Qt::Network TESTDATA ${test_data} + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp b/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp index 282bec95ef..b51053effd 100644 --- a/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp +++ b/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.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 <QTest> @@ -138,13 +138,13 @@ void tst_QSslCertificate::initTestCase() testDataDir += QLatin1String("/"); QDir dir(testDataDir + "certificates"); - QFileInfoList fileInfoList = dir.entryInfoList(QDir::Files | QDir::Readable); + const QFileInfoList fileInfoList = dir.entryInfoList(QDir::Files | QDir::Readable); QRegularExpression rxCert(QLatin1String("^.+\\.(pem|der)$")); QRegularExpression rxSan(QLatin1String("^(.+\\.(?:pem|der))\\.san$")); QRegularExpression rxPubKey(QLatin1String("^(.+\\.(?:pem|der))\\.pubkey$")); QRegularExpression rxDigest(QLatin1String("^(.+\\.(?:pem|der))\\.digest-(md5|sha1)$")); QRegularExpressionMatch match; - foreach (QFileInfo fileInfo, fileInfoList) { + for (const QFileInfo &fileInfo : fileInfoList) { if ((match = rxCert.match(fileInfo.fileName())).hasMatch()) certInfoList << CertInfo(fileInfo, @@ -207,7 +207,7 @@ void tst_QSslCertificate::createTestRows() { QTest::addColumn<QString>("absFilePath"); QTest::addColumn<QSsl::EncodingFormat>("format"); - foreach (CertInfo certInfo, certInfoList) { + for (const CertInfo &certInfo : std::as_const(certInfoList)) { QTest::newRow(certInfo.fileInfo.fileName().toLatin1()) << certInfo.fileInfo.absoluteFilePath() << certInfo.format; } @@ -338,7 +338,7 @@ void tst_QSslCertificate::digest_data() QTest::addColumn<QSsl::EncodingFormat>("format"); QTest::addColumn<QString>("absFilePath_digest_md5"); QTest::addColumn<QString>("absFilePath_digest_sha1"); - foreach (CertInfo certInfo, certInfoList) { + for (const CertInfo &certInfo : std::as_const(certInfoList)) { QString certName = certInfo.fileInfo.fileName(); QTest::newRow(certName.toLatin1()) << certInfo.fileInfo.absoluteFilePath() @@ -391,7 +391,7 @@ void tst_QSslCertificate::subjectAlternativeNames_data() QTest::addColumn<QSsl::EncodingFormat>("format"); QTest::addColumn<QString>("subjAltNameFilePath"); - foreach (CertInfo certInfo, certInfoList) { + for (const CertInfo &certInfo : std::as_const(certInfoList)) { QString certName = certInfo.fileInfo.fileName(); if (subjAltNameMap.contains(certName)) QTest::newRow(certName.toLatin1()) @@ -529,7 +529,7 @@ void tst_QSslCertificate::publicKey_data() QTest::addColumn<QSsl::EncodingFormat>("format"); QTest::addColumn<QString>("pubkeyFilePath"); - foreach (CertInfo certInfo, certInfoList) { + for (const CertInfo &certInfo : std::as_const(certInfoList)) { QString certName = certInfo.fileInfo.fileName(); if (pubkeyMap.contains(certName)) QTest::newRow(certName.toLatin1()) @@ -1082,7 +1082,7 @@ void tst_QSslCertificate::verify() toVerify = QSslCertificate::fromPath(testDataDir + "verify-certs/test-addons-mozilla-org-cert.pem", QSsl::Pem, QSslCertificate::PatternSyntax::FixedString); errors = QSslCertificate::verify(toVerify); bool foundBlack = false; - foreach (const QSslError &error, errors) { + for (const QSslError &error : std::as_const(errors)) { if (error.error() == QSslError::CertificateBlacklisted) { foundBlack = true; break; @@ -1128,9 +1128,8 @@ QString tst_QSslCertificate::toString(const QList<QSslError>& errors) { QStringList errorStrings; - foreach (const QSslError& error, errors) { + for (const QSslError &error : errors) errorStrings.append(QLatin1Char('"') + error.errorString() + QLatin1Char('"')); - } return QLatin1String("[ ") + errorStrings.join(QLatin1String(", ")) + QLatin1String(" ]"); } diff --git a/tests/auto/network/ssl/qsslcipher/CMakeLists.txt b/tests/auto/network/ssl/qsslcipher/CMakeLists.txt index b748e5019d..8538f56acd 100644 --- a/tests/auto/network/ssl/qsslcipher/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslcipher/CMakeLists.txt @@ -5,9 +5,16 @@ ## tst_qsslcipher Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsslcipher LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qsslcipher SOURCES tst_qsslcipher.cpp LIBRARIES Qt::Network + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslcipher/tst_qsslcipher.cpp b/tests/auto/network/ssl/qsslcipher/tst_qsslcipher.cpp index 3b24dd0403..6810149c11 100644 --- a/tests/auto/network/ssl/qsslcipher/tst_qsslcipher.cpp +++ b/tests/auto/network/ssl/qsslcipher/tst_qsslcipher.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 <QTest> diff --git a/tests/auto/network/ssl/qssldiffiehellmanparameters/CMakeLists.txt b/tests/auto/network/ssl/qssldiffiehellmanparameters/CMakeLists.txt index 74008ce99a..3ac34b2f6c 100644 --- a/tests/auto/network/ssl/qssldiffiehellmanparameters/CMakeLists.txt +++ b/tests/auto/network/ssl/qssldiffiehellmanparameters/CMakeLists.txt @@ -5,9 +5,16 @@ ## tst_qssldiffiehellmanparameters Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qssldiffiehellmanparameters LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qssldiffiehellmanparameters SOURCES tst_qssldiffiehellmanparameters.cpp LIBRARIES Qt::Network + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp b/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp index 8233d8883d..d8c6d9158a 100644 --- a/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp +++ b/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> -// 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 <QTest> diff --git a/tests/auto/network/ssl/qsslellipticcurve/CMakeLists.txt b/tests/auto/network/ssl/qsslellipticcurve/CMakeLists.txt index 1be59f5739..dd031a991b 100644 --- a/tests/auto/network/ssl/qsslellipticcurve/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslellipticcurve/CMakeLists.txt @@ -5,9 +5,16 @@ ## tst_qsslellipticcurve Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsslellipticcurve LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qsslellipticcurve SOURCES tst_qsslellipticcurve.cpp LIBRARIES Qt::Network + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslellipticcurve/tst_qsslellipticcurve.cpp b/tests/auto/network/ssl/qsslellipticcurve/tst_qsslellipticcurve.cpp index ab4b457333..96ef7e9828 100644 --- a/tests/auto/network/ssl/qsslellipticcurve/tst_qsslellipticcurve.cpp +++ b/tests/auto/network/ssl/qsslellipticcurve/tst_qsslellipticcurve.cpp @@ -1,6 +1,5 @@ // Copyright (C) 2014 Governikus GmbH & Co. KG. -// 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 <QTest> @@ -66,7 +65,8 @@ void tst_QSslEllipticCurve::fromShortName_data() QTest::newRow("QString()") << QString() << QSslEllipticCurve() << false; QTest::newRow("\"\"") << QString("") << QSslEllipticCurve() << false; QTest::newRow("does-not-exist") << QStringLiteral("does-not-exist") << QSslEllipticCurve() << false; - Q_FOREACH (QSslEllipticCurve ec, QSslConfiguration::supportedEllipticCurves()) { + const auto supported = QSslConfiguration::supportedEllipticCurves(); + for (QSslEllipticCurve ec : supported) { const QString sN = ec.shortName(); QTest::newRow(qPrintable("supported EC \"" + sN + '"')) << sN << ec << true; // At least in the OpenSSL impl, the short name is case-sensitive. That feels odd. @@ -99,7 +99,8 @@ void tst_QSslEllipticCurve::fromLongName_data() QTest::newRow("QString()") << QString() << QSslEllipticCurve() << false; QTest::newRow("\"\"") << QString("") << QSslEllipticCurve() << false; QTest::newRow("does-not-exist") << QStringLiteral("does-not-exist") << QSslEllipticCurve() << false; - Q_FOREACH (QSslEllipticCurve ec, QSslConfiguration::supportedEllipticCurves()) { + const auto supported = QSslConfiguration::supportedEllipticCurves(); + for (QSslEllipticCurve ec : supported) { const QString lN = ec.longName(); QTest::newRow(qPrintable("supported EC \"" + lN + '"')) << lN << ec << true; } diff --git a/tests/auto/network/ssl/qsslerror/CMakeLists.txt b/tests/auto/network/ssl/qsslerror/CMakeLists.txt index 77fcf6f832..006bec3ef2 100644 --- a/tests/auto/network/ssl/qsslerror/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslerror/CMakeLists.txt @@ -5,9 +5,16 @@ ## tst_qsslerror Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsslerror LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qsslerror SOURCES tst_qsslerror.cpp LIBRARIES Qt::Network + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslerror/tst_qsslerror.cpp b/tests/auto/network/ssl/qsslerror/tst_qsslerror.cpp index 6cbcc30beb..981fe3a0d5 100644 --- a/tests/auto/network/ssl/qsslerror/tst_qsslerror.cpp +++ b/tests/auto/network/ssl/qsslerror/tst_qsslerror.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 <QtNetwork/qtnetworkglobal.h> diff --git a/tests/auto/network/ssl/qsslkey/CMakeLists.txt b/tests/auto/network/ssl/qsslkey/CMakeLists.txt index 60d6adc13c..aae017562a 100644 --- a/tests/auto/network/ssl/qsslkey/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslkey/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qsslkey Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsslkey LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -21,9 +27,10 @@ qt_internal_add_test(tst_qsslkey LIBRARIES Qt::Network TESTDATA ${test_data} + BUNDLE_ANDROID_OPENSSL_LIBS ) -qt_internal_extend_target(tst_qsslkey CONDITION QT_FEATURE_private_tests AND QT_FEATURE_openssl_linked +qt_internal_extend_target(tst_qsslkey CONDITION QT_FEATURE_developer_build AND QT_FEATURE_openssl_linked LIBRARIES WrapOpenSSL::WrapOpenSSL ) @@ -31,7 +38,7 @@ qt_internal_extend_target(tst_qsslkey CONDITION QT_FEATURE_private_tests AND QT_ ## Scopes: ##################################################################### -qt_internal_extend_target(tst_qsslkey CONDITION QT_FEATURE_private_tests +qt_internal_extend_target(tst_qsslkey CONDITION QT_FEATURE_developer_build LIBRARIES Qt::CorePrivate Qt::NetworkPrivate diff --git a/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp b/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp index 12f29bf107..79bae3c270 100644 --- a/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp +++ b/tests/auto/network/ssl/qsslkey/tst_qsslkey.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 <QTest> #include <qsslkey.h> @@ -15,6 +15,8 @@ #include <QtCore/qdebug.h> #include <QtCore/qlist.h> +using namespace Qt::StringLiterals; + #ifdef QT_BUILD_INTERNAL #if QT_CONFIG(ssl) #include "private/qsslkey_p.h" @@ -249,7 +251,7 @@ void tst_QSslKey::createPlainTestRows(bool pemOnly) QTest::addColumn<QSsl::KeyType>("type"); QTest::addColumn<int>("length"); QTest::addColumn<QSsl::EncodingFormat>("format"); - foreach (KeyInfo keyInfo, keyInfoList) { + for (const KeyInfo &keyInfo : std::as_const(keyInfoList)) { if (pemOnly && keyInfo.format != QSsl::EncodingFormat::Pem) continue; @@ -467,13 +469,18 @@ void tst_QSslKey::toEncryptedPemOrDer_data() QTest::addColumn<QSsl::EncodingFormat>("format"); QTest::addColumn<QString>("password"); - QStringList passwords; - passwords << " " << "foobar" << "foo bar" - << "aAzZ`1234567890-=~!@#$%^&*()_+[]{}\\|;:'\",.<>/?"; // ### add more (?) - foreach (KeyInfo keyInfo, keyInfoList) { + const QString passwords[] = { + u" "_s, + u"foobar"_s, + u"foo bar"_s, + u"aAzZ`1234567890-=~!@#$%^&*()_+[]{}\\|;:'\",.<>/?"_s, + // ### add more (?) + }; + + for (const KeyInfo &keyInfo : std::as_const(keyInfoList)) { if (keyInfo.fileInfo.fileName().contains("pkcs8")) continue; // pkcs8 keys are encrypted in a different way than the other keys - foreach (QString password, passwords) { + for (const QString &password : passwords) { const QByteArray testName = keyInfo.fileInfo.fileName().toLatin1() + '-' + (keyInfo.algorithm == QSsl::Rsa ? "RSA" : (keyInfo.algorithm == QSsl::Dsa ? "DSA" : "EC")) @@ -573,7 +580,7 @@ void tst_QSslKey::passphraseChecks() QVERIFY(keyFile.exists()); { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey); @@ -581,7 +588,7 @@ void tst_QSslKey::passphraseChecks() } { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, ""); @@ -589,7 +596,7 @@ void tst_QSslKey::passphraseChecks() } { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "WRONG!"); @@ -597,7 +604,7 @@ void tst_QSslKey::passphraseChecks() } { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, passphrase); @@ -615,7 +622,7 @@ void tst_QSslKey::noPassphraseChecks() QFile keyFile(fileName); { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey); @@ -623,7 +630,7 @@ void tst_QSslKey::noPassphraseChecks() } { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, ""); @@ -631,7 +638,7 @@ void tst_QSslKey::noPassphraseChecks() } { if (!keyFile.isOpen()) - keyFile.open(QIODevice::ReadOnly); + QVERIFY(keyFile.open(QIODevice::ReadOnly)); else keyFile.reset(); QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "xxx"); diff --git a/tests/auto/network/ssl/qsslserver/CMakeLists.txt b/tests/auto/network/ssl/qsslserver/CMakeLists.txt index 584a031622..5957b2720e 100644 --- a/tests/auto/network/ssl/qsslserver/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslserver/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qsslserver LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() @@ -19,4 +25,5 @@ qt_internal_add_test(tst_qsslserver Qt::CorePrivate Qt::NetworkPrivate TESTDATA ${test_data} + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp index 13a3201345..26d3a50a5b 100644 --- a/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp +++ b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 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 <QTest> #include <QDebug> diff --git a/tests/auto/network/ssl/qsslsocket/BLACKLIST b/tests/auto/network/ssl/qsslsocket/BLACKLIST index 749c59d968..b990516676 100644 --- a/tests/auto/network/ssl/qsslsocket/BLACKLIST +++ b/tests/auto/network/ssl/qsslsocket/BLACKLIST @@ -1,11 +1,6 @@ [deprecatedProtocols] windows -[spontaneousWrite] -windows-7sp1 [connectToHostEncrypted] macos -[setSslConfiguration] -windows-10 msvc-2015 -windows-7sp1 [connectToHostEncryptedWithVerificationPeerName] macos diff --git a/tests/auto/network/ssl/qsslsocket/CMakeLists.txt b/tests/auto/network/ssl/qsslsocket/CMakeLists.txt index df644e79c4..456deacb60 100644 --- a/tests/auto/network/ssl/qsslsocket/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslsocket/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qsslsocket LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() @@ -21,6 +27,7 @@ qt_internal_add_test(tst_qsslsocket Qt::TestPrivate TESTDATA ${test_data} QT_TEST_SERVER_LIST "squid" "danted" "cyrus" "apache2" "echo" + BUNDLE_ANDROID_OPENSSL_LIBS ) qt_internal_extend_target(tst_qsslsocket CONDITION QT_FEATURE_private_tests AND QT_FEATURE_openssl_linked diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index 99b150a983..b45d6b5d8f 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2014 Governikus GmbH & Co. KG. -// 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/private/qtnetworkglobal_p.h> @@ -166,9 +166,7 @@ private slots: void protocol(); void protocolServerSide_data(); void protocolServerSide(); -#if QT_CONFIG(openssl) void serverCipherPreferences(); -#endif void setCaCertificates(); void setLocalCertificate(); void localCertificateChain(); @@ -293,6 +291,7 @@ private: QSslSocket *socket; QList<QSslError> storedExpectedSslErrors; bool isTestingOpenSsl = false; + bool isSecurityLevel0Required = false; bool opensslResolved = false; bool isTestingSecureTransport = false; bool isTestingSchannel = false; @@ -412,6 +411,9 @@ void tst_QSslSocket::initTestCase() flukeCertificateError = QSslError::SelfSignedCertificate; #if QT_CONFIG(openssl) opensslResolved = qt_auto_test_resolve_OpenSSL_symbols(); + // This is where OpenSSL moved several protocols under + // non-default (0) security level (the default is 1). + isSecurityLevel0Required = OPENSSL_VERSION_NUMBER >= 0x30100010; #else opensslResolved = false; // Not 'unused variable' anymore. #endif @@ -810,6 +812,10 @@ void tst_QSslSocket::simpleConnect() if (!QSslSocket::supportsSsl()) return; + // Starting from OpenSSL v 3.1.1 deprecated protocol versions (we want to use when connecting) are not available by default. + if (isSecurityLevel0Required) + QSKIP("Testing with OpenSSL backend, but security level 0 is required for TLS v1.1 or earlier"); + QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; @@ -871,6 +877,10 @@ void tst_QSslSocket::simpleConnectWithIgnore() if (!QSslSocket::supportsSsl()) return; + // Starting from OpenSSL v 3.1.1 deprecated protocol versions (we want to use when connecting) are not available by default. + if (isSecurityLevel0Required) + QSKIP("Testing with OpenSSL backend, but security level 0 is required for TLS v1.1 or earlier"); + QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; @@ -915,6 +925,10 @@ void tst_QSslSocket::simpleConnectWithIgnore() void tst_QSslSocket::sslErrors_data() { + // Starting from OpenSSL v 3.1.1 deprecated protocol versions (we want to use in 'sslErrors' test) are not available by default. + if (isSecurityLevel0Required) + QSKIP("Testing with OpenSSL backend, but security level 0 is required for TLS v1.1 or earlier"); + QTest::addColumn<QString>("host"); QTest::addColumn<int>("port"); @@ -1296,6 +1310,7 @@ void tst_QSslSocket::privateKey() #if QT_CONFIG(openssl) void tst_QSslSocket::privateKeyOpaque() { +#ifndef OPENSSL_NO_DEPRECATED_3_0 if (!isTestingOpenSsl) QSKIP("The active TLS backend does not support private opaque keys"); @@ -1329,6 +1344,7 @@ void tst_QSslSocket::privateKeyOpaque() QFETCH_GLOBAL(bool, setProxy); if (setProxy && !socket->waitForEncrypted(10000)) QSKIP("Skipping flaky test - See QTBUG-29941"); +#endif // OPENSSL_NO_DEPRECATED_3_0 } #endif // Feature 'openssl'. @@ -1645,8 +1661,6 @@ void tst_QSslSocket::protocolServerSide() QCOMPARE(client.isEncrypted(), works); } -#if QT_CONFIG(openssl) - void tst_QSslSocket::serverCipherPreferences() { if (!isTestingOpenSsl) @@ -1741,8 +1755,6 @@ void tst_QSslSocket::serverCipherPreferences() } } -#endif // Feature 'openssl'. - void tst_QSslSocket::setCaCertificates() { @@ -1967,6 +1979,10 @@ void tst_QSslSocket::waitForConnectedEncryptedReadyRead() if (!QSslSocket::supportsSsl()) return; + // Starting from OpenSSL v 3.1.1 deprecated protocol versions (we want to use here) are not available by default. + if (isSecurityLevel0Required) + QSKIP("Testing with OpenSSL backend, but security level 0 is required for TLS v1.1 or earlier"); + QSslSocketPtr socket = newSocket(); this->socket = socket.data(); @@ -3080,7 +3096,14 @@ void tst_QSslSocket::blacklistedCertificates() QList<QSslError> sslErrors = receiver->sslHandshakeErrors(); QVERIFY(sslErrors.size() > 0); // there are more errors (self signed cert and hostname mismatch), but we only care about the blacklist error - QCOMPARE(sslErrors.at(0).error(), QSslError::CertificateBlacklisted); + std::optional<QSslError> blacklistedError; + for (const QSslError &error : sslErrors) { + if (error.error() == QSslError::CertificateBlacklisted) { + blacklistedError = error; + break; + } + } + QVERIFY2(blacklistedError, "CertificateBlacklisted error not found!"); } void tst_QSslSocket::versionAccessors() @@ -3106,6 +3129,10 @@ void tst_QSslSocket::encryptWithoutConnecting() void tst_QSslSocket::resume_data() { + // Starting from OpenSSL v 3.1.1 deprecated protocol versions (we want to use in 'resume' test) are not available by default. + if (isSecurityLevel0Required) + QSKIP("Testing with OpenSSL backend, but security level 0 is required for TLS v1.1 or earlier"); + QTest::addColumn<bool>("ignoreErrorsAfterPause"); QTest::addColumn<QList<QSslError> >("errorsToIgnore"); QTest::addColumn<bool>("expectSuccess"); @@ -3449,7 +3476,13 @@ void tst_QSslSocket::dhServer() return; SslServer server; - server.ciphers = {QSslCipher("DHE-RSA-AES256-SHA"), QSslCipher("DHE-DSS-AES256-SHA")}; + QSslCipher rsaCipher("DHE-RSA-AES256-SHA"); + QSslCipher dssCipher("DHE-DSS-AES256-SHA"); + if (rsaCipher.isNull()) + QSKIP("The current backend doesn't support DHE-RSA-AES256-SHA"); + if (dssCipher.isNull()) + QSKIP("The current backend doesn't support DHE-DSS-AES256-SHA"); + server.ciphers = { rsaCipher, dssCipher }; QVERIFY(server.listen()); QEventLoop loop; @@ -3477,9 +3510,10 @@ void tst_QSslSocket::dhServerCustomParamsNull() if (setProxy) return; + const QSslCipher cipherWithDH("DHE-RSA-AES256-SHA256"); SslServer server; - server.ciphers = {QSslCipher("DHE-RSA-AES256-SHA"), QSslCipher("DHE-DSS-AES256-SHA")}; - server.protocol = Test::TlsV1_0; + server.ciphers = {cipherWithDH}; + server.protocol = QSsl::TlsV1_2; QSslConfiguration cfg = server.config; cfg.setDiffieHellmanParameters(QSslDiffieHellmanParameters()); @@ -3492,7 +3526,6 @@ void tst_QSslSocket::dhServerCustomParamsNull() QSslSocket client; QSslConfiguration config = client.sslConfiguration(); - config.setProtocol(Test::TlsV1_0); client.setSslConfiguration(config); socket = &client; connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), &loop, SLOT(quit())); @@ -3503,7 +3536,8 @@ void tst_QSslSocket::dhServerCustomParamsNull() loop.exec(); - QVERIFY(client.state() != QAbstractSocket::ConnectedState); + QCOMPARE(client.state(), QAbstractSocket::ConnectedState); + QCOMPARE(client.sessionCipher(), cipherWithDH); } void tst_QSslSocket::dhServerCustomParams() @@ -3518,7 +3552,9 @@ void tst_QSslSocket::dhServerCustomParams() return; SslServer server; - server.ciphers = {QSslCipher("DHE-RSA-AES256-SHA"), QSslCipher("DHE-DSS-AES256-SHA")}; + const QSslCipher cipherWithDH("DHE-RSA-AES256-SHA256"); + server.ciphers = {cipherWithDH}; + server.protocol = QSsl::TlsV1_2; QSslConfiguration cfg = server.config; @@ -3548,7 +3584,8 @@ void tst_QSslSocket::dhServerCustomParams() loop.exec(); - QVERIFY(client.state() == QAbstractSocket::ConnectedState); + QCOMPARE(client.state(), QAbstractSocket::ConnectedState); + QCOMPARE(client.sessionCipher(), cipherWithDH); } #endif // QT_CONFIG(openssl) @@ -3564,7 +3601,10 @@ void tst_QSslSocket::ecdhServer() return; SslServer server; - server.ciphers = {QSslCipher("ECDHE-RSA-AES128-SHA")}; + QSslCipher cipher("ECDHE-RSA-AES128-SHA"); + if (cipher.isNull()) + QSKIP("The current backend doesn't support ECDHE-RSA-AES128-SHA"); + server.ciphers = {cipher}; QVERIFY(server.listen()); QEventLoop loop; diff --git a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/CMakeLists.txt b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/CMakeLists.txt index 2201f38fd7..34e026cb8b 100644 --- a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qsslsocket_onDemandCertificates_member LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() @@ -16,4 +22,5 @@ qt_internal_add_test(tst_qsslsocket_onDemandCertificates_member Qt::CorePrivate Qt::NetworkPrivate QT_TEST_SERVER_LIST "squid" "danted" + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/tst_qsslsocket_onDemandCertificates_member.cpp b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/tst_qsslsocket_onDemandCertificates_member.cpp index 3e18f37b9b..119891c916 100644 --- a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/tst_qsslsocket_onDemandCertificates_member.cpp +++ b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/tst_qsslsocket_onDemandCertificates_member.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 <QtNetwork> diff --git a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/CMakeLists.txt b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/CMakeLists.txt index d338237532..696eec98ee 100644 --- a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/CMakeLists.txt +++ b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/CMakeLists.txt @@ -1,6 +1,12 @@ # 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_qsslsocket_onDemandCertificates_static LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + if(NOT QT_FEATURE_private_tests) return() endif() @@ -16,4 +22,5 @@ qt_internal_add_test(tst_qsslsocket_onDemandCertificates_static Qt::CorePrivate Qt::NetworkPrivate QT_TEST_SERVER_LIST "squid" "danted" + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/tst_qsslsocket_onDemandCertificates_static.cpp b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/tst_qsslsocket_onDemandCertificates_static.cpp index 95aba0b29c..c5475da581 100644 --- a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/tst_qsslsocket_onDemandCertificates_static.cpp +++ b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/tst_qsslsocket_onDemandCertificates_static.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 <QtNetwork> diff --git a/tests/auto/network/ssl/shared/qopenssl_symbols.h b/tests/auto/network/ssl/shared/qopenssl_symbols.h index 7f1f6c8285..c98e90d424 100644 --- a/tests/auto/network/ssl/shared/qopenssl_symbols.h +++ b/tests/auto/network/ssl/shared/qopenssl_symbols.h @@ -1,6 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2014 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only /**************************************************************************** ** @@ -502,6 +502,12 @@ QStringList findAllLibCrypto() #ifdef Q_OS_WIN +#if (OPENSSL_VERSION_NUMBER >> 28) < 3 +#define QT_OPENSSL_VERSION "1_1" +#elif OPENSSL_VERSION_MAJOR == 3 // Starting with 3.0 this define is available +#define QT_OPENSSL_VERSION "3" +#endif // > 3 intentionally left undefined + struct LoadedOpenSsl { std::unique_ptr<QSystemLibrary> ssl, crypto; }; @@ -540,8 +546,9 @@ static LoadedOpenSsl loadOpenSsl() #define QT_SSL_SUFFIX #endif - tryToLoadOpenSslWin32Library(QLatin1String("libssl-1_1" QT_SSL_SUFFIX), - QLatin1String("libcrypto-1_1" QT_SSL_SUFFIX), result); + tryToLoadOpenSslWin32Library(QLatin1String("libssl-" QT_OPENSSL_VERSION QT_SSL_SUFFIX), + QLatin1String("libcrypto-" QT_OPENSSL_VERSION QT_SSL_SUFFIX), + result); #undef QT_SSL_SUFFIX return result; diff --git a/tests/auto/network/ssl/shared/tlshelpers.h b/tests/auto/network/ssl/shared/tlshelpers.h index 71db837d4b..de3c2ea51a 100644 --- a/tests/auto/network/ssl/shared/tlshelpers.h +++ b/tests/auto/network/ssl/shared/tlshelpers.h @@ -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> |