import { NextResponse } from "next/server" import { readFile } from "node:fs/promises" import { existsSync } from "node:fs" import { join } from "node:path" import { getManualsFilesRoot } from "@/lib/manuals-paths" import { getManualAssetFromStorage } from "@/lib/manuals-object-storage" export const dynamic = "force-dynamic" function decodeSegments(pathArray: string[]) { return pathArray.map((segment) => { try { let decoded = segment while (decoded !== decodeURIComponent(decoded)) { decoded = decodeURIComponent(decoded) } return decoded } catch { return segment } }) } function invalidPath(pathValue: string) { return pathValue.includes("..") || pathValue.startsWith("/") } export async function GET( _request: Request, { params }: { params: Promise<{ path: string[] }> }, ) { try { const { path: pathArray } = await params const decodedPath = decodeSegments(pathArray) const filePath = decodedPath.join("/") if (invalidPath(filePath) || !filePath.toLowerCase().endsWith(".pdf")) { return new NextResponse("Invalid path", { status: 400 }) } const storageObject = await getManualAssetFromStorage("manuals", filePath) if (storageObject) { return new NextResponse(Buffer.from(storageObject.body), { headers: { "Content-Type": storageObject.contentType || "application/pdf", "Content-Disposition": `inline; filename="${encodeURIComponent(decodedPath.at(-1) || "manual.pdf")}"`, "Cache-Control": "public, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff", }, }) } const manualsDir = getManualsFilesRoot() const fullPath = join(manualsDir, filePath) const normalizedFullPath = fullPath.replace(/\\/g, "/") const normalizedManualsDir = manualsDir.replace(/\\/g, "/") if (!existsSync(normalizedFullPath) && !existsSync(fullPath)) { return new NextResponse("File not found", { status: 404 }) } const fileToRead = existsSync(normalizedFullPath) ? normalizedFullPath : fullPath const resolvedPath = fileToRead.replace(/\\/g, "/") if (!resolvedPath.startsWith(normalizedManualsDir)) { return new NextResponse("Invalid path", { status: 400 }) } const fileBuffer = await readFile(fileToRead) return new NextResponse(fileBuffer, { headers: { "Content-Type": "application/pdf", "Content-Disposition": `inline; filename="${encodeURIComponent(decodedPath.at(-1) || "manual.pdf")}"`, "Cache-Control": "public, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff", }, }) } catch (error) { console.error("[manuals-api] failed to serve manual", error) return new NextResponse("Internal server error", { status: 500 }) } }