sdrangel/plugins/feature/skymap/webserver.cpp

229 lines
8.2 KiB
C++
Raw Normal View History

2024-02-14 08:20:33 -05:00
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// 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>
#include <QRegularExpression>
#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"));
m_mimeTypes.insert(".geojson", new MimeType("application/geo+json"));
}
Fix memleaks found with AddressSanitizer/LeakSanitizer Found with: ASAN_OPTIONS="detect_odr_violation=1,strip_path_prefix=$(pwd)/" build/sdrangel Fixes: Direct leak of 16 byte(s) in 1 object(s) allocated from: #0 0x7f3c3e0f46b8 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95 #1 0x7f3bfdef913c in WebServer::WebServer(unsigned short&, QObject*) sdrangel/plugins/feature/skymap/webserver.cpp:34 #2 0x7f3bfdf10b56 in SkyMapGUI::SkyMapGUI(PluginAPI*, FeatureUISet*, Feature*, QWidget*) sdrangel/plugins/feature/skymap/skymapgui.cpp:220 #3 0x7f3bfdf0eb20 in SkyMapGUI::create(PluginAPI*, FeatureUISet*, Feature*) sdrangel/plugins/feature/skymap/skymapgui.cpp:44 #4 0x7f3bfdef75e9 in SkyMapPlugin::createFeatureGUI(FeatureUISet*, Feature*) const sdrangel/plugins/feature/skymap/skymapplugin.cpp:72 #5 0x7f3c3d60938b in MainWindow::featureAddClicked(Workspace*, int) sdrangel/sdrgui/mainwindow.cpp:2888 #6 0x7f3c3d624621 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0, 1>, QtPrivate::List<Workspace*, int>, void, void (MainWindow::*)(Workspace*, int)>::call(void (MainWindow::*)(Workspace*, int), MainWindow*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:152 #7 0x7f3c3d61feed in void QtPrivate::FunctionPointer<void (MainWindow::*)(Workspace*, int)>::call<QtPrivate::List<Workspace*, int>, void>(void (MainWindow::*)(Workspace*, int), MainWindow*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:185 #8 0x7f3c3d61d376 in QtPrivate::QSlotObject<void (MainWindow::*)(Workspace*, int), QtPrivate::List<Workspace*, int>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:418 #9 0x7f3c387062b1 (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x3062b1) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #10 0x7f3c3d5d5ca4 in Workspace::addFeature(Workspace*, int) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_workspace.cpp:393 #11 0x7f3c3d90c6a2 in Workspace::addFeatureEmitted(int) sdrangel/sdrgui/gui/workspace.cpp:413 #12 0x7f3c3d9245bb in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<int>, void, void (Workspace::*)(int)>::call(void (Workspace::*)(int), Workspace*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:152 #13 0x7f3c3d91fb83 in void QtPrivate::FunctionPointer<void (Workspace::*)(int)>::call<QtPrivate::List<int>, void>(void (Workspace::*)(int), Workspace*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:185 #14 0x7f3c3d91cc86 in QtPrivate::QSlotObject<void (Workspace::*)(int), QtPrivate::List<int>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:418 #15 0x7f3c387062b1 (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x3062b1) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #16 0x7f3c3d5c3c77 in FeatureAddDialog::addFeature(int) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_featureadddialog.cpp:141 #17 0x7f3c3d6d0d79 in FeatureAddDialog::apply(QAbstractButton*) sdrangel/sdrgui/gui/featureadddialog.cpp:53 #18 0x7f3c3d5c380e in FeatureAddDialog::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_featureadddialog.cpp:82 #19 0x7f3c38705fcc (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x305fcc) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #20 0x7f3c395029b2 in QDialogButtonBox::clicked(QAbstractButton*) (/lib/x86_64-linux-gnu/libQt5Widgets.so.5+0x3029b2) (BuildId: dfefd27f084c0dd066215fc79825fceae604f481) and more
2024-05-04 12:00:01 -04:00
WebServer::~WebServer()
{
Fix memleaks found with AddressSanitizer/LeakSanitizer Found with: ASAN_OPTIONS="detect_odr_violation=1,strip_path_prefix=$(pwd)/" build/sdrangel Fixes: Direct leak of 8 byte(s) in 1 object(s) allocated from: #0 0x7fabe9cf46b8 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95 #1 0x7faba9afa508 in WebServer::addSubstitution(QString, QString, QString) sdrangel/plugins/feature/skymap/webserver.cpp:83 #2 0x7faba9b11591 in SkyMapGUI::SkyMapGUI(PluginAPI*, FeatureUISet*, Feature*, QWidget*) sdrangel/plugins/feature/skymap/skymapgui.cpp:224 #3 0x7faba9b0f0a0 in SkyMapGUI::create(PluginAPI*, FeatureUISet*, Feature*) sdrangel/plugins/feature/skymap/skymapgui.cpp:44 #4 0x7faba9af70e9 in SkyMapPlugin::createFeatureGUI(FeatureUISet*, Feature*) const sdrangel/plugins/feature/skymap/skymapplugin.cpp:72 #5 0x7fabe920938b in MainWindow::featureAddClicked(Workspace*, int) sdrangel/sdrgui/mainwindow.cpp:2888 #6 0x7fabe9224621 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0, 1>, QtPrivate::List<Workspace*, int>, void, void (MainWindow::*)(Workspace*, int)>::call(void (MainWindow::*)(Workspace*, int), MainWindow*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:152 #7 0x7fabe921feed in void QtPrivate::FunctionPointer<void (MainWindow::*)(Workspace*, int)>::call<QtPrivate::List<Workspace*, int>, void>(void (MainWindow::*)(Workspace*, int), MainWindow*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:185 #8 0x7fabe921d376 in QtPrivate::QSlotObject<void (MainWindow::*)(Workspace*, int), QtPrivate::List<Workspace*, int>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:418 #9 0x7fabe43062b1 (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x3062b1) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #10 0x7fabe91d5ca4 in Workspace::addFeature(Workspace*, int) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_workspace.cpp:393 #11 0x7fabe950c6a2 in Workspace::addFeatureEmitted(int) sdrangel/sdrgui/gui/workspace.cpp:413 #12 0x7fabe95245bb in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<int>, void, void (Workspace::*)(int)>::call(void (Workspace::*)(int), Workspace*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:152 #13 0x7fabe951fb83 in void QtPrivate::FunctionPointer<void (Workspace::*)(int)>::call<QtPrivate::List<int>, void>(void (Workspace::*)(int), Workspace*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:185 #14 0x7fabe951cc86 in QtPrivate::QSlotObject<void (Workspace::*)(int), QtPrivate::List<int>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:418 #15 0x7fabe43062b1 (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x3062b1) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #16 0x7fabe91c3c77 in FeatureAddDialog::addFeature(int) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_featureadddialog.cpp:141 #17 0x7fabe92d0d79 in FeatureAddDialog::apply(QAbstractButton*) sdrangel/sdrgui/gui/featureadddialog.cpp:53 #18 0x7fabe91c380e in FeatureAddDialog::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_featureadddialog.cpp:82 #19 0x7fabe4305fcc (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x305fcc) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #20 0x7fabe51029b2 in QDialogButtonBox::clicked(QAbstractButton*) (/lib/x86_64-linux-gnu/libQt5Widgets.so.5+0x3029b2) (BuildId: dfefd27f084c0dd066215fc79825fceae604f481) and more
2024-05-04 12:02:32 -04:00
qDeleteAll(m_substitutions);
Fix memleaks found with AddressSanitizer/LeakSanitizer Found with: ASAN_OPTIONS="detect_odr_violation=1,strip_path_prefix=$(pwd)/" build/sdrangel Fixes: Direct leak of 16 byte(s) in 1 object(s) allocated from: #0 0x7f3c3e0f46b8 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95 #1 0x7f3bfdef913c in WebServer::WebServer(unsigned short&, QObject*) sdrangel/plugins/feature/skymap/webserver.cpp:34 #2 0x7f3bfdf10b56 in SkyMapGUI::SkyMapGUI(PluginAPI*, FeatureUISet*, Feature*, QWidget*) sdrangel/plugins/feature/skymap/skymapgui.cpp:220 #3 0x7f3bfdf0eb20 in SkyMapGUI::create(PluginAPI*, FeatureUISet*, Feature*) sdrangel/plugins/feature/skymap/skymapgui.cpp:44 #4 0x7f3bfdef75e9 in SkyMapPlugin::createFeatureGUI(FeatureUISet*, Feature*) const sdrangel/plugins/feature/skymap/skymapplugin.cpp:72 #5 0x7f3c3d60938b in MainWindow::featureAddClicked(Workspace*, int) sdrangel/sdrgui/mainwindow.cpp:2888 #6 0x7f3c3d624621 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0, 1>, QtPrivate::List<Workspace*, int>, void, void (MainWindow::*)(Workspace*, int)>::call(void (MainWindow::*)(Workspace*, int), MainWindow*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:152 #7 0x7f3c3d61feed in void QtPrivate::FunctionPointer<void (MainWindow::*)(Workspace*, int)>::call<QtPrivate::List<Workspace*, int>, void>(void (MainWindow::*)(Workspace*, int), MainWindow*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:185 #8 0x7f3c3d61d376 in QtPrivate::QSlotObject<void (MainWindow::*)(Workspace*, int), QtPrivate::List<Workspace*, int>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:418 #9 0x7f3c387062b1 (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x3062b1) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #10 0x7f3c3d5d5ca4 in Workspace::addFeature(Workspace*, int) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_workspace.cpp:393 #11 0x7f3c3d90c6a2 in Workspace::addFeatureEmitted(int) sdrangel/sdrgui/gui/workspace.cpp:413 #12 0x7f3c3d9245bb in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<int>, void, void (Workspace::*)(int)>::call(void (Workspace::*)(int), Workspace*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:152 #13 0x7f3c3d91fb83 in void QtPrivate::FunctionPointer<void (Workspace::*)(int)>::call<QtPrivate::List<int>, void>(void (Workspace::*)(int), Workspace*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:185 #14 0x7f3c3d91cc86 in QtPrivate::QSlotObject<void (Workspace::*)(int), QtPrivate::List<int>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:418 #15 0x7f3c387062b1 (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x3062b1) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #16 0x7f3c3d5c3c77 in FeatureAddDialog::addFeature(int) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_featureadddialog.cpp:141 #17 0x7f3c3d6d0d79 in FeatureAddDialog::apply(QAbstractButton*) sdrangel/sdrgui/gui/featureadddialog.cpp:53 #18 0x7f3c3d5c380e in FeatureAddDialog::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) build-sdrangel-Desktop_qt5-Debug/sdrgui/sdrgui_autogen/DMHXEJ42XS/moc_featureadddialog.cpp:82 #19 0x7f3c38705fcc (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x305fcc) (BuildId: ed2abb344a128279a866aa6c4a79f3fa5c87c59e) #20 0x7f3c395029b2 in QDialogButtonBox::clicked(QAbstractButton*) (/lib/x86_64-linux-gnu/libQt5Widgets.so.5+0x3029b2) (BuildId: dfefd27f084c0dd066215fc79825fceae604f481) and more
2024-05-04 12:00:01 -04:00
qDeleteAll(m_mimeTypes);
}
2024-02-14 08:20:33 -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;
}
void WebServer::addFile(const QString &path, const QByteArray &data)
{
m_files.insert(path, data);
}
void WebServer::sendFile(QTcpSocket* socket, const QByteArray &data, MimeType *mimeType, const QString &path)
{
2024-02-27 10:40:48 -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);
2024-02-14 08:20:33 -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();
//qDebug() << "WebServer HTTP Request: " << line;
QStringList tokens = QString(line).split(QRegularExpression("[ \r\n][ \r\n]*"));
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 skymapping 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() << "SkyMapping " << path << " to " << newPath;
path = newPath;
}
// See if we can find the file in our resources
QResource res(path);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
if (res.isValid() && (res.uncompressedSize() > 0))
{
QByteArray data = res.uncompressedData();
sendFile(socket, data, mimeType, path);
}
#else
if (res.isValid() && (res.size() > 0))
{
QByteArray data = QByteArray::fromRawData((const char *)res.data(), res.size());
if (res.isCompressed()) {
data = qUncompress(data);
}
sendFile(socket, data, mimeType, path);
}
#endif
else if (m_files.contains(path))
{
// Path is a file held in memory
sendFile(socket, m_files.value(path).data(), mimeType, path);
}
else
{
// See if we can find a file on disk
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();
}