diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 251d17b0b4..d25be851bf 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -1101,6 +1101,13 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader } } + // Discard all informational (1xx) replies with the exception of 101. + // Also see RFC 9110 (Chapter 15.2) + if (statusCode == 100 || (102 <= statusCode && statusCode <= 199)) { + httpReplyPrivate->clearHttpLayerInformation(); + return; + } + if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) { QHttpNetworkConnectionPrivate::ParseRedirectResult result = m_connection->d_func()->parseRedirectResponse(httpReply); diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp index 4aa840e6c2..7c5ccc8ff5 100644 --- a/tests/auto/network/access/http2/http2srv.cpp +++ b/tests/auto/network/access/http2/http2srv.cpp @@ -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; @@ -837,6 +843,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); diff --git a/tests/auto/network/access/http2/http2srv.h b/tests/auto/network/access/http2/http2srv.h index 13dca0377e..dc94318527 100644 --- a/tests/auto/network/access/http2/http2srv.h +++ b/tests/auto/network/access/http2/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); @@ -210,6 +212,7 @@ private: 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 111de8478a..b624f6e436 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -72,6 +72,8 @@ private slots: void defaultQnamHttp2Configuration(); void singleRequest_data(); void singleRequest(); + void informationalRequest_data(); + void informationalRequest(); void multipleRequests(); void flowControlClientSide(); void flowControlServerSide(); @@ -300,6 +302,70 @@ void tst_Http2::singleRequest() #endif // QT_CONFIG(ssl) } +void tst_Http2::informationalRequest_data() +{ + QTest::addColumn("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(), 200); + QVERIFY(!reply->hasRawHeader("a_random_header_field")); + QVERIFY(!reply->hasRawHeader("another_random_header_field")); +} + void tst_Http2::multipleRequests() { clearHTTP2State();