#!/usr/bin/env python3 """ Minimaler Entwicklungs-Server für die Prompt Templates Webansicht. Startet auf Port 8080 und dient die statischen Dateien aus. """ import http.server import socketserver import os import socket import json DIRECTORY = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.abspath(os.path.join(DIRECTORY, '..')) def find_free_port(start_port=9000): """Finde einen freien Port ab start_port""" port = start_port while port < 10000: try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(('', port)) return port except OSError: port += 1 return None class Handler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=DIRECTORY, **kwargs) def _validate_template_path(self, path): """Validiert, dass der Pfad auf Schritte Beschränkt ist und innerhalb von ROOT_DIR/templates/ oder DIRECTORY liegt.""" # /templates.json ist ein Sonderfall - exakter Pfad im web/-Verzeichnis if path == '/templates.json': return os.path.join(DIRECTORY, 'templates.json') # Nur /templates/* Pfade erlauben if not path.startswith('/templates/'): return None # Extrahiere den relativen Pfad nach /templates/ rel_path = path[len('/templates/'):] # 'system/test.json' oder '../etc/passwd' # 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(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(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 def do_PUT(self): # Nur PUT auf /templates/* Pfade erlauben file_path = self._validate_template_path(self.path) if file_path is None: self.send_error(403, "Forbidden: Invalid path") return # Verzeichnis prüfen - muss existieren file_dir = os.path.dirname(file_path) if not os.path.exists(file_dir) or not os.path.isdir(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 <= 0: self.send_error(400, "No content provided") return try: file_content = self.rfile.read(content_length) # Datei schreiben with open(file_path, 'wb') as f: f.write(file_content) self.send_response(200) self.send_header('Content-type', 'text/plain') self.end_headers() self.wfile.write(b"File saved successfully") except Exception as e: self.send_error(500, f"Failed to save file: {e}") def do_GET(self): # Für Root-Pfad: index.html servieren if self.path == '/' or self.path == '/index.html': self.path = '/index.html' return super().do_GET() # Anfragen für /templates.json oder /templates/* umleiten file_path = self._validate_template_path(self.path) if file_path is not None: if os.path.exists(file_path) and not os.path.isdir(file_path): try: with open(file_path, 'rb') as f: self.send_response(200) self.send_header('Content-type', 'text/plain' if file_path.endswith('.md') else 'application/json') self.end_headers() self.wfile.write(f.read()) return except Exception as e: self.send_error(500, f"Error serving file: {e}") return else: self.send_error(404, "File not found") return return super().do_GET() def main(): PORT = 8081 print(f"Serving on http://localhost:{PORT}") with socketserver.TCPServer(("", PORT), Handler) as httpd: print(f"Serving Prompt Templates on http://localhost:{PORT}") print(f"Press Ctrl+C to stop") print(f"Directory: {DIRECTORY}") try: httpd.serve_forever() except KeyboardInterrupt: print("\nServer stopped") if __name__ == "__main__": main()