diff --git a/.current_version b/.current_version index 4a85099..44723eb 100644 --- a/.current_version +++ b/.current_version @@ -1 +1 @@ -260924_084534 \ No newline at end of file +260930_081832 \ No newline at end of file diff --git a/_version.h b/_version.h index 58fb4a6..fa42154 100644 --- a/_version.h +++ b/_version.h @@ -1,6 +1,6 @@ #ifndef _VERSION_H #define _VERSION_H -#define VERSION "260924_084534" +#define VERSION "260930_081832" #endif // _VERSION_H diff --git a/main.cpp b/main.cpp index 26d5b2d..aeb3aa5 100644 --- a/main.cpp +++ b/main.cpp @@ -14,6 +14,10 @@ #include #include #include +#include +#include +#include +#include #include "_version.h" @@ -166,6 +170,25 @@ static QHttpServerResponse withCors(QHttpServerResponse &&resp) return std::move(resp); } +// ---------------------------------------------------------------------------- +// Archives (cf synthèse §32) - répertoire d'upload périodique du bateau vers +// ship2net. Chemin à confirmer/ajuster si différent en pratique sur le CM5. +// ---------------------------------------------------------------------------- +static const QString REPERTOIRE_ARCHIVES = "/home/koriolan/shipheart/server/upload"; + +// Un nom d'archive valide correspond STRICTEMENT au format généré côté CM5 : +// __.sql.gz.enc +// Validation impérative avant de construire un chemin sur le disque à partir +// d'un nom reçu en paramètre d'URL - sans elle, un nom arbitraire (par +// exemple "../../etc/passwd") pourrait faire lire ou supprimer n'importe quel +// fichier du système, pas seulement une archive légitime. +static bool nom_archive_est_valide(const QString &nom) +{ + static const QRegularExpression motif( + "^[0-9A-Fa-f]{12}_\\d{6}_\\d{6}_[0-9A-Fa-f]{16}\\.sql\\.gz\\.enc$"); + return motif.match(nom).hasMatch(); +} + // ---------------------------------------------------------------------------- // Vérifie que la connexion MariaDB (ouverte une seule fois au démarrage) est // toujours active, et la rétablit sinon. isOpen() peut rester vrai même après @@ -1277,6 +1300,93 @@ int main(int argc, char *argv[]) return withCors(QHttpServerResponse(QJsonDocument(reponse).toJson())); }); + // ------------------------------------------------------------------ + // GET /archives - liste les archives en attente de traitement par le VPS + // (cf synthèse §32.5). Remplace le transport sshpass/scp existant. + // ------------------------------------------------------------------ + server.route("/archives", [](const QHttpServerRequest &request) { + Q_UNUSED(request) + + QDir dossier(REPERTOIRE_ARCHIVES); + QJsonArray archives; + const QFileInfoList fichiers = dossier.entryInfoList(QDir::Files, QDir::Name); + for (const QFileInfo &info : fichiers) { + // Tout fichier qui ne correspond pas au format attendu est + // ignoré silencieusement (ex: un .gitkeep, un fichier temporaire + // laissé par une écriture interrompue) : + if (!nom_archive_est_valide(info.fileName())) continue; + + QJsonObject o; + o["nom"] = info.fileName(); + o["taille"] = info.size(); + archives.append(o); + } + + QJsonObject reponse; + reponse["status"] = "ok"; + reponse["archives"] = archives; + return withCors(QHttpServerResponse(QJsonDocument(reponse).toJson())); + }); + + // ------------------------------------------------------------------ + // GET /archives/ - contenu brut d'une archive précise. + // ------------------------------------------------------------------ + server.route("/archives/", QHttpServerRequest::Method::Get, + [](const QString &nom, const QHttpServerRequest &request) { + Q_UNUSED(request) + + if (!nom_archive_est_valide(nom)) { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "nom d'archive invalide"; + return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::BadRequest)); + } + + QFile fichier(REPERTOIRE_ARCHIVES + "/" + nom); + if (!fichier.open(QIODevice::ReadOnly)) { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "archive introuvable"; + return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotFound)); + } + const QByteArray contenu = fichier.readAll(); + fichier.close(); + + return withCors(QHttpServerResponse("application/octet-stream", contenu)); + }); + + // ------------------------------------------------------------------ + // DELETE /archives/ - confirmation de traitement par le serveur, + // entraîne la suppression locale. Idempotent : supprimer une archive déjà + // absente renvoie "ok" (pas une erreur), pour ne pas gêner une + // confirmation qui arriverait en double (par exemple après une coupure + // réseau juste après un premier appel déjà réussi côté disque, mais dont + // la réponse ne serait jamais arrivée jusqu'au VPS). + // ------------------------------------------------------------------ + server.route("/archives/", QHttpServerRequest::Method::Delete, + [](const QString &nom, const QHttpServerRequest &request) { + Q_UNUSED(request) + + if (!nom_archive_est_valide(nom)) { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "nom d'archive invalide"; + return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::BadRequest)); + } + + QFile fichier(REPERTOIRE_ARCHIVES + "/" + nom); + if (fichier.exists() && !fichier.remove()) { + QJsonObject err; + err["status"] = "erreur"; + err["message"] = "suppression impossible"; + return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::InternalServerError)); + } + + QJsonObject reponse; + reponse["status"] = "ok"; + return withCors(QHttpServerResponse(QJsonDocument(reponse).toJson())); + }); + auto tcpServer = new QTcpServer(); if (!tcpServer->listen(QHostAddress::Any, 8080) || !server.bind(tcpServer)) { qCritical() << "Impossible de démarrer le serveur HTTP sur le port 8080."; diff --git a/main.cpp:Zone.Identifier b/main.cpp:Zone.Identifier index 35c9e5e..ae97c26 100644 Binary files a/main.cpp:Zone.Identifier and b/main.cpp:Zone.Identifier differ diff --git a/main_OLD.cpp b/main_OLD.cpp index 027d1aa..26d5b2d 100644 --- a/main_OLD.cpp +++ b/main_OLD.cpp @@ -15,6 +15,8 @@ #include #include +#include "_version.h" + // ============================================================================ // shipheart_api - Phase 4 : première tranche verticale, domaine "circuit" // ---------------------------------------------------------------------------- @@ -193,6 +195,7 @@ static void assurer_connexion_bdd() int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); + qInfo() << "shipheart_api version" << VERSION; // --- Connexion MariaDB locale --- QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); diff --git a/versions/shipheart_api_260930_081503 b/versions/shipheart_api_260930_081503 new file mode 100755 index 0000000..42b8c22 Binary files /dev/null and b/versions/shipheart_api_260930_081503 differ diff --git a/versions/shipheart_api_260930_081625 b/versions/shipheart_api_260930_081625 new file mode 100755 index 0000000..8f0949e Binary files /dev/null and b/versions/shipheart_api_260930_081625 differ diff --git a/versions/shipheart_api_260930_081832 b/versions/shipheart_api_260930_081832 new file mode 100755 index 0000000..6a87f46 Binary files /dev/null and b/versions/shipheart_api_260930_081832 differ