| 75 | |
| 76 | |
| 77 | def resolve_icon_file(static_root: Path, rel_file: str) -> Path: |
| 78 | rel = rel_file.replace("\\", "/").lstrip("/") |
| 79 | if not rel or ".." in rel.split("/"): |
| 80 | raise ValueError("invalid icon path") |
| 81 | icons_root = (static_root / "icons").resolve() |
| 82 | full = (icons_root / rel).resolve() |
| 83 | try: |
| 84 | full.relative_to(icons_root) |
| 85 | except ValueError as e: |
| 86 | raise ValueError("icon path escapes icons directory") from e |
| 87 | return full |
| 88 | |
| 89 | |
| 90 | def load_rgba_resized(path: Path, tw: int, th: int) -> tuple[int, int, bytes]: |