summaryrefslogtreecommitdiffstats
path: root/tests/auto/httpserver
diff options
context:
space:
mode:
authorMichal Klocek <michal.klocek@qt.io>2021-02-11 10:03:24 +0100
committerMichal Klocek <michal.klocek@qt.io>2021-05-22 14:10:10 +0200
commit97dcbd4019456b9a1c567faddb0521b7505d80fc (patch)
tree9c77c5640b1563d853c79898cd64d87252fe8c41 /tests/auto/httpserver
parentdd523573f2981cc58d4da0ec6e2b061a6172a8eb (diff)
Add tests to the cmake build
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 <michael.bruning@qt.io>
Diffstat (limited to 'tests/auto/httpserver')
-rw-r--r--tests/auto/httpserver/CMakeLists.txt7
-rw-r--r--tests/auto/httpserver/data/hedgehog.html9
-rw-r--r--tests/auto/httpserver/data/hedgehog.pngbin0 -> 11273 bytes
-rw-r--r--tests/auto/httpserver/data/loadprogress/downloadable.tar.gzbin0 -> 131 bytes
-rw-r--r--tests/auto/httpserver/data/loadprogress/main.html30
-rw-r--r--tests/auto/httpserver/data/loadprogress/page1.html8
-rw-r--r--tests/auto/httpserver/data/loadprogress/page2.html15
-rw-r--r--tests/auto/httpserver/data/loadprogress/page3.html20
-rw-r--r--tests/auto/httpserver/data/loadprogress/page4.html8
-rw-r--r--tests/auto/httpserver/data/loadprogress/page5.html20
-rw-r--r--tests/auto/httpserver/data/loadprogress/page6.html13
-rw-r--r--tests/auto/httpserver/data/loadprogress/page7.html13
-rw-r--r--tests/auto/httpserver/data/loadprogress/page8.html20
-rw-r--r--tests/auto/httpserver/data/notification.html70
-rw-r--r--tests/auto/httpserver/httpreqrep.cpp143
-rw-r--r--tests/auto/httpserver/httpreqrep.h109
-rw-r--r--tests/auto/httpserver/httpserver.cmake38
-rw-r--r--tests/auto/httpserver/httpserver.cpp148
-rw-r--r--tests/auto/httpserver/httpserver.h105
-rw-r--r--tests/auto/httpserver/httpsserver.h85
-rw-r--r--tests/auto/httpserver/proxy_server.cpp108
-rw-r--r--tests/auto/httpserver/proxy_server.h67
22 files changed, 1036 insertions, 0 deletions
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 @@
+<!doctype html>
+<html>
+ <head>
+ <title>BREAKING NEWS: 15 Hedgehogs With Things That Look Like Hedgehogs</title>
+ </head>
+ <body>
+ <img src="hedgehog.png"/>
+ </body>
+</html>
diff --git a/tests/auto/httpserver/data/hedgehog.png b/tests/auto/httpserver/data/hedgehog.png
new file mode 100644
index 000000000..4d56d8633
--- /dev/null
+++ b/tests/auto/httpserver/data/hedgehog.png
Binary files 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
--- /dev/null
+++ b/tests/auto/httpserver/data/loadprogress/downloadable.tar.gz
Binary files 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 @@
+<html>
+<head><title>Load Progress Test Page</title>
+ <style>
+ .monospace { font-family: "Lucida Console", Courier, monospace; }
+ </style>
+ <title>page1</title>
+ <script>
+ function addP(t) {
+ var p = document.createElement('p')
+ p.class = 'monospace'
+ p.innerHTML = t
+ var d = document.createElement('div')
+ d.appendChild(p)
+ document.body.appendChild(d)
+ }
+ window.addEventListener('DOMContentLoaded', (event) => { addP('DOMContentLoaded') })
+ </script>
+</head>
+<body>
+ <h1>Hello.</h1>
+ <script>
+ addP('sometext')
+ </script>
+ <p class="monospace">body in monospace</p>
+ <iframe id="page1" src="page1.html"></iframe>
+ <iframe id="page2" src="page2.html"></iframe>
+ <iframe id="page3" src="page3.html"></iframe>
+ <iframe id="page4" src="page4.html"></iframe>
+</body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page1</title>
+ </head>
+ <body>
+ <div><a href="page2.html#anchor">page2</a></div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page2</title>
+ </head>
+ <style>
+ .fardown {
+ position: absolute;
+ top: 2500px;
+ }
+ </style>
+ <body>
+ <div><a href="#anchor">page2</a></div>
+ <div class="fardown" id="anchor">page2 anchor</div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page3</title>
+ </head>
+ <script>
+ setTimeout(function(){
+ document.getElementById('anchorLink').click();
+ },500);
+ </script>
+ <style>
+ .fardown {
+ position: absolute;
+ top: 2500px;
+ }
+ </style>
+ <body>
+ <div><a id="anchorLink" href="#anchor">page3</a></div>
+ <div class="fardown" id="anchor">page3 anchor</div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page4</title>
+ </head>
+ <body onload="document.getElementById('downloadLink').focus();">
+ <a id="downloadLink" href="downloadable.tar.gz">download</a>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page5</title>
+ </head>
+ <script>
+ addEventListener('DOMContentLoaded', (event) => {
+ document.getElementById('anchorLink').click();
+ });
+ </script>
+ <style>
+ .fardown {
+ position: absolute;
+ top: 2500px;
+ }
+ </style>
+ <body>
+ <div><a id="anchorLink" href="#anchor">go to the anchor</a></div>
+ <div class="fardown" id="anchor">here is the anchor</div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page6</title>
+ </head>
+ <script>
+ addEventListener('DOMContentLoaded', (event) => {
+ document.getElementById('anchorLink').click();
+ });
+ </script>
+ <body>
+ <div><a id="anchorLink" href="page2.html#anchor">go to another page</a></div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>page6</title>
+ </head>
+ <script>
+ setTimeout(function(){
+ document.getElementById('anchorLink').click();
+ },500);
+ </script>
+ <body>
+ <div><a id="anchorLink" href="page2.html#anchor">go to another page</a></div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>Page with js navigation in the end of document to anchor within the page</title>
+ </head>
+ <style>
+ .fardown {
+ position: absolute;
+ top: 2500px;
+ }
+ </style>
+ <body>
+ <div><a id="anchorLink" href="#anchor">go to the anchor</a></div>
+ <div class="fardown" id="anchor">here is the anchor</div>
+ <script>
+ addEventListener('load', (event) => {
+ window.location.replace(document.getElementById('anchorLink').href)
+ })
+ </script>
+ </body>
+</html>
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 @@
+<!doctype html>
+<html>
+<head>
+<title>Desktop Notifications Demo</title>
+<script>
+ function resetPermission() { document.Notification = 'default' }
+
+ function getPermission() { return document.Notification }
+
+ function sendNotification(title, body) {
+ let notification = new Notification(title, { body: body, dir: 'rtl', lang: 'de', tag: 'tst' })
+ notification.onclick = function() { console.info('onclick') }
+ notification.onclose = function() { console.info('onclose') }
+ notification.onerror = function(error) { console.info('onerror: ' + error) }
+ notification.onshow = function() { console.info('onshow') }
+ }
+
+ function makeNotification() {
+ let title = document.getElementById("title").value
+ let body = document.getElementById("body").value
+ console.log('making notification:', title)
+ sendNotification(title, body)
+ }
+
+ function requestPermission(callback) {
+ Notification.requestPermission().then(function (permission) {
+ document.Notification = permission
+ if (callback)
+ callback(permission)
+ })
+ }
+
+ function displayNotification() {
+ console.info('notifications are ' + document.Notification)
+
+ let state = document.getElementById('state')
+
+ if (document.Notification === 'denied') {
+ state.innerHTML = 'Notifications disabled'
+ } else if (document.Notification === 'granted') {
+ makeNotification()
+ state.innerHTML = 'notification created'
+ } else {
+ state.innerHTML = 'requesting permission...'
+ requestPermission(function (permission) {
+ console.info('notifications request: ' + permission)
+ if (permission === 'granted') {
+ makeNotification()
+ state.innerHTML = 'permission granted, notification created'
+ } else if (permission === 'denied')
+ state.innerHTML = 'Notifications are disabled'
+ })
+ }
+ }
+
+ document.addEventListener("DOMContentLoaded", function() {
+ document.Notification = Notification.permission
+ })
+</script>
+</head>
+<body>
+ <form name="NotificationForm" id="notificationForm">
+ Title: <input type="text" id="title" placeholder="Notification title" value='sample title'><br>
+ Body: <input type="text" id="body" placeholder="Notification body" value='default body'><br>
+ <input type="button" value="Display Notification" onclick="displayNotification()"><br>
+ <input type="button" value="Reset Permission" onclick="resetPermission()">
+ </form>
+ <div id='state'></div>
+</body>
+</html>
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 <QTcpSocket>
+
+#include <map>
+#include <utility>
+
+// 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<QByteArray, QByteArray> m_requestHeaders;
+ int m_responseStatusCode = -1;
+ std::map<QByteArray, QByteArray> 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
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
+ )
+
+ 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 <QFile>
+#include <QLoggingCategory>
+#include <QMimeDatabase>
+
+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 <QTcpServer>
+#include <QUrl>
+
+// 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("<html><body>Hello, World!</body></html>");
+// 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 <QDebug>
+#include <QFile>
+#include <QSslKey>
+#include <QSslSocket>
+#include <QSslConfiguration>
+#include <QTcpServer>
+
+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<QSslSocket::SocketError>::of(&QSslSocket::errorOccurred),
+ [] (QSslSocket::SocketError e) { qWarning() << "! Socket Error:" << e; });
+ connect(socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
+ [] (const QList<QSslError> &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 <QDataStream>
+#include <QTcpSocket>
+#include <QDebug>
+
+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<QTcpSocket*>(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 <QObject>
+#include <QTcpServer>
+
+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