#!/usr/bin/env python3 """ HTTP-Handler für den Template-Server. Behandelt GET- und PUT-Anfragen für Templates und das Frontend. """ import http.server import logging from pathlib import Path from urllib.parse import urlparse # Support für direkte Ausführung und Package-Import try: from .path_validator import PathValidator from .file_ops import read_file_binary, write_file, directory_exists from .content_types import get_content_type except ImportError: from path_validator import PathValidator from file_ops import read_file_binary, write_file, directory_exists from content_types import get_content_type logger = logging.getLogger(__name__) MAX_BODY_SIZE = 10 * 1024 * 1024 # 10 MB class Handler(http.server.SimpleHTTPRequestHandler): """HTTP-Handler für Template-Anfragen.""" validator = None # Wird von serve.py gesetzt directory = None # Wird von serve.py gesetzt def __init__(self, *args, directory=None, **kwargs): super().__init__(*args, directory=directory or self.directory, **kwargs) def send_response(self, code, message=None): if self.request_version != 'HTTP/0.9': if message is None: if code in self.responses: message = self.responses[code][0] else: message = '' if not hasattr(self, '_headers_buffer'): self._headers_buffer = [] self._headers_buffer.append(("%s %d %s\r\n" % (self.protocol_version, code, message)).encode('latin-1', 'strict')) # Write Server and Date headers (mimicking base class behavior) self.send_header('Server', self.version_string()) self.send_header('Date', self.date_time_string()) # Security headers self.send_header('X-Content-Type-Options', 'nosniff') self.send_header('X-Frame-Options', 'DENY') self.send_header('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; frame-ancestors 'none'") def do_PUT(self): """Speichert eine Template-Datei.""" file_path = self.validator.resolve_template_path(urlparse(self.path).path) if file_path is None: self.send_error(403, "Forbidden: Invalid path") return # Verzeichnis prüfen - muss existieren file_dir = Path(file_path).parent if not directory_exists(str(file_dir)): self.send_error(404, "Directory not found") return # Content-Type prüfen content_type = self.headers.get("Content-Type", "") if "text/plain" not in content_type and not content_type.startswith("text/"): self.send_error(400, "Unsupported content type") return # Inhalt lesen und speichern content_length = int(self.headers.get("Content-Length", 0)) if content_length > MAX_BODY_SIZE: self.send_error(413, "Request body too large") return if content_length <= 0: self.send_error(400, "No content provided") return try: file_content = self.rfile.read(content_length) if not write_file(file_path, file_content): raise Exception("write_file returned False") response_content_type = get_content_type(file_path) self.send_response(200) self.send_header("Content-type", response_content_type) self.end_headers() self.wfile.write(b"File saved successfully") except Exception as e: logger.error("Failed to save file: %s", e) self.send_error(500, "Failed to save file") def do_GET(self): """Liefert Dateien aus.""" parsed_path = urlparse(self.path).path # Für Root-Pfad: index.html servieren if parsed_path == "/" or parsed_path == "/index.html": self.path = "/index.html" return super().do_GET() # Anfragen für /templates.json oder /templates/* umleiten file_path = self.validator.resolve_template_path(urlparse(self.path).path) if file_path is not None: content_type = get_content_type(file_path) file_content = read_file_binary(file_path) if file_content is None: self.send_error(404, "File not found") return self.send_response(200) self.send_header("Content-type", content_type) self.end_headers() self.wfile.write(file_content) return return super().do_GET()