Files
olivv-cs-ComfyUI-FunPack/core/serve.py
T
digital-garbage dd70d23e57 core: cache fonts, keep everything else uncached
A font filename carries family, weight range and subset, so its bytes never
change -- re-fetching 133 KB on every reload to stay honest about a file that
cannot go stale is the wrong trade. Code stays no-store: the editor is edited
while it is running.

*.woff2 declared binary so autocrlf can never rewrite one.
2026-08-28 14:52:22 +03:00

95 lines
3.1 KiB
Python

"""Static serving for the app and for module JS.
The resolution is a pure function returning a `Served` value, so the allowlist and
the traversal guard are testable without aiohttp or a running ComfyUI.
"""
import mimetypes
from dataclasses import dataclass, field
from pathlib import Path
from typing import FrozenSet
# The editor iterates fast; a cached stale module is worse than a re-read.
NO_STORE = {"Cache-Control": "no-store, max-age=0"}
# Fonts are the exception: the bytes for a given filename never change (family,
# weight range and subset are all in the name), and they are by far the largest
# thing served. Re-fetching 133 KB on every reload to stay honest about a file
# that cannot go stale is a bad trade. Replacing a face means a new filename.
IMMUTABLE = {"Cache-Control": "public, max-age=31536000, immutable"}
IMMUTABLE_SUFFIXES = frozenset({".woff2"})
# Some platforms guess text/plain for .js. With ES modules that is fatal — the
# browser refuses the module outright — so every type we care about is explicit.
EXPLICIT_TYPES = {
".js": "text/javascript",
".css": "text/css",
".html": "text/html",
".svg": "image/svg+xml",
".woff2": "font/woff2",
".json": "application/json",
}
@dataclass(frozen=True)
class Served:
status: int
body: bytes = b""
content_type: str = "text/plain"
headers: dict = field(default_factory=lambda: dict(NO_STORE))
def headers_for(path: Path) -> dict:
if path.suffix.lower() in IMMUTABLE_SUFFIXES:
return dict(IMMUTABLE)
return dict(NO_STORE)
def content_type_for(path: Path) -> str:
suffix = path.suffix.lower()
if suffix in EXPLICIT_TYPES:
return EXPLICIT_TYPES[suffix]
return mimetypes.guess_type(str(path))[0] or "application/octet-stream"
def resolve(root: Path, rel: str, allowed: FrozenSet[str]) -> Path:
"""Path for `rel` under `root`, or raise.
ValueError -> the request tried to leave the root (403)
LookupError -> nothing to serve here (404)
"""
root = root.resolve()
cleaned = (rel or "").lstrip("/")
if "\x00" in cleaned:
raise ValueError("null byte in path")
try:
target = (root / cleaned).resolve()
except (OSError, ValueError) as exc: # malformed path, symlink loop
raise ValueError("unresolvable path") from exc
# The guard is on the RESOLVED path, so `..` and symlinks are both covered.
if target != root and root not in target.parents:
raise ValueError("outside root")
if target.is_dir():
if ".html" not in allowed:
raise LookupError("directory")
target = target / "index.html"
if target.suffix.lower() not in allowed:
raise LookupError("extension not allowed")
if not target.is_file():
raise LookupError("not a file")
return target
def serve(root: Path, rel: str, allowed: FrozenSet[str]) -> Served:
try:
target = resolve(root, rel, allowed)
except ValueError:
return Served(403)
except LookupError:
return Served(404)
return Served(200, target.read_bytes(), content_type_for(target),
headers_for(target))