Files
qt-migration-legatus/qt6-after/qt6-migrated-legatus/Lts/Services/FileManagement/thp_script_httq.cpp
T
berrux-dev 5485a90385 Lts/Services: finalize Qt6/C++17 migration cleanup
Remove deprecated Qt5 patterns remaining in Services:

- Replace remaining SIGNAL/SLOT string syntax with function pointer syntax
  (NmeaManagement/socket_handler.cpp, AudioVideoManagement/ModuleTests/mvs_tests.cpp)

- Remove Qt::UniqueConnection (deprecated in Qt6, default behavior) from
  19 files across AES, CES plugins, and cross-services (47 occurrences)

- Remove umbrella #include <QtCore> from fire_automaton.h and
  aes_test_fire_automaton.h (specific includes suffice)

25 files changed, 74 insertions(+), 81 deletions(-)
2026-09-21 06:43:28 +00:00

754 lines
22 KiB
C++
Executable File

#include "thp_script_httq.h"
#include "logger.h"
#include <httq/HttpRequest.h>
#include <httq/DataStream.h>
#include <nglsettings/nglsettings_connections.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <Common2Core/tpm_tools.h>
#include <Common2Core/tpm_js_parser.h>
#include <QWebSocket>
#include <QWebSocketServer>
#include <QtCore/QFile>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QTemporaryFile>
//#include <QSaveFile>
#include <QtCore/QDateTime>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QUrlQuery>
#include <QDebug>
#define L QStringLiteral
using namespace TakeHomePackageManager;
namespace FileManagement
{
// LoggerFactory
LoggerFactory::LoggerFactory(QObject *parent)
: httq::LoggerFactory(parent)
{
}
Logger *LoggerFactory::createLogger(QObject *parent)
{
return new Logger(this, parent);
}
LoggerFactory *ThpScriptServer::createLoggerFactory()
{
return new LoggerFactory(this);
}
// Logger
Logger::Logger(LoggerFactory *loggerFactory, QObject *parent)
: httq::Logger(loggerFactory, parent)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(QString::fromUtf8(parent->metaObject()->className()).replace(QStringLiteral("::"), QStringLiteral(".")).toStdString())) // man man
{}
void Logger::debug(const QString &message)
{
/*DEBUG_LOG*/ERROR_LOG(m_logger) << message;
}
void Logger::warning(const QString &message)
{
WARNING_LOG(m_logger) << message;
}
void Logger::error(const QString &message)
{
ERROR_LOG(m_logger) << message;
}
// ThpScriptServer
ThpScriptServer::ThpScriptServer(QObject *parent)
: AbstractServer(parent)
, mWsSvr(new QWebSocketServer({}, QWebSocketServer::NonSecureMode, this))
, m_logger(getLogger("ThpScriptServer"))
{
if (!listen(NGL::Connections::port(NGL::Connections::eConnection::FLM_THP_SERVER)))
{
ERROR_LOG(m_logger) << "cannot listen on port " << NGL::Connections::port(NGL::Connections::eConnection::FLM_THP_SERVER);
Q_ASSERT(false);
}
QDir().mkpath(scriptsLocation(serverThpDir()));
connect(this, &ThpScriptServer::signalBroadcastNewContentFile,
this, [this]()
{
emit signalBroadcastMessage(readContentFile());
});
connect(this, &ThpScriptServer::signalBroadcastNewScriptsJsFile,
this, [this]()
{
emit signalBroadcastMessage2(readScriptsJsFile());
});
connect(mWsSvr, &QWebSocketServer::newConnection,
this, [this]()
{
while (mWsSvr->hasPendingConnections())
{
QWebSocket *cli = mWsSvr->nextPendingConnection();
if (cli == nullptr)
return;
bool failed = false;
switch (QStringList{{L("/thp"), L("/thp2")}}.indexOf(cli->requestUrl().path()))
{
case 0: // old
{
connect(this, &ThpScriptServer::signalBroadcastMessage,
cli, [cli](const QString &msg)
{
cli->sendTextMessage(msg);
});
cli->sendTextMessage(readContentFile());
break;
}
case 1: // THP2
{
connect(this, &ThpScriptServer::signalBroadcastMessage2,
cli, [cli](const QString &msg)
{
cli->sendTextMessage(msg);
});
cli->sendTextMessage(readScriptsJsFile());
break;
}
default:
{
ERROR_LOG(m_logger) << "ws unknown path: " << cli->requestUrl().path();
failed = true;
break;
}
}
if (failed)
{
cli->deleteLater();
continue;
}
connect(cli, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
this, [cli]()
{
cli->deleteLater();
//qWarning() << "ws error!!!";
});
connect(cli, &QWebSocket::disconnected,
this, [cli]()
{
cli->deleteLater();
//qWarning() << "ws disco!!!";
});
}
});
}
const QString ThpScriptServer::readScriptsJsFile() const
{
return QString::fromUtf8(QJsonDocument(ScriptsJsParser(serverThpDir(), false).get()).toJson());
}
const QString ThpScriptServer::readContentFile() const
{
QFile *f = new QFile(thpDirectory() + QStringLiteral("content.json"));
f->deleteLater();
if (!f->open(QIODevice::ReadOnly))
{
WARNING_LOG(m_logger) << "Error opening " << f->fileName() << " for reading: " << f->errorString();
return QStringLiteral("[]");
}
return QString::fromUtf8(f->readAll());
}
class DataStreamError : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
bool ThpScriptServer::newHttpConnection(httq::HttpRequest *request)
{
try
{
if (handleRequestSync(*request))
{
request->deleteLater();
}
else if (!handleRequest(request))
{
throw(L("unroutable"));
}
return true;
}
catch(const DataStreamError &err)
{
DEBUG_LOG(m_logger) << "handler threw DataStreamError: " << err.what();
// do not delete request as a failed datastream deletes it itself
return false;
}
catch(const std::runtime_error &err)
{
errorHandler(request, HttpException(QString::fromUtf8(err.what()), 400));
return false;
}
catch(const QString &error)
{
errorHandler(request, HttpException(error, 400));
return false;
}
catch(const HttpException &he)
{
errorHandler(request, he);
return false;
}
catch(...)
{
errorHandler(request, HttpException(L("<unknown exception!>"), 400));
return false;
}
}
QString ThpScriptServer::thpDirectory()
{
// TODO (what exactly?)
return QDir::tempPath() + QStringLiteral("/THP/");
}
QString ThpScriptServer::thpDirectory(int exerciseId)
{
return thpDirectory() + QStringLiteral("%1/").arg(exerciseId);
}
QString ThpScriptServer::thpFileName(int exerciseId, int scriptId)
{
return thpDirectory(exerciseId) + QString::number(scriptId) + QStringLiteral(".tar.bz2");
}
QJsonObject ThpScriptServer::toJson(const ThpServerInformation &tsi)
{
return QJsonObject({
{QStringLiteral("exerciseId"), tsi.exerciseId},
{QStringLiteral("exerciseName"), tsi.exerciseName},
{QStringLiteral("scriptId"), tsi.scriptId},
{QStringLiteral("uploadTime"), tsi.uploadTime},
{QStringLiteral("fileSize"), tsi.fileSize},
{QStringLiteral("name"), tsi.description}
//{QStringLiteral("md5sumHex"), tsi.md5sumHex}
});
}
ThpServerInformation ThpScriptServer::fromJson(const QJsonValue &val)
{
const QJsonObject obj = val.toObject();
ThpServerInformation tsi;
tsi.exerciseId = obj[QStringLiteral("exerciseId")].toInt();
tsi.exerciseName = obj[QStringLiteral("exerciseName")].toString();
tsi.scriptId = obj[QStringLiteral("scriptId")].toInt();
tsi.uploadTime = qRound64(obj[QStringLiteral("uploadTime")].toDouble());
tsi.fileSize = qRound64(obj[QStringLiteral("fileSize")].toDouble());
tsi.description = obj[QStringLiteral("name")].toString();
//tsi.md5sumHex = obj[QStringLiteral("md5sumHex")].toString();
return tsi;
}
void ThpScriptServer::errorHandler(httq::HttpRequest *request, const HttpException &e) const
{
DEBUG_LOG(m_logger) << "handler threw error: " << e.error << " (http status = " << e.status << ")";
QJsonObject root;
root[QStringLiteral("error")] = e.error;
#ifdef LTS_DEBUG
root[QStringLiteral("DEBUG")] = request->url().path();
#endif
request->write(e.status, root);
request->deleteLater();
}
void ThpScriptServer::changeList(const ThpServerInformation &tsi, httq::HttpRequest *request, bool remove)
{
try
{
QFile contentFile(thpDirectory() + QStringLiteral("content.json"), this);
if (!contentFile.open(QIODevice::ReadWrite))
{
ERROR_LOG(m_logger) << "Error opening file '" << contentFile.fileName() << "' for read/write: " << contentFile.errorString();
throw(HttpException(QStringLiteral("cannot open content file '%1': %2").arg(contentFile.fileName(), contentFile.errorString())));
}
if (contentFile.size() == 0) // file was just created by the open command - fill it with an valid, but empty json array
{
contentFile.write(QStringLiteral("[]").toUtf8());
contentFile.seek(0);
}
QJsonParseError err;
QJsonDocument doc(QJsonDocument::fromJson(contentFile.readAll(), &err));
if (err.error != QJsonParseError::NoError)
{
ERROR_LOG(m_logger) << "content file's json invalid at" << err.offset << ":" << err.errorString();
throw(HttpException(QStringLiteral("content file's json invalid at %1: %2").arg(err.offset).arg(err.errorString())));
}
QJsonArray arr(doc.array());
QVector<ThpServerInformation> tsiArray;
tsiArray.reserve(arr.size() + 1);
for (const QJsonValue &val : qAsConst(arr))
{
ThpServerInformation tsix(fromJson(val));
if (tsix.scriptId == tsi.scriptId && tsix.exerciseId == tsi.exerciseId)
continue;
tsiArray += std::move(tsix);
}
if (!remove)
tsiArray += tsi;
arr = {};
std::transform(tsiArray.constBegin(), tsiArray.constEnd(), std::back_inserter(arr),
[](const ThpServerInformation &tsix)
{
return toJson(tsix);
});
if (!contentFile.resize(0) || !contentFile.write(QJsonDocument(arr).toJson()))
{
ERROR_LOG(m_logger) << "Error writing content.json: " << contentFile.errorString();
throw(HttpException(QStringLiteral("error writing content file: %1").arg(contentFile.errorString())));
}
request->write(200, noError());
contentFile.flush(); // needed as signalBroadcastNewContentFile reads from the file, which has old data in it until it's flushed
emit signalBroadcastNewContentFile(); // content file has changed, as a new script was uploaded - tell all the clients
request->deleteLater();
}
catch (const HttpException &e)
{
errorHandler(request, e);
}
}
QJsonObject ThpScriptServer::noError()
{
static QJsonObject obj({{ QStringLiteral("error"), QString() }});
return obj;
}
QString ThpScriptServer::queryGetString(const QUrlQuery &q, const QString &key)
{
if (!q.hasQueryItem(key))
throw(HttpException(QStringLiteral("%1 missing").arg(key), 400));
return q.queryItemValue(key);
}
int ThpScriptServer::queryGetInt(const QUrlQuery &q, const QString &key)
{
const QString value(queryGetString(q, key));
bool k = false;
int num = value.toInt(&k);
if (!k)
throw(HttpException(QStringLiteral("%1 not an int").arg(key), 400));
return num;
}
bool ThpScriptServer::handleRequestSync(httq::HttpRequest &request) noexcept(false)
{
//qWarning() << "url" << request->url() << "method" << (request->method() == HTTP_GET ? "GET" : request->method() == HTTP_POST ? "POST" : "?");
const QUrl &url = request.url();
if (url.path() == L("/thp2"))
{
switch (request.method())
{
case HTTP_GET: // DOWNLOAD THP
{
const QUrlQuery &q(request.query());
const QString scriptId(queryGetString(q, L("scriptId")));
auto esi = ScriptsJsParser(serverThpDir()).getExportedScriptInformation(scriptId);
request.addHeader(L("x-thp-info"), QString::fromUtf8(QJsonDocument(esi.toJson().toObject()).toJson(QJsonDocument::JsonFormat::Compact)));
QFile f(scriptArchiveLocation(serverThpDir(), scriptId));
openFile(f, true, true);
httq::DataStream *ds = request.createDataStreamToClient(200, L("application/octet-stream"), &f, f.size());
try
{
awaitWithCancelationSignalx(ds, &httq::DataStream::signalDone, ds, &QObject::destroyed);
}
catch (const std::runtime_error &err)
{
throw DataStreamError("Writing DataStream failed! Client disconnect?");
}
return true;
break;
}
case HTTP_POST: // UPLOAD THP
{
const QString thpInfo(request.requestHeaders().value(L("x-thp-info")));
if (thpInfo.isEmpty())
{
qWarning() << "headers" << request.requestHeaders();
throw L("x-thp-info header missing");
}
auto esi = ExportedScriptInformation::fromJson(JsonObject::fromJson(thpInfo.toUtf8()).object());
const auto destDir(scriptLocation(serverThpDir(), esi.id()));
if (!QDir().mkpath(destDir)) // NOTE: we don't check if id is evil here
{
throw(L("Cannot create directory %1 - THP upload not possible").arg(destDir));
}
const auto destFile(scriptArchiveLocation(serverThpDir(), esi.id()));
QFileInfo fi(destFile);
if (fi.exists() && !QFile::remove(destFile))
{
throw L("Cannot remove old THP upload file %1 - THP upload not possible").arg(destDir);
}
auto saveFile = SaveFile::open(destFile);
qWarning() << "######### STEP 1" << request.contentLength();
httq::DataStream *ds = request.createDataStreamFromClient(saveFile.get(), request.contentLength()); // NOTE: auto-deletes itself
qWarning() << "######### STEP 2";
try
{
qWarning() << "######### STEP 3";
awaitWithCancelationSignalx(ds, &httq::DataStream::signalDone, ds, &QObject::destroyed); // destroyed before signalDone? => error
qWarning() << "######### STEP 4";
}
catch (const std::runtime_error &err)
{
throw DataStreamError("Reading DataStream failed! Client disconnect?");
}
qWarning() << "######### STEP 5";
auto parser = ScriptsJsParser(serverThpDir(), false);
parser.addExportedScriptInformation(esi);
parser.save();
qWarning() << "######### STEP 6";
request.write(201, QJsonObject());
qWarning() << "######### STEP 7";
emit signalBroadcastNewScriptsJsFile(); // scripts.js file has changed, as a new script was uploaded - tell all the clients
return true;
}
case HTTP_DELETE:
{
const QUrlQuery &q(request.query());
const QString scriptId(queryGetString(q, L("scriptId")));
auto parser = ScriptsJsParser(serverThpDir(), true);
parser.removeExportedScriptInformation(scriptId);
parser.save();
const auto scriptDir(scriptLocation(serverThpDir(), scriptId));
if (!QDir().rmdir(scriptDir)) // NOTE: we don't check if id is evil here
{
throw L("Failed to remove script dir '%1' - THP delete not possible").arg(scriptDir);
}
const auto scriptArchive(scriptArchiveLocation(serverThpDir(), scriptId));
if (!QFile::remove(scriptArchive)) // NOTE: we don't check if id is evil here
{
throw L("Failed to remove script archive '%1' - THP delete not possible").arg(scriptArchive);
}
request.write(200, QJsonObject());
emit signalBroadcastNewScriptsJsFile(); // scripts.js file has changed, as a new script was uploaded - tell all the clients
return true;
}
default:
{
WARNING_LOG(m_logger) << "unsupported method for /thp2";
throw HttpException(L("unsupported method for /thp2: %1").arg((int)request.method()), 405);
}
}
}
return false;
}
bool ThpScriptServer::handleRequest(httq::HttpRequest *request) noexcept(false)
{
//qWarning() << "url" << request->url() << "method" << (request->method() == HTTP_GET ? "GET" : request->method() == HTTP_POST ? "POST" : "?");
const QUrl &url = request->url();
switch (request->method())
{
case HTTP_GET:
{
if (url.path() == QStringLiteral("/list"))
{
QFile *f = new QFile(thpDirectory() + QStringLiteral("content.json"));
if (!f->open(QIODevice::ReadOnly))
{
f->deleteLater();
request->write(200, noError());
request->deleteLater();
return true;
}
httq::DataStream *ds = request->createDataStreamToClient(200, QStringLiteral("application/json"), f, f->size());
connect(ds, &httq::DataStream::signalDone,
this, [request, f]()
{
f->deleteLater();
request->deleteLater();
});
return true;
}
else if (url.path() == QStringLiteral("/thp"))
{
const QUrlQuery &q(request->query());
int exerciseId(queryGetInt(q, QStringLiteral("exerciseId")));
int scriptId(queryGetInt(q, QStringLiteral("scriptId")));
QFile *f = new QFile(thpFileName(exerciseId, scriptId));
if (!f->open(QIODevice::ReadOnly))
{
f->deleteLater();
throw(HttpException(QStringLiteral("thp for exercise %1, script %2 not found").arg(exerciseId).arg(scriptId), 404));
return true;
}
httq::DataStream *ds = request->createDataStreamToClient(200, QStringLiteral("application/octet-stream"), f, f->size());
connect(ds, &httq::DataStream::signalDone,
this, [request, f]()
{
f->deleteLater();
request->deleteLater();
});
return true;
}
break;
}
case HTTP_DELETE: // REMOVE THP
{
if (url.path() == QStringLiteral("/thp"))
{
const QUrlQuery &q(request->query());
ThpServerInformation tsi;
tsi.exerciseId = queryGetInt(q, QStringLiteral("exerciseId"));
tsi.scriptId = queryGetInt(q, QStringLiteral("scriptId"));
const QString destFile = thpFileName(tsi.exerciseId, tsi.scriptId);
if (!QFile::remove(destFile))
{
const QString err(QStringLiteral("Cannot remove old THP upload file %1 - removing THP not possible").arg(destFile));
ERROR_LOG(m_logger) << err;
throw(HttpException(err));
}
changeList(tsi, request, true);
request->write(200, noError());
return true;
}
break;
}
case HTTP_POST: // UPLOAD THP
{
if (url.path() == QStringLiteral("/thp"))
{
const QUrlQuery &q(request->query());
ThpServerInformation tsi;
tsi.exerciseId = queryGetInt(q, QStringLiteral("exerciseId"));
tsi.scriptId = queryGetInt(q, QStringLiteral("scriptId"));
tsi.exerciseName = queryGetString(q, QStringLiteral("exerciseName"));
tsi.description = q.queryItemValue(QStringLiteral("name"));
tsi.fileSize = request->contentLength();
tsi.uploadTime = QDateTime::currentMSecsSinceEpoch();
// qWarning() << "tsi.exerciseName=" << tsi.exerciseName
// << " tsi.scriptId=" << tsi.scriptId
// << " tsi.description (name)=" << tsi.description
// << " tsi.fileSize=" << tsi.fileSize
// //<< " tsi.md5sumHex=" << tsi.md5sumHex
// << " tsi.uploadTime=" << tsi.uploadTime;
const QString destDir = thpDirectory(tsi.exerciseId);
if (!QDir().mkpath(destDir))
{
const QString err(QStringLiteral("Cannot create directory %1 - THP upload not possible").arg(destDir));
ERROR_LOG(m_logger) << err;
throw(HttpException(err));
}
const QString destFile = thpFileName(tsi.exerciseId, tsi.scriptId);
QFileInfo fi(destFile);
if (fi.exists() && !QFile::remove(destFile))
{
const QString err(QStringLiteral("Cannot remove old THP upload file %1 - THP upload not possible").arg(destDir));
ERROR_LOG(m_logger) << err;
throw(HttpException(err));
}
// qWarning() << "New upload request - scriptId: " << tsi.scriptId
// << " exerciseId: " << tsi.exerciseId
// << " fileSize: " << tsi.fileSize
// //<< " md5sum: " << tsi.md5sumHex
// ;
QTemporaryFile *f = new QTemporaryFile(this);
if (!f->open())
{
f->deleteLater();
throw(HttpException(QStringLiteral("Error opening temp file '%1' for writing: %2").arg(f->fileName(), f->errorString())));
}
httq::DataStream *ds = request->createDataStreamFromClient(f, request->contentLength());
connect(ds, &httq::DataStream::signalDone, // step 2 =>
this, [this, f, request, tsi]()
{
try
{
const QString destDir = thpDirectory(tsi.exerciseId);
QDir d(destDir);
if (!d.exists() && !d.mkpath(destDir))
{
const QString errstr(QStringLiteral("Can create directory '%1' - THP upload not possible").arg(destDir));
ERROR_LOG(m_logger) << errstr;
throw(HttpException(errstr));
}
const QString destFile = thpFileName(tsi.exerciseId, tsi.scriptId);
QFileInfo fi(destFile);
if (fi.exists() && !QFile::remove(destFile))
{
const QString errstr(QStringLiteral("Can not remove old file '%1'").arg(destDir));
ERROR_LOG(m_logger) << errstr;
throw(HttpException(errstr));
}
// calling fileName() makes sure setAutoRemove(false) is honored on all systems
const QString tmpFilename = f->fileName();
if (!f->rename(destFile))
{
const QString errstr(QStringLiteral("Can not rename %1 to %2: %3").arg(tmpFilename, destFile, f->errorString()));
ERROR_LOG(m_logger) << errstr;
throw(HttpException(errstr));
}
f->setAutoRemove(false);
//qWarning() << QStringLiteral("Renamed %1 to %2").arg(tmpFilename, destFile);
// now update info.json
}
catch (const HttpException &e)
{
errorHandler(request, e);
}
changeList(tsi, request, false);
});
return true;
}
break;
}
default:
{
WARNING_LOG(m_logger) << "unsupported method";
throw(HttpException(QStringLiteral("unsupported method"), 405));
}
}
return false;
}
} // namespace FileManagement