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

1"""Shared asset lookup for current and legacy static URLs.""" 

2 

3import re 

4from functools import lru_cache 

5from pathlib import Path 

6 

7from fastapi.responses import FileResponse, Response 

8 

9from ..security.path_validator import PathValidator 

10 

11 

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" 

15 

16 

17@lru_cache(maxsize=1) 

18def _combined_theme_css() -> str: 

19 from .themes import theme_registry 

20 

21 return theme_registry.get_combined_css() 

22 

23 

24def static_file_response(path: str) -> FileResponse | Response | None: 

25 """Serve a contained asset, preferring Vite output over source files. 

26 

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). 

31 

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 ) 

45 

46 from .fastapi_app import STATIC_DIR 

47 

48 try: 

49 root = Path(STATIC_DIR).resolve(strict=True) 

50 except (ValueError, OSError, RuntimeError): 

51 return None 

52 

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))) 

58 

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 

72 

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 ) 

86 

87 return None