diff --git a/server/api/helpers/attachments/process-uploaded-file.js b/server/api/helpers/attachments/process-uploaded-file.js index e4779698..786c7797 100644 --- a/server/api/helpers/attachments/process-uploaded-file.js +++ b/server/api/helpers/attachments/process-uploaded-file.js @@ -2,10 +2,11 @@ const fs = require('fs'); const path = require('path'); const rimraf = require('rimraf'); const moveFile = require('move-file'); -const filenamify = require('filenamify'); const { v4: uuid } = require('uuid'); const sharp = require('sharp'); +const filenamify = require('../../../utils/filenamify'); + module.exports = { inputs: { file: { @@ -17,7 +18,6 @@ module.exports = { async fn(inputs) { const dirname = uuid(); - // FIXME: https://github.com/sindresorhus/filenamify/issues/13 const filename = filenamify(inputs.file.filename); const rootPath = path.join(sails.config.custom.attachmentsPath, dirname); diff --git a/server/package-lock.json b/server/package-lock.json index 6487f43c..5d4126f3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,6 @@ "bcrypt": "^5.1.1", "dotenv": "^16.4.5", "dotenv-cli": "^7.4.2", - "filenamify": "^4.3.0", "jsonwebtoken": "^9.0.2", "knex": "^3.1.0", "lodash": "^4.17.21", @@ -2918,30 +2917,6 @@ "node": ">=10" } }, - "node_modules/filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "dependencies": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7320,25 +7295,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-outer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7462,25 +7418,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/trim-repeated/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", diff --git a/server/package.json b/server/package.json index 115e152c..00f682a6 100644 --- a/server/package.json +++ b/server/package.json @@ -30,7 +30,6 @@ "bcrypt": "^5.1.1", "dotenv": "^16.4.5", "dotenv-cli": "^7.4.2", - "filenamify": "^4.3.0", "jsonwebtoken": "^9.0.2", "knex": "^3.1.0", "lodash": "^4.17.21", diff --git a/server/utils/filenamify.js b/server/utils/filenamify.js new file mode 100644 index 00000000..7674af11 --- /dev/null +++ b/server/utils/filenamify.js @@ -0,0 +1,76 @@ +/* https://github.com/sindresorhus/filename-reserved-regex */ + +function filenameReservedRegex() { + return /[<>:"/\\|?*\u0000-\u001F]/g; // eslint-disable-line no-control-regex +} + +function windowsReservedNameRegex() { + return /^(con|prn|aux|nul|com\d|lpt\d)$/i; +} + +/* https://github.com/sindresorhus/filenamify */ + +// Doesn't make sense to have longer filenames +const MAX_FILENAME_LENGTH = 100; + +const reRelativePath = /^\.+(\\|\/)|^\.+$/; +const reTrailingPeriods = /\.+$/; + +function filenamify(string, options = {}) { + const reControlChars = /[\u0000-\u001F\u0080-\u009F]/g; // eslint-disable-line no-control-regex + const reRepeatedReservedCharacters = /([<>:"/\\|?*\u0000-\u001F]){2,}/g; // eslint-disable-line no-control-regex + + if (typeof string !== 'string') { + throw new TypeError('Expected a string'); + } + + const replacement = options.replacement === undefined ? '!' : options.replacement; + + if (filenameReservedRegex().test(replacement) && reControlChars.test(replacement)) { + throw new Error('Replacement string cannot contain reserved filename characters'); + } + + /* eslint-disable no-param-reassign */ + if (replacement.length > 0) { + string = string.replace(reRepeatedReservedCharacters, '$1'); + } + + string = string.normalize('NFD'); + string = string.replace(reRelativePath, replacement); + string = string.replace(filenameReservedRegex(), replacement); + string = string.replace(reControlChars, replacement); + string = string.replace(reTrailingPeriods, ''); + + if (replacement.length > 0) { + const startedWithDot = string[0] === '.'; + + // We removed the whole filename + if (!startedWithDot && string[0] === '.') { + string = replacement + string; + } + + // We removed the whole extension + if (string[string.length - 1] === '.') { + string += replacement; + } + } + + string = windowsReservedNameRegex().test(string) ? string + replacement : string; + const allowedLength = + typeof options.maxLength === 'number' ? options.maxLength : MAX_FILENAME_LENGTH; + if (string.length > allowedLength) { + const extensionIndex = string.lastIndexOf('.'); + if (extensionIndex === -1) { + string = string.slice(0, allowedLength); + } else { + const filename = string.slice(0, extensionIndex); + const extension = string.slice(extensionIndex); + string = filename.slice(0, Math.max(1, allowedLength - extension.length)) + extension; + } + } + /* eslint-enable no-param-reassign */ + + return string; +} + +module.exports = filenamify;