39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import fssync from "node:fs";
|
|
import path from "node:path";
|
|
import mime from "mime";
|
|
|
|
const BASE = "/app/files";
|
|
|
|
function safeJoin(base: string, reqPath: string) {
|
|
const decoded = decodeURIComponent(reqPath || "/");
|
|
const normalized = path.posix.normalize("/" + decoded).replace(/^(\.\.(\/|\\|$))+/g, "");
|
|
const full = path.join(base, normalized);
|
|
if (!full.startsWith(base)) throw new Error("Invalid path");
|
|
return full;
|
|
}
|
|
|
|
export async function GET(req: Request) {
|
|
try {
|
|
const { searchParams } = new URL(req.url);
|
|
const p = searchParams.get("path");
|
|
if (!p) return NextResponse.json({ ok: false, error: "Missing path" }, { status: 400 });
|
|
|
|
const abs = safeJoin(BASE, p);
|
|
if (!fssync.existsSync(abs) || !fssync.statSync(abs).isFile()) {
|
|
return NextResponse.json({ ok: false, error: "Not found" }, { status: 404 });
|
|
}
|
|
|
|
const stream = fssync.createReadStream(abs);
|
|
const type = mime.getType(abs) || "application/octet-stream";
|
|
return new Response(stream as any, {
|
|
headers: {
|
|
"Content-Type": type,
|
|
"Cache-Control": "public, max-age=3600",
|
|
},
|
|
});
|
|
} catch (err: any) {
|
|
return NextResponse.json({ ok: false, error: err?.message || "Error" }, { status: 400 });
|
|
}
|
|
}
|
|
|