From ea814ffa15c551cb3a3ec48bb43d8cacd32992bc Mon Sep 17 00:00:00 2001 From: XIN PENG Date: Tue, 17 Feb 2026 08:14:40 -0800 Subject: [PATCH] fix: detect HEIC/HEIF from raw Base64 image signatures --- src/tools/look-at/mime-type-inference.test.ts | 36 +++++++++++++++++++ src/tools/look-at/mime-type-inference.ts | 8 ++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/tools/look-at/mime-type-inference.test.ts diff --git a/src/tools/look-at/mime-type-inference.test.ts b/src/tools/look-at/mime-type-inference.test.ts new file mode 100644 index 000000000..ed76e1b47 --- /dev/null +++ b/src/tools/look-at/mime-type-inference.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "bun:test" +import { extractBase64Data, inferMimeTypeFromBase64, inferMimeTypeFromFilePath } from "./mime-type-inference" + +describe("mime type inference", () => { + test("returns MIME from data URL prefix", () => { + const mime = inferMimeTypeFromBase64("data:image/heic;base64,AAAAGGZ0eXBoZWlj") + expect(mime).toBe("image/heic") + }) + + test("detects HEIC from raw base64 magic bytes", () => { + const heicHeader = Buffer.from("00000018667479706865696300000000", "hex").toString("base64") + const mime = inferMimeTypeFromBase64(heicHeader) + expect(mime).toBe("image/heic") + }) + + test("detects HEIF from raw base64 magic bytes", () => { + const heifHeader = Buffer.from("00000018667479706865696600000000", "hex").toString("base64") + const mime = inferMimeTypeFromBase64(heifHeader) + expect(mime).toBe("image/heif") + }) + + test("falls back to png when base64 signature is unknown", () => { + const mime = inferMimeTypeFromBase64("dW5rbm93biBiaW5hcnk=") + expect(mime).toBe("image/png") + }) + + test("infers heic from file extension", () => { + const mime = inferMimeTypeFromFilePath("/tmp/photo.HEIC") + expect(mime).toBe("image/heic") + }) + + test("extracts raw base64 data from data URL", () => { + const base64 = extractBase64Data("data:image/png;base64,abc123") + expect(base64).toBe("abc123") + }) +}) diff --git a/src/tools/look-at/mime-type-inference.ts b/src/tools/look-at/mime-type-inference.ts index 3a7c0010a..0718259de 100644 --- a/src/tools/look-at/mime-type-inference.ts +++ b/src/tools/look-at/mime-type-inference.ts @@ -8,12 +8,18 @@ export function inferMimeTypeFromBase64(base64Data: string): string { try { const cleanData = base64Data.replace(/^data:[^;]+;base64,/, "") - const header = atob(cleanData.slice(0, 16)) + const header = Buffer.from(cleanData.slice(0, 256), "base64").toString("binary") if (header.startsWith("\x89PNG")) return "image/png" if (header.startsWith("\xFF\xD8\xFF")) return "image/jpeg" if (header.startsWith("GIF8")) return "image/gif" if (header.startsWith("RIFF") && header.includes("WEBP")) return "image/webp" + if (header.includes("ftypheic") || header.includes("ftypheix") || header.includes("ftyphevc") || header.includes("ftyphevx")) { + return "image/heic" + } + if (header.includes("ftypheif") || header.includes("ftypmif1") || header.includes("ftypmsf1")) { + return "image/heif" + } if (header.startsWith("%PDF")) return "application/pdf" } catch { // invalid base64 - fall through