"""Eepsite static file server. Serves static files from a docroot directory. Bound to localhost, accessed via I2PTunnel's HTTP server tunnel which forwards I2P connections to this server. Ported from Jetty eepsite configuration in Java I2P. """ from __future__ import annotations import mimetypes from dataclasses import dataclass, field from pathlib import Path # Ensure common types are registered mimetypes.init() _CONTENT_TYPES = { ".html": "text/html", ".htm": "text/html", ".css": "text/css", ".js": "application/javascript", ".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".txt": "text/plain", ".xml": "application/xml", } _DEFAULT_INDEX = "index.html" @dataclass class EepsiteConfig: """Configuration for the eepsite server.""" host: str = "127.0.0.1" port: int = 7658 docroot: Path = field(default_factory=lambda: Path.home() / ".i2p-python" / "eepsite" / "docroot") class EepsiteServer: """Static file server for personal .i2p websites.""" def __init__(self, config: EepsiteConfig) -> None: self._config = config self._docroot = config.docroot.resolve() def resolve_path(self, request_path: str) -> Path | None: """Resolve a URL path to a file in the docroot. Returns None if the file doesn't exist or path traversal is detected. """ # Normalize the path clean = request_path.lstrip("/") if not clean: clean = _DEFAULT_INDEX target = (self._docroot / clean).resolve() # Block path traversal try: target.relative_to(self._docroot) except ValueError: return None # If it's a directory, try index.html if target.is_dir(): target = target / _DEFAULT_INDEX if target.exists() and target.is_file(): return target return None @staticmethod def content_type(filename: str) -> str: """Get the content type for a filename.""" suffix = Path(filename).suffix.lower() return _CONTENT_TYPES.get(suffix, "application/octet-stream")