"""Environment Jinja2 partagé (A10 : autoescape activé partout). Une seule instance au lieu de 29 `Environment(loader=FileSystemLoader(...))` sans autoescape — 326 interpolations `{{ … }}` étaient servies crues et tous les `|safe` du codebase étaient des no-op. """ from __future__ import annotations import pathlib from contextvars import ContextVar from jinja2 import Environment, FileSystemLoader, select_autoescape # A20 : nonce de script par requête, posé par le middleware CSP, lu par les # templates via `{{ csp_nonce() }}` (vide hors requête — pas de header CSP # dans ce cas, donc rien n'est bloqué). CSP_NONCE: ContextVar[str] = ContextVar("csp_nonce", default="") # A43 : jeton CSRF rendu côté serveur dans `hx-headers` (base.html) — posé # par le middleware CSRF AVANT call_next, lu via `{{ csrf_token() }}` # (vide = cookie absent sur cette requête, htmx:configRequest re-lit le # cookie au moment de l'appel → jamais de « __CSRF_PLACEHOLDER__ » servi). CSRF_TOKEN: ContextVar[str] = ContextVar("csrf_token", default="") ENV = Environment( loader=FileSystemLoader("app/templates"), autoescape=select_autoescape(["html"]), ) # A40 : version des assets statiques première main, lue une seule fois (le # fichier VERSION fait foi ; repli "dev" si l'image ne le contient pas). try: ASSET_VERSION = (pathlib.Path(__file__).resolve().parent.parent / "VERSION").read_text( encoding="utf-8" ).strip() except OSError: # pragma: no cover ASSET_VERSION = "dev" ENV.globals["asset_version"] = ASSET_VERSION ENV.globals["csp_nonce"] = lambda: CSP_NONCE.get() ENV.globals["csrf_token"] = lambda: CSRF_TOKEN.get()