from flask import Flask, request, jsonify, send_from_directory, abort from flask_cors import CORS import bcrypt import os import re app = Flask(__name__) CORS(app, origins="*", allow_headers="*", methods=["GET", "POST", "DELETE", "OPTIONS"]) SAVES_DIR = os.environ.get("SAVES_DIR", "/saves") os.makedirs(SAVES_DIR, exist_ok=True) def safe_filename(name): name = re.sub(r"[^\w\-. ]", "", name).strip() if not name: abort(400, "Invalid save name") return name + ".puC" def get_hash_path(name): return os.path.join(SAVES_DIR, safe_filename(name).replace(".puC", ".hash")) def check_password(name, req): hash_path = get_hash_path(name) if not os.path.exists(hash_path): return # unprotected client_hash = req.headers.get("X-Password-Hash", "") if not client_hash: abort(401, "Password required") stored = open(hash_path, "rb").read() if not bcrypt.checkpw(client_hash.encode(), stored): abort(401, "Wrong password") @app.route("/saves", methods=["GET"]) def list_saves(): saves = sorted(f[:-4] for f in os.listdir(SAVES_DIR) if f.endswith(".puC")) protected = sorted(f[:-5] for f in os.listdir(SAVES_DIR) if f.endswith(".hash")) return jsonify({"saves": saves, "protected": protected}) @app.route("/saves/", methods=["GET"]) def get_save(name): check_password(name, request) filename = safe_filename(name) path = os.path.join(SAVES_DIR, filename) if not os.path.exists(path): abort(404) return send_from_directory(SAVES_DIR, filename, as_attachment=True) @app.route("/saves/", methods=["POST"]) def store_save(name): check_password(name, request) filename = safe_filename(name) data = request.get_data() if not data: abort(400, "No data") with open(os.path.join(SAVES_DIR, filename), "wb") as f: f.write(data) ph = request.headers.get("X-Password-Hash", "") if ph: hashed = bcrypt.hashpw(ph.encode(), bcrypt.gensalt()) open(get_hash_path(name), "wb").write(hashed) return jsonify({"ok": True, "name": name}) @app.route("/saves/", methods=["DELETE"]) def delete_save(name): check_password(name, request) filename = safe_filename(name) path = os.path.join(SAVES_DIR, filename) if not os.path.exists(path): abort(404) os.remove(path) hash_path = get_hash_path(name) if os.path.exists(hash_path): os.remove(hash_path) return jsonify({"ok": True}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)