Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added resources/UNICORN-UPPERCASE.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'])
)
}
Expand Down
41 changes: 40 additions & 1 deletion tests/body_parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
81 changes: 81 additions & 0 deletions tests/multipart.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
packageFileSize,
unicornFilePath,
unicornNoExtFilePath,
unicornUppercaseFilePath,
} from './helpers.ts'

const BASE_URL = new URL('./tmp/', import.meta.url)
Expand Down Expand Up @@ -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<string, MultipartFile | MultipartFile[]> = 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<string, MultipartFile | MultipartFile[]> = null
const server = createServer(async (req, res) => {
Expand Down Expand Up @@ -928,6 +965,50 @@ test.group('Multipart', () => {
assert.lengthOf(report.errors, 0)
})

test('validates uppercase extension', async ({ assert }) => {
let files: null | Record<string, MultipartFile | MultipartFile[]> = 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<string, MultipartFile | MultipartFile[]> = null

Expand Down