From 2739c6af6f33cb5d0ecaf34b2ddc2b12073db9cb Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Wed, 11 Mar 2026 14:48:36 +0200 Subject: [PATCH] fix: extreme PDF compression producing empty output on large files The multipart body parser was using the first \r\n\r\n as the file content start, but this could miss the actual file part. Now properly iterates through parts to find the one with filename= header, and uses lastIndexOf for the closing boundary to avoid false matches inside PDF binary data. Co-Authored-By: Claude Opus 4.6 --- src/app/api/compress-pdf/extreme/route.ts | 67 ++++++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/app/api/compress-pdf/extreme/route.ts b/src/app/api/compress-pdf/extreme/route.ts index 1a9dcb3..3d4b445 100644 --- a/src/app/api/compress-pdf/extreme/route.ts +++ b/src/app/api/compress-pdf/extreme/route.ts @@ -120,18 +120,16 @@ export async function POST(req: NextRequest) { const nodeStream = Readable.fromWeb(req.body as import("stream/web").ReadableStream); await pipeline(nodeStream, createWriteStream(rawPath)); - // Extract the PDF from multipart: find the double CRLF after headers, - // then read until the boundary marker before the end + // Extract the PDF binary from the multipart body. + // Multipart format: + // --boundary\r\n + // Content-Disposition: form-data; name="fileInput"; filename="x.pdf"\r\n + // Content-Type: application/pdf\r\n + // \r\n + // + // \r\n--boundary--\r\n const rawBuf = await readFile(rawPath); - const headerEnd = rawBuf.indexOf(Buffer.from("\r\n\r\n")); - if (headerEnd === -1) { - return NextResponse.json( - { error: "Lipsește fișierul PDF." }, - { status: 400 }, - ); - } - // Extract boundary from Content-Type header const contentType = req.headers.get("content-type") || ""; const boundaryMatch = contentType.match(/boundary=(?:"([^"]+)"|([^\s;]+))/); const boundary = boundaryMatch?.[1] ?? boundaryMatch?.[2] ?? ""; @@ -143,11 +141,50 @@ export async function POST(req: NextRequest) { ); } - // File content starts after first double CRLF, ends before closing boundary - const closingBoundary = Buffer.from(`\r\n--${boundary}`); - const fileStart = headerEnd + 4; - const fileEnd = rawBuf.indexOf(closingBoundary, fileStart); - const pdfData = fileEnd !== -1 ? rawBuf.subarray(fileStart, fileEnd) : rawBuf.subarray(fileStart); + const boundaryBuf = Buffer.from(`--${boundary}`); + + // Find the part that contains a filename (the file upload part). + // There may be multiple parts — we need the one with "filename=". + let fileStart = -1; + let searchFrom = 0; + + while (searchFrom < rawBuf.length) { + const partStart = rawBuf.indexOf(boundaryBuf, searchFrom); + if (partStart === -1) break; + + // Skip past boundary line to get to headers + const headersStart = rawBuf.indexOf(Buffer.from("\r\n"), partStart); + if (headersStart === -1) break; + + const headerEnd = rawBuf.indexOf(Buffer.from("\r\n\r\n"), headersStart); + if (headerEnd === -1) break; + + // Check if this part's headers contain a filename + const headers = rawBuf.subarray(headersStart, headerEnd).toString("utf8"); + if (headers.includes("filename=")) { + fileStart = headerEnd + 4; // skip \r\n\r\n + break; + } + + // Move past this part's headers to search for next boundary + searchFrom = headerEnd + 4; + } + + if (fileStart === -1) { + return NextResponse.json( + { error: "Lipsește fișierul PDF." }, + { status: 400 }, + ); + } + + // Find the closing boundary after the file content. + // Search from the END of the buffer backwards, since the PDF binary + // could theoretically contain the boundary string by coincidence. + const closingMarker = Buffer.from(`\r\n--${boundary}`); + const fileEnd = rawBuf.lastIndexOf(closingMarker); + const pdfData = (fileEnd > fileStart) + ? rawBuf.subarray(fileStart, fileEnd) + : rawBuf.subarray(fileStart); await writeFile(inputPath, pdfData); const originalSize = pdfData.length;