Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
"""
|
2026-03-29 08:25:50 -05:00
|
|
|
Platform Gateway — Image proxy with domain allowlist.
|
Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import urllib.request
|
|
|
|
|
import urllib.parse
|
|
|
|
|
|
|
|
|
|
from config import _ssl_ctx
|
|
|
|
|
|
2026-03-29 08:25:50 -05:00
|
|
|
ALLOWED_IMAGE_DOMAINS = {
|
|
|
|
|
"i.redd.it",
|
|
|
|
|
"preview.redd.it",
|
|
|
|
|
"external-preview.redd.it",
|
|
|
|
|
"i.imgur.com",
|
|
|
|
|
"imgur.com",
|
|
|
|
|
"images-na.ssl-images-amazon.com",
|
|
|
|
|
"m.media-amazon.com",
|
|
|
|
|
"cdn.discordapp.com",
|
|
|
|
|
"media.discordapp.net",
|
|
|
|
|
"pbs.twimg.com",
|
|
|
|
|
"abs.twimg.com",
|
|
|
|
|
"upload.wikimedia.org",
|
|
|
|
|
"images.unsplash.com",
|
|
|
|
|
}
|
|
|
|
|
|
Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
|
|
|
|
|
def handle_image_proxy(handler):
|
2026-03-29 08:25:50 -05:00
|
|
|
"""Proxy external images to bypass hotlink protection."""
|
Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
qs = urllib.parse.urlparse(handler.path).query
|
|
|
|
|
params = urllib.parse.parse_qs(qs)
|
|
|
|
|
url = params.get("url", [None])[0]
|
|
|
|
|
if not url:
|
|
|
|
|
handler._send_json({"error": "Missing url parameter"}, 400)
|
|
|
|
|
return
|
2026-03-29 08:25:50 -05:00
|
|
|
|
|
|
|
|
# Validate URL scheme and domain allowlist
|
|
|
|
|
try:
|
|
|
|
|
parsed = urllib.parse.urlparse(url)
|
|
|
|
|
if parsed.scheme not in ("http", "https"):
|
|
|
|
|
handler._send_json({"error": "Invalid URL scheme"}, 400)
|
|
|
|
|
return
|
|
|
|
|
hostname = parsed.hostname or ""
|
|
|
|
|
if hostname not in ALLOWED_IMAGE_DOMAINS:
|
|
|
|
|
allowed = any(
|
|
|
|
|
hostname == d or hostname.endswith(f".{d}")
|
|
|
|
|
for d in ALLOWED_IMAGE_DOMAINS
|
|
|
|
|
)
|
|
|
|
|
if not allowed:
|
|
|
|
|
print(f"[ImageProxy] Blocked request for domain: {hostname}")
|
|
|
|
|
handler._send_json({"error": "Domain not allowed"}, 403)
|
|
|
|
|
return
|
|
|
|
|
except Exception:
|
|
|
|
|
handler._send_json({"error": "Invalid URL"}, 400)
|
|
|
|
|
return
|
|
|
|
|
|
Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
try:
|
|
|
|
|
req = urllib.request.Request(url, headers={
|
|
|
|
|
"User-Agent": "Mozilla/5.0 (compatible; PlatformProxy/1.0)",
|
|
|
|
|
"Accept": "image/*,*/*",
|
2026-03-29 08:25:50 -05:00
|
|
|
"Referer": parsed.scheme + "://" + parsed.netloc + "/",
|
Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
})
|
|
|
|
|
resp = urllib.request.urlopen(req, timeout=10, context=_ssl_ctx)
|
|
|
|
|
body = resp.read()
|
|
|
|
|
ct = resp.headers.get("Content-Type", "image/jpeg")
|
2026-03-29 08:25:50 -05:00
|
|
|
# Only serve actual image content types
|
|
|
|
|
if not ct.startswith("image/"):
|
|
|
|
|
handler._send_json({"error": "Response is not an image"}, 400)
|
|
|
|
|
return
|
Refactor gateway into modular architecture
Split 1878-line server.py into 15 focused modules:
- config.py: all env vars and constants
- database.py: schema, init, seed logic
- sessions.py: session/token CRUD
- proxy.py: proxy_request, SERVICE_MAP, resolve_service
- responses.py: ResponseMixin for handler helpers
- auth.py: login/logout/register handlers
- dashboard.py: dashboard, apps, connections, pinning
- command.py: AI command bar
- integrations/booklore.py: auth, books, cover, import
- integrations/kindle.py: send-to-kindle, file finder
- integrations/karakeep.py: save/delete bookmarks
- integrations/qbittorrent.py: download status
- integrations/image_proxy.py: external image proxy
server.py is now thin routing only (~344 lines).
All routes, methods, status codes, and responses preserved exactly.
Added PYTHONUNBUFFERED=1 to Dockerfile for live logging.
2026-03-29 00:14:46 -05:00
|
|
|
handler.send_response(200)
|
|
|
|
|
handler.send_header("Content-Type", ct)
|
|
|
|
|
handler.send_header("Content-Length", len(body))
|
|
|
|
|
handler.send_header("Cache-Control", "public, max-age=86400")
|
|
|
|
|
handler.end_headers()
|
|
|
|
|
handler.wfile.write(body)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[ImageProxy] Error fetching {url}: {e}")
|
|
|
|
|
handler._send_json({"error": "Failed to fetch image"}, 502)
|