From 97dcbd4019456b9a1c567faddb0521b7505d80fc Mon Sep 17 00:00:00 2001 From: Michal Klocek Date: Thu, 11 Feb 2021 10:03:24 +0100 Subject: Add tests to the cmake build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use QT_TESTCASE_SOURCEDIR instead of TESTS_SOURCE_DIR. Introduce Test::HttpServer and Test::Util targets. Query shared data location from server. Clean up "shared" resources. Note QT_TESTCASE_SOURCEDIR must be turned into the canonical form since the user can call on windows: "cmake \path\to\foo" instead of "cmake c:\path\to\foo" which will break all file:// urls. Note this patch breaks qmake builds. Task-number: QTBUG-91760 Change-Id: Ibc1f904ac9acd375d1ff70ff80f0c533497e3f20 Reviewed-by: Michael BrĂ¼ning --- tests/auto/httpserver/CMakeLists.txt | 7 + tests/auto/httpserver/data/hedgehog.html | 9 ++ tests/auto/httpserver/data/hedgehog.png | Bin 0 -> 11273 bytes .../data/loadprogress/downloadable.tar.gz | Bin 0 -> 131 bytes tests/auto/httpserver/data/loadprogress/main.html | 30 +++++ tests/auto/httpserver/data/loadprogress/page1.html | 8 ++ tests/auto/httpserver/data/loadprogress/page2.html | 15 +++ tests/auto/httpserver/data/loadprogress/page3.html | 20 +++ tests/auto/httpserver/data/loadprogress/page4.html | 8 ++ tests/auto/httpserver/data/loadprogress/page5.html | 20 +++ tests/auto/httpserver/data/loadprogress/page6.html | 13 ++ tests/auto/httpserver/data/loadprogress/page7.html | 13 ++ tests/auto/httpserver/data/loadprogress/page8.html | 20 +++ tests/auto/httpserver/data/notification.html | 70 ++++++++++ tests/auto/httpserver/httpreqrep.cpp | 143 ++++++++++++++++++++ tests/auto/httpserver/httpreqrep.h | 109 +++++++++++++++ tests/auto/httpserver/httpserver.cmake | 38 ++++++ tests/auto/httpserver/httpserver.cpp | 148 +++++++++++++++++++++ tests/auto/httpserver/httpserver.h | 105 +++++++++++++++ tests/auto/httpserver/httpsserver.h | 85 ++++++++++++ tests/auto/httpserver/proxy_server.cpp | 108 +++++++++++++++ tests/auto/httpserver/proxy_server.h | 67 ++++++++++ 22 files changed, 1036 insertions(+) create mode 100644 tests/auto/httpserver/CMakeLists.txt create mode 100644 tests/auto/httpserver/data/hedgehog.html create mode 100644 tests/auto/httpserver/data/hedgehog.png create mode 100644 tests/auto/httpserver/data/loadprogress/downloadable.tar.gz create mode 100644 tests/auto/httpserver/data/loadprogress/main.html create mode 100644 tests/auto/httpserver/data/loadprogress/page1.html create mode 100644 tests/auto/httpserver/data/loadprogress/page2.html create mode 100644 tests/auto/httpserver/data/loadprogress/page3.html create mode 100644 tests/auto/httpserver/data/loadprogress/page4.html create mode 100644 tests/auto/httpserver/data/loadprogress/page5.html create mode 100644 tests/auto/httpserver/data/loadprogress/page6.html create mode 100644 tests/auto/httpserver/data/loadprogress/page7.html create mode 100644 tests/auto/httpserver/data/loadprogress/page8.html create mode 100644 tests/auto/httpserver/data/notification.html create mode 100644 tests/auto/httpserver/httpreqrep.cpp create mode 100644 tests/auto/httpserver/httpreqrep.h create mode 100644 tests/auto/httpserver/httpserver.cmake create mode 100644 tests/auto/httpserver/httpserver.cpp create mode 100644 tests/auto/httpserver/httpserver.h create mode 100644 tests/auto/httpserver/httpsserver.h create mode 100644 tests/auto/httpserver/proxy_server.cpp create mode 100644 tests/auto/httpserver/proxy_server.h (limited to 'tests/auto/httpserver') diff --git a/tests/auto/httpserver/CMakeLists.txt b/tests/auto/httpserver/CMakeLists.txt new file mode 100644 index 000000000..7d4ddd030 --- /dev/null +++ b/tests/auto/httpserver/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.18) +project(minimal LANGUAGES CXX) + +find_package(Qt6 COMPONENTS Core) +find_package(Qt6 COMPONENTS Network) + +include(httpserver.cmake) diff --git a/tests/auto/httpserver/data/hedgehog.html b/tests/auto/httpserver/data/hedgehog.html new file mode 100644 index 000000000..d8abbcd48 --- /dev/null +++ b/tests/auto/httpserver/data/hedgehog.html @@ -0,0 +1,9 @@ + + + + BREAKING NEWS: 15 Hedgehogs With Things That Look Like Hedgehogs + + + + + diff --git a/tests/auto/httpserver/data/hedgehog.png b/tests/auto/httpserver/data/hedgehog.png new file mode 100644 index 000000000..4d56d8633 Binary files /dev/null and b/tests/auto/httpserver/data/hedgehog.png differ diff --git a/tests/auto/httpserver/data/loadprogress/downloadable.tar.gz b/tests/auto/httpserver/data/loadprogress/downloadable.tar.gz new file mode 100644 index 000000000..741cb8ca6 Binary files /dev/null and b/tests/auto/httpserver/data/loadprogress/downloadable.tar.gz differ diff --git a/tests/auto/httpserver/data/loadprogress/main.html b/tests/auto/httpserver/data/loadprogress/main.html new file mode 100644 index 000000000..3b7d2034b --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/main.html @@ -0,0 +1,30 @@ + +Load Progress Test Page + + page1 + + + +

