prompt_template/web/serve.py

152 lines
5.4 KiB
Python
Raw Normal View History

#!/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()