diff --git a/client/src/components/attachments/Attachments/Item.module.scss b/client/src/components/attachments/Attachments/Item.module.scss index 64991fc5..9ee545bb 100644 --- a/client/src/components/attachments/Attachments/Item.module.scss +++ b/client/src/components/attachments/Attachments/Item.module.scss @@ -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; } } diff --git a/server/api/helpers/attachments/process-uploaded-file.js b/server/api/helpers/attachments/process-uploaded-file.js index f1146cc3..624ac63f 100644 --- a/server/api/helpers/attachments/process-uploaded-file.js +++ b/server/api/helpers/attachments/process-uploaded-file.js @@ -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, }); diff --git a/server/api/helpers/background-images/process-uploaded-file.js b/server/api/helpers/background-images/process-uploaded-file.js index 803d123a..2812d060 100644 --- a/server/api/helpers/background-images/process-uploaded-file.js +++ b/server/api/helpers/background-images/process-uploaded-file.js @@ -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( diff --git a/server/api/helpers/users/process-uploaded-avatar-file.js b/server/api/helpers/users/process-uploaded-avatar-file.js index f71c4787..28fe01a4 100644 --- a/server/api/helpers/users/process-uploaded-avatar-file.js +++ b/server/api/helpers/users/process-uploaded-avatar-file.js @@ -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( diff --git a/server/constants.js b/server/constants.js index 7505dc4a..8db88010 100644 --- a/server/constants.js +++ b/server/constants.js @@ -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, }; diff --git a/server/package-lock.json b/server/package-lock.json index 5e0e859e..355355af 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -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", diff --git a/server/package.json b/server/package.json index 60105b27..ac9499c8 100644 --- a/server/package.json +++ b/server/package.json @@ -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",