Hello.

+ +

body in monospace

+ + + + + + diff --git a/tests/auto/httpserver/data/loadprogress/page1.html b/tests/auto/httpserver/data/loadprogress/page1.html new file mode 100644 index 000000000..9b11ce887 --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page1.html @@ -0,0 +1,8 @@ + + + page1 + + +
page2
+ + diff --git a/tests/auto/httpserver/data/loadprogress/page2.html b/tests/auto/httpserver/data/loadprogress/page2.html new file mode 100644 index 000000000..223817c8c --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page2.html @@ -0,0 +1,15 @@ + + + page2 + + + +
page2
+
page2 anchor
+ + diff --git a/tests/auto/httpserver/data/loadprogress/page3.html b/tests/auto/httpserver/data/loadprogress/page3.html new file mode 100644 index 000000000..d38ca31f0 --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page3.html @@ -0,0 +1,20 @@ + + + page3 + + + + +
page3
+
page3 anchor
+ + diff --git a/tests/auto/httpserver/data/loadprogress/page4.html b/tests/auto/httpserver/data/loadprogress/page4.html new file mode 100644 index 000000000..61976b4fb --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page4.html @@ -0,0 +1,8 @@ + + + page4 + + + download + + diff --git a/tests/auto/httpserver/data/loadprogress/page5.html b/tests/auto/httpserver/data/loadprogress/page5.html new file mode 100644 index 000000000..47709ff08 --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page5.html @@ -0,0 +1,20 @@ + + + page5 + + + + +
go to the anchor
+
here is the anchor
+ + diff --git a/tests/auto/httpserver/data/loadprogress/page6.html b/tests/auto/httpserver/data/loadprogress/page6.html new file mode 100644 index 000000000..98042701a --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page6.html @@ -0,0 +1,13 @@ + + + page6 + + + +
go to another page
+ + diff --git a/tests/auto/httpserver/data/loadprogress/page7.html b/tests/auto/httpserver/data/loadprogress/page7.html new file mode 100644 index 000000000..42538c5de --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page7.html @@ -0,0 +1,13 @@ + + + page6 + + + +
go to another page
+ + diff --git a/tests/auto/httpserver/data/loadprogress/page8.html b/tests/auto/httpserver/data/loadprogress/page8.html new file mode 100644 index 000000000..8ebdddf97 --- /dev/null +++ b/tests/auto/httpserver/data/loadprogress/page8.html @@ -0,0 +1,20 @@ + + + Page with js navigation in the end of document to anchor within the page + + + +
go to the anchor
+
here is the anchor
+ + + diff --git a/tests/auto/httpserver/data/notification.html b/tests/auto/httpserver/data/notification.html new file mode 100644 index 000000000..1d1e9c411 --- /dev/null +++ b/tests/auto/httpserver/data/notification.html @@ -0,0 +1,70 @@ + + + +Desktop Notifications Demo + + + +
+ Title:
+ Body:
+
+ +
+
+ + diff --git a/tests/auto/httpserver/httpreqrep.cpp b/tests/auto/httpserver/httpreqrep.cpp new file mode 100644 index 000000000..ee9dbbaa9 --- /dev/null +++ b/tests/auto/httpserver/httpreqrep.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "httpreqrep.h" + +HttpReqRep::HttpReqRep(QTcpSocket *socket, QObject *parent) + : QObject(parent), m_socket(socket) +{ + m_socket->setParent(this); + connect(m_socket, &QTcpSocket::readyRead, this, &HttpReqRep::handleReadyRead); + connect(m_socket, &QTcpSocket::disconnected, this, &HttpReqRep::handleDisconnected); +} + +void HttpReqRep::sendResponse(int statusCode) +{ + if (m_state != State::REQUEST_RECEIVED) + return; + m_responseStatusCode = statusCode; + m_socket->write("HTTP/1.1 "); + m_socket->write(QByteArray::number(m_responseStatusCode)); + m_socket->write(" OK?\r\n"); + for (const auto & kv : m_responseHeaders) { + m_socket->write(kv.first); + m_socket->write(": "); + m_socket->write(kv.second); + m_socket->write("\r\n"); + } + m_socket->write("Connection: close\r\n"); + m_socket->write("\r\n"); + m_socket->write(m_responseBody); + m_state = State::DISCONNECTING; + m_socket->disconnectFromHost(); + Q_EMIT responseSent(); +} + +void HttpReqRep::sendResponse(const QByteArray &response) +{ + m_socket->write(response); + m_state = State::DISCONNECTING; + m_socket->disconnectFromHost(); + Q_EMIT responseSent(); +} + +void HttpReqRep::close() +{ + if (m_state != State::REQUEST_RECEIVED) + return; + m_state = State::DISCONNECTING; + m_socket->disconnectFromHost(); + Q_EMIT error(QStringLiteral("missing response")); +} + +QByteArray HttpReqRep::requestHeader(const QByteArray &key) const +{ + auto it = m_requestHeaders.find(key.toLower()); + if (it != m_requestHeaders.end()) + return it->second; + return {}; +} + +void HttpReqRep::handleReadyRead() +{ + while (m_socket->canReadLine()) { + switch (m_state) { + case State::RECEIVING_REQUEST: { + const auto requestLine = m_socket->readLine(); + const auto requestLineParts = requestLine.split(' '); + if (requestLineParts.size() != 3 || !requestLineParts[2].toUpper().startsWith("HTTP/")) { + m_state = State::DISCONNECTING; + m_socket->disconnectFromHost(); + Q_EMIT error(QStringLiteral("invalid request line")); + return; + } + m_requestMethod = requestLineParts[0]; + m_requestPath = requestLineParts[1]; + m_state = State::RECEIVING_HEADERS; + break; + } + case State::RECEIVING_HEADERS: { + const auto headerLine = m_socket->readLine(); + if (headerLine == QByteArrayLiteral("\r\n")) { + m_state = State::REQUEST_RECEIVED; + Q_EMIT requestReceived(); + return; + } + int colonIndex = headerLine.indexOf(':'); + if (colonIndex < 0) { + m_state = State::DISCONNECTING; + m_socket->disconnectFromHost(); + Q_EMIT error(QStringLiteral("invalid header line")); + return; + } + auto headerKey = headerLine.left(colonIndex).trimmed().toLower(); + auto headerValue = headerLine.mid(colonIndex + 1).trimmed().toLower(); + m_requestHeaders.emplace(headerKey, headerValue); + break; + } + default: + return; + } + } +} + +void HttpReqRep::handleDisconnected() +{ + switch (m_state) { + case State::RECEIVING_REQUEST: + case State::RECEIVING_HEADERS: + case State::REQUEST_RECEIVED: + Q_EMIT error(QStringLiteral("unexpected disconnect")); + break; + case State::DISCONNECTING: + break; + case State::DISCONNECTED: + Q_UNREACHABLE(); + } + m_state = State::DISCONNECTED; + Q_EMIT closed(); +} diff --git a/tests/auto/httpserver/httpreqrep.h b/tests/auto/httpserver/httpreqrep.h new file mode 100644 index 000000000..e1979e050 --- /dev/null +++ b/tests/auto/httpserver/httpreqrep.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef HTTPREQREP_H +#define HTTPREQREP_H + +#include + +#include +#include + +// Represents an HTTP request-response exchange. +class HttpReqRep : public QObject +{ + Q_OBJECT +public: + explicit HttpReqRep(QTcpSocket *socket, QObject *parent = nullptr); + + Q_INVOKABLE void sendResponse(int statusCode = 200); + void sendResponse(const QByteArray &response); + void close(); + bool isClosed() const { return m_state == State::DISCONNECTED; } + + // Request parameters (only valid after requestReceived()) + + QByteArray requestMethod() const { return m_requestMethod; } + QByteArray requestPath() const { return m_requestPath; } + QByteArray requestHeader(const QByteArray &key) const; + + // Response parameters (can be set until sendResponse()/close()). + + int responseStatus() const { return m_responseStatusCode; } + void setResponseStatus(int statusCode) + { + m_responseStatusCode = statusCode; + } + void setResponseHeader(const QByteArray &key, QByteArray value) + { + m_responseHeaders[key.toLower()] = std::move(value); + } + QByteArray responseBody() const { return m_responseBody; } + Q_INVOKABLE void setResponseBody(QByteArray content) + { + m_responseHeaders["content-length"] = QByteArray::number(content.size()); + m_responseBody = std::move(content); + } + +Q_SIGNALS: + // Emitted when the request has been correctly parsed. + void requestReceived(); + // Emitted on first call to sendResponse(). + void responseSent(); + // Emitted when something goes wrong. + void error(const QString &error); + // Emitted during or some time after sendResponse() or close(). + void closed(); + +private Q_SLOTS: + void handleReadyRead(); + void handleDisconnected(); + +private: + enum class State { + // Waiting for first line of request. + RECEIVING_REQUEST, // Next: RECEIVING_HEADERS or DISCONNECTING. + // Waiting for header lines. + RECEIVING_HEADERS, // Next: REQUEST_RECEIVED or DISCONNECTING. + // Request parsing succeeded, waiting for sendResponse() or close(). + REQUEST_RECEIVED, // Next: DISCONNECTING. + // Waiting for network. + DISCONNECTING, // Next: DISCONNECTED. + // Connection is dead. + DISCONNECTED, // Next: - + }; + QTcpSocket *m_socket = nullptr; + State m_state = State::RECEIVING_REQUEST; + QByteArray m_requestMethod; + QByteArray m_requestPath; + std::map m_requestHeaders; + int m_responseStatusCode = -1; + std::map m_responseHeaders; + QByteArray m_responseBody; +}; + +#endif // !HTTPREQREP_H diff --git a/tests/auto/httpserver/httpserver.cmake b/tests/auto/httpserver/httpserver.cmake new file mode 100644 index 000000000..78d13055c --- /dev/null +++ b/tests/auto/httpserver/httpserver.cmake @@ -0,0 +1,38 @@ +if (NOT TARGET Test::HttpServer) + + set(CMAKE_AUTOMOC ON) + set(CMAKE_AUTORCC ON) + set(CMAKE_AUTOUIC ON) + + add_library(httpserver STATIC + ${CMAKE_CURRENT_LIST_DIR}/httpreqrep.cpp + ${CMAKE_CURRENT_LIST_DIR}/httpreqrep.h + ${CMAKE_CURRENT_LIST_DIR}/httpserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/httpserver.h + ${CMAKE_CURRENT_LIST_DIR}/proxy_server.h + ${CMAKE_CURRENT_LIST_DIR}/proxy_server.cpp + ) + + if(QT_FEATURE_ssl) + target_sources(httpserver INTERFACE ${CMAKE_CURRENT_LIST_DIR}/httpsserver.h) + endif() + + add_library(Test::HttpServer ALIAS httpserver) + + target_include_directories(httpserver INTERFACE + $ + ) + + target_link_libraries(httpserver PUBLIC + Qt::Core + Qt::Network + ) + + target_compile_definitions(httpserver PRIVATE + SERVER_SOURCE_DIR="${CMAKE_CURRENT_LIST_DIR}" + ) + + set_target_properties(httpserver PROPERTIES + SHARED_DATA "${CMAKE_CURRENT_LIST_DIR}/data" + ) +endif() diff --git a/tests/auto/httpserver/httpserver.cpp b/tests/auto/httpserver/httpserver.cpp new file mode 100644 index 000000000..10147ae6c --- /dev/null +++ b/tests/auto/httpserver/httpserver.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "httpserver.h" + +#include +#include +#include + +Q_LOGGING_CATEGORY(gHttpServerLog, "HttpServer") + +HttpServer::HttpServer(QObject *parent) + : HttpServer(new QTcpServer, "http", QHostAddress::LocalHost, 0, parent) +{ +} + +HttpServer::HttpServer(const QHostAddress &hostAddress, quint16 port, QObject *parent) + : HttpServer(new QTcpServer, "http", hostAddress, port, parent) +{ +} + +HttpServer::HttpServer(QTcpServer *tcpServer, const QString &protocol, + const QHostAddress &hostAddress, quint16 port, QObject *parent) + : QObject(parent), m_tcpServer(tcpServer), m_hostAddress(hostAddress), m_port(port) +{ + m_url.setHost(hostAddress.toString()); + m_url.setScheme(protocol); + connect(tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection); +} + +HttpServer::~HttpServer() +{ + delete m_tcpServer; +} + +bool HttpServer::start() +{ + m_error = false; + m_expectingError = false; + m_ignoreNewConnection = false; + + if (!m_tcpServer->listen(m_hostAddress, m_port)) { + qCWarning(gHttpServerLog).noquote() << m_tcpServer->errorString(); + return false; + } + + m_url.setPort(m_tcpServer->serverPort()); + return true; +} + +bool HttpServer::stop() +{ + m_tcpServer->close(); + return m_error == m_expectingError; +} + +void HttpServer::setExpectError(bool b) +{ + m_expectingError = b; +} + +QUrl HttpServer::url(const QString &path) const +{ + auto copy = m_url; + copy.setPath(path); + return copy; +} + +void HttpServer::handleNewConnection() +{ + if (m_ignoreNewConnection) + return; + + auto rr = new HttpReqRep(m_tcpServer->nextPendingConnection(), this); + connect(rr, &HttpReqRep::requestReceived, [this, rr]() { + Q_EMIT newRequest(rr); + if (rr->isClosed()) // was explicitly answered + return; + + // if request wasn't handled or purposely ignored for default behavior + // then try to serve htmls from resources dirs if set + if (rr->requestMethod() == "GET") { + for (auto &&dir : qAsConst(m_dirs)) { + QFile f(dir + rr->requestPath()); + if (f.exists()) { + if (f.open(QFile::ReadOnly)) { + QMimeType mime = QMimeDatabase().mimeTypeForFileNameAndData(f.fileName(), &f); + rr->setResponseHeader(QByteArrayLiteral("Content-Type"), mime.name().toUtf8()); + rr->setResponseBody(f.readAll()); + rr->sendResponse(); + } else { + qWarning() << "Can't open resource" << f.fileName() << ": " << f.errorString(); + rr->sendResponse(500); // internal server error + } + break; + } else { + qWarning() << "Can't open resource" << dir + rr->requestPath(); + } + } + } + + if (!rr->isClosed()) + rr->sendResponse(404); + }); + connect(rr, &HttpReqRep::responseSent, [rr]() { + qCInfo(gHttpServerLog).noquote() << rr->requestMethod() << rr->requestPath() + << rr->responseStatus() << rr->responseBody().size(); + }); + connect(rr, &HttpReqRep::error, [this, rr](const QString &error) { + qCWarning(gHttpServerLog).noquote() << rr->requestMethod() << rr->requestPath() + << error; + m_error = true; + }); + + if (!m_tcpServer->isListening()) { + m_ignoreNewConnection = true; + connect(rr, &HttpReqRep::closed, rr, &QObject::deleteLater); + } +} + +QString HttpServer::sharedDataDir() const +{ + return SERVER_SOURCE_DIR + QLatin1String("/data"); +} diff --git a/tests/auto/httpserver/httpserver.h b/tests/auto/httpserver/httpserver.h new file mode 100644 index 000000000..acc742775 --- /dev/null +++ b/tests/auto/httpserver/httpserver.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +#include "httpreqrep.h" + +#include +#include + +// Listens on a TCP socket and creates HttpReqReps for each connection. +// +// Usage: +// +// HttpServer server; +// connect(&server, &HttpServer::newRequest, [](HttpReqRep *rr) { +// if (rr->requestPath() == "/myPage.html") { +// rr->setResponseBody("Hello, World!"); +// rr->sendResponse(); +// } +// }); +// QVERIFY(server.start()); +// /* do stuff */ +// QVERIFY(server.stop()); +// +// HttpServer owns the HttpReqRep objects. The signal handler should not store +// references to HttpReqRep objects. +// +// Only if a handler calls sendResponse() will a response be actually sent. This +// means that multiple handlers can be connected to the signal, with different +// handlers responsible for different paths. +class HttpServer : public QObject +{ + Q_OBJECT +public: + explicit HttpServer(QObject *parent = nullptr); + HttpServer(const QHostAddress &hostAddress, quint16 port, QObject *parent = nullptr); + HttpServer(QTcpServer *server, const QString &protocol, const QHostAddress &address, + quint16 port, QObject *parent = nullptr); + + ~HttpServer() override; + + // Must be called to start listening. + // + // Returns true if a TCP port has been successfully bound. + Q_INVOKABLE Q_REQUIRED_RESULT bool start(); + + // Stops listening and performs final error checks. + Q_INVOKABLE Q_REQUIRED_RESULT bool stop(); + + Q_INVOKABLE void setExpectError(bool b); + + // Full URL for given relative path + Q_INVOKABLE QUrl url(const QString &path = QStringLiteral("/")) const; + + Q_INVOKABLE QString sharedDataDir() const; + + Q_INVOKABLE void setResourceDirs(const QStringList &dirs) { m_dirs = dirs; } + + Q_INVOKABLE void setHostDomain(const QString &host) { m_url.setHost(host); } + +Q_SIGNALS: + // Emitted after a HTTP request has been successfully parsed. + void newRequest(HttpReqRep *reqRep); + +private Q_SLOTS: + void handleNewConnection(); + +private: + QTcpServer *m_tcpServer; + QUrl m_url; + QStringList m_dirs; + bool m_error = false; + bool m_ignoreNewConnection = false; + bool m_expectingError = false; + QHostAddress m_hostAddress; + quint16 m_port; +}; + +#endif // !HTTPSERVER_H diff --git a/tests/auto/httpserver/httpsserver.h b/tests/auto/httpserver/httpsserver.h new file mode 100644 index 000000000..b257e69a7 --- /dev/null +++ b/tests/auto/httpserver/httpsserver.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef HTTPSSERVER_H +#define HTTPSSERVER_H + +#include "httpreqrep.h" +#include "httpserver.h" + +#include +#include +#include +#include +#include +#include + +struct SslTcpServer : QTcpServer +{ + SslTcpServer(const QString &certPath, const QString &keyPath) { + sslconf.setLocalCertificateChain(QSslCertificate::fromPath(certPath)); + sslconf.setPrivateKey(readKey(keyPath)); + } + + void incomingConnection(qintptr d) override { + auto socket = new QSslSocket(this); + socket->setSslConfiguration(sslconf); + + if (!socket->setSocketDescriptor(d)) { + qWarning() << "Failed to setup ssl socket!"; + delete socket; + return; + } + + connect(socket, QOverload::of(&QSslSocket::errorOccurred), + [] (QSslSocket::SocketError e) { qWarning() << "! Socket Error:" << e; }); + connect(socket, QOverload &>::of(&QSslSocket::sslErrors), + [] (const QList &le) { qWarning() << "! SSL Errors:\n" << le; }); + + addPendingConnection(socket); + socket->startServerEncryption(); + } + + QSslKey readKey(const QString &path) const { + QFile file(path); + file.open(QIODevice::ReadOnly); + return QSslKey(file.readAll(), QSsl::Rsa, QSsl::Pem); + } + + QSslConfiguration sslconf; +}; + +struct HttpsServer : HttpServer +{ + HttpsServer(const QString &certPath, const QString &keyPath, QObject *parent = nullptr) + : HttpServer(new SslTcpServer(certPath, keyPath), "https", QHostAddress::LocalHost, 0, + parent) + { + } +}; + +#endif diff --git a/tests/auto/httpserver/proxy_server.cpp b/tests/auto/httpserver/proxy_server.cpp new file mode 100644 index 000000000..3c5588603 --- /dev/null +++ b/tests/auto/httpserver/proxy_server.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "proxy_server.h" +#include +#include +#include + +ProxyServer::ProxyServer(QObject *parent) : QObject(parent) +{ + connect(&m_server, &QTcpServer::newConnection, this, &ProxyServer::handleNewConnection); +} + +void ProxyServer::setPort(int port) +{ + m_port = port; +} + +void ProxyServer::setCredentials(const QByteArray &user, const QByteArray password) +{ + m_auth.append(user); + m_auth.append(':'); + m_auth.append(password); + m_auth = m_auth.toBase64(); + m_authenticate = true; +} + +void ProxyServer::setCookie(const QByteArray &cookie) +{ + m_cookie.append(QByteArrayLiteral("Cookie: ")); + m_cookie.append(cookie); +} + + +bool ProxyServer::isListening() +{ + return m_server.isListening(); +} + +void ProxyServer::run() +{ + if (!m_server.listen(QHostAddress::LocalHost, m_port)) + qFatal("Could not start the test server"); +} + +void ProxyServer::handleNewConnection() +{ + // do one connection at the time + Q_ASSERT(m_data.isEmpty()); + QTcpSocket *socket = m_server.nextPendingConnection(); + Q_ASSERT(socket); + connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); + connect(socket, &QAbstractSocket::readyRead, this, &ProxyServer::handleReadReady); +} + +void ProxyServer::handleReadReady() +{ + QTcpSocket *socket = qobject_cast(sender()); + Q_ASSERT(socket); + + m_data.append(socket->readAll()); + + if (!m_data.endsWith("\r\n\r\n")) + return; + + if (m_authenticate && !m_data.contains(QByteArrayLiteral("Proxy-Authorization: Basic"))) { + socket->write("HTTP/1.1 407 Proxy Authentication Required\nProxy-Authenticate: " + "Basic realm=\"Proxy requires authentication\"\r\n" + "content-length: 0\r\n" + "\r\n"); + return; + } + + if (m_authenticate && m_data.contains(m_auth)) { + emit authenticationSuccess(); + } + + if (m_data.contains(m_cookie)) { + emit cookieMatch(); + } + m_data.clear(); + emit requestReceived(); +} diff --git a/tests/auto/httpserver/proxy_server.h b/tests/auto/httpserver/proxy_server.h new file mode 100644 index 000000000..57e69efe7 --- /dev/null +++ b/tests/auto/httpserver/proxy_server.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROXY_SERVER_H +#define PROXY_SERVER_H + +#include +#include + +class ProxyServer : public QObject +{ + Q_OBJECT + +public: + explicit ProxyServer(QObject *parent = nullptr); + void setCredentials(const QByteArray &user, const QByteArray password); + void setCookie(const QByteArray &cookie); + bool isListening(); + void setPort(int port); + +public slots: + void run(); + +private slots: + void handleNewConnection(); + void handleReadReady(); + +signals: + void authenticationSuccess(); + void cookieMatch(); + void requestReceived(); + +private: + int m_port = 5555; + QByteArray m_data; + QTcpServer m_server; + QByteArray m_auth; + QByteArray m_cookie; + bool m_authenticate = false; +}; + +#endif // PROXY_SERVER_H -- cgit v1.2.3