From 60c83b5ec7063d3861234dbd65103c71c659749a Mon Sep 17 00:00:00 2001 From: groenroos Date: Mon, 21 Feb 2022 00:59:00 +0000 Subject: [PATCH] #137: Support wildcards for filetype --- lib/Utils.js | 24 ++++++++++++++++++++++++ lib/Validation.js | 16 +++++++++++++--- test/lib/Utils.test.js | 25 +++++++++++++++++++++++++ test/lib/Validation.test.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/lib/Utils.js b/lib/Utils.js index 7fd717d..fb8875c 100644 --- a/lib/Utils.js +++ b/lib/Utils.js @@ -116,4 +116,28 @@ export default class Utils { return false; } } + + + /** + * Test a string against a rule that has asterisk wildcards + * + * @param {string} string String to be tested + * @param {string} rule Pattern to be tested against + * @returns {boolean} Whether or not the string matches the pattern + */ + matchWildcard(string, rule) { + const escapeRegex = string => string.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'); + return new RegExp(`^${String(rule).split('*').map(element => escapeRegex(element)).join('.*')}$`).test(String(string)); + } + + + /** + * Make sure the value passed is an array + * + * @param {any} array Value to be coerced + * @returns {array} Array + */ + coerceArray(array) { + return Array.isArray(array) ? array : [array]; + } } diff --git a/lib/Validation.js b/lib/Validation.js index 2619f8b..e16ba5a 100755 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -4,6 +4,10 @@ * Make sure the given data is valid. */ +/* Dependencies */ +import Utils from './Utils.js'; + + /** * The Validation class */ @@ -366,10 +370,16 @@ export default class Validation { validateFileType(file, key, rule) { if (rule.filetype) { /* Make sure it's an array */ - const acceptedTypes = Array.isArray(rule.filetype) ? rule.filetype : [rule.filetype]; + const acceptedTypes = new Utils().coerceArray(rule.filetype); + + /* The filetype indicators we want to compare against (mime, group or ext) */ + const filetypes = [file.mimetype, file.extension, file.group]; + + /* Ensure the file matches the given filetype */ + const match = acceptedTypes.some(accepted => filetypes.some(compared => new Utils().matchWildcard(compared, accepted))); - /* Ensure the file matches the given filetype (mime, group or ext) */ - if (!(acceptedTypes.includes(file.mimetype) || acceptedTypes.includes(file.extension) || acceptedTypes.includes(file.group))) { + /* If not, send error */ + if (!match) { this.errors.push({ status: '422', code: '2001', diff --git a/test/lib/Utils.test.js b/test/lib/Utils.test.js index eead411..564b535 100644 --- a/test/lib/Utils.test.js +++ b/test/lib/Utils.test.js @@ -84,6 +84,31 @@ test('converts values to true boolean', t => { t.falsy(t.context.utils.trueBoolean(null)); }); +test('matches string against wildcard pattern', t => { + t.true(t.context.utils.matchWildcard('string', 'str*')); + t.true(t.context.utils.matchWildcard('string', '*ing')); + t.true(t.context.utils.matchWildcard('string', '*tr*')); + + t.false(t.context.utils.matchWildcard('value', 'str*')); + t.false(t.context.utils.matchWildcard('value', '*ing')); + t.false(t.context.utils.matchWildcard('value', '*tr*')); + + t.true(t.context.utils.matchWildcard(5000, '5*')); + t.false(t.context.utils.matchWildcard(6000, '5*')); + + /* Even when the pattern has some other regex-y characters */ + t.true(t.context.utils.matchWildcard('et tu, brute?', 'et tu, *?')); + t.true(t.context.utils.matchWildcard('file.jpg', 'file.*')); + t.false(t.context.utils.matchWildcard('files', 'file.*')); +}); + +test('coerces any value into an array', t => { + t.deepEqual(t.context.utils.coerceArray('string'), ['string']); + t.deepEqual(t.context.utils.coerceArray(1), [1]); + t.deepEqual(t.context.utils.coerceArray(false), [false]); + t.deepEqual(t.context.utils.coerceArray([1, 2, 3]), [1, 2, 3]); +}); + test.after.always(t => { fs.chmodSync(path.join(__dirname, '../_data/inaccessible'), 0o755); diff --git a/test/lib/Validation.test.js b/test/lib/Validation.test.js index 64137af..964d010 100644 --- a/test/lib/Validation.test.js +++ b/test/lib/Validation.test.js @@ -430,6 +430,34 @@ test('invalidates invalid file against file type', t => { t.is(t.context.validator.errors.length, 6); }); +test('validates valid file against file type with wildcards', t => { + t.context.validator.validateFileType({ mimetype: 'image/jpeg' }, 'attachment', { filetype: 'image/*' }); + t.is(t.context.validator.errors.length, 0); + + t.context.validator.validateFileType({ mimetype: 'video/ogg' }, 'attachment', { filetype: '*/ogg' }); + t.is(t.context.validator.errors.length, 0); + + t.context.validator.validateFileType({ extension: 'jpg' }, 'attachment', { filetype: '*p*' }); + t.is(t.context.validator.errors.length, 0); + + t.context.validator.validateFileType({ group: 'image' }, 'attachment', { filetype: '*age' }); + t.is(t.context.validator.errors.length, 0); +}); + +test('invalidates invalid file against file type with wildcards', t => { + t.context.validator.validateFileType({ mimetype: 'video/ogg' }, 'attachment', { filetype: 'image/*' }); + t.is(t.context.validator.errors.length, 1); + + t.context.validator.validateFileType({ mimetype: 'image/jpeg' }, 'attachment', { filetype: '*/ogg' }); + t.is(t.context.validator.errors.length, 2); + + t.context.validator.validateFileType({ extension: 'gif' }, 'attachment', { filetype: '*p*' }); + t.is(t.context.validator.errors.length, 3); + + t.context.validator.validateFileType({ group: 'video' }, 'attachment', { filetype: '*age' }); + t.is(t.context.validator.errors.length, 4); +}); + /* validateFileMinwidth */