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 fs = require('fs');
const { join } = require('path'); const { join } = require('path');
const { ApplicationError } = require('@strapi/utils').errors;
const sharp = require('sharp'); const sharp = require('sharp');
const { getService } = require('../utils'); 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_PROCESS = ['jpeg', 'png', 'webp', 'tiff', 'svg', 'gif'];
const FORMATS_TO_OPTIMIZE = ['jpeg', 'png', 'webp', 'tiff']; const FORMATS_TO_OPTIMIZE = ['jpeg', 'png', 'webp', 'tiff'];
@ -47,12 +46,7 @@ const THUMBNAIL_RESIZE_OPTIONS = {
const resizeFileTo = async (file, options, { name, hash }) => { const resizeFileTo = async (file, options, { name, hash }) => {
const filePath = join(file.tmpWorkingDirectory, hash); const filePath = join(file.tmpWorkingDirectory, hash);
try { await writeStreamToFile(file.getStream().pipe(sharp().resize(options)), filePath);
await writeStreamToFile(file.getStream().pipe(sharp().resize(options)), filePath);
} catch (err) {
throw new ApplicationError('File is not a valid image');
}
const newFile = { const newFile = {
name, name,
hash, hash,
@ -108,11 +102,7 @@ const optimize = async file => {
} }
const filePath = join(file.tmpWorkingDirectory, `optimized-${file.hash}`); const filePath = join(file.tmpWorkingDirectory, `optimized-${file.hash}`);
try { await writeStreamToFile(file.getStream().pipe(transformer), filePath);
await writeStreamToFile(file.getStream().pipe(transformer), filePath);
} catch {
throw new ApplicationError('File is not a valid image');
}
newFile.getStream = () => fs.createReadStream(filePath); newFile.getStream = () => fs.createReadStream(filePath);
} }
@ -190,6 +180,20 @@ const isSupportedImage = (...args) => {
return isOptimizableImage(...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 => { const isOptimizableImage = async file => {
let format; let format;
try { try {
@ -216,6 +220,7 @@ const isImage = async file => {
module.exports = () => ({ module.exports = () => ({
isSupportedImage, isSupportedImage,
isFaultyImage,
isOptimizableImage, isOptimizableImage,
isImage, isImage,
getDimensions, getDimensions,

View File

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

View File

@ -2,6 +2,7 @@
/** /**
* Utils file containing file treatment utils * Utils file containing file treatment utils
*/ */
const { Writable } = require('stream');
const bytesToKbytes = bytes => Math.round((bytes / 1000) * 100) / 100; const bytesToKbytes = bytes => Math.round((bytes / 1000) * 100) / 100;
@ -26,8 +27,22 @@ const getStreamSize = stream =>
stream.resume(); 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 = { module.exports = {
streamToBuffer, streamToBuffer,
bytesToKbytes, bytesToKbytes,
getStreamSize, getStreamSize,
writableStreamDiscardData: writableStreamDiscard,
}; };