/** * dashboard-image-rules — reine Regeln fuer das Bilderrahmen-Widget * (quick-260921-pi9). Kein Nest, kein Prisma: Grenzen und die * Magic-Byte-Erkennung, damit Dienst und Controller dieselben Werte * anwenden und die Erkennung direkt an den Bytes testbar ist. * * Warum Magic Bytes und nicht `file.mimetype` oder die Dateiendung * (T-PI9-01): beides behauptet der Browser, beides kann eine umbenannte * HTML-, SVG- oder PDF-Datei tragen. Was hier NICHT als eine der vier * Signaturen erkannt wird, kommt nicht in die Datenbank — und der erkannte * Typ ist zugleich der Typ, mit dem die Auslieferung antwortet (T-PI9-08). * * Bewusst KEIN `file-type`-Paket (T-PI9-SC): vier feste Signaturen sind * eine Handvoll Zeilen und brauchen keine Abhaengigkeit. */ /** Hoechstgroesse je Datei: 5 MiB (multer `limits.fileSize` je Route). */ export const DASHBOARD_IMAGE_MAX_BYTES = 5 * 1024 * 1024; /** Hoechstzahl je Benutzer (Zaehler je Mandant+Benutzer vor dem Anlegen). */ export const DASHBOARD_IMAGE_MAX_COUNT = 30; export type DashboardImageMime = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'; const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; const JPEG_SIGNATURE = [0xff, 0xd8, 0xff]; const GIF87A = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]; // "GIF87a" const GIF89A = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]; // "GIF89a" const RIFF = [0x52, 0x49, 0x46, 0x46]; // "RIFF" const WEBP = [0x57, 0x45, 0x42, 0x50]; // "WEBP" function startsWith(buffer: Uint8Array, signature: number[], offset = 0): boolean { if (buffer.length < offset + signature.length) return false; for (let i = 0; i < signature.length; i++) { if (buffer[offset + i] !== signature[i]) return false; } return true; } /** * Erkennt PNG, JPEG, GIF und WebP an den ersten Bytes; alles andere * (leer, Text, SVG, PDF, RIFF ohne WEBP an Stelle 8) ergibt `null`. * Wirft nie — ein zu kurzer Puffer ist schlicht kein Bild. */ export function detectImageMime(buffer: Uint8Array): DashboardImageMime | null { if (startsWith(buffer, PNG_SIGNATURE)) return 'image/png'; if (startsWith(buffer, JPEG_SIGNATURE)) return 'image/jpeg'; if (startsWith(buffer, GIF87A) || startsWith(buffer, GIF89A)) return 'image/gif'; if (startsWith(buffer, RIFF) && startsWith(buffer, WEBP, 8)) return 'image/webp'; return null; }