Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sequa::host_web — service HTTP de l'interface et ouverture du navigateur
Brique web du Studio host (studio/host/web/). Elle sert l'interface construite
(studio/ui/dist) en HTTP local et ouvre le navigateur de l'auteur. Références :
ADR 0006 et
spécification Studio - Core 0.2 §7.
Rôle
UiServer: serveur HTTP statique en lecture seule (GET et HEAD) sur127.0.0.1:9080par défaut."/"sertindex.html, la query est ignorée pour trouver le fichier.urls.hpp: construction de l'URL de l'interface (?core=<url WebSocket encodée>) et de l'URL WebSocket du Core (ws://host:port/?token=…), encodage pourcentage RFC 3986.browser.hpp: validation stricte des URL (http/https uniquement) et ouverture du navigateur (ShellExecuteW sous Windows,opensous macOS,xdg-opensous Linux).
API publique
struct UiServerOptions {
std::string host = "127.0.0.1";
std::uint16_t port = 9080;
std::filesystem::path root; // studio/ui/dist
std::function<void(std::string_view)> log; // une ligne par requête
};
class UiServer {
public:
explicit UiServer(UiServerOptions options);
~UiServer(); // stop() propre
std::string start(); // "" si OK, sinon la raison
void stop();
bool running() const noexcept;
const UiServerOptions& options() const noexcept;
};
std::string percent_encode(std::string_view value);
std::string build_ui_url(std::string_view ui_base_url, std::string_view core_ws_url);
std::string build_core_ws_url(std::string_view host, std::uint16_t port, std::string_view token);
bool is_openable_url(std::string_view url);
bool open_browser(std::string_view url);
Sécurité des chemins
request->uri d'IXWebSocket n'est pas décodée par la bibliothèque : c'est UiServer
qui décode le pourcentage une seule fois, puis refuse (404, jamais 500) :
- octet nul (
%00), antislash, segments.ou.., segments vides (//), chemin absolu, lettre de lecteur (C:), segments commençant par.(fichiers cachés), double décodage (%252elaisse un triplet après le premier décodage).
Le fichier est ensuite résolu par std::filesystem::weakly_canonical et doit rester
dans la racine canonique (protection contre les liens symboliques) et être un fichier
régulier. Ce contrôle est plus strict que le rappel par défaut d'IXWebSocket, qui se
contente de rogner les .. et ne décode jamais l'URI.
Sous Windows, start() vérifie en plus le port de façon exclusive
(SO_EXCLUSIVEADDRUSE) avant d'écouter : IXWebSocket active SO_REUSEADDR, qui
autoriserait sinon deux processus à écouter le même port. La technique est reprise de
core/src/server/server.cpp.
En-têtes renvoyés systématiquement : Content-Type, Cache-Control: no-store,
X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer (et Allow sur 405).
Tests
sequa_host_web_tests (doctest) : types MIME, HEAD, 405, 404, query ignorée, journal,
port occupé, racine absente, tests purs des URL, et dix tentatives d'évasion qui doivent
toutes renvoyer 404 sans jamais livrer le fichier secret placé hors de la racine.
Licences
- Ce code : MPL 2.0 (voir l'en-tête SPDX de chaque fichier).
- IXWebSocket : BSD-3-Clause.