Spaces:
Running
Running
Sanitize cross-platform media filenames
Browse files- app/services/validator.py +19 -51
app/services/validator.py
CHANGED
|
@@ -9,44 +9,11 @@ from app.core.config import Settings
|
|
| 9 |
from app.core.exceptions import InputError
|
| 10 |
|
| 11 |
MEDIA_EXTENSIONS = {
|
| 12 |
-
".3gp",
|
| 13 |
-
".
|
| 14 |
-
".
|
| 15 |
-
".
|
| 16 |
-
".
|
| 17 |
-
".bin",
|
| 18 |
-
".bmp",
|
| 19 |
-
".flac",
|
| 20 |
-
".gif",
|
| 21 |
-
".heic",
|
| 22 |
-
".jpeg",
|
| 23 |
-
".jpg",
|
| 24 |
-
".m4a",
|
| 25 |
-
".m4v",
|
| 26 |
-
".mkv",
|
| 27 |
-
".m3u",
|
| 28 |
-
".m3u8",
|
| 29 |
-
".mov",
|
| 30 |
-
".mp3",
|
| 31 |
-
".mp4",
|
| 32 |
-
".mpeg",
|
| 33 |
-
".mpg",
|
| 34 |
-
".oga",
|
| 35 |
-
".ogg",
|
| 36 |
-
".opus",
|
| 37 |
-
".png",
|
| 38 |
-
".svg",
|
| 39 |
-
".tif",
|
| 40 |
-
".tiff",
|
| 41 |
-
".ts",
|
| 42 |
-
".vtt",
|
| 43 |
-
".wav",
|
| 44 |
-
".webm",
|
| 45 |
-
".webp",
|
| 46 |
-
".wmv",
|
| 47 |
-
".srt",
|
| 48 |
-
".ass",
|
| 49 |
-
".ssa",
|
| 50 |
}
|
| 51 |
|
| 52 |
|
|
@@ -55,8 +22,16 @@ class MediaValidator:
|
|
| 55 |
self.settings = settings
|
| 56 |
|
| 57 |
def safe_filename(self, filename: str | None, fallback: str = "input.bin") -> str:
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
raise InputError("Invalid filename")
|
| 61 |
return name[:240]
|
| 62 |
|
|
@@ -72,22 +47,15 @@ class MediaValidator:
|
|
| 72 |
if suffix and suffix not in MEDIA_EXTENSIONS:
|
| 73 |
raise InputError("Unsupported media file extension", details={"extension": suffix})
|
| 74 |
normalized_mime = mime_type.split(";", 1)[0].lower()
|
| 75 |
-
allowed = normalized_mime.startswith(
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
"application/octet-stream",
|
| 79 |
-
"application/x-subrip",
|
| 80 |
-
"application/vnd.apple.mpegurl",
|
| 81 |
-
"application/x-mpegurl",
|
| 82 |
}
|
| 83 |
if normalized_mime and not allowed:
|
| 84 |
raise InputError("Unsupported media MIME type", details={"mime_type": mime_type})
|
| 85 |
|
| 86 |
def validate_probe(self, metadata: dict[str, Any]) -> None:
|
| 87 |
-
streams = [
|
| 88 |
-
*(metadata.get("video_streams") or []),
|
| 89 |
-
*(metadata.get("audio_streams") or []),
|
| 90 |
-
]
|
| 91 |
if not streams:
|
| 92 |
raise InputError("The input has no decodable video or audio stream")
|
| 93 |
for stream in streams:
|
|
|
|
| 9 |
from app.core.exceptions import InputError
|
| 10 |
|
| 11 |
MEDIA_EXTENSIONS = {
|
| 12 |
+
".3gp", ".aac", ".aiff", ".apng", ".avi", ".bin", ".bmp", ".flac",
|
| 13 |
+
".gif", ".heic", ".jpeg", ".jpg", ".m4a", ".m4v", ".mkv", ".m3u",
|
| 14 |
+
".m3u8", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".oga", ".ogg",
|
| 15 |
+
".opus", ".png", ".svg", ".tif", ".tiff", ".ts", ".vtt", ".wav",
|
| 16 |
+
".webm", ".webp", ".wmv", ".srt", ".ass", ".ssa",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
}
|
| 18 |
|
| 19 |
|
|
|
|
| 22 |
self.settings = settings
|
| 23 |
|
| 24 |
def safe_filename(self, filename: str | None, fallback: str = "input.bin") -> str:
|
| 25 |
+
# Treat both POSIX and Windows separators as path delimiters regardless
|
| 26 |
+
# of the server OS; never allow a client path to become a stored name.
|
| 27 |
+
raw = str(filename or fallback).replace("\\", "/")
|
| 28 |
+
name = raw.rsplit("/", 1)[-1].replace("\x00", "")
|
| 29 |
+
if (
|
| 30 |
+
not name
|
| 31 |
+
or name in {".", ".."}
|
| 32 |
+
or any(ord(char) < 32 or ord(char) == 127 for char in name)
|
| 33 |
+
or '"' in name
|
| 34 |
+
):
|
| 35 |
raise InputError("Invalid filename")
|
| 36 |
return name[:240]
|
| 37 |
|
|
|
|
| 47 |
if suffix and suffix not in MEDIA_EXTENSIONS:
|
| 48 |
raise InputError("Unsupported media file extension", details={"extension": suffix})
|
| 49 |
normalized_mime = mime_type.split(";", 1)[0].lower()
|
| 50 |
+
allowed = normalized_mime.startswith(("audio/", "video/", "image/", "text/")) or normalized_mime in {
|
| 51 |
+
"application/octet-stream", "application/x-subrip",
|
| 52 |
+
"application/vnd.apple.mpegurl", "application/x-mpegurl",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
if normalized_mime and not allowed:
|
| 55 |
raise InputError("Unsupported media MIME type", details={"mime_type": mime_type})
|
| 56 |
|
| 57 |
def validate_probe(self, metadata: dict[str, Any]) -> None:
|
| 58 |
+
streams = [*(metadata.get("video_streams") or []), *(metadata.get("audio_streams") or [])]
|
|
|
|
|
|
|
|
|
|
| 59 |
if not streams:
|
| 60 |
raise InputError("The input has no decodable video or audio stream")
|
| 61 |
for stream in streams:
|