59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
|
|
/**
|
||
|
|
* Serve files from /Users/guto/ufo/processing/* and /Users/guto/ufo/raw/* via
|
||
|
|
* /api/static/png/<doc>/<file>, /api/static/crops/<doc>/<page>/<file>, etc.
|
||
|
|
*
|
||
|
|
* Sandboxed to UFO_ROOT to prevent path traversal.
|
||
|
|
*/
|
||
|
|
import fs from "node:fs/promises";
|
||
|
|
import path from "node:path";
|
||
|
|
import { NextResponse } from "next/server";
|
||
|
|
import { UFO_ROOT } from "@/lib/wiki";
|
||
|
|
|
||
|
|
const ALLOWED_ROOTS = ["processing", "raw"];
|
||
|
|
|
||
|
|
function mimeFor(ext: string): string {
|
||
|
|
switch (ext.toLowerCase()) {
|
||
|
|
case ".png": return "image/png";
|
||
|
|
case ".jpg":
|
||
|
|
case ".jpeg": return "image/jpeg";
|
||
|
|
case ".webp": return "image/webp";
|
||
|
|
case ".gif": return "image/gif";
|
||
|
|
case ".svg": return "image/svg+xml";
|
||
|
|
case ".pdf": return "application/pdf";
|
||
|
|
case ".txt": return "text/plain; charset=utf-8";
|
||
|
|
case ".json": return "application/json";
|
||
|
|
case ".mp4": return "video/mp4";
|
||
|
|
default: return "application/octet-stream";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function GET(_req: Request, ctx: { params: Promise<{ path: string[] }> }) {
|
||
|
|
const { path: parts } = await ctx.params;
|
||
|
|
if (!parts || parts.length < 2) {
|
||
|
|
return new NextResponse("Bad Request", { status: 400 });
|
||
|
|
}
|
||
|
|
const root = parts[0];
|
||
|
|
if (!ALLOWED_ROOTS.includes(root)) {
|
||
|
|
return new NextResponse("Forbidden", { status: 403 });
|
||
|
|
}
|
||
|
|
const rel = parts.slice(1).join("/");
|
||
|
|
const abs = path.resolve(UFO_ROOT, root, rel);
|
||
|
|
// Path traversal guard
|
||
|
|
const expectedPrefix = path.resolve(UFO_ROOT, root) + path.sep;
|
||
|
|
if (!abs.startsWith(expectedPrefix) && abs !== path.resolve(UFO_ROOT, root)) {
|
||
|
|
return new NextResponse("Forbidden", { status: 403 });
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const buf = await fs.readFile(abs);
|
||
|
|
const ext = path.extname(abs);
|
||
|
|
return new NextResponse(buf, {
|
||
|
|
headers: {
|
||
|
|
"content-type": mimeFor(ext),
|
||
|
|
"cache-control": "public, max-age=3600",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
} catch {
|
||
|
|
return new NextResponse("Not Found", { status: 404 });
|
||
|
|
}
|
||
|
|
}
|