check if image is faulty in enhanceFile

This commit is contained in:
Marc-Roig 2022-08-04 12:56:58 +02:00
parent 096dba7983
commit d61104de72
3 changed files with 56 additions and 20 deletions

View File

@ -4,11 +4,10 @@
*/
const fs = require('fs');
const { join } = require('path');
const { ApplicationError } = require('@strapi/utils').errors;
const sharp = require('sharp');
const { getService } = require('../utils');
const { bytesToKbytes } = require('../utils/file');
const { bytesToKbytes, writableStreamDiscard } = require('../utils/file');
const FORMATS_TO_PROCESS = ['jpeg', 'png', 'webp', 'tiff', 'svg', 'gif'];
const FORMATS_TO_OPTIMIZE = ['jpeg', 'png', 'webp', 'tiff'];
@ -47,12 +46,7 @@ const THUMBNAIL_RESIZE_OPTIONS = {
const resizeFileTo = async (file, options, { name, hash }) => {
const filePath = join(file.tmpWorkingDirectory, hash);
try {
await writeStreamToFile(file.getStream().pipe(sharp().resize(options)), filePath);
} catch (err) {
throw new ApplicationError('File is not a valid image');
}
await writeStreamToFile(file.getStream().pipe(sharp().resize(options)), filePath);
const newFile = {
name,
hash,
@ -108,11 +102,7 @@ const optimize = async file => {
}
const filePath = join(file.tmpWorkingDirectory, `optimized-${file.hash}`);
try {
await writeStreamToFile(file.getStream().pipe(transformer), filePath);
} catch {
throw new ApplicationError('File is not a valid image');
}
await writeStreamToFile(file.getStream().pipe(transformer), filePath);
newFile.getStream = () => fs.createReadStream(filePath);
}
@ -190,6 +180,20 @@ const isSupportedImage = (...args) => {
return isOptimizableImage(...args);
};
/**
* Applies a simple image transformation to see if the image is faulty/corrupted.
*/
const isFaultyImage = file =>
new Promise(resolve => {
file
.getStream()
.pipe(sharp().rotate())
.on('error', () => resolve(true))
.pipe(writableStreamDiscard())
.on('error', () => resolve(true))
.on('close', () => resolve(false));
});
const isOptimizableImage = async file => {
let format;
try {
@ -216,6 +220,7 @@ const isImage = async file => {
module.exports = () => ({
isSupportedImage,
isFaultyImage,
isOptimizableImage,
isImage,
getDimensions,

View File

@ -22,6 +22,7 @@ const { NotFoundError } = require('@strapi/utils').errors;
const { MEDIA_UPDATE, MEDIA_CREATE, MEDIA_DELETE } = webhookUtils.webhookEvents;
const { ApplicationError } = require('@strapi/utils/lib/errors');
const { FILE_MODEL_UID } = require('../constants');
const { getService } = require('../utils');
const { bytesToKbytes } = require('../utils/file');
@ -106,7 +107,7 @@ module.exports = ({ strapi }) => ({
return entity;
},
async enhanceFile(file, fileInfo = {}, metas = {}) {
async enhanceAndValidateFile(file, fileInfo = {}, metas = {}) {
const currentFile = await this.formatFileInfo(
{
filename: file.name,
@ -121,14 +122,29 @@ module.exports = ({ strapi }) => ({
);
currentFile.getStream = () => fs.createReadStream(file.path);
const { optimize, isOptimizableImage } = strapi.plugin('upload').service('image-manipulation');
const { optimize, isImage, isFaultyImage, isOptimizableImage } = strapi
.plugin('upload')
.service('image-manipulation');
if (!(await isOptimizableImage(currentFile))) {
return currentFile;
if (await isImage(currentFile)) {
if (await isFaultyImage(currentFile)) {
throw new ApplicationError('File is not a valid image');
}
if (!(await isOptimizableImage(currentFile))) {
return currentFile;
}
}
return optimize(currentFile);
},
// TODO V5: remove enhanceFile
async enhanceFile(file, fileInfo = {}, metas = {}) {
process.emitWarning(
'[deprecated] In future versions, `enhanceFile` will be removed. Replace it with `enhanceAndValidateFile` instead.'
);
return this.enhanceAndValidateFile(file, fileInfo, metas);
},
async upload({ data, files }, { user } = {}) {
// create temporary folder to store files for stream manipulation
const tmpWorkingDirectory = await createAndAssignTmpWorkingDirectoryToFiles(files);
@ -142,7 +158,7 @@ module.exports = ({ strapi }) => ({
const fileInfoArray = Array.isArray(fileInfo) ? fileInfo : [fileInfo];
const doUpload = async (file, fileInfo) => {
const fileData = await this.enhanceFile(file, fileInfo, metas);
const fileData = await this.enhanceAndValidateFile(file, fileInfo, metas);
return this.uploadFileAndPersist(fileData, { user });
};
@ -266,7 +282,7 @@ module.exports = ({ strapi }) => ({
try {
const { fileInfo } = data;
fileData = await this.enhanceFile(file, fileInfo);
fileData = await this.enhanceAndValidateFile(file, fileInfo);
// keep a constant hash and extension so the file url doesn't change when the file is replaced
_.assign(fileData, {
@ -386,7 +402,7 @@ module.exports = ({ strapi }) => ({
try {
const enhancedFiles = await Promise.all(
arr.map(file => {
return this.enhanceFile(
return this.enhanceAndValidateFile(
file,
{ folder: apiUploadFolder.id },
{

View File

@ -2,6 +2,7 @@
/**
* Utils file containing file treatment utils
*/
const { Writable } = require('stream');
const bytesToKbytes = bytes => Math.round((bytes / 1000) * 100) / 100;
@ -26,8 +27,22 @@ const getStreamSize = stream =>
stream.resume();
});
/**
* Create a writeable Node.js stream that discards received data.
* Useful for testing, draining a stream of data, etc.
*/
function writableStreamDiscard(options) {
return new Writable({
...options,
write(chunk, encding, callback) {
setImmediate(callback);
},
});
}
module.exports = {
streamToBuffer,
bytesToKbytes,
getStreamSize,
writableStreamDiscardData: writableStreamDiscard,
};