From 469d81f8a58a5acd1ac0dc6033c32a18302e3a4e Mon Sep 17 00:00:00 2001 From: Tobias Wolf Date: Fri, 19 Aug 2022 14:30:22 +0200 Subject: [PATCH] Separate uploaded file validation logic from the model "Attachments" --- models/attachments.js | 38 +++---------------------------- models/fileValidation.js | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 models/fileValidation.js diff --git a/models/attachments.js b/models/attachments.js index 8ae852248..3816a407a 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -1,15 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { FilesCollection } from 'meteor/ostrio:files'; -import { exec } from 'node:child_process'; -import { promisify } from 'node:util'; +import { isFileValid } from './fileValidation'; import { createBucket } from './lib/grid/createBucket'; import fs from 'fs'; -import FileType from 'file-type'; import path from 'path'; import { AttachmentStoreStrategyFilesystem, AttachmentStoreStrategyGridFs} from '/models/lib/attachmentStoreStrategy'; import FileStoreStrategyFactory, {moveToStorage, rename, STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS} from '/models/lib/fileStoreStrategy'; -let asyncExec; let attachmentUploadExternalProgram; let attachmentUploadMimeTypes = []; let attachmentUploadSize = 0; @@ -17,7 +14,6 @@ let attachmentBucket; let storagePath; if (Meteor.isServer) { - asyncExec = promisify(exec); attachmentBucket = createBucket('attachments'); if (process.env.ATTACHMENTS_UPLOAD_MIME_TYPES) { @@ -155,34 +151,7 @@ if (Meteor.isServer) { check(fileObjId, String); const fileObj = Attachments.findOne({_id: fileObjId}); - let isValid = true; - - if (attachmentUploadMimeTypes.length) { - const mimeTypeResult = Promise.await(FileType.fromFile(fileObj.path)); - - const mimeType = (mimeTypeResult ? mimeTypeResult.mime : fileObj.type); - const baseMimeType = mimeType.split('/', 1)[0]; - - isValid = attachmentUploadMimeTypes.includes(mimeType) || attachmentUploadMimeTypes.includes(baseMimeType + '/*') || attachmentUploadMimeTypes.includes('*'); - - if (!isValid) { - console.log("Validation of uploaded file failed: file " + fileObj.path + " - mimetype " + mimeType); - } - } - - if (attachmentUploadSize && fileObj.size > attachmentUploadSize) { - console.log("Validation of uploaded file failed: file " + fileObj.path + " - size " + fileObj.size); - isValid = false; - } - - if (isValid && attachmentUploadExternalProgram) { - Promise.await(asyncExec(attachmentUploadExternalProgram.replace("{file}", '"' + fileObj.path + '"'))); - isValid = fs.existsSync(fileObj.path); - - if (!isValid) { - console.log("Validation of uploaded file failed: file " + fileObj.path + " has been deleted externally"); - } - } + const isValid = Promise.await(isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram)); if (!isValid) { Attachments.remove(fileObjId); @@ -192,12 +161,11 @@ if (Meteor.isServer) { check(fileObjId, String); check(storageDestination, String); - Meteor.defer(() => Meteor.call('validateAttachment', fileObjId)); + Meteor.call('validateAttachment', fileObjId); const fileObj = Attachments.findOne({_id: fileObjId}); if (fileObj) { - console.debug("Validation of uploaded file completed: file " + fileObj.path + " - storage destination " + storageDestination); Meteor.defer(() => Meteor.call('moveAttachmentToStorage', fileObjId, storageDestination)); } }, diff --git a/models/fileValidation.js b/models/fileValidation.js new file mode 100644 index 000000000..5fa473e7c --- /dev/null +++ b/models/fileValidation.js @@ -0,0 +1,48 @@ +import { Meteor } from 'meteor/meteor'; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import fs from 'fs'; +import FileType from 'file-type'; + +let asyncExec; + +if (Meteor.isServer) { + asyncExec = promisify(exec); +} + +export async function isFileValid(fileObj, mimeTypesAllowed, sizeAllowed, externalCommandLine) { + let isValid = true; + + if (mimeTypesAllowed.length) { + const mimeTypeResult = await FileType.fromFile(fileObj.path); + + const mimeType = (mimeTypeResult ? mimeTypeResult.mime : fileObj.type); + const baseMimeType = mimeType.split('/', 1)[0]; + + isValid = mimeTypesAllowed.includes(mimeType) || mimeTypesAllowed.includes(baseMimeType + '/*') || mimeTypesAllowed.includes('*'); + + if (!isValid) { + console.log("Validation of uploaded file failed: file " + fileObj.path + " - mimetype " + mimeType); + } + } + + if (isValid && sizeAllowed && fileObj.size > sizeAllowed) { + console.log("Validation of uploaded file failed: file " + fileObj.path + " - size " + fileObj.size); + isValid = false; + } + + if (isValid && externalCommandLine) { + await asyncExec(externalCommandLine.replace("{file}", '"' + fileObj.path + '"')); + isValid = fs.existsSync(fileObj.path); + + if (!isValid) { + console.log("Validation of uploaded file failed: file " + fileObj.path + " has been deleted externally"); + } + } + + if (isValid) { + console.debug("Validation of uploaded file successful: file " + fileObj.path); + } + + return isValid; +}