完善观众端目录与回放状态降级
This commit is contained in:
1 parent
3060828774
commit
a8067db922
7 files changed
+196
-9
No files matched your search
@@ -2,6 +2,7 @@ import json
|
||||
import secrets
|
||||
import asyncio
|
||||
import time
|
||||
import ntpath
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, Request
|
||||
@@ -62,7 +63,8 @@ def _media_path(filename: str | None) -> Path | None:
|
||||
return None
|
||||
root = Path(settings.replay_media_dir).resolve()
|
||||
supplied = Path(filename)
|
||||
if supplied.is_absolute() or supplied.drive or ".." in supplied.parts:
|
||||
windows_drive, _ = ntpath.splitdrive(filename)
|
||||
if supplied.is_absolute() or supplied.drive or windows_drive or ".." in supplied.parts:
|
||||
raise HTTPException(status_code=422, detail="回放文件不在允许的媒体目录中")
|
||||
candidate = (root / supplied).resolve()
|
||||
if candidate != root and root not in candidate.parents:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path, PureWindowsPath
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from routes.streams import _media_path, _normalize_chapters, _status_payload
|
||||
|
||||
|
||||
class ReplayWorkflowTest(unittest.TestCase):
|
||||
def test_media_path_accepts_nested_file_in_media_root(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
replay = Path(directory, "events", "session.mp4")
|
||||
replay.parent.mkdir()
|
||||
replay.write_bytes(b"video")
|
||||
with patch("routes.streams.settings.replay_media_dir", directory):
|
||||
self.assertEqual(_media_path("events/session.mp4"), replay.resolve())
|
||||
|
||||
def test_media_path_rejects_parent_traversal_and_absolute_paths(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
with patch("routes.streams.settings.replay_media_dir", directory):
|
||||
for unsafe in ("../secret.mp4", "/tmp/secret.mp4"):
|
||||
with self.subTest(unsafe=unsafe), self.assertRaises(HTTPException):
|
||||
_media_path(unsafe)
|
||||
|
||||
def test_media_path_rejects_windows_absolute_path_on_any_platform(self):
|
||||
value = str(PureWindowsPath("C:/media/replay.mp4"))
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
with patch("routes.streams.settings.replay_media_dir", directory):
|
||||
with self.assertRaises(HTTPException):
|
||||
_media_path(value)
|
||||
|
||||
def test_chapters_are_trimmed_validated_and_sorted(self):
|
||||
chapters = _normalize_chapters([
|
||||
{"title": " Closing ", "time": "62.5"},
|
||||
{"title": "Opening", "time": 0},
|
||||
])
|
||||
self.assertEqual(chapters, [
|
||||
{"title": "Opening", "time": 0.0},
|
||||
{"title": "Closing", "time": 62.5},
|
||||
])
|
||||
|
||||
def test_chapters_reject_invalid_title_and_time(self):
|
||||
for chapters in ([{"title": "", "time": 1}], [{"title": "Bad", "time": -1}]):
|
||||
with self.subTest(chapters=chapters), self.assertRaises(HTTPException):
|
||||
_normalize_chapters(chapters)
|
||||
|
||||
def test_status_payload_exposes_pending_live_and_ended_updates(self):
|
||||
stream = SimpleNamespace(id=7, replay_file=None, chapters_json="[]")
|
||||
for status in ("pending", "live", "ended"):
|
||||
stream.status = SimpleNamespace(value=status)
|
||||
self.assertEqual(_status_payload(stream)["status"], status)
|
||||
|
||||
stream.replay_file = "session.mp4"
|
||||
stream.chapters_json = json.dumps([{"title": "Intro", "time": 0}])
|
||||
payload = _status_payload(stream)
|
||||
self.assertEqual(payload["replay_url"], "/api/streams/7/replay")
|
||||
self.assertEqual(payload["chapters"], [{"title": "Intro", "time": 0}])
|
||||
Reference in new issue
Block a user