|
6 | 6 | import io |
7 | 7 | import ipaddress |
8 | 8 | import json |
9 | | -import mimetypes |
10 | | -import os |
11 | 9 | import socket |
12 | 10 | import sys |
13 | 11 | import tempfile |
|
40 | 38 | ) |
41 | 39 |
|
42 | 40 |
|
43 | | -mimetypes.add_type("application/manifest+json", ".webmanifest") |
44 | | -mimetypes.add_type("text/javascript", ".js") |
45 | | -mimetypes.add_type("text/css", ".css") |
46 | | - |
47 | 41 | LOCAL_ONLY_HOSTS = {"127.0.0.1", "::1", "localhost"} |
48 | 42 | WILDCARD_HOSTS = {"0.0.0.0", "::"} |
| 43 | +CONTENT_TYPES_BY_SUFFIX = { |
| 44 | + ".css": "text/css; charset=utf-8", |
| 45 | + ".html": "text/html; charset=utf-8", |
| 46 | + ".js": "text/javascript; charset=utf-8", |
| 47 | + ".json": "application/json; charset=utf-8", |
| 48 | + ".png": "image/png", |
| 49 | + ".webmanifest": "application/manifest+json; charset=utf-8", |
| 50 | +} |
49 | 51 |
|
50 | 52 |
|
51 | 53 | def _clean_filename(filename: Any, source_kind: str) -> str: |
@@ -172,15 +174,31 @@ def analyze_payload(payload: dict[str, Any]) -> dict[str, Any]: |
172 | 174 |
|
173 | 175 |
|
174 | 176 | def _resolve_under(root: Path, relative_path: str) -> Path | None: |
175 | | - root = root.resolve() |
176 | | - target = (root / relative_path).resolve() |
177 | | - if target == root or root not in target.parents: |
| 177 | + resolved_root = root.resolve() |
| 178 | + normalized_path = relative_path.replace("\\", "/").strip() |
| 179 | + if not normalized_path: |
| 180 | + return None |
| 181 | + relative = PurePosixPath(normalized_path) |
| 182 | + if relative.is_absolute(): |
| 183 | + return None |
| 184 | + if any(part in {"", ".", ".."} for part in relative.parts): |
| 185 | + return None |
| 186 | + target = root.joinpath(*relative.parts).resolve() |
| 187 | + try: |
| 188 | + target.relative_to(resolved_root) |
| 189 | + except ValueError: |
| 190 | + return None |
| 191 | + if target == resolved_root: |
178 | 192 | return None |
179 | 193 | if not target.is_file(): |
180 | 194 | return None |
181 | 195 | return target |
182 | 196 |
|
183 | 197 |
|
| 198 | +def _content_type_for_path(target: Path) -> str: |
| 199 | + return CONTENT_TYPES_BY_SUFFIX.get(target.suffix.lower(), "application/octet-stream") |
| 200 | + |
| 201 | + |
184 | 202 | def _discover_candidate_ipv4_addresses() -> list[str]: |
185 | 203 | addresses: set[str] = set() |
186 | 204 |
|
@@ -334,7 +352,7 @@ def _send_file_or_404(self, target: Path | None) -> None: |
334 | 352 | self._send_json({"ok": False, "error": "Datei nicht gefunden."}, status=404) |
335 | 353 | return |
336 | 354 |
|
337 | | - content_type = mimetypes.guess_type(str(target))[0] or "application/octet-stream" |
| 355 | + content_type = _content_type_for_path(target) |
338 | 356 | try: |
339 | 357 | body = target.read_bytes() |
340 | 358 | except OSError as exc: |
|
0 commit comments