Created
May 30, 2024 05:57
-
-
Save SwapnilSoni1999/c1b96667f67d7c69e357b0d0e596b1ee to your computer and use it in GitHub Desktop.
Universal libmagic file validator, Multer Helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from 'fs' | |
import { FileMagic, MagicFlags } from '@npcz/magic' | |
import { extension } from 'mime-types' | |
import path from 'path' | |
import os from 'os' | |
FileMagic.magicFile = require.resolve( | |
'@npcz/magic/dist/magic.mgc' | |
) | |
if ( | |
process.platform === 'darwin' || | |
process.platform === 'linux' | |
) { | |
FileMagic.defaultFlags = MagicFlags.MAGIC_PRESERVE_ATIME | |
} | |
const getMagic = async () => { | |
const instance = await FileMagic.getInstance() | |
return instance | |
} | |
const getMimeType = async ( | |
dataOrFilePath: Buffer | string | |
) => { | |
let filePath = '' | |
if (typeof dataOrFilePath === 'string') { | |
filePath = dataOrFilePath | |
} else if (dataOrFilePath instanceof Buffer) { | |
const fileId = Math.random().toString(36).substring(7) | |
const tempPath = path.join(os.tmpdir(), fileId) | |
fs.writeFileSync(tempPath, dataOrFilePath) | |
filePath = tempPath | |
} | |
if (!fs.existsSync(filePath)) { | |
throw new Error('[libmagic] File not found') | |
} | |
const magic = await getMagic() | |
const mimeType = magic.detectMimeType(filePath) | |
return mimeType | |
} | |
const validateMimeType = async ( | |
dataOrFilePath: Buffer | string, | |
allowedMimeTypes: string[] | |
): Promise<{ | |
isValid: boolean | |
mimeType: string | |
allowedMimeTypes: string[] | |
}> => { | |
let filePath = '' | |
if (typeof dataOrFilePath === 'string') { | |
filePath = dataOrFilePath | |
} else if (dataOrFilePath instanceof Buffer) { | |
const fileId = Math.random().toString(36).substring(7) | |
const tempPath = path.join(os.tmpdir(), fileId) | |
fs.writeFileSync(tempPath, dataOrFilePath) | |
filePath = tempPath | |
} | |
const mimeType = await getMimeType(filePath) | |
const isValid = allowedMimeTypes.includes(mimeType) | |
if (dataOrFilePath instanceof Buffer) { | |
fs.unlinkSync(filePath) | |
} | |
return { isValid, mimeType, allowedMimeTypes } | |
} | |
const getExtension = async ( | |
dataOrMimeType: Buffer | string | |
) => { | |
let mimeType = '' | |
if (typeof dataOrMimeType === 'string') { | |
mimeType = dataOrMimeType | |
} else if (dataOrMimeType instanceof Buffer) { | |
mimeType = await getMimeType(dataOrMimeType) | |
} | |
return extension(mimeType) | |
} | |
export default { | |
getMimeType, | |
validateMimeType, | |
getExtension | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import magicLib from '@/lib/magic.lib' | |
import { NextFunction, Request, Response } from 'express' | |
interface FileValidationArgs { | |
field: string | |
allowedMimeTypes: string[] | |
strict?: boolean | |
} | |
interface FileValidationOptions { | |
minFieldsRequired?: number | |
} | |
const fileValidator = ( | |
args: FileValidationArgs[], | |
options?: FileValidationOptions | |
) => { | |
return async ( | |
req: Request, | |
res: Response, | |
next: NextFunction | |
) => { | |
const files: Express.Multer.File[] = [] | |
if (req.file) { | |
files.push(req.file) | |
} | |
if (req.files) { | |
if (Array.isArray(req.files)) { | |
files.push(...req.files) | |
} else { | |
const fields = Object.keys(req.files) | |
const minRequired = | |
options?.minFieldsRequired ?? fields.length | |
console.log(req.files) | |
console.log({ fields, minRequired }) | |
if (minRequired > fields.length) { | |
return res.status(400).json({ | |
message: `Any of the ${minRequired} files are required.`, | |
requiredFields: args.map((item) => ({ | |
field: item.field, | |
allowedMimeTypes: item.allowedMimeTypes | |
})) | |
}) | |
} | |
for (const { field, strict = true } of args) { | |
if (req.files[field]) { | |
const fieldFiles = req.files[field] | |
const isNotArray = !Array.isArray(fieldFiles) | |
if (isNotArray && strict) { | |
return res.status(400).json({ | |
message: `File ${field} is required.` | |
}) | |
} | |
if (!isNotArray) { | |
files.push(...fieldFiles) | |
} | |
} | |
} | |
} | |
} | |
for (const file of files) { | |
const validation = args.find( | |
(arg) => arg.field === file.fieldname | |
) | |
if (!validation) { | |
return res | |
.status(400) | |
.json({ message: 'Invalid file field' }) | |
} | |
const result = await magicLib.validateMimeType( | |
file.buffer, | |
validation?.allowedMimeTypes | |
) | |
if (!result.isValid) { | |
return res | |
.status(400) | |
.json({ message: 'Invalid file type.', result }) | |
} | |
} | |
return next() | |
} | |
} | |
export default fileValidator |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment