const ALLOWED_MIME_TYPES = [ 'image/jpeg', 'image/png', ]; const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png']; // Magic bytes (file signatures) for image validation const MAGIC_BYTES: Record = { 'image/jpeg': [Buffer.from([0xff, 0xd8, 0xff])], 'image/png': [Buffer.from([0x89, 0x50, 0x4e, 0x47])], }; const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB export function validateImageFile(file: Express.Multer.File): { valid: boolean; error?: string } { // 1. Check file size if (file.size > MAX_FILE_SIZE) { return { valid: false, error: 'File size must be under 2MB' }; } // 2. Check MIME type if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) { return { valid: false, error: 'Only JPEG and PNG images are allowed' }; } // 3. Check file extension const ext = file.originalname.split('.').pop()?.toLowerCase(); if (!ext || !ALLOWED_EXTENSIONS.includes(ext)) { return { valid: false, error: 'Invalid file extension' }; } // 4. Validate magic bytes (check first 512 bytes for polyglot detection) const expectedSignatures = MAGIC_BYTES[file.mimetype]; if (expectedSignatures) { const fileHeader = file.buffer.subarray(0, 512); const isValid = expectedSignatures.some(sig => fileHeader.subarray(0, sig.length).equals(sig)); if (!isValid) { return { valid: false, error: 'File content does not match its type' }; } } return { valid: true }; } export function getSafeExtension(mimeType: string): string { const map: Record = { 'image/jpeg': 'jpg', 'image/png': 'png', }; return map[mimeType] || 'jpg'; }