diff options
Diffstat (limited to 'src/app.py')
| -rw-r--r-- | src/app.py | 49 |
1 files changed, 44 insertions, 5 deletions
@@ -3,14 +3,29 @@ import base64 import hashlib import os +import re +import secrets import subprocess import time from pathlib import Path from threading import Lock +import magic from flask import Flask, Response, jsonify, request, send_file, send_from_directory app = Flask(__name__, static_folder=None) +app.config["MAX_CONTENT_LENGTH"] = 25 * 1000 * 1000 # 25 MB upload cap + +UPLOAD_DIR = Path(__file__).parent / "uploads" +UPLOAD_DIR.mkdir(mode=0o755, exist_ok=True) + +ALLOWED_MIME = { + "image/png": "png", + "image/jpeg": "jpg", + "image/gif": "gif", + "image/webp": "webp", + "image/bmp": "bmp", +} _CSP = ( "default-src 'self'; " @@ -203,13 +218,37 @@ def nyan_png(): return send_from_directory(app.root_path, "nyan.png") +@app.route("/uploads/<filename>") +def uploads(filename: str): + if Path(filename).suffix.lower() == ".svg": + return Response("not found", status=404) + return send_from_directory(UPLOAD_DIR, filename) + + @app.route("/upload", methods=["POST"]) def upload(): - return jsonify( - { - "error": "server-side uploads are disabled; images stay in browser local storage" - } - ), 410 + if "image" not in request.files: + return jsonify({"error": "no file provided"}), 400 + + f = request.files["image"] + if not f.filename: + return jsonify({"error": "empty filename"}), 400 + + data = f.read() + if not data: + return jsonify({"error": "empty file"}), 400 + + mime = magic.from_buffer(data, mime=True) + if mime not in ALLOWED_MIME: + return jsonify({"error": f"invalid file type: {mime}"}), 400 + + ext = ALLOWED_MIME[mime] + basename = re.sub(r"[^a-zA-Z0-9_-]", "_", Path(f.filename).stem)[:64] or "image" + filename = f"{basename}_{secrets.token_hex(4)}.{ext}" + + (UPLOAD_DIR / filename).write_bytes(data) + + return jsonify({"filename": filename, "url": f"uploads/{filename}"}) @app.route("/fonts") |
