makearmy-app/app/api/files/raw/route.ts

40 lines
1.4 KiB
TypeScript

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 });
}
}