export const MAX_FORM_ANSWER_IMAGE_BYTES = 50 * 1024 * 1024; const ALLOWED_IMAGE_MIME_TYPES = new Set([ "image/png", "image/jpeg", "image/webp", "image/gif", ]); const ALLOWED_IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "gif"]); export function validateImageUploadFile( file: Pick, bytes?: Uint8Array ) { if (!ALLOWED_IMAGE_MIME_TYPES.has(file.type)) { throw new Error("Only PNG, JPG, WebP, and GIF images are allowed"); } if (file.size > MAX_FORM_ANSWER_IMAGE_BYTES) { throw new Error("Image must be 50MB or smaller"); } if (file.size === 0) { throw new Error("Image cannot be empty"); } const extension = (file.name.split(".").pop() || "").toLowerCase(); if (!ALLOWED_IMAGE_EXTENSIONS.has(extension)) { throw new Error("Invalid image extension"); } if (bytes && !hasMatchingImageSignature(file.type, bytes)) { throw new Error("Image contents do not match the declared image type"); } } function hasMatchingImageSignature(type: string, bytes: Uint8Array) { if (type === "image/png") { return bytes.length >= 8 && bytes.slice(0, 8).every((value, index) => value === [137, 80, 78, 71, 13, 10, 26, 10][index]); } if (type === "image/jpeg") { return bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff; } if (type === "image/gif") { return bytes.length >= 6 && (String.fromCharCode(...bytes.slice(0, 6)) === "GIF87a" || String.fromCharCode(...bytes.slice(0, 6)) === "GIF89a"); } if (type === "image/webp") { return bytes.length >= 12 && String.fromCharCode(...bytes.slice(0, 4)) === "RIFF" && String.fromCharCode(...bytes.slice(8, 12)) === "WEBP"; } return false; } export function cdnUrl(id: string | null | undefined) { return id ? `/cdn/${id}` : ""; } export type CdnResponseRecord = { data: Buffer; type: string; size: number; }; export function cdnImageResponse(record: CdnResponseRecord) { return new Response(new Uint8Array(record.data), { headers: { "Content-Type": record.type, "Content-Length": String(record.size), "Cache-Control": "public, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff", }, }); }