diff --git a/test/UnitTestFramework/Fixtures/LocalHttpTestServer.cc b/test/UnitTestFramework/Fixtures/LocalHttpTestServer.cc index a3ffc7e8edfb..133950815a3a 100644 --- a/test/UnitTestFramework/Fixtures/LocalHttpTestServer.cc +++ b/test/UnitTestFramework/Fixtures/LocalHttpTestServer.cc @@ -1,6 +1,7 @@ #include "LocalHttpTestServer.h" #include +#include #include #include @@ -8,6 +9,30 @@ namespace TestFixtures { +namespace { +constexpr qsizetype MAX_REQUEST_HEADER_SIZE = 64 * 1024; + +struct RawResponderState +{ + QByteArray request; + bool responseSent = false; +}; + +QByteArray httpReasonPhrase(int statusCode) +{ + switch (statusCode) { + case 200: return QByteArrayLiteral("OK"); + case 204: return QByteArrayLiteral("No Content"); + case 206: return QByteArrayLiteral("Partial Content"); + case 304: return QByteArrayLiteral("Not Modified"); + case 400: return QByteArrayLiteral("Bad Request"); + case 404: return QByteArrayLiteral("Not Found"); + case 500: return QByteArrayLiteral("Internal Server Error"); + default: return QByteArrayLiteral("Status"); + } +} +} // namespace + LocalHttpTestServer::~LocalHttpTestServer() { close(); @@ -41,22 +66,6 @@ QString LocalHttpTestServer::url(const QString& path) const return QStringLiteral("http://%1:%2%3").arg(host).arg(port()).arg(path); } -namespace { -QByteArray httpReasonPhrase(int statusCode) -{ - switch (statusCode) { - case 200: return QByteArrayLiteral("OK"); - case 204: return QByteArrayLiteral("No Content"); - case 206: return QByteArrayLiteral("Partial Content"); - case 304: return QByteArrayLiteral("Not Modified"); - case 400: return QByteArrayLiteral("Bad Request"); - case 404: return QByteArrayLiteral("Not Found"); - case 500: return QByteArrayLiteral("Internal Server Error"); - default: return QByteArrayLiteral("Status"); - } -} -} // namespace - void LocalHttpTestServer::installHttpResponder(const QByteArray& body, int statusCode, const QByteArray& contentType, int cacheMaxAge) { @@ -80,16 +89,31 @@ void LocalHttpTestServer::installRawResponder(const QByteArray& rawResponse) (void) QObject::connect(&_server, &QTcpServer::newConnection, &_server, [this, rawResponse]() { while (_server.hasPendingConnections()) { QTcpSocket* const socket = _server.nextPendingConnection(); - (void) QObject::connect( - socket, &QTcpSocket::readyRead, socket, - [socket, rawResponse]() { - socket->readAll(); - socket->write(rawResponse); - socket->flush(); - socket->disconnectFromHost(); - }, - Qt::SingleShotConnection); (void) QObject::connect(socket, &QTcpSocket::disconnected, socket, &QObject::deleteLater); + + const auto state = QSharedPointer::create(); + const auto sendResponseWhenRequestComplete = [socket, rawResponse, state]() { + if (state->responseSent) { + return; + } + + state->request.append(socket->readAll()); + if (!state->request.contains(QByteArrayLiteral("\r\n\r\n"))) { + if (state->request.size() > MAX_REQUEST_HEADER_SIZE) { + state->responseSent = true; + socket->disconnectFromHost(); + } + return; + } + + state->responseSent = true; + socket->write(rawResponse); + socket->flush(); + socket->disconnectFromHost(); + }; + + (void) QObject::connect(socket, &QTcpSocket::readyRead, socket, sendResponseWhenRequestComplete); + sendResponseWhenRequestComplete(); } }); } diff --git a/test/UnitTestFramework/Tests/CMakeLists.txt b/test/UnitTestFramework/Tests/CMakeLists.txt index ce042e408f0a..d30982487aa7 100644 --- a/test/UnitTestFramework/Tests/CMakeLists.txt +++ b/test/UnitTestFramework/Tests/CMakeLists.txt @@ -1,5 +1,7 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/LocalHttpTestServerTest.cc + ${CMAKE_CURRENT_SOURCE_DIR}/LocalHttpTestServerTest.h ${CMAKE_CURRENT_SOURCE_DIR}/MultiSignalSpyTest.cc ${CMAKE_CURRENT_SOURCE_DIR}/MultiSignalSpyTest.h ${CMAKE_CURRENT_SOURCE_DIR}/SignalEmitter.h @@ -15,6 +17,7 @@ target_sources(${CMAKE_PROJECT_NAME} target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +add_qgc_test(LocalHttpTestServerTest LABELS Unit) add_qgc_test(MultiSignalSpyTest LABELS Unit) add_qgc_test(TestBaseClassesTest LABELS Unit) add_qgc_test(TestFixturesTest LABELS Unit) diff --git a/test/UnitTestFramework/Tests/LocalHttpTestServerTest.cc b/test/UnitTestFramework/Tests/LocalHttpTestServerTest.cc new file mode 100644 index 000000000000..c0f233b6f665 --- /dev/null +++ b/test/UnitTestFramework/Tests/LocalHttpTestServerTest.cc @@ -0,0 +1,40 @@ +#include "LocalHttpTestServerTest.h" + +#include "Fixtures/LocalHttpTestServer.h" + +#include +#include +#include + +void LocalHttpTestServerTest::_testFragmentedRequestHeader() +{ + TestFixtures::LocalHttpTestServer server; + QVERIFY(server.listen()); + server.installHttpResponder(QByteArrayLiteral("ready")); + + const QUrl serverUrl(server.url()); + QVERIFY(serverUrl.isValid()); + + QTcpSocket client; + client.connectToHost(serverUrl.host(), static_cast(serverUrl.port())); + QVERIFY(client.waitForConnected(TestTimeout::mediumMs())); + + QSignalSpy readyReadSpy(&client, &QTcpSocket::readyRead); + const QByteArray firstRequestFragment = + QByteArrayLiteral("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n"); + QCOMPARE(client.write(firstRequestFragment), firstRequestFragment.size()); + QVERIFY(client.waitForBytesWritten(TestTimeout::mediumMs())); + QVERIFY(!readyReadSpy.wait(TestTimeout::shortMs())); + QCOMPARE(client.state(), QAbstractSocket::ConnectedState); + + const QByteArray finalRequestFragment = QByteArrayLiteral("\r\n"); + QCOMPARE(client.write(finalRequestFragment), finalRequestFragment.size()); + QVERIFY(client.waitForBytesWritten(TestTimeout::mediumMs())); + + QTRY_COMPARE_WITH_TIMEOUT(client.state(), QAbstractSocket::UnconnectedState, TestTimeout::mediumMs()); + const QByteArray response = client.readAll(); + QVERIFY(response.startsWith(QByteArrayLiteral("HTTP/1.1 200 OK\r\n"))); + QVERIFY(response.endsWith(QByteArrayLiteral("\r\n\r\nready"))); +} + +UT_REGISTER_TEST(LocalHttpTestServerTest, TestLabel::Unit) diff --git a/test/UnitTestFramework/Tests/LocalHttpTestServerTest.h b/test/UnitTestFramework/Tests/LocalHttpTestServerTest.h new file mode 100644 index 000000000000..2c077ab1e533 --- /dev/null +++ b/test/UnitTestFramework/Tests/LocalHttpTestServerTest.h @@ -0,0 +1,11 @@ +#pragma once + +#include "UnitTest.h" + +class LocalHttpTestServerTest : public UnitTest +{ + Q_OBJECT + +private slots: + void _testFragmentedRequestHeader(); +};