Spaces:
Running
Running
| 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; |