Ghost/core/server/web/api/middleware/upload.js

128 lines
3.8 KiB
JavaScript
Raw Normal View History

const path = require('path');
const os = require('os');
const multer = require('multer');
const fs = require('fs-extra');
const errors = require('@tryghost/errors');
const config = require('../../../../shared/config');
const tpl = require('@tryghost/tpl');
const logging = require('@tryghost/logging');
const messages = {
db: {
missingFile: 'Please select a database file to import.',
invalidFile: 'Unsupported file. Please try any of the following formats: {extensions}'
},
redirects: {
missingFile: 'Please select a JSON file.',
invalidFile: 'Please select a valid JSON file to import.'
},
routes: {
missingFile: 'Please select a YAML file.',
invalidFile: 'Please select a valid YAML file to import.'
},
themes: {
missingFile: 'Please select a theme.',
invalidFile: 'Please select a valid zip file.'
},
images: {
missingFile: 'Please select an image.',
invalidFile: 'Please select a valid image.'
},
icons: {
missingFile: 'Please select an icon.',
invalidFile: 'Icon must be a square .ico or .png file between 60px 1,000px, under 100kb.'
}
};
const enabledClear = config.get('uploadClear') || true;
const upload = multer({dest: os.tmpdir()});
const deleteSingleFile = file => fs.unlink(file.path).catch(err => logging.error(err));
const single = name => (req, res, next) => {
const singleUpload = upload.single(name);
singleUpload(req, res, (err) => {
if (err) {
return next(err);
}
if (enabledClear) {
const deleteFiles = () => {
res.removeListener('finish', deleteFiles);
res.removeListener('close', deleteFiles);
if (!req.disableUploadClear) {
if (req.files) {
return req.files.forEach(deleteSingleFile);
}
if (req.file) {
return deleteSingleFile(req.file);
}
}
};
if (!req.disableUploadClear) {
res.on('finish', deleteFiles);
res.on('close', deleteFiles);
}
}
next();
});
};
const checkFileExists = (fileData) => {
return !!(fileData.mimetype && fileData.path);
};
const checkFileIsValid = (fileData, types, extensions) => {
const type = fileData.mimetype;
if (types.includes(type) && extensions.includes(fileData.ext)) {
return true;
}
return false;
};
const validation = function (options) {
const type = options.type;
// if we finish the data/importer logic, we forward the request to the specified importer
return function uploadValidation(req, res, next) {
const extensions = (config.get('uploads')[type] && config.get('uploads')[type].extensions) || [];
const contentTypes = (config.get('uploads')[type] && config.get('uploads')[type].contentTypes) || [];
req.file = req.file || {};
req.file.name = req.file.originalname;
req.file.type = req.file.mimetype;
// Check if a file was provided
if (!checkFileExists(req.file)) {
return next(new errors.ValidationError({
message: tpl(messages[type].missingFile)
}));
}
req.file.ext = path.extname(req.file.name).toLowerCase();
// Check if the file is valid
if (!checkFileIsValid(req.file, contentTypes, extensions)) {
return next(new errors.UnsupportedMediaTypeError({
message: tpl(messages[type].invalidFile, {extensions: extensions})
}));
}
next();
};
};
module.exports = {
single,
validation
};
// Exports for testing only
module.exports._test = {
checkFileExists,
checkFileIsValid
};