"""Input-Validators für die FastAPI-Endpoints. Eigenes Modul, damit die Unit-Tests die Helper direkt importieren können ohne den vollen FastAPI-/slowapi-Stack aus app.main mitzuziehen. Issue #57 Befunde #3 (Path-Traversal via drucksache) und #7 (Search-Query-DoS). """ from __future__ import annotations import re from fastapi import HTTPException # Drucksache-Format: erlaubt sind alle bisher beobachteten Schreibweisen: # "8/6390", "18/12345", "8/6390(neu)", "23/3700-A", "21/754S" (HB: S=Stadtbürgerschaft). # Restriktiv genug für Path-Traversal-Schutz (#57 Befund #3). _DRUCKSACHE_RE = re.compile(r"^\d{1,3}/\d{1,7}[A-Z]?([-(].{1,20})?$") def validate_drucksache(drucksache: str) -> str: """Lehnt jede Drucksache-ID ab, die nicht dem erwarteten Format entspricht. Gemeinsamer Validation-Funnel für alle Endpoints, die ``drucksache`` als Parameter nehmen. Issue #57 Befund #3. """ if not drucksache or not _DRUCKSACHE_RE.match(drucksache): raise HTTPException( status_code=400, detail=f"Ungültige Drucksache-ID: {drucksache!r}", ) return drucksache # Längen-Limit für freie Search-Queries — verhindert, dass jemand mit einem # 10-MB-Query die SQL-LIKE-Pattern oder den HTTP-Adapter aushungert. # Issue #57 Befund #7. MAX_SEARCH_QUERY_LEN = 200 def validate_search_query(q: str) -> str: if q is None: raise HTTPException(status_code=400, detail="Suchbegriff fehlt") if len(q) > MAX_SEARCH_QUERY_LEN: raise HTTPException( status_code=400, detail=f"Suchbegriff zu lang (max {MAX_SEARCH_QUERY_LEN} Zeichen, war {len(q)})", ) return q