import * as z from "zod"; export const MAX_MEDIA_BYTES = 20 * 1024 * 1024; export const IMAGE_MIME_TYPES = [ "image/png", "image/jpeg", "image/webp", "image/gif", ] as const; export const mediaUploadSchema = z.strictObject({ fileName: z.string().trim().min(1).max(255), mimeType: z.enum(IMAGE_MIME_TYPES), byteSize: z.number().int().positive().max(MAX_MEDIA_BYTES), }); export type MediaUploadInput = z.output; export function safeObjectFileName(fileName: string): string { const normalized = fileName.normalize("NFKC").replace(/[\\/\u0000-\u001f\u007f]+/gu, "-"); const cleaned = normalized.replace(/[^\p{L}\p{M}\p{N}._-]+/gu, "-").replace(/-+/gu, "-"); return cleaned.replace(/^[-.]+|[-.]+$/gu, "").slice(0, 120) || "image"; } function startsWith(bytes: Uint8Array, signature: number[]): boolean { return signature.every((value, index) => bytes[index] === value); } export function hasValidImageSignature( bytes: Uint8Array, mimeType: (typeof IMAGE_MIME_TYPES)[number], ): boolean { if (mimeType === "image/png") { return startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); } if (mimeType === "image/jpeg") { return startsWith(bytes, [0xff, 0xd8, 0xff]); } if (mimeType === "image/webp") { return ( startsWith(bytes, [0x52, 0x49, 0x46, 0x46]) && bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50 ); } return ( startsWith(bytes, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) || startsWith(bytes, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]) ); } export function parseByteRange( rangeHeader: string | null, size: number, ): { start: number; end: number } | null { if (!rangeHeader) return null; const match = /^bytes=(\d*)-(\d*)$/u.exec(rangeHeader.trim()); if (!match) return null; const [, startText, endText] = match; if (!startText && !endText) return null; if (!startText) { const suffix = Number(endText); if (!Number.isSafeInteger(suffix) || suffix <= 0) return null; return { start: Math.max(0, size - suffix), end: size - 1 }; } const start = Number(startText); const end = endText ? Number(endText) : size - 1; if ( !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || start >= size ) { return null; } return { start, end: Math.min(end, size - 1) }; }