feat: Download-Knopf fuer fertige Rips — Dateien direkt im Browser statt scp
Ampel / ampel (push) Successful in 29s

- GET /jobs/{id}/files: Dateiliste aus job.output_path (Name + Groesse)
- GET /jobs/{id}/files/{name}: FileResponse-Stream; Validierung strikt —
  output_path muss unter /app/media liegen, nackter Dateiname (kein
  Slash/.., kein Dotfile), realpath-Check gegen Symlink-Ausbrueche.
  Mit Test (test_dateiname_validierung_blockt_pfad_tricks).
- UI: 'Download'-Knopf in der Aktion-Spalte bei fertigen Jobs; die
  Dateiliste mit Groessen + Download-Links lebt im Job-Detail-Popup
  (ein Dropdown wuerde im overflow-x-auto-Tabellencontainer clippen).
- nginx: proxy_buffering off + proxy_read_timeout 3600s waren fuer SSE
  schon gesetzt — grosse Downloads brauchen keine Aenderung.

Wunsch aus der Uebernahme-Session (Commander-Sammelliste 24.07.).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-24 09:01:38 +02:00
parent 5d9be4d046
commit ab75134931
6 changed files with 149 additions and 4 deletions
+12
View File
@@ -22,6 +22,18 @@ def test_main_importierbar_und_routen_verdrahtet():
assert pfad in routen, f"Route {pfad} fehlt"
def test_dateiname_validierung_blockt_pfad_tricks():
"""Download-Endpoint: nur nackte Dateinamen — kein .., kein Slash, kein Dotfile."""
from main import _sicherer_dateiname
assert _sicherer_dateiname("film.mkv") is True
assert _sicherer_dateiname("../../etc/passwd") is False
assert _sicherer_dateiname("a/b.mkv") is False
assert _sicherer_dateiname("a\\b.mkv") is False
assert _sicherer_dateiname(".versteckt") is False
assert _sicherer_dateiname("") is False
def test_worker_task_name_passt_zum_celery_client():
"""API schickt an 'worker.tasks.rip_disc' — der Name ist Vertrag mit dem Worker."""
import inspect