blob: 2c27c285adc207ee80f04a3488222bc7be236023 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Network Auth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** 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 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** 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 WEBSERVER_H
#define WEBSERVER_H
#include <functional>
#include <cctype>
#include <QtCore/qcoreapplication.h>
#include <QtNetwork/qtcpserver.h>
#include <QTcpSocket>
class WebServer : public QTcpServer
{
public:
class HttpRequest {
friend class WebServer;
quint16 port = 0;
enum class State {
ReadingMethod,
ReadingUrl,
ReadingStatus,
ReadingHeader,
ReadingBody,
AllDone
} state = State::ReadingMethod;
QByteArray fragment;
int bytesLeft = 0;
bool readMethod(QTcpSocket *socket);
bool readUrl(QTcpSocket *socket);
bool readStatus(QTcpSocket *socket);
bool readHeaders(QTcpSocket *socket);
bool readBody(QTcpSocket *socket);
public:
enum class Method {
Unknown,
Head,
Get,
Put,
Post,
Delete,
} method = Method::Unknown;
QUrl url;
QPair<quint8, quint8> version;
QMap<QByteArray, QByteArray> headers;
QByteArray body;
};
typedef std::function<void(const HttpRequest &request, QTcpSocket *socket)> Handler;
WebServer(Handler handler, QObject *parent = nullptr);
QUrl url(const QString &path);
private:
Handler handler;
QMap<QTcpSocket *, HttpRequest> clients;
};
WebServer::WebServer(Handler handler, QObject *parent) :
QTcpServer(parent),
handler(handler)
{
connect(this, &QTcpServer::newConnection, [=]() {
auto socket = nextPendingConnection();
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
connect(socket, &QTcpSocket::readyRead, [=]() {
if (!clients.contains(socket))
clients[socket].port = serverPort();
auto *request = &clients[socket];
auto ok = true;
while (socket->bytesAvailable()) {
if (Q_LIKELY(request->state == HttpRequest::State::ReadingMethod))
if (Q_UNLIKELY(!(ok = request->readMethod(socket))))
qWarning("Invalid Method");
if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingUrl))
if (Q_UNLIKELY(!(ok = request->readUrl(socket))))
qWarning("Invalid URL");
if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingStatus))
if (Q_UNLIKELY(!(ok = request->readStatus(socket))))
qWarning("Invalid Status");
if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingHeader))
if (Q_UNLIKELY(!(ok = request->readHeaders(socket))))
qWarning("Invalid Header");
if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingBody))
if (Q_UNLIKELY(!(ok = request->readBody(socket))))
qWarning("Invalid Body");
}
if (Q_UNLIKELY(!ok)) {
socket->disconnectFromHost();
clients.remove(socket);
} else if (Q_LIKELY(request->state == HttpRequest::State::AllDone)) {
Q_ASSERT(handler);
if (request->headers.contains("Host")) {
const auto parts = request->headers["Host"].split(':');
request->url.setHost(parts.at(0));
if (parts.size() == 2)
request->url.setPort(parts.at(1).toUInt());
}
handler(*request, socket);
socket->disconnectFromHost();
clients.remove(socket);
}
});
});
const auto ok = listen(QHostAddress::LocalHost);
Q_ASSERT(ok);
}
QUrl WebServer::url(const QString &path)
{
const QString format("http://127.0.0.1:%1%2");
return QUrl(format.arg(serverPort()).arg(path.startsWith('/') ? path : "/" + path));
}
bool WebServer::HttpRequest::readMethod(QTcpSocket *socket)
{
bool finished = false;
while (socket->bytesAvailable() && !finished) {
const auto c = socket->read(1).at(0);
if (std::isspace(c))
finished = true;
else if (std::isupper(c) && fragment.size() < 8)
fragment += c;
else
return false;
}
if (finished) {
if (fragment == "HEAD")
method = Method::Head;
else if (fragment == "GET")
method = Method::Get;
else if (fragment == "PUT")
method = Method::Put;
else if (fragment == "POST")
method = Method::Post;
else if (fragment == "DELETE")
method = Method::Delete;
else
qWarning("Invalid operation %s", fragment.data());
state = State::ReadingUrl;
fragment.clear();
return method != Method::Unknown;
}
return true;
}
bool WebServer::HttpRequest::readUrl(QTcpSocket *socket)
{
bool finished = false;
while (socket->bytesAvailable() && !finished) {
const auto c = socket->read(1).at(0);
if (std::isspace(c))
finished = true;
else
fragment += c;
}
if (finished) {
if (!fragment.startsWith("/")) {
qWarning("Invalid URL path %s", fragment.constData());
return false;
}
url.setUrl(QStringLiteral("http://127.0.0.1:") + QString::number(port) +
QString::fromUtf8(fragment));
state = State::ReadingStatus;
if (!url.isValid()) {
qWarning("Invalid URL %s", fragment.constData());
return false;
}
fragment.clear();
}
return true;
}
bool WebServer::HttpRequest::readStatus(QTcpSocket *socket)
{
bool finished = false;
while (socket->bytesAvailable() && !finished) {
fragment += socket->read(1);
if (fragment.endsWith("\r\n")) {
finished = true;
fragment.resize(fragment.size() - 2);
}
}
if (finished) {
if (!std::isdigit(fragment.at(fragment.size() - 3)) ||
fragment.at(fragment.size() - 2) != '.' ||
!std::isdigit(fragment.at(fragment.size() - 1))) {
qWarning("Invalid version");
return false;
}
version = qMakePair(fragment.at(fragment.size() - 3) - '0',
fragment.at(fragment.size() - 1) - '0');
state = State::ReadingHeader;
fragment.clear();
}
return true;
}
bool WebServer::HttpRequest::readHeaders(QTcpSocket *socket)
{
while (socket->bytesAvailable()) {
fragment += socket->read(1);
if (fragment.endsWith("\r\n")) {
if (fragment == "\r\n") {
state = State::ReadingBody;
fragment.clear();
return true;
} else {
fragment.chop(2);
const int index = fragment.indexOf(':');
if (index == -1)
return false;
const QByteArray key = fragment.mid(0, index).trimmed();
const QByteArray value = fragment.mid(index + 1).trimmed();
headers.insert(key, value);
fragment.clear();
}
}
}
return true;
}
bool WebServer::HttpRequest::readBody(QTcpSocket *socket)
{
if (headers.contains("Content-Length")) {
bool conversionResult;
bytesLeft = headers["Content-Length"].toInt(&conversionResult);
if (Q_UNLIKELY(!conversionResult))
return false;
fragment.resize(bytesLeft);
}
while (bytesLeft) {
int got = socket->read(&fragment.data()[fragment.size() - bytesLeft], bytesLeft);
if (got < 0)
return false; // error
bytesLeft -= got;
if (bytesLeft)
qApp->processEvents();
}
fragment.swap(body);
state = State::AllDone;
return true;
}
#endif // WEBSERVER_H