"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.imageUpload = void 0; exports.detectImageType = detectImageType; exports.assertImageFile = assertImageFile; exports.assertImageFiles = assertImageFiles; const path_1 = __importDefault(require("path")); const multer_1 = __importDefault(require("multer")); const errors_1 = require("../errors"); const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB const ALLOWED_IMAGE_TYPES = new Map([ ['image/jpeg', ['.jpg', '.jpeg']], ['image/png', ['.png']], ['image/webp', ['.webp']], ['image/gif', ['.gif']], ]); /** * Shared multer instance used by all upload endpoints. * Files are kept in memory so services receive a Buffer — no temp-file cleanup needed. */ exports.imageUpload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: MAX_FILE_SIZE, files: 20 }, }); function detectImageType(buffer) { if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) { return { mime: 'image/jpeg', ext: '.jpg' }; } if (buffer.length >= 8 && buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47 && buffer[4] === 0x0d && buffer[5] === 0x0a && buffer[6] === 0x1a && buffer[7] === 0x0a) { return { mime: 'image/png', ext: '.png' }; } if (buffer.length >= 12 && buffer.subarray(0, 4).toString('ascii') === 'RIFF' && buffer.subarray(8, 12).toString('ascii') === 'WEBP') { return { mime: 'image/webp', ext: '.webp' }; } if (buffer.length >= 6) { const sig = buffer.subarray(0, 6).toString('ascii'); if (sig === 'GIF87a' || sig === 'GIF89a') return { mime: 'image/gif', ext: '.gif' }; } return null; } function assertSafeImageContent(file) { const detected = detectImageType(file.buffer); if (!detected || !ALLOWED_IMAGE_TYPES.has(detected.mime)) { throw new errors_1.ValidationError('Unsupported or spoofed image file'); } if (file.mimetype !== detected.mime) { throw new errors_1.ValidationError(`MIME type does not match file content for "${file.originalname}"`); } const ext = path_1.default.extname(file.originalname).toLowerCase(); const allowedExtensions = ALLOWED_IMAGE_TYPES.get(detected.mime) ?? []; if (ext && !allowedExtensions.includes(ext)) { throw new errors_1.ValidationError(`File extension does not match file content for "${file.originalname}"`); } } /** * Asserts that a file was provided and is an image by content, not by client claims. */ function assertImageFile(file, fieldLabel = 'file') { if (!file) throw new errors_1.ValidationError(`A ${fieldLabel} is required`); assertSafeImageContent(file); } /** * Asserts that at least one file was provided and all files are image content. */ function assertImageFiles(files, fieldLabel = 'photos') { if (!files || files.length === 0) throw new errors_1.ValidationError(`At least one ${fieldLabel} file is required`); for (const file of files) assertSafeImageContent(file); } //# sourceMappingURL=index.js.map