const path = require("node:path"); const fs = require("node:fs/promises"); const mime = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json", ".wasm": "application/wasm", ".glb": "model/gltf-binary", ".gltf": "model/gltf+json", ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", }; const csp = "default-src 'none'; script-src 'self' 'unsafe-eval'; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' data: blob:; font-src 'self' data:; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-src 'none'"; async function readGame(root, url, method = "GET") { if (!["GET", "HEAD"].includes(method)) return { status: 405, body: "Method not allowed" }; let u, name; try { u = new URL(url); name = decodeURIComponent(u.pathname); } catch { return { status: 400, body: "Bad URL" }; } if ( u.protocol !== "forma:" || u.hostname !== "game" || u.port || u.username || u.password || name.includes("\\") || name.includes("\0") ) return { status: 403, body: "Forbidden" }; const file = path.resolve(root, "." + (name === "/" ? "/index.html" : name)); if (!file.startsWith(path.resolve(root) + path.sep)) return { status: 403, body: "Forbidden" }; try { const real = await fs.realpath(file); if (!real.startsWith((await fs.realpath(root)) + path.sep)) return { status: 403, body: "Forbidden" }; const bytes = await fs.readFile(real); return { status: 200, headers: { "Content-Type": mime[path.extname(file)] || "application/octet-stream", "Content-Security-Policy": csp, "X-Content-Type-Options": "nosniff", "Cache-Control": "no-store", }, body: method === "HEAD" ? null : bytes, }; } catch { return { status: 404, body: "Not found" }; } } module.exports = { readGame, csp };