sendou.ink/app/features/img-upload/image-bytes.server.ts
2026-07-28 16:17:33 +03:00

54 lines
1.2 KiB
TypeScript

import invariant from "~/utils/invariant";
export type ImageExtension = "webp" | "png" | "jpeg";
/**
* Decodes a base64 image data URL to a buffer, resolving the format from the buffer's own magic
* bytes rather than trusting the client-declared mime type, and asserting it is one of
* `allowedExtensions`.
*/
export function dataUrlToImageBuffer(
dataUrl: string,
allowedExtensions: ReadonlyArray<ImageExtension>,
) {
const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
const buffer = Buffer.from(base64, "base64");
const extension = imageExtensionFromMagicBytes(buffer);
invariant(
extension && allowedExtensions.includes(extension),
`Submitted image is not a valid ${allowedExtensions.join(" or ")}`,
);
return { buffer, extension };
}
function imageExtensionFromMagicBytes(buffer: Buffer): ImageExtension | null {
if (
buffer.length > 12 &&
buffer.toString("ascii", 0, 4) === "RIFF" &&
buffer.toString("ascii", 8, 12) === "WEBP"
) {
return "webp";
}
if (
buffer.length > 8 &&
buffer[0] === 0x89 &&
buffer.toString("ascii", 1, 4) === "PNG"
) {
return "png";
}
if (
buffer.length > 3 &&
buffer[0] === 0xff &&
buffer[1] === 0xd8 &&
buffer[2] === 0xff
) {
return "jpeg";
}
return null;
}