2022-02-04 15:40:43 -05:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
2023-11-18 07:12:18 -05:00
|
|
|
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
2022-02-04 15:40:43 -05:00
|
|
|
// //
|
|
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
|
|
// it under the terms of the GNU General Public License as published by //
|
|
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
|
|
// (at your option) any later version. //
|
|
|
|
// //
|
|
|
|
// This program is distributed in the hope that it will be useful, //
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
|
|
// GNU General Public License V3 for more details. //
|
|
|
|
// //
|
|
|
|
// You should have received a copy of the GNU General Public License //
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#include <QResource>
|
|
|
|
#include <QFile>
|
2023-08-06 04:08:53 -04:00
|
|
|
#include <QRegularExpression>
|
2022-02-04 15:40:43 -05:00
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
#include "webserver.h"
|
|
|
|
|
|
|
|
// port - port to listen on / is listening on. Use 0 for any free port.
|
|
|
|
WebServer::WebServer(quint16 &port, QObject* parent) :
|
|
|
|
QTcpServer(parent),
|
|
|
|
m_defaultMimeType("application/octet-stream")
|
|
|
|
{
|
|
|
|
listen(QHostAddress::Any, port);
|
|
|
|
port = serverPort();
|
|
|
|
qDebug() << "WebServer on port " << port;
|
|
|
|
|
|
|
|
m_mimeTypes.insert(".html", new MimeType("text/html; charset=\"utf-8\"", false));
|
|
|
|
m_mimeTypes.insert(".png", new MimeType("image/png"));
|
|
|
|
m_mimeTypes.insert(".glb", new MimeType("model/gltf-binary"));
|
|
|
|
m_mimeTypes.insert(".glbe", new MimeType("model/gltf-binary"));
|
|
|
|
m_mimeTypes.insert(".js", new MimeType("text/javascript"));
|
|
|
|
m_mimeTypes.insert(".css", new MimeType("text/css"));
|
|
|
|
m_mimeTypes.insert(".json", new MimeType("application/json"));
|
2022-07-20 12:41:11 -04:00
|
|
|
m_mimeTypes.insert(".geojson", new MimeType("application/geo+json"));
|
2022-02-04 15:40:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void WebServer::incomingConnection(qintptr socket)
|
|
|
|
{
|
|
|
|
QTcpSocket* s = new QTcpSocket(this);
|
|
|
|
connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
|
|
|
|
connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
|
|
|
|
s->setSocketDescriptor(socket);
|
|
|
|
//addPendingConnection(socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't include leading or trailing / in from
|
|
|
|
void WebServer::addPathSubstitution(const QString &from, const QString &to)
|
|
|
|
{
|
|
|
|
qDebug() << "Mapping " << from << " to " << to;
|
|
|
|
m_pathSubstitutions.insert(from, to);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebServer::addSubstitution(QString path, QString from, QString to)
|
|
|
|
{
|
|
|
|
Substitution *s = new Substitution(from, to);
|
|
|
|
if (m_substitutions.contains(path))
|
|
|
|
{
|
|
|
|
QList<Substitution *> *list = m_substitutions.value(path);
|
|
|
|
QMutableListIterator<Substitution *> i(*list);
|
|
|
|
while (i.hasNext()) {
|
|
|
|
Substitution *sub = i.next();
|
|
|
|
if (sub->m_from == from) {
|
|
|
|
i.remove();
|
|
|
|
delete sub;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list->append(s);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QList<Substitution *> *list = new QList<Substitution *>();
|
|
|
|
list->append(s);
|
|
|
|
m_substitutions.insert(path, list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString WebServer::substitute(QString path, QString html)
|
|
|
|
{
|
|
|
|
QList<Substitution *> *list = m_substitutions.value(path);
|
|
|
|
for (const auto s : *list) {
|
|
|
|
html = html.replace(s->m_from, s->m_to);
|
|
|
|
}
|
|
|
|
return html;
|
|
|
|
}
|
|
|
|
|
2022-07-20 12:41:11 -04:00
|
|
|
void WebServer::addFile(const QString &path, const QByteArray &data)
|
|
|
|
{
|
|
|
|
m_files.insert(path, data);
|
|
|
|
}
|
|
|
|
|
2022-02-04 15:40:43 -05:00
|
|
|
void WebServer::sendFile(QTcpSocket* socket, const QByteArray &data, MimeType *mimeType, const QString &path)
|
|
|
|
{
|
2024-02-27 10:40:06 -05:00
|
|
|
QString header = QString("HTTP/1.0 200 Ok\r\nContent-Type: %1\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *\r\nAccess-Control-Allow-Origin: *\r\n\r\n").arg(mimeType->m_type);
|
2022-02-04 15:40:43 -05:00
|
|
|
if (mimeType->m_binary)
|
|
|
|
{
|
|
|
|
// Send file as binary
|
|
|
|
QByteArray headerUtf8 = header.toUtf8();
|
|
|
|
socket->write(headerUtf8);
|
|
|
|
socket->write(data);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Send file as text
|
|
|
|
QString html = QString(data);
|
|
|
|
// Make any substitutions in the content of the file
|
|
|
|
if (m_substitutions.contains(path)) {
|
|
|
|
html = substitute(path, html);
|
|
|
|
}
|
|
|
|
QTextStream os(socket);
|
|
|
|
os.setAutoDetectUnicode(true);
|
|
|
|
os << header << html;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebServer::readClient()
|
|
|
|
{
|
|
|
|
QTcpSocket* socket = (QTcpSocket*)sender();
|
|
|
|
if (socket->canReadLine())
|
|
|
|
{
|
|
|
|
QString line = socket->readLine();
|
2024-02-27 10:40:06 -05:00
|
|
|
qDebug() << "WebServer HTTP Request: " << line;
|
2022-02-04 15:40:43 -05:00
|
|
|
|
2023-08-06 04:08:53 -04:00
|
|
|
QStringList tokens = QString(line).split(QRegularExpression("[ \r\n][ \r\n]*"));
|
2022-02-04 15:40:43 -05:00
|
|
|
if (tokens[0] == "GET")
|
|
|
|
{
|
|
|
|
// Get file type from extension
|
|
|
|
QString path = tokens[1];
|
|
|
|
MimeType *mimeType = &m_defaultMimeType;
|
|
|
|
int extensionIdx = path.lastIndexOf(".");
|
|
|
|
if (extensionIdx != -1) {
|
|
|
|
QString extension = path.mid(extensionIdx);
|
|
|
|
if (m_mimeTypes.contains(extension)) {
|
|
|
|
mimeType = m_mimeTypes[extension];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try mapping path
|
|
|
|
QStringList dirs = path.split('/');
|
|
|
|
if ((dirs.length() >= 2) && m_pathSubstitutions.contains(dirs[1]))
|
|
|
|
{
|
|
|
|
dirs[1] = m_pathSubstitutions.value(dirs[1]);
|
|
|
|
dirs.removeFirst();
|
|
|
|
QString newPath = dirs.join('/');
|
|
|
|
//qDebug() << "Mapping " << path << " to " << newPath;
|
|
|
|
path = newPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if we can find the file in our resources
|
|
|
|
QResource res(path);
|
2022-02-04 16:33:04 -05:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
2022-02-04 15:40:43 -05:00
|
|
|
if (res.isValid() && (res.uncompressedSize() > 0))
|
|
|
|
{
|
|
|
|
QByteArray data = res.uncompressedData();
|
|
|
|
sendFile(socket, data, mimeType, path);
|
|
|
|
}
|
2022-02-04 16:33:04 -05:00
|
|
|
#else
|
|
|
|
if (res.isValid() && (res.size() > 0))
|
|
|
|
{
|
|
|
|
QByteArray data = QByteArray::fromRawData((const char *)res.data(), res.size());
|
2022-02-09 11:41:40 -05:00
|
|
|
if (res.isCompressed()) {
|
|
|
|
data = qUncompress(data);
|
|
|
|
}
|
2022-02-04 16:33:04 -05:00
|
|
|
sendFile(socket, data, mimeType, path);
|
|
|
|
}
|
|
|
|
#endif
|
2022-07-20 12:41:11 -04:00
|
|
|
else if (m_files.contains(path))
|
|
|
|
{
|
|
|
|
// Path is a file held in memory
|
|
|
|
sendFile(socket, m_files.value(path).data(), mimeType, path);
|
|
|
|
}
|
2022-02-04 15:40:43 -05:00
|
|
|
else
|
|
|
|
{
|
2022-07-20 12:41:11 -04:00
|
|
|
// See if we can find a file on disk
|
2022-02-04 15:40:43 -05:00
|
|
|
QFile file(path);
|
|
|
|
if (file.open(QIODevice::ReadOnly))
|
|
|
|
{
|
|
|
|
QByteArray data = file.readAll();
|
|
|
|
if (path.endsWith(".glbe")) {
|
|
|
|
for (int i = 0; i < data.size(); i++) {
|
|
|
|
data[i] = data[i] ^ 0x55;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sendFile(socket, data, mimeType, path);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qDebug() << "WebServer " << path << " not found";
|
|
|
|
// File not found
|
|
|
|
QTextStream os(socket);
|
|
|
|
os.setAutoDetectUnicode(true);
|
|
|
|
os << "HTTP/1.0 404 Not Found\r\n"
|
|
|
|
"Content-Type: text/html; charset=\"utf-8\"\r\n"
|
|
|
|
"\r\n"
|
|
|
|
"<html>\n"
|
|
|
|
"<body>\n"
|
|
|
|
"<h1>404 Not Found</h1>\n"
|
|
|
|
"</body>\n"
|
|
|
|
"</html>\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
socket->close();
|
|
|
|
|
|
|
|
if (socket->state() == QTcpSocket::UnconnectedState) {
|
|
|
|
delete socket;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebServer::discardClient()
|
|
|
|
{
|
|
|
|
QTcpSocket* socket = (QTcpSocket*)sender();
|
|
|
|
socket->deleteLater();
|
|
|
|
}
|