basyx commited on
Commit
fece13c
·
verified ·
1 Parent(s): ae0d834

Sanitize cross-platform media filenames

Browse files
Files changed (1) hide show
  1. 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
- ".aac",
14
- ".aiff",
15
- ".apng",
16
- ".avi",
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
- name = Path(filename or fallback).name.replace("\x00", "")
59
- if not name or name in {".", ".."}:
 
 
 
 
 
 
 
 
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
- ("audio/", "video/", "image/", "text/")
77
- ) or normalized_mime in {
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: