Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtHttpServer/qabstracthttpserver.h>
#include <QtHttpServer/qhttpserverrequest.h>
#include <QtHttpServer/qhttpserverresponder.h>
#include <private/qabstracthttpserver_p.h>
#include <private/qhttpserverhttp1protocolhandler_p.h>
#include <private/qhttpserverrequest_p.h>
#include <QtCore/qloggingcategory.h>
#include <QtNetwork/qtcpserver.h>
#include <QtNetwork/qtcpsocket.h>
#if QT_CONFIG(ssl) && QT_CONFIG(http)
#include <QtNetwork/private/qhttp2connection_p.h>
#endif
#if QT_CONFIG(localserver)
#include <QtNetwork/qlocalserver.h>
#include <QtNetwork/qlocalsocket.h>
#endif
#if QT_CONFIG(ssl)
#include <QtNetwork/qsslserver.h>
#endif
#if QT_CONFIG(http) && QT_CONFIG(ssl)
#include <private/qhttpserverhttp2protocolhandler_p.h>
#endif
#include <algorithm>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcHttpServer, "qt.httpserver", QtWarningMsg)
bool LessHostAddress::operator()(const QHostAddress &lhs, const QHostAddress &rhs) const
{
bool lhsIsIPv4 = false;
bool rhsIsIPv4 = false;
quint32 lhsIpAddressV4 = lhs.toIPv4Address(&lhsIsIPv4);
quint32 rhsIpAddressV4 = rhs.toIPv4Address(&rhsIsIPv4);
if (lhsIsIPv4 != rhsIsIPv4)
return lhsIsIPv4;
if (lhsIsIPv4)
return lhsIpAddressV4 < rhsIpAddressV4;
Q_IPV6ADDR lhsIpAddressV6 = lhs.toIPv6Address();
Q_IPV6ADDR rhsIpAddressV6 = rhs.toIPv6Address();
for (int i = 0; i < 16; ++i) {
if (lhsIpAddressV6[i] != rhsIpAddressV6[i])
return lhsIpAddressV6[i] < rhsIpAddressV6[i];
}
return false;
}
/*!
\internal
*/
QAbstractHttpServerPrivate::QAbstractHttpServerPrivate()
{
restartHeartbeatTimer();
}
/*!
\internal
*/
void QAbstractHttpServerPrivate::handleNewConnections()
{
Q_Q(QAbstractHttpServer);
#if QT_CONFIG(ssl) && QT_CONFIG(http)
if (auto *sslServer = qobject_cast<QSslServer *>(q->sender())) {
while (auto socket = qobject_cast<QSslSocket *>(sslServer->nextPendingConnection())) {
if (socket->sslConfiguration().nextNegotiatedProtocol()
== QSslConfiguration::ALPNProtocolHTTP2) {
createHttp2Handler(socket);
} else {
createHttp1Handler(socket);
}
}
return;
}
#endif
auto tcpServer = qobject_cast<QTcpServer *>(q->sender());
Q_ASSERT(tcpServer);
while (auto socket = tcpServer->nextPendingConnection())
createHttp1Handler(socket);
}
/*!
\internal
*/
bool QAbstractHttpServerPrivate::verifyThreadAffinity(const QObject *contextObject) const {
Q_Q(const QAbstractHttpServer);
if (contextObject && (contextObject->thread() != q->thread())) {
qCWarning(lcHttpServer, "QAbstractHttpServer: "
"the context object must reside in the same thread");
return false;
}
return true;
}
void QAbstractHttpServerPrivate::createHttp1Handler(QIODevice *socket)
{
using namespace std::chrono_literals;
Q_Q(QAbstractHttpServer);
if (hasTooManyConnections(socket)) {
if (auto tcpSocket = qobject_cast<QTcpSocket *>(socket)) {
QByteArray buffer;
buffer.append("HTTP/1.1 429 Too Many Requests");
buffer.append("\r\nConnection: close\r\n\r\n");
tcpSocket->write(buffer);
tcpSocket->flush();
QTimer::singleShot(1s, tcpSocket, &QObject::deleteLater);
}
return;
}
auto handler = new QHttpServerHttp1ProtocolHandler(q, socket, &requestFilter, &configuration);
updateSocketCounter(socket);
QObject::connect(&heartbeatTimer, &QTimer::timeout,
handler, &QHttpServerHttp1ProtocolHandler::checkKeepAliveTimeout);
}
#if QT_CONFIG(ssl) && QT_CONFIG(http)
void QAbstractHttpServerPrivate::createHttp2Handler(QIODevice *socket)
{
using namespace std::chrono_literals;
Q_Q(QAbstractHttpServer);
if (hasTooManyConnections(socket)) {
if (auto tcpSocket = qobject_cast<QTcpSocket *>(socket)) {
auto *connection = QHttp2Connection::createDirectServerConnection(
socket, q->http2Configuration());
connection->close(Http2::Http2Error::ENHANCE_YOUR_CALM);
QTimer::singleShot(1s, tcpSocket, &QObject::deleteLater);
}
return;
}
auto handler = new QHttpServerHttp2ProtocolHandler(q, socket, &requestFilter);
updateSocketCounter(socket);
QObject::connect(&heartbeatTimer, &QTimer::timeout,
handler, &QHttpServerHttp2ProtocolHandler::checkKeepAliveTimeout);
}
#endif
bool QAbstractHttpServerPrivate::hasTooManyConnections(QIODevice *socket)
{
if (configuration.maximumConnections() != 0
&& totalConnections >= configuration.maximumConnections())
return true;
if (configuration.maximumConnectionsPerHost() == 0)
return false;
if (auto tcpSocket = qobject_cast<QTcpSocket *>(socket)) {
auto foundNumberOfConnections = connectionsPerHost.find(tcpSocket->peerAddress());
if (foundNumberOfConnections != connectionsPerHost.end()) {
if (foundNumberOfConnections->second >= configuration.maximumConnectionsPerHost()) {
qCInfo(lcHttpServer) << "QAbstractHttpServer:"
<< "closing socket: too many connections from"
<< tcpSocket->peerAddress().toString();
return true;
}
}
}
return false;
}
void QAbstractHttpServerPrivate::updateSocketCounter(QIODevice *socket)
{
// If there are no limits on connections per host or in total, there's no point in tracking
// the amount of connections, so we skip doing so.
if (configuration.maximumConnectionsPerHost() == 0 && configuration.maximumConnections() == 0)
return;
if (auto tcpSocket = qobject_cast<QTcpSocket *>(socket)) {
auto [it, _] = connectionsPerHost.try_emplace(tcpSocket->peerAddress(), 0);
++(it->second);
++totalConnections;
QObjectPrivate::connect(tcpSocket, &QAbstractSocket::disconnected, this,
&QAbstractHttpServerPrivate::socketDisconnected);
}
}
void QAbstractHttpServerPrivate::socketDisconnected()
{
Q_Q(QAbstractHttpServer);
if (auto tcpSocket = qobject_cast<QTcpSocket *>(q->sender())) {
--totalConnections;
auto foundNumberOfConnections = connectionsPerHost.find(tcpSocket->peerAddress());
if (foundNumberOfConnections != connectionsPerHost.end()) {
if (foundNumberOfConnections->second == 1)
connectionsPerHost.erase(foundNumberOfConnections);
else
--(foundNumberOfConnections->second);
}
}
}
#if QT_CONFIG(ssl)
void QAbstractHttpServerPrivate::handleStartedEncryptionHandshake(QSslSocket *socket)
{
if (hasTooManyConnections(socket))
socket->close();
}
#endif
void QAbstractHttpServerPrivate::restartHeartbeatTimer()
{
if (heartbeatTimer.isActive())
heartbeatTimer.stop();
const auto timerInterval = qMax(configuration.keepAliveTimeout() / 2,
std::chrono::seconds(5));
heartbeatTimer.start(timerInterval);
}
#if QT_CONFIG(localserver)
/*!
\internal
*/
void QAbstractHttpServerPrivate::handleNewLocalConnections()
{
Q_Q(QAbstractHttpServer);
auto localServer = qobject_cast<QLocalServer *>(q->sender());
Q_ASSERT(localServer);
while (auto socket = localServer->nextPendingConnection())
createHttp1Handler(socket);
}
#endif
/*!
\class QAbstractHttpServer
\since 6.4
\inmodule QtHttpServer
\brief API to subclass to implement an HTTP server.
Subclass this class and override handleRequest() and missingHandler() to
create an HTTP server. Use bind() to start listening to all the incoming
connections to a server.
This is a low level API, see \l QHttpServer for a higher level API to
implement an HTTP server.
*/
/*!
Creates an instance of QAbstractHttpServer with the parent \a parent.
*/
QAbstractHttpServer::QAbstractHttpServer(QObject *parent)
: QAbstractHttpServer(*new QAbstractHttpServerPrivate, parent)
{}
/*!
Destroys an instance of QAbstractHttpServer.
*/
QAbstractHttpServer::~QAbstractHttpServer()
= default;
/*!
\internal
*/
QAbstractHttpServer::QAbstractHttpServer(QAbstractHttpServerPrivate &dd, QObject *parent)
: QObject(dd, parent)
{
#if defined(QT_WEBSOCKETS_LIB)
Q_D(QAbstractHttpServer);
connect(&d->websocketServer, &QWebSocketServer::newConnection,
this, &QAbstractHttpServer::newWebSocketConnection);
#endif
}
/*!
Returns the list of ports this instance of QAbstractHttpServer
is listening to.
This function has the same guarantee as QObject::children,
the latest server added is the last entry in the vector.
\sa servers()
*/
QList<quint16> QAbstractHttpServer::serverPorts() const
{
QList<quint16> ports;
auto children = findChildren<QTcpServer *>();
ports.reserve(children.size());
std::transform(children.cbegin(), children.cend(), std::back_inserter(ports),
[](const QTcpServer *server) { return server->serverPort(); });
return ports;
}
/*!
Bind the given TCP \a server, over which the transmission happens,
to the HTTP server. It is possible to call this function
multiple times with different instances of TCP \a server to
handle multiple connections and ports, for example both SSL and
non-encrypted connections.
After calling this function, every \e new connection will be
handled and forwarded by the HTTP server.
It is the user's responsibility to call QTcpServer::listen() on
the \a server before calling this function. If \a server is not
listening, nothing will happen and \c false will be returned.
If successful the \a server will be parented to this HTTP server
and \c true is returned.
To allow usage of HTTP 2, bind to a QSslServer where
QSslConfiguration::setAllowedNextProtocols() has been called with
the arguments \c {{ QSslConfiguration::ALPNProtocolHTTP2 }}.
\sa QTcpServer, QTcpServer::listen(), QSslConfiguration::setAllowedNextProtocols()
*/
bool QAbstractHttpServer::bind(QTcpServer *server)
{
Q_D(QAbstractHttpServer);
if (!server)
return false;
if (!server->isListening()) {
qCWarning(lcHttpServer) << "The TCP server" << server << "is not listening.";
return false;
}
server->setParent(this);
QObjectPrivate::connect(server, &QTcpServer::pendingConnectionAvailable, d,
&QAbstractHttpServerPrivate::handleNewConnections,
Qt::UniqueConnection);
#if QT_CONFIG(ssl)
if (auto *sslServer = qobject_cast<QSslServer *>(server)) {
QObjectPrivate::connect(sslServer,&QSslServer::startedEncryptionHandshake, d,
&QAbstractHttpServerPrivate::handleStartedEncryptionHandshake,
Qt::UniqueConnection);
}
#endif
return true;
}
#if QT_CONFIG(localserver)
/*!
Bind the given QLocalServer \a server, over which the transmission
happens, to the HTTP server. It is possible to call this function
multiple times with different instances of \a server to
handle multiple connections.
After calling this function, every \e new connection will be
handled and forwarded by the HTTP server.
It is the user's responsibility to call QLocalServer::listen() on
the \a server before calling this function. If \a server is not
listening, nothing will happen and \c false will be returned.
If the \a server is nullptr false is returned.
If successful the \a server will be parented to this HTTP server
and \c true is returned.
\sa QLocalServer, QLocalServer::listen()
*/
bool QAbstractHttpServer::bind(QLocalServer *server)
{
Q_D(QAbstractHttpServer);
if (!server)
return false;
if (!server->isListening()) {
qCWarning(lcHttpServer) << "The local server" << server << "is not listening.";
return false;
}
server->setParent(this);
QObjectPrivate::connect(server, &QLocalServer::newConnection,
d, &QAbstractHttpServerPrivate::handleNewLocalConnections,
Qt::UniqueConnection);
return true;
}
#endif
/*!
Returns the TCP and SSL servers this HTTP server will handle connections from.
\sa serverPorts()
*/
QList<QTcpServer *> QAbstractHttpServer::servers() const
{
return findChildren<QTcpServer *>();
}
#if QT_CONFIG(localserver)
/*!
Returns the local servers of this HTTP server.
\sa serverPorts()
*/
QList<QLocalServer *> QAbstractHttpServer::localServers() const
{
return findChildren<QLocalServer *>();
}
#endif
#if defined(QT_WEBSOCKETS_LIB)
/*!
\fn QAbstractHttpServer::newWebSocketConnection()
This signal is emitted every time a new WebSocket connection is
available.
\sa hasPendingWebSocketConnections(), nextPendingWebSocketConnection(),
addWebSocketUpgradeVerifier()
*/
/*!
Returns \c true if the server has pending WebSocket connections;
otherwise returns \c false.
\sa newWebSocketConnection(), nextPendingWebSocketConnection(),
addWebSocketUpgradeVerifier()
*/
bool QAbstractHttpServer::hasPendingWebSocketConnections() const
{
Q_D(const QAbstractHttpServer);
return d->websocketServer.hasPendingConnections();
}
/*!
Returns the next pending connection as a connected QWebSocket
object. \nullptr is returned if this function is called when
there are no pending connections.
\note The returned QWebSocket object cannot be used from another
thread.
\sa newWebSocketConnection(), hasPendingWebSocketConnections(),
addWebSocketUpgradeVerifier()
*/
std::unique_ptr<QWebSocket> QAbstractHttpServer::nextPendingWebSocketConnection()
{
Q_D(QAbstractHttpServer);
return std::unique_ptr<QWebSocket>(d->websocketServer.nextPendingConnection());
}
/*!
\internal
*/
QHttpServerWebSocketUpgradeResponse
QAbstractHttpServer::verifyWebSocketUpgrade(const QHttpServerRequest &request) const
{
Q_D(const QAbstractHttpServer);
QScopedValueRollback guard(d->handlingWebSocketUpgrade, true);
for (auto &verifier : d->webSocketUpgradeVerifiers) {
if (verifier.context && verifier.slotObject && d->verifyThreadAffinity(verifier.context)) {
auto response = QHttpServerWebSocketUpgradeResponse::passToNext();
void *args[] = { &response, const_cast<QHttpServerRequest *>(&request) };
verifier.slotObject->call(const_cast<QObject *>(verifier.context.data()), args);
if (response.type() != QHttpServerWebSocketUpgradeResponse::ResponseType::PassToNext)
return response;
}
}
return QHttpServerWebSocketUpgradeResponse::passToNext();
}
/*!
\fn template <typename Handler> void QAbstractHttpServer::addWebSocketUpgradeVerifier(const QObject *context, Handler &&func)
Adds a callback function \a func that verifies incoming WebSocket
upgrades. It is called using the provided \a context object.
An upgrade succeeds if at least one registered callback returns
\l{QHttpServerWebSocketUpgradeResponse::ResponseType::}{Accept} and
no prior callback has returned
\l{QHttpServerWebSocketUpgradeResponse::ResponseType::}{Deny}. If no
callbacks are registered, or all return
\l{QHttpServerWebSocketUpgradeResponse::ResponseType::}{PassToNext},
the missingHandler() function is called. Callbacks are executed in the
order they were registered, and they cannot call this function
themselves.
\note The \a func has to implement the signature
\c{QHttpServerWebSocketUpgradeResponse (*)(const QHttpServerRequest &)}.
\code
server.addWebSocketUpgradeVerifier(
&server, [](const QHttpServerRequest &request) {
if (request.url().path() == "/allowed"_L1)
return QHttpServerWebSocketUpgradeResponse::accept();
else
return QHttpServerWebSocketUpgradeResponse::passToNext();
});
\endcode
\since 6.8
\sa QHttpServerRequest, QHttpServerWebSocketUpgradeResponse, hasPendingWebSocketConnections(),
nextPendingWebSocketConnection(), newWebSocketConnection(), missingHandler()
*/
/*!
\internal
*/
void QAbstractHttpServer::addWebSocketUpgradeVerifierImpl(const QObject *context,
QtPrivate::QSlotObjectBase *slotObjRaw)
{
QtPrivate::SlotObjUniquePtr slotObj{slotObjRaw}; // adopts
Q_ASSERT(slotObj);
Q_D(QAbstractHttpServer);
if (d->handlingWebSocketUpgrade) {
qWarning("Registering WebSocket upgrade verifiers while handling them is not allowed");
return;
}
d->webSocketUpgradeVerifiers.push_back({context, std::move(slotObj)});
}
#endif
/*!
\fn QAbstractHttpServer::handleRequest(const QHttpServerRequest &request,
QHttpServerResponder &responder)
Override this function to handle each incoming \a request, by examining
the \a request and sending the appropriate response back to \a responder.
Return \c true if the \a request was handled successfully. If this method
returns \c false, missingHandler() will be called afterwards.
*/
/*!
\fn QAbstractHttpServer::missingHandler(const QHttpServerRequest &request,
QHttpServerResponder &responder)
Override this function to handle each incoming \a request that was not
handled by \l handleRequest(). This function is called whenever \l
handleRequest() returns \c false, or if there is a WebSocket upgrade
attempt and either there are no connections to newWebSocketConnection() or
there are no matching WebSocket verifiers. The \a request and \a responder
parameters are the same as handleRequest() was called with.
\sa handleRequest(), addWebSocketUpgradeVerifier()
*/
#if QT_CONFIG(ssl)
/*!
\since 6.8
Returns server's HTTP/2 configuration parameters.
\sa setHttp2Configuration()
*/
QHttp2Configuration QAbstractHttpServer::http2Configuration() const
{
Q_D(const QAbstractHttpServer);
return d->h2Configuration;
}
/*!
\since 6.8
Sets server's HTTP/2 configuration parameters.
The next HTTP/2 connection will use the given \a configuration.
\sa http2Configuration()
*/
void QAbstractHttpServer::setHttp2Configuration(const QHttp2Configuration &configuration)
{
Q_D(QAbstractHttpServer);
d->h2Configuration = configuration;
}
#endif
/*!
\since 6.9
Sets this server's general configuration parameters to \a config.
\note The new configuration will be applied both to already established
connections and all next connections.
\sa configuration()
*/
void QAbstractHttpServer::setConfiguration(const QHttpServerConfiguration &config)
{
Q_D(QAbstractHttpServer);
d->configuration = config;
d->requestFilter.setConfiguration(config);
d->restartHeartbeatTimer();
}
/*!
\since 6.9
Returns this server's general configuration parameters.
\sa setConfiguration()
*/
QHttpServerConfiguration QAbstractHttpServer::configuration() const
{
Q_D(const QAbstractHttpServer);
return d->configuration;
}
QT_END_NAMESPACE
#include "moc_qabstracthttpserver.cpp"