Skip to content

Commit 8245fce

Browse files
authored
fix: manage big file uploads (#389)
1 parent 3c0563f commit 8245fce

File tree

2 files changed

+75
-7
lines changed

2 files changed

+75
-7
lines changed

index.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -444,17 +444,25 @@ function fastifyMultipart (fastify, options, done) {
444444
return this._buf
445445
}
446446
const fileChunks = []
447+
let err
447448
for await (const chunk of this.file) {
448449
fileChunks.push(chunk)
449450

450451
if (throwFileSizeLimit && this.file.truncated) {
451-
const err = new RequestFileTooLargeError()
452+
err = new RequestFileTooLargeError()
452453
err.part = this
453454

454455
onError(err)
455-
throw err
456+
fileChunks.length = 0
456457
}
457458
}
459+
if (err) {
460+
// throwing in the async iterator will
461+
// cause the file.destroy() to be called
462+
// The stream has already been managed by
463+
// busboy instead
464+
throw err
465+
}
458466
this._buf = Buffer.concat(fileChunks)
459467
return this._buf
460468
}
@@ -541,10 +549,6 @@ function fastifyMultipart (fastify, options, done) {
541549
let part
542550
while ((part = await parts()) != null) {
543551
if (part.file) {
544-
// part.file.truncated is true when a configured file size limit is reached
545-
if (part.file.truncated && throwFileSizeLimit) {
546-
throw new RequestFileTooLargeError()
547-
}
548552
return part
549553
}
550554
}

test/multipart-fileLimit.test.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict'
22

3+
const fs = require('fs')
34
const crypto = require('crypto')
45
const test = require('tap').test
56
const FormData = require('form-data')
@@ -49,7 +50,7 @@ test('should throw fileSize limitation error when consuming the stream', async f
4950
method: 'POST'
5051
}
5152

52-
const randomFileBuffer = Buffer.alloc(600000)
53+
const randomFileBuffer = Buffer.alloc(600_000)
5354
crypto.randomFillSync(randomFileBuffer)
5455

5556
const req = http.request(opts)
@@ -67,6 +68,69 @@ test('should throw fileSize limitation error when consuming the stream', async f
6768
}
6869
})
6970

71+
test('should throw fileSize limitation error when consuming the stream MBs', async function (t) {
72+
t.plan(4)
73+
74+
const fastify = Fastify()
75+
t.teardown(fastify.close.bind(fastify))
76+
77+
fastify.register(multipart, {
78+
throwFileSizeLimit: true,
79+
limits: {
80+
fileSize: 5_000_000 // 5MB
81+
}
82+
})
83+
84+
fastify.post('/', async function (req, reply) {
85+
t.ok(req.isMultipart())
86+
87+
const part = await req.file()
88+
t.pass('the file is not consumed yet')
89+
90+
try {
91+
await part.toBuffer()
92+
t.fail('it should throw')
93+
} catch (error) {
94+
t.ok(error)
95+
reply.send(error)
96+
}
97+
})
98+
99+
await fastify.listen({ port: 0 })
100+
101+
// request
102+
const form = new FormData()
103+
const opts = {
104+
hostname: '127.0.0.1',
105+
port: fastify.server.address().port,
106+
path: '/',
107+
headers: form.getHeaders(),
108+
method: 'POST'
109+
}
110+
111+
const randomFileBuffer = Buffer.alloc(15_000_000)
112+
crypto.randomFillSync(randomFileBuffer)
113+
114+
const tmpFile = 'test/random-file'
115+
fs.writeFileSync(tmpFile, randomFileBuffer)
116+
117+
const req = http.request(opts)
118+
form.append('upload', fs.createReadStream(tmpFile))
119+
120+
form.pipe(req)
121+
122+
try {
123+
const [res] = await once(req, 'response')
124+
t.equal(res.statusCode, 413)
125+
res.resume()
126+
await once(res, 'end')
127+
128+
fs.unlinkSync(tmpFile)
129+
} catch (error) {
130+
t.error(error, 'request')
131+
}
132+
})
133+
70134
test('should NOT throw fileSize limitation error when consuming the stream', async function (t) {
71135
t.plan(5)
72136

0 commit comments

Comments
 (0)