diff --git a/apps/api/src/routes/tools/image-to-pdf.ts b/apps/api/src/routes/tools/image-to-pdf.ts index bc2e8420..001a1eb9 100644 --- a/apps/api/src/routes/tools/image-to-pdf.ts +++ b/apps/api/src/routes/tools/image-to-pdf.ts @@ -7,8 +7,10 @@ import sharp from "sharp"; import { z } from "zod"; import { autoOrient } from "../../lib/auto-orient.js"; import { formatZodErrors } from "../../lib/errors.js"; +import { validateImageBuffer } from "../../lib/file-validation.js"; import { sanitizeFilename } from "../../lib/filename.js"; -import { ensureSharpCompat } from "../../lib/heic-converter.js"; +import { decodeToSharpCompat, needsCliDecode } from "../../lib/format-decoders.js"; +import { decodeHeic } from "../../lib/heic-converter.js"; import { createWorkspace } from "../../lib/workspace.js"; const targetSizeSchema = z.object({ @@ -179,8 +181,21 @@ export function registerImageToPdf(app: FastifyInstance) { const preparedBuffers: Buffer[] = []; for (const file of files) { - const compatBuffer = await autoOrient(await ensureSharpCompat(file.buffer)); - preparedBuffers.push(compatBuffer); + let buf = file.buffer; + + const validation = await validateImageBuffer(buf, file.filename); + if (!validation.valid) { + return reply.status(400).send({ error: `Invalid image: ${validation.reason}` }); + } + + if (validation.format === "heif") { + buf = await decodeHeic(buf); + } else if (needsCliDecode(validation.format)) { + const fileExt = file.filename.split(".").pop()?.toLowerCase(); + buf = await decodeToSharpCompat(buf, validation.format, fileExt); + } + + preparedBuffers.push(await autoOrient(buf)); } let imageBuffers: Buffer[]; diff --git a/tests/integration/image-to-pdf.test.ts b/tests/integration/image-to-pdf.test.ts index 5cec2f38..2bb055ef 100644 --- a/tests/integration/image-to-pdf.test.ts +++ b/tests/integration/image-to-pdf.test.ts @@ -216,7 +216,7 @@ describe("image-to-pdf", () => { // ── Branch coverage: lines 143-147 (processing failure) ─────────── - it("returns 422 when processing fails on corrupted image data", async () => { + it("returns 400 when image format is unrecognized", async () => { const corruptedBuffer = Buffer.alloc(100, 0xff); const { body, contentType } = createMultipartPayload([ { name: "file", filename: "bad.png", contentType: "image/png", content: corruptedBuffer }, @@ -230,9 +230,9 @@ describe("image-to-pdf", () => { body, }); - expect(res.statusCode).toBe(422); + expect(res.statusCode).toBe(400); const json = JSON.parse(res.body); - expect(json.error).toContain("PDF creation failed"); + expect(json.error).toContain("Invalid image"); }); // ── HEIC input handling ───────────────────────────────────────────