From 14f332fff27876111faa4daf6f661f9013d580d9 Mon Sep 17 00:00:00 2001 From: Jan Stevens Date: Mon, 13 Oct 2025 14:28:15 +0200 Subject: [PATCH 1/2] fix: #72 allow case insensitive comparision of the extensions list --- resources/UNICORN-UPPERCASE.PNG | Bin 0 -> 36642 bytes src/multipart/validators/extensions.ts | 8 +-- tests/body_parser.spec.ts | 41 ++++++++++++- tests/helpers.ts | 3 + tests/multipart.spec.ts | 81 +++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 resources/UNICORN-UPPERCASE.PNG diff --git a/resources/UNICORN-UPPERCASE.PNG b/resources/UNICORN-UPPERCASE.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6169ed9acc3a123bd1d384a67955b81eb2d5ec86 GIT binary patch literal 36642 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 literal 0 HcmV?d00001 diff --git a/src/multipart/validators/extensions.ts b/src/multipart/validators/extensions.ts index 6770f93..408082e 100644 --- a/src/multipart/validators/extensions.ts +++ b/src/multipart/validators/extensions.ts @@ -41,7 +41,7 @@ export class ExtensionValidator { } this.validated = false - this.#allowedExtensions = extnames + this.#allowedExtensions = extnames?.map((ext) => ext.toLowerCase()) } /** @@ -63,7 +63,7 @@ export class ExtensionValidator { const suffix = this.#allowedExtensions!.length === 1 ? 'is' : 'are' const message = [ - `Invalid file extension ${this.#file.extname}.`, + `Invalid file extension ${this.#file.extname?.toLowerCase()}.`, `Only ${this.#allowedExtensions!.join(', ')} ${suffix} allowed`, ].join(' ') @@ -89,7 +89,7 @@ export class ExtensionValidator { /** * Valid extension type */ - if (this.#allowedExtensions!.includes(this.#file.extname)) { + if (this.#allowedExtensions!.includes(this.#file.extname.toLowerCase())) { return } @@ -105,7 +105,7 @@ export class ExtensionValidator { /** * Valid extension type */ - if (this.#allowedExtensions!.includes(this.#file.extname || '')) { + if (this.#allowedExtensions!.includes(this.#file.extname?.toLowerCase() || '')) { return } diff --git a/tests/body_parser.spec.ts b/tests/body_parser.spec.ts index 747af5b..2e0a0a2 100644 --- a/tests/body_parser.spec.ts +++ b/tests/body_parser.spec.ts @@ -26,7 +26,12 @@ import { AppFactory } from '@adonisjs/application/factories' import { Multipart } from '../src/multipart/main.ts' import { type MultipartFile } from '../src/multipart/file.ts' import { BodyParserMiddlewareFactory } from '../factories/middleware_factory.ts' -import { packageFilePath, packageFileSize, unicornFilePath } from './helpers.ts' +import { + packageFilePath, + packageFileSize, + unicornFilePath, + unicornUppercaseFilePath, +} from './helpers.ts' test.group('BodyParser Middleware', () => { test('do not parse get requests', async ({ assert }) => { @@ -1189,6 +1194,40 @@ test.group('BodyParser Middleware | multipart', () => { ]) }) + test('validate uppercase file extension', async ({ assert }) => { + const server = createServer(async (req, res) => { + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + const middleware = new BodyParserMiddlewareFactory().create() + + await middleware.handle(ctx, async () => { + res.writeHead(200, { 'content-type': 'application/json' }) + const pkgFile = ctx.request.file('image')! + pkgFile.sizeLimit = 100_000 + pkgFile.validate() + + pkgFile.allowedExtensions = ['png'] + pkgFile.validate() + + res.end( + JSON.stringify({ + tmpPath: pkgFile.tmpPath!, + size: pkgFile.size, + validated: pkgFile.validated, + isValid: pkgFile.isValid, + errors: pkgFile.errors, + }) + ) + }) + }) + + const { body } = await supertest(server).post('/').attach('image', unicornUppercaseFilePath) + assert.exists(body.tmpPath) + assert.isTrue(body.validated) + assert.isTrue(body.isValid) + }) + test('calling validate multiple times must be a noop', async ({ assert }) => { const server = createServer(async (req, res) => { const request = new RequestFactory().merge({ req, res }).create() diff --git a/tests/helpers.ts b/tests/helpers.ts index 3ac190f..4be990a 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -18,6 +18,9 @@ export const largePdfFile = fileURLToPath( export const xlsFilePath = fileURLToPath(new URL('../resources/sample.xls', import.meta.url)) export const xlsxFilePath = fileURLToPath(new URL('../resources/sample.xlsx', import.meta.url)) export const unicornFilePath = fileURLToPath(new URL('../resources/unicorn.png', import.meta.url)) +export const unicornUppercaseFilePath = fileURLToPath( + new URL('../resources/UNICORN-UPPERCASE.PNG', import.meta.url) +) export const unicornNoExtFilePath = fileURLToPath( new URL('../resources/unicorn-wo-ext', import.meta.url) ) diff --git a/tests/multipart.spec.ts b/tests/multipart.spec.ts index cd7889b..d72d147 100644 --- a/tests/multipart.spec.ts +++ b/tests/multipart.spec.ts @@ -32,6 +32,7 @@ import { packageFileSize, unicornFilePath, unicornNoExtFilePath, + unicornUppercaseFilePath, } from './helpers.ts' const BASE_URL = new URL('./tmp/', import.meta.url) @@ -639,6 +640,42 @@ test.group('Multipart', () => { ]) }) + test('does not report extension validation errors when file extension is uppercase', async ({ + assert, + }) => { + let files: null | Record = null + const server = createServer(async (req, res) => { + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + const multipart = new Multipart(ctx, { maxFields: 1000, limit: 100_000 }) + + multipart.onFile( + '*', + { + extnames: ['PNG'], + }, + (part, reporter) => { + return new Promise((resolve, reject) => { + part.on('error', reject) + part.on('end', resolve) + part.on('data', reporter) + }) + } + ) + + await multipart.process() + files = ctx.request['__raw_files'] || null + res.end() + }) + + await supertest(server).post('/').attach('image', unicornUppercaseFilePath) + + assert.property(files, 'image') + assert.isTrue(files!.image instanceof MultipartFile && files!.image.isValid) + assert.equal(files!.image instanceof MultipartFile && files!.image.state, 'consumed') + }) + test('do not run validations when deferValidations is set to true', async ({ assert }) => { let files: null | Record = null const server = createServer(async (req, res) => { @@ -928,6 +965,50 @@ test.group('Multipart', () => { assert.lengthOf(report.errors, 0) }) + test('validates uppercase extension', async ({ assert }) => { + let files: null | Record = null + const server = createServer(async (req, res) => { + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + const multipart = new Multipart(ctx, { maxFields: 1000, limit: 100_000 }) + + multipart.onFile( + '*', + { + extnames: ['PNG'], + }, + (part, reporter) => { + return new Promise((resolve, reject) => { + part.on('error', reject) + part.on('end', resolve) + part.on('data', reporter) + }) + } + ) + + try { + await multipart.process() + } catch (error) { + console.log(error) + } + files = ctx.request['__raw_files'] || null + res.end() + }) + + await supertest(server).post('/').attach('image', unicornUppercaseFilePath) + + assert.property(files, 'image') + assert.instanceOf(files!.image, MultipartFile) + + const image = files!.image as MultipartFile + + assert.isTrue(image.isValid) + assert.equal(image.state, 'consumed') + assert.equal(image.extname, 'PNG') + assert.lengthOf(image.errors, 0) + }) + test('process medium sized files', async ({ assert }) => { let files: null | Record = null From 445e4f4d7500364302cecaab5d6d02704514d1b9 Mon Sep 17 00:00:00 2001 From: Jan Stevens Date: Tue, 14 Oct 2025 08:52:13 +0200 Subject: [PATCH 2/2] fix: always return extension as lowercase --- src/multipart/validators/extensions.ts | 8 ++++---- src/utils.ts | 4 ++-- tests/multipart.spec.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/multipart/validators/extensions.ts b/src/multipart/validators/extensions.ts index 408082e..6770f93 100644 --- a/src/multipart/validators/extensions.ts +++ b/src/multipart/validators/extensions.ts @@ -41,7 +41,7 @@ export class ExtensionValidator { } this.validated = false - this.#allowedExtensions = extnames?.map((ext) => ext.toLowerCase()) + this.#allowedExtensions = extnames } /** @@ -63,7 +63,7 @@ export class ExtensionValidator { const suffix = this.#allowedExtensions!.length === 1 ? 'is' : 'are' const message = [ - `Invalid file extension ${this.#file.extname?.toLowerCase()}.`, + `Invalid file extension ${this.#file.extname}.`, `Only ${this.#allowedExtensions!.join(', ')} ${suffix} allowed`, ].join(' ') @@ -89,7 +89,7 @@ export class ExtensionValidator { /** * Valid extension type */ - if (this.#allowedExtensions!.includes(this.#file.extname.toLowerCase())) { + if (this.#allowedExtensions!.includes(this.#file.extname)) { return } @@ -105,7 +105,7 @@ export class ExtensionValidator { /** * Valid extension type */ - if (this.#allowedExtensions!.includes(this.#file.extname?.toLowerCase() || '')) { + if (this.#allowedExtensions!.includes(this.#file.extname || '')) { return } diff --git a/src/utils.ts b/src/utils.ts index 2efdb97..5261454 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -43,7 +43,7 @@ export async function getFileType( */ const magicType = await fileTypeFromBuffer(fileContents) if (magicType) { - return Object.assign({ ext: magicType.ext }, parseMimeType(magicType.mime)) + return Object.assign({ ext: magicType.ext.toLowerCase() }, parseMimeType(magicType.mime)) } return null @@ -61,7 +61,7 @@ export function computeFileTypeFromName( * and pull type/subtype from the headers content type. */ return Object.assign( - { ext: extname(clientName).replace(/^\./, '') }, + { ext: extname(clientName).replace(/^\./, '').toLowerCase() }, parseMimeType(headers['content-type']) ) } diff --git a/tests/multipart.spec.ts b/tests/multipart.spec.ts index d72d147..cfebb1f 100644 --- a/tests/multipart.spec.ts +++ b/tests/multipart.spec.ts @@ -653,7 +653,7 @@ test.group('Multipart', () => { multipart.onFile( '*', { - extnames: ['PNG'], + extnames: ['png'], }, (part, reporter) => { return new Promise((resolve, reject) => { @@ -976,7 +976,7 @@ test.group('Multipart', () => { multipart.onFile( '*', { - extnames: ['PNG'], + extnames: ['png'], }, (part, reporter) => { return new Promise((resolve, reject) => { @@ -1005,7 +1005,7 @@ test.group('Multipart', () => { assert.isTrue(image.isValid) assert.equal(image.state, 'consumed') - assert.equal(image.extname, 'PNG') + assert.equal(image.extname, 'png') assert.lengthOf(image.errors, 0) })