import { NextResponse } from "next/server"; import fs from "fs"; import fsp from "fs/promises"; import path from "path"; import mime from "mime"; const ROOT = (process.env.FILES_ROOT || "/files").trim(); function safeJoin(root: string, reqPath: string) { const clean = (reqPath || "").replace(/^\/+/, ""); const joined = path.normalize(path.join(root, clean)); const rootNorm = path.normalize(root.endsWith(path.sep) ? root : root + path.sep); if (!joined.startsWith(rootNorm) && path.normalize(joined) !== path.normalize(root)) { throw new Error("Path traversal blocked"); } return joined; } export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); const reqPath = searchParams.get("path") || ""; if (!reqPath) return new NextResponse("Missing path", { status: 400 }); const abs = safeJoin(ROOT, reqPath); const stat = await fsp.stat(abs).catch(() => null); if (!stat || !stat.isFile()) return new NextResponse("Not found", { status: 404 }); const ctype = mime.getType(abs) || "application/octet-stream"; const stream = fs.createReadStream(abs); return new NextResponse(stream as any, { headers: { "Content-Type": ctype, "Cache-Control": "public, max-age=3600", }, }); } catch (e: any) { return new NextResponse(String(e?.message || e), { status: 400 }); } }