const path = require("path"); const multer = require("multer"); const IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "webp"]); const IMAGE_MIME_TYPES = new Set([ "image/jpeg", "image/jpg", "image/png", "image/x-png", "image/webp", ]); const DOCUMENT_EXTENSIONS = new Set([ "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "csv", ]); const DOCUMENT_MIME_TYPES = new Set([ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "text/plain", "text/csv", ]); const getFileKindChecker = (kind = "any") => { return (file) => { const ext = path.extname(file.originalname || "").toLowerCase().replace(".", ""); const mime = String(file.mimetype || "").toLowerCase(); // Both extension AND MIME must match; no fallback to block SVG and other bypass vectors. const isImage = IMAGE_EXTENSIONS.has(ext) && IMAGE_MIME_TYPES.has(mime); const isDocument = DOCUMENT_EXTENSIONS.has(ext) && DOCUMENT_MIME_TYPES.has(mime); if (kind === "image") return isImage; if (kind === "document") return isDocument; return isImage || isDocument; }; }; const buildFileFilter = (kind) => { const isAllowed = getFileKindChecker(kind); return (req, file, cb) => { if (isAllowed(file)) { cb(null, true); return; } if (kind === "image") { const err = new Error("Only JPG, JPEG, PNG, and WEBP image files are allowed"); err.statusCode = 400; cb(err); return; } if (kind === "document") { const err = new Error("Only supported document files are allowed"); err.statusCode = 400; cb(err); return; } const err = new Error("Unsupported file type"); err.statusCode = 400; cb(err); }; }; const createUpload = (options = {}) => { const { fileSize = 50 * 1024 * 1024, kind = "any", } = options; return multer({ storage: multer.memoryStorage(), limits: { fileSize, fields: 20, fieldNameSize: 100, fieldSize: 1 * 1024 * 1024, }, fileFilter: buildFileFilter(kind), }); }; const sharp = require("sharp"); const createImageValidator = () => async (req, res, next) => { if (!req.file) return next(); try { await sharp(req.file.buffer).metadata(); next(); } catch { return res.status(400).json({ message: "File is not a valid image" }); } }; module.exports = createUpload; module.exports.createUpload = createUpload; module.exports.createImageValidator = createImageValidator;