feat: Improve mime type detection, limit image processing size

This commit is contained in:
Maksim Eltyshev
2025-12-19 17:24:22 +01:00
parent 9bf7e9de3d
commit fb5d5233bf
7 changed files with 150 additions and 20 deletions

View File

@@ -26,8 +26,10 @@
display: flex;
font-size: 20px;
font-weight: bold;
height: fit-content;
justify-content: center;
line-height: 1.2;
text-align: center;
width: fit-content;
}
}

View File

@@ -5,12 +5,12 @@
const fsPromises = require('fs').promises;
const { rimraf } = require('rimraf');
const { fileTypeFromFile } = require('file-type');
const { getEncoding } = require('istextorbinary');
const mime = require('mime');
const sharp = require('sharp');
const filenamify = require('../../../utils/filenamify');
const { MAX_SIZE_TO_GET_ENCODING } = require('../../../constants');
const { MAX_SIZE_TO_GET_ENCODING, MAX_SIZE_TO_PROCESS_AS_IMAGE } = require('../../../constants');
module.exports = {
inputs: {
@@ -24,7 +24,8 @@ module.exports = {
const fileManager = sails.hooks['file-manager'].getInstance();
const filename = filenamify(inputs.file.filename);
const mimeType = mime.getType(filename);
const fileType = await fileTypeFromFile(inputs.file.fd);
const { mime: mimeType = null } = fileType || {};
const { size } = inputs.file;
const { id: uploadedFileId } = await UploadedFile.qm.createOne({
@@ -65,7 +66,7 @@ module.exports = {
image: null,
};
if (!['image/svg+xml', 'application/pdf'].includes(mimeType)) {
if (mimeType && mimeType.startsWith('image/') && size <= MAX_SIZE_TO_PROCESS_AS_IMAGE) {
let image = sharp(buffer || filePath || inputs.file.fd, {
animated: true,
});

View File

@@ -5,9 +5,11 @@
const { v4: uuid } = require('uuid');
const { rimraf } = require('rimraf');
const mime = require('mime');
const { fileTypeFromFile } = require('file-type');
const sharp = require('sharp');
const { MAX_SIZE_TO_PROCESS_AS_IMAGE } = require('../../../constants');
module.exports = {
inputs: {
file: {
@@ -21,8 +23,13 @@ module.exports = {
},
async fn(inputs) {
const mimeType = mime.getType(inputs.file.filename);
if (['image/svg+xml', 'application/pdf'].includes(mimeType)) {
const fileManager = sails.hooks['file-manager'].getInstance();
const fileType = await fileTypeFromFile(inputs.file.fd);
const { mime: mimeType = null } = fileType || {};
const { size } = inputs.file;
if (!mimeType || !mimeType.startsWith('image/') || size > MAX_SIZE_TO_PROCESS_AS_IMAGE) {
await rimraf(inputs.file.fd);
throw 'fileIsNotImage';
}
@@ -47,11 +54,6 @@ module.exports = {
throw 'fileIsNotImage';
}
const fileManager = sails.hooks['file-manager'].getInstance();
const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
const size = originalBuffer.length;
const { id: uploadedFileId } = await UploadedFile.qm.createOne({
mimeType,
size,
@@ -60,6 +62,7 @@ module.exports = {
});
const dirPathSegment = `${sails.config.custom.backgroundImagesPathSegment}/${uploadedFileId}`;
const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
try {
await fileManager.save(

View File

@@ -5,9 +5,11 @@
const { v4: uuid } = require('uuid');
const { rimraf } = require('rimraf');
const mime = require('mime');
const { fileTypeFromFile } = require('file-type');
const sharp = require('sharp');
const { MAX_SIZE_TO_PROCESS_AS_IMAGE } = require('../../../constants');
module.exports = {
inputs: {
file: {
@@ -21,8 +23,13 @@ module.exports = {
},
async fn(inputs) {
const mimeType = mime.getType(inputs.file.filename);
if (['image/svg+xml', 'application/pdf'].includes(mimeType)) {
const fileManager = sails.hooks['file-manager'].getInstance();
const fileType = await fileTypeFromFile(inputs.file.fd);
const { mime: mimeType = null } = fileType || {};
const { size } = inputs.file;
if (!mimeType || !mimeType.startsWith('image/') || size > MAX_SIZE_TO_PROCESS_AS_IMAGE) {
await rimraf(inputs.file.fd);
throw 'fileIsNotImage';
}
@@ -47,11 +54,6 @@ module.exports = {
throw 'fileIsNotImage';
}
const fileManager = sails.hooks['file-manager'].getInstance();
const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
const size = originalBuffer.length;
const { id: uploadedFileId } = await UploadedFile.qm.createOne({
mimeType,
size,
@@ -60,6 +62,7 @@ module.exports = {
});
const dirPathSegment = `${sails.config.custom.userAvatarsPathSegment}/${uploadedFileId}`;
const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
try {
await fileManager.save(

View File

@@ -5,9 +5,11 @@ const AccessTokenSteps = {
const POSITION_GAP = 65536;
const MAX_SIZE_TO_GET_ENCODING = 8 * 1024 * 1024;
const MAX_SIZE_TO_PROCESS_AS_IMAGE = 64 * 1024 * 1024;
module.exports = {
AccessTokenSteps,
POSITION_GAP,
MAX_SIZE_TO_GET_ENCODING,
MAX_SIZE_TO_PROCESS_AS_IMAGE,
};

118
server/package-lock.json generated
View File

@@ -15,6 +15,7 @@
"dotenv-cli": "^7.4.4",
"escape-html": "^1.0.3",
"escape-markdown": "^1.0.4",
"file-type": "^21.1.1",
"fs-extra": "^11.3.3",
"i18n-2": "^0.7.3",
"ico-to-png": "^0.2.2",
@@ -1020,6 +1021,16 @@
"node": ">=18.0.0"
}
},
"node_modules/@borewit/text-codec": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
"integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@canvas/image-data": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.1.0.tgz",
@@ -2674,6 +2685,29 @@
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@tokenizer/inflate": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
"integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"token-types": "^6.1.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
@@ -5203,6 +5237,24 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-type": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz",
"integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==",
"license": "MIT",
"dependencies": {
"@tokenizer/inflate": "^0.4.1",
"strtok3": "^10.3.4",
"token-types": "^6.1.1",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -6059,6 +6111,26 @@
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -10693,6 +10765,22 @@
],
"license": "MIT"
},
"node_modules/strtok3": {
"version": "10.3.4",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
"integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/superagent": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz",
@@ -10983,6 +11071,24 @@
"node": ">=0.6"
}
},
"node_modules/token-types": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz",
"integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==",
"license": "MIT",
"dependencies": {
"@borewit/text-codec": "^0.1.0",
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
@@ -11180,6 +11286,18 @@
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
"integrity": "sha512-5gSP1liv10Gjp8cMEnFd6shzkL/D6W1uhXSFNCxDC+YI8+L8wkCYCbJ7n77Ezb4wE/xzMogecE+DtamEe9PZjg=="
},
"node_modules/uint8array-extras": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unbox-primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",

View File

@@ -52,6 +52,7 @@
"dotenv-cli": "^7.4.4",
"escape-html": "^1.0.3",
"escape-markdown": "^1.0.4",
"file-type": "^21.1.1",
"fs-extra": "^11.3.3",
"i18n-2": "^0.7.3",
"ico-to-png": "^0.2.2",