summaryrefslogtreecommitdiffstats
path: root/tests/auto/network/access/http2/tst_http2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/network/access/http2/tst_http2.cpp')
-rw-r--r--tests/auto/network/access/http2/tst_http2.cpp260
1 files changed, 259 insertions, 1 deletions
diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
index 6702f25b16..bf8c779909 100644
--- a/tests/auto/network/access/http2/tst_http2.cpp
+++ b/tests/auto/network/access/http2/tst_http2.cpp
@@ -110,6 +110,13 @@ private slots:
void connectToHost_data();
void connectToHost();
void maxFrameSize();
+ void http2DATAFrames();
+
+ void authenticationRequired_data();
+ void authenticationRequired();
+
+ void redirect_data();
+ void redirect();
protected slots:
// Slots to listen to our in-process server:
@@ -154,6 +161,7 @@ private:
int windowUpdates = 0;
bool prefaceOK = false;
bool serverGotSettingsACK = false;
+ bool POSTResponseHEADOnly = true;
static const RawSettings defaultServerSettings;
};
@@ -267,6 +275,10 @@ void tst_Http2::singleRequest()
request.setAttribute(h2Attribute, QVariant(true));
auto reply = manager->get(request);
+#if QT_CONFIG(ssl)
+ QSignalSpy encSpy(reply, &QNetworkReply::encrypted);
+#endif // QT_CONFIG(ssl)
+
connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
// Since we're using self-signed certificates,
// ignore SSL errors:
@@ -281,6 +293,11 @@ void tst_Http2::singleRequest()
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(reply->isFinished());
+
+#if QT_CONFIG(ssl)
+ if (connectionType == H2Type::h2Alpn || connectionType == H2Type::h2Direct)
+ QCOMPARE(encSpy.count(), 1);
+#endif // QT_CONFIG(ssl)
}
void tst_Http2::multipleRequests()
@@ -767,6 +784,246 @@ void tst_Http2::maxFrameSize()
QVERIFY(serverGotSettingsACK);
}
+void tst_Http2::http2DATAFrames()
+{
+ using namespace Http2;
+
+ {
+ // 0. DATA frame with payload, no padding.
+
+ FrameWriter writer(FrameType::DATA, FrameFlag::EMPTY, 1);
+ writer.append(uchar(1));
+ writer.append(uchar(2));
+ writer.append(uchar(3));
+
+ const Frame frame = writer.outboundFrame();
+ const auto &buffer = frame.buffer;
+ // Frame's header is 9 bytes + 3 bytes of payload
+ // (+ 0 bytes of padding and no padding length):
+ QCOMPARE(int(buffer.size()), 12);
+
+ QVERIFY(!frame.padding());
+ QCOMPARE(int(frame.payloadSize()), 3);
+ QCOMPARE(int(frame.dataSize()), 3);
+ QCOMPARE(frame.dataBegin() - buffer.data(), 9);
+ QCOMPARE(char(*frame.dataBegin()), uchar(1));
+ }
+
+ {
+ // 1. DATA with padding.
+
+ const int padLength = 10;
+ FrameWriter writer(FrameType::DATA, FrameFlag::END_STREAM | FrameFlag::PADDED, 1);
+ writer.append(uchar(padLength)); // The length of padding is 1 byte long.
+ writer.append(uchar(1));
+ for (int i = 0; i < padLength; ++i)
+ writer.append(uchar(0));
+
+ const Frame frame = writer.outboundFrame();
+ const auto &buffer = frame.buffer;
+ // Frame's header is 9 bytes + 1 byte for padding length
+ // + 1 byte of data + 10 bytes of padding:
+ QCOMPARE(int(buffer.size()), 21);
+
+ QCOMPARE(frame.padding(), padLength);
+ QCOMPARE(int(frame.payloadSize()), 12); // Includes padding, its length + data.
+ QCOMPARE(int(frame.dataSize()), 1);
+
+ // Skipping 9 bytes long header and padding length:
+ QCOMPARE(frame.dataBegin() - buffer.data(), 10);
+
+ QCOMPARE(char(frame.dataBegin()[0]), uchar(1));
+ QCOMPARE(char(frame.dataBegin()[1]), uchar(0));
+
+ QVERIFY(frame.flags().testFlag(FrameFlag::END_STREAM));
+ QVERIFY(frame.flags().testFlag(FrameFlag::PADDED));
+ }
+ {
+ // 2. DATA with PADDED flag, but 0 as padding length.
+
+ FrameWriter writer(FrameType::DATA, FrameFlag::END_STREAM | FrameFlag::PADDED, 1);
+
+ writer.append(uchar(0)); // Number of padding bytes is 1 byte long.
+ writer.append(uchar(1));
+
+ const Frame frame = writer.outboundFrame();
+ const auto &buffer = frame.buffer;
+
+ // Frame's header is 9 bytes + 1 byte for padding length + 1 byte of data
+ // + 0 bytes of padding:
+ QCOMPARE(int(buffer.size()), 11);
+
+ QCOMPARE(frame.padding(), 0);
+ QCOMPARE(int(frame.payloadSize()), 2); // Includes padding (0 bytes), its length + data.
+ QCOMPARE(int(frame.dataSize()), 1);
+
+ // Skipping 9 bytes long header and padding length:
+ QCOMPARE(frame.dataBegin() - buffer.data(), 10);
+
+ QCOMPARE(char(*frame.dataBegin()), uchar(1));
+
+ QVERIFY(frame.flags().testFlag(FrameFlag::END_STREAM));
+ QVERIFY(frame.flags().testFlag(FrameFlag::PADDED));
+ }
+}
+
+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();
+ serverPort = 0;
+ 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);
+ // 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::redirect_data()
+{
+ QTest::addColumn<int>("maxRedirects");
+ QTest::addColumn<int>("redirectCount");
+ QTest::addColumn<bool>("success");
+
+ QTest::addRow("1-redirects-none-allowed-failure") << 0 << 1 << false;
+ QTest::addRow("1-redirects-success") << 1 << 1 << true;
+ QTest::addRow("2-redirects-1-allowed-failure") << 1 << 2 << false;
+}
+
+void tst_Http2::redirect()
+{
+ QFETCH(const int, maxRedirects);
+ QFETCH(const int, redirectCount);
+ QFETCH(const bool, success);
+ const QByteArray redirectUrl = "/b.html";
+
+ clearHTTP2State();
+ serverPort = 0;
+
+ ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
+ targetServer->setRedirect(redirectUrl, redirectCount);
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1 + maxRedirects;
+
+ auto originalUrl = requestUrl(defaultConnectionType());
+ auto url = originalUrl;
+ url.setPath("/index.html");
+ QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true));
+ request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
+ request.setMaximumRedirectsAllowed(maxRedirects);
+
+ QScopedPointer<QNetworkReply> reply;
+ reply.reset(manager->get(request));
+
+ 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::NoError);
+ QCOMPARE(reply->url().toString(),
+ originalUrl.resolved(QString::fromLatin1(redirectUrl)).toString());
+ } else if (maxRedirects < redirectCount) {
+ QCOMPARE(reply->error(), QNetworkReply::TooManyRedirectsError);
+ }
+ QTRY_VERIFY(serverGotSettingsACK);
+}
+
void tst_Http2::serverStarted(quint16 port)
{
serverPort = port;
@@ -778,6 +1035,7 @@ void tst_Http2::clearHTTP2State()
windowUpdates = 0;
prefaceOK = false;
serverGotSettingsACK = false;
+ POSTResponseHEADOnly = true;
}
void tst_Http2::runEventLoop(int ms)
@@ -910,7 +1168,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)