Spaces:
Runtime error
Runtime error
| 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<string, Buffer[]> = { | |
| '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<string, string> = { | |
| 'image/jpeg': 'jpg', | |
| 'image/png': 'png', | |
| }; | |
| return map[mimeType] || 'jpg'; | |
| } | |