Files

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) sur 127.0.0.1:9080 par défaut. "/" sert index.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, open sous macOS, xdg-open sous 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 (%252e laisse 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.