diff options
Diffstat (limited to 'tests/auto/network/access')
-rw-r--r-- | tests/auto/network/access/http2/http2srv.cpp | 32 | ||||
-rw-r--r-- | tests/auto/network/access/http2/http2srv.h | 8 | ||||
-rw-r--r-- | tests/auto/network/access/http2/tst_http2.cpp | 93 | ||||
-rw-r--r-- | tests/auto/network/access/qnetworkreply/BLACKLIST | 5 |
4 files changed, 133 insertions, 5 deletions
diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp index a8eebf5a24..e2f039e2f7 100644 --- a/tests/auto/network/access/http2/http2srv.cpp +++ b/tests/auto/network/access/http2/http2srv.cpp @@ -120,6 +120,11 @@ void Http2Server::setResponseBody(const QByteArray &body) responseBody = body; } +void Http2Server::setAuthenticationHeader(const QByteArray &authentication) +{ + authenticationHeader = authentication; +} + void Http2Server::emulateGOAWAY(int timeout) { Q_ASSERT(timeout >= 0); @@ -138,6 +143,17 @@ bool Http2Server::isClearText() const return connectionType == H2Type::h2c || connectionType == H2Type::h2cDirect; } +QByteArray Http2Server::requestAuthorizationHeader() +{ + const auto isAuthHeader = [](const HeaderField &field) { + return field.name == "authorization"; + }; + const auto requestHeaders = decoder.decodedHeader(); + const auto authentication = + std::find_if(requestHeaders.cbegin(), requestHeaders.cend(), isAuthHeader); + return authentication == requestHeaders.cend() ? QByteArray() : authentication->value; +} + void Http2Server::startServer() { if (listen()) { @@ -732,10 +748,15 @@ void Http2Server::handleDATA() } if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) { - closedStreams.insert(streamID); // Enter "half-closed remote" state. - streamWindows.erase(it); + if (responseBody.isEmpty()) { + closedStreams.insert(streamID); // Enter "half-closed remote" state. + streamWindows.erase(it); + } emit receivedData(streamID); } + emit receivedDATAFrame(streamID, + QByteArray(reinterpret_cast<const char *>(inboundFrame.dataBegin()), + inboundFrame.dataSize())); } void Http2Server::handleWINDOW_UPDATE() @@ -816,6 +837,9 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) if (emptyBody) writer.addFlag(FrameFlag::END_STREAM); + // We assume any auth is correct. Leaves the checking to the test itself + const bool hasAuth = !requestAuthorizationHeader().isEmpty(); + HttpHeader header; if (redirectWhileReading) { if (redirectSent) { @@ -832,6 +856,10 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) header.push_back({"location", url.arg(isClearText() ? QStringLiteral("http") : QStringLiteral("https"), QString::number(targetPort)).toLatin1()}); + } else if (!authenticationHeader.isEmpty() && !hasAuth) { + header.push_back({ ":status", "401" }); + header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader)); + authenticationHeader.clear(); } 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 3105684d59..013af86cc8 100644 --- a/tests/auto/network/access/http2/http2srv.h +++ b/tests/auto/network/access/http2/http2srv.h @@ -86,11 +86,15 @@ public: // To be called before server started: void enablePushPromise(bool enabled, const QByteArray &path = QByteArray()); void setResponseBody(const QByteArray &body); + // No authentication data is generated for the method, the full header value must be set + void setAuthenticationHeader(const QByteArray &authentication); void emulateGOAWAY(int timeout); void redirectOpenStream(quint16 targetPort); bool isClearText() const; + QByteArray requestAuthorizationHeader(); + // Invokables, since we can call them from the main thread, // but server (can) work on its own thread. Q_INVOKABLE void startServer(); @@ -127,6 +131,8 @@ Q_SIGNALS: void decompressionFailed(quint32 streamID); void receivedRequest(quint32 streamID); void receivedData(quint32 streamID); + // Emitted for every DATA frame. Includes the content of the frame as \a body. + void receivedDATAFrame(quint32 streamID, const QByteArray &body); void windowUpdate(quint32 streamID); void sendingData(); @@ -211,6 +217,8 @@ private: bool redirectSent = false; quint16 targetPort = 0; QAtomicInt interrupted; + + QByteArray authenticationHeader; 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 6702f25b16..0d851cd3df 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -111,6 +111,9 @@ private slots: void connectToHost(); void maxFrameSize(); + void authenticationRequired_data(); + void authenticationRequired(); + protected slots: // Slots to listen to our in-process server: void serverStarted(quint16 port); @@ -154,6 +157,7 @@ private: int windowUpdates = 0; bool prefaceOK = false; bool serverGotSettingsACK = false; + bool POSTResponseHEADOnly = true; static const RawSettings defaultServerSettings; }; @@ -767,6 +771,92 @@ void tst_Http2::maxFrameSize() QVERIFY(serverGotSettingsACK); } +void tst_Http2::authenticationRequired_data() +{ + QTest::addColumn<bool>("success"); + QTest::addColumn<bool>("responseHEADOnly"); + + QTest::addRow("failed-auth") << false << true; + QTest::addRow("successful-auth") << 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; +} + +void tst_Http2::authenticationRequired() +{ + clearHTTP2State(); + QFETCH(const bool, responseHEADOnly); + POSTResponseHEADOnly = responseHEADOnly; + + QFETCH(const bool, success); + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + targetServer->setResponseBody("Hello"); + targetServer->setAuthenticationHeader("Basic realm=\"Shadow\""); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + auto url = requestUrl(defaultConnectionType()); + url.setPath("/index.html"); + QNetworkRequest request(url); + + QByteArray expectedBody = "Hello, World!"; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->post(request, expectedBody)); + + bool authenticationRequested = false; + connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(), + [&](QNetworkReply *, QAuthenticator *auth) { + authenticationRequested = true; + if (success) { + auth->setUser("admin"); + auth->setPassword("admin"); + } + }); + + QByteArray receivedBody; + connect(targetServer.get(), &Http2Server::receivedDATAFrame, reply.get(), + [&receivedBody](quint32 streamID, const QByteArray &body) { + if (streamID == 3) // The expected body is on the retry, so streamID == 3 + receivedBody += body; + }); + + if (success) + connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + else + connect(reply.get(), &QNetworkReply::errorOccurred, this, &tst_Http2::replyFinishedWithError); + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + if (!success) + QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); + // else: no error (is checked in tst_Http2::replyFinished) + + QVERIFY(authenticationRequested); + + const auto isAuthenticated = [](QByteArray bv) { + return bv == "Basic YWRtaW46YWRtaW4="; // admin:admin + }; + // Get the "authorization" header out from the server and make sure it's as expected: + auto reqAuthHeader = targetServer->requestAuthorizationHeader(); + QCOMPARE(isAuthenticated(reqAuthHeader), success); + if (success) + QCOMPARE(receivedBody, expectedBody); +} + void tst_Http2::serverStarted(quint16 port) { serverPort = port; @@ -778,6 +868,7 @@ void tst_Http2::clearHTTP2State() windowUpdates = 0; prefaceOK = false; serverGotSettingsACK = false; + POSTResponseHEADOnly = true; } void tst_Http2::runEventLoop(int ms) @@ -910,7 +1001,7 @@ void tst_Http2::receivedData(quint32 streamID) Q_ASSERT(srv); QMetaObject::invokeMethod(srv, "sendResponse", Qt::QueuedConnection, Q_ARG(quint32, streamID), - Q_ARG(bool, true /*HEADERS only*/)); + Q_ARG(bool, POSTResponseHEADOnly /*true = HEADERS only*/)); } void tst_Http2::windowUpdated(quint32 streamID) diff --git a/tests/auto/network/access/qnetworkreply/BLACKLIST b/tests/auto/network/access/qnetworkreply/BLACKLIST index 2bc1c85cba..52857010e2 100644 --- a/tests/auto/network/access/qnetworkreply/BLACKLIST +++ b/tests/auto/network/access/qnetworkreply/BLACKLIST @@ -32,13 +32,14 @@ windows-10 # QTBUG-66247 b2qt windows-10 msvc-2015 -ubuntu -rhel +linux [ioHttpRedirectPolicy] opensuse-leap b2qt ubuntu windows-10 +sles-15 +opensuse-15.1 [putToFtp] windows-10 [putWithServerClosingConnectionImmediately] |