Coverage for src/local_deep_research/web/static_files.py: 92%
38 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
1"""Shared asset lookup for current and legacy static URLs."""
3import re
4from functools import lru_cache
5from pathlib import Path
7from fastapi.responses import FileResponse, Response
9from ..security.path_validator import PathValidator
12_HASHED_FILENAME_RE = re.compile(r"\.[A-Za-z0-9_-]{8,}\.")
13_REVALIDATE = "public, max-age=0, must-revalidate"
14_IMMUTABLE = "public, max-age=31536000, immutable"
17@lru_cache(maxsize=1)
18def _combined_theme_css() -> str:
19 from .themes import theme_registry
21 return theme_registry.get_combined_css()
24def static_file_response(path: str) -> FileResponse | Response | None:
25 """Serve a contained asset, preferring Vite output over source files.
27 Except for CSS composed from bundled themes, the root is always the
28 app's ``STATIC_DIR``, read per call. There is deliberately no directory
29 parameter, so no caller can point this helper at another tree (e.g. the
30 shared research-outputs directory).
32 Explicit ``dist/`` URLs and implicit build paths use the same cache
33 policy. Invalid paths, failed lookups and symlink escapes are misses;
34 callers retain their route's existing 404 response format.
35 """
36 # A clean wheel does not contain this generated file, and an installed
37 # package may be read-only for the service user. Serve it from the bundled
38 # theme sources instead of relying on the best-effort startup write.
39 if path == "css/themes.css":
40 return Response(
41 _combined_theme_css(),
42 media_type="text/css",
43 headers={"Cache-Control": _REVALIDATE},
44 )
46 from .fastapi_app import STATIC_DIR
48 try:
49 root = Path(STATIC_DIR).resolve(strict=True)
50 except (ValueError, OSError, RuntimeError):
51 return None
53 dist = root / "dist"
54 lookups = []
55 if path.startswith("dist/"):
56 lookups.append((dist, path[len("dist/") :], True))
57 lookups.extend(((dist, path, True), (root, path, False)))
59 for base, relative_path, is_dist in lookups:
60 try:
61 validated = PathValidator.validate_safe_path(
62 relative_path, base, allow_absolute=False
63 )
64 if validated is None: 64 ↛ 65line 64 didn't jump to line 65 because the condition on line 64 was never true
65 continue
66 candidate = validated.resolve(strict=True)
67 if not candidate.is_relative_to(root) or not candidate.is_file():
68 continue
69 except (ValueError, OSError, RuntimeError):
70 # Includes NUL bytes, overlong names, missing files and loops.
71 continue
73 cache_control = (
74 _IMMUTABLE
75 if is_dist and _HASHED_FILENAME_RE.search(relative_path)
76 else _REVALIDATE
77 )
78 return FileResponse(
79 str(candidate),
80 # Infer the asset type from its public name while opening only
81 # the resolved, contained target. Static assets render inline.
82 filename=validated.name,
83 content_disposition_type="inline",
84 headers={"Cache-Control": cache_control},
85 )
87 return None