prompt_template/web/path_validator.py
Michael 9890763f0f refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory

Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt

Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00

79 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""
Pfad-Validierung für Template-Dateien.
Verhindert Path-Traversal-Angriffe und validiert, dass angeforderte
Pfade innerhalb des erlaubten Verzeichnisses liegen.
"""
import os
from typing import Optional
from urllib.parse import unquote, urlparse
class PathValidator:
"""Validiert und auflöst URL-Pfade zu Dateisystem-Pfaden."""
def __init__(self, root_dir: str, web_dir: str):
"""
Args:
root_dir: Projekt-Root-Verzeichnis (enthält templates/)
web_dir: web/-Verzeichnis (enthält templates.json)
"""
self.root_dir = root_dir
self.web_dir = web_dir
def resolve_template_path(self, path: str) -> Optional[str]:
"""
Resolve einen Request-Pfad zu einem Dateisystem-Pfad.
Gibt None zurück, falls der Pfad ungültig oder nicht autorisiert ist.
Beispiele:
/templates.json -> <web_dir>/templates.json
/templates/system/foo.json -> <root_dir>/templates/system/foo.json
"""
parsed = urlparse(path).path
path = parsed
# /templates.json ist ein Sonderfall - exakter Pfad im web/-Verzeichnis
if path == "/templates.json":
return os.path.join(self.web_dir, "templates.json")
# Nur /templates/* Pfade erlauben
if not path.startswith("/templates/"):
return None
# Extrahiere den relativen Pfad nach /templates/
rel_path = path[len("/templates/") :]
rel_path = unquote(rel_path)
# Ablehnen bei leeren Pfaden oder absoluten Pfaden
if not rel_path or os.path.isabs(rel_path):
return None
# Ablehnen bei '..' Sequenzen (vor der Normalisierung!)
if ".." in rel_path.split(os.sep):
return None
# Pfad normalisieren
normalized_rel = os.path.normpath(rel_path)
# Nach Normalisierung nochmal prüfen
if normalized_rel.startswith("..") or os.path.isabs(normalized_rel):
return None
# Vollständigen Pfad konstruieren
full_path = os.path.join(self.root_dir, "templates", normalized_rel)
# Explizite Prüfung, dass der Pfad innerhalb von ROOT_DIR/templates/ liegt
templates_base = os.path.abspath(os.path.join(self.root_dir, "templates"))
full_path_abs = os.path.abspath(full_path)
if (
not full_path_abs.startswith(templates_base + os.sep)
and full_path_abs != templates_base
):
return None
return full_path_abs