md2star.gui_server module

md2star local GUI server, Overleaf-style Markdown editor with live PDF preview.

Launches a localhost-only HTTP server that fronts md2star’s conversion engine: the browser sends Markdown + a small JSON options blob; the server invokes convert(fmt, ...) from md2star.pipeline against a temp file and streams the resulting bytes back. The frontend renders the PDF result inline via PDF.js (no DOCX/PPTX rendering in the browser; those formats are simply offered as a download).

Endpoints

  • GET  / → index.html (shipped under md2star/data/gui/).

  • GET  /app.js → the frontend ES module.

  • GET  /favicon.svg → tiny inline SVG, no network calls.

  • POST /render → run the converter, stream back the bytes.

  • POST /shutdown → clean process exit (used by the in-page

    “Quit server” button so users do not have to find the terminal).

The server binds to 127.0.0.1 only (no LAN exposure) and uses ThreadingHTTPServer so a slow /render does not block fast /preview reads. There is no auth and no sandbox, it is intended to run on the same machine as the user invoking it, just like Jupyter or Vite’s dev server.

Security note

Two different questions, and both have to be answered.

What may a request touch? Every /fs/* path is funnelled through _safe_within_root(), which rejects .. segments, absolute paths and symlink escapes, so a request can never read or write outside the single folder root the user opened.

Who is asking? Binding to loopback keeps the port off the network, but it does not keep it away from the user’s own browser: any page they have open can post to 127.0.0.1:8765. A cross-origin POST with a CORS-safelisted content type is a simple request, so there is no preflight to refuse, and an attacker never has to read a reply — /fs/open with {"path": "~"} followed by /fs/delete is pure side effect. So every request first passes _Handler._caller_is_this_page(), which checks Origin (cross-site calls) and Host (DNS rebinding) before a handler runs.

Both halves are unit-tested in tests/test_gui_security.py, the second with a live server and the exact headers a browser would send.

Author

Warith Harchaoui

md2star.gui_server.LINT_DEADLINE_S = 20.0

Every string in index.html this server will translate, listed rather than discovered. A declared list is reviewable, is what the .po is extracted from, and cannot quietly rewrite a class name or a bit of JavaScript the way a clever scan eventually would. Format names, file names and the product’s own name are deliberately absent: they are the same in every language. tests/test_gui_i18n.py checks each one still occurs in the page, so the list cannot rot while the interface moves on. How long the GUI waits for the optional AI lint before answering without it. The CLI has no such limit on purpose: a conversion is not interactive, and a model that takes two minutes to load is still worth waiting for there. A browser is not — found when a resident vision model had to be swapped out and a lint request blocked for over a minute with the editor spinning.

md2star.gui_server.localise(html, accept_language)[source]

Serve the page in the visitor’s language, when there is a catalogue.

A browser’s Accept-Language is a person stating a preference, so unlike the CLI — which answers to MD2STAR_LANG and nothing else, see md2star.i18n — the GUI honours it. Strings with no translation keep their English source, so a partial catalogue gives a partly translated interface rather than a broken one, and the lang attribute is updated with them so a screen reader pronounces what it is actually reading.

Parameters:
  • html (str)

  • accept_language (str | None)

Return type:

str

md2star.gui_server.serve(*, port=8765, no_browser=False, bind='127.0.0.1')[source]

Serve the editor until Ctrl-C. The body of md2star gui.

The flags arrive parsed: click owns this package’s only command line (see md2star.cli), so there is no argv to re-read here.

Parameters:
  • port (int, optional) – Preferred port. On a loopback bind an in-use port falls back to the next free one; on a non-loopback bind the request is honoured as given, because an operator exposing a port on purpose means that port.

  • no_browser (bool, optional) – Do not auto-open a browser. The URL is still printed.

  • bind (str, optional) – Bind address. 0.0.0.0 exposes the server to the LAN with no authentication, which is why it prints the banner below.

Returns:

0 once the server has been stopped cleanly.

Return type:

int