import { NextRequest, NextResponse } from "next/server"; import fs from "fs"; import path from "path"; export async function GET( req: NextRequest, { params }: { params: { filename: string } } ) { try { const rawFilename = params.filename; // 1. Strict filename validation: alphanumeric + hyphens/underscores + safe image extensions only const validFilenameRegex = /^[a-zA-Z0-9_-]+\.(jpg|jpeg|png|webp)$/i; if (!rawFilename || !validFilenameRegex.test(rawFilename)) { return new NextResponse("Bad Request: Invalid or disallowed image filename", { status: 400 }); } const filename = path.basename(rawFilename); const imagesDir = path.resolve(process.cwd(), "data", "images"); const filePath = path.resolve(imagesDir, filename); // 2. Prevent path traversal outside data/images directory if (!filePath.startsWith(imagesDir)) { return new NextResponse("Access Denied", { status: 403 }); } if (!fs.existsSync(filePath)) { return new NextResponse("Image Not Found", { status: 404 }); } const fileBuffer = fs.readFileSync(filePath); const ext = path.extname(filename).toLowerCase(); let contentType = "image/png"; if (ext === ".jpg" || ext === ".jpeg") contentType = "image/jpeg"; else if (ext === ".webp") contentType = "image/webp"; return new NextResponse(fileBuffer, { status: 200, headers: { "Content-Type": contentType, "X-Content-Type-Options": "nosniff", "Content-Security-Policy": "default-src 'none'; sandbox", "Content-Disposition": `${req.nextUrl.searchParams.get("download") === "1" ? "attachment" : "inline"}; filename="${filename}"`, "Cache-Control": "public, max-age=31536000, immutable", }, }); } catch (error) { console.error("[API Image Serve] Error reading file:", error); return new NextResponse("Server Error", { status: 500 }); } }