-
-
Couldn't load subscription status.
- Fork 116
Description
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the bug has not already been reported
Fastify version
5.6.1
Plugin version
9.2.1
Node.js version
22.17.1
Operating system
macOS
Operating system version (i.e. 20.04, 11.3, 10)
15.6.1
Description
When calling req.file() a malformed request can cause data.file.on('error') to throw before the promise resolves. This makes it impossible to listen for the error, and causes an Unhandled 'error' event aka uncaughtException and kills the node process if not handled at the process level.
I've created a minimal repro which is based on the usage in the README.MD
https://github.com/itslenny/fastify-multipart-crash-minimal-repro
Error Output
Additional Details
Adding any await before calling req.file() will cause an error to be emitted on data.file before the promise is resolved. This makes it impossible to listen for an error event causing a uncaughtException crash.
fastify.register(require('@fastify/multipart'))
fastify.post('/', async function (req, reply) {
// this line causes an uncaught exception, comment it out and it goes away
// it seems if there is any async operation before req.file() the file.on('error') fires before the promise resolves
// this causes an uncaught exception and exits node
await new Promise(r => setImmediate(r))
const data = await req.file()
// when the crash happens this line never logs (meaning the above promise never resolves)
console.log('AFTER req.file()')
})This is a curl command that triggers the crash
curl -X POST http://localhost:3000 \
-H "Content-Type: multipart/form-data; boundary=----MyBoundary" \
--data-binary $'------MyBoundary\r\nContent-Disposition: form-data; name="file"; filename="my-picture.jpg"\r\nContent-Type: image/jpeg\r\n\r\nABC'Link to code that reproduces the bug
https://github.com/itslenny/fastify-multipart-crash-minimal-repro
Expected Behavior
Prevent uncaught exception crash
Simply adding file.on('error', () => {}) to the top of onFile in index.js prevents the server from crashing, but does result in an exception that cannot be caught.
Allow listening for error before promise resolves
One option would be to pass in an onFile listener which would allow attaching a listener before the promise resolves
const data = await req.file({
onFile: (file) => {
file.on('error', err => {
// handle error at the app level
}
}
})Busboy considerations
The error that causes the crash is emitted here in busboy. If it also emitted on boy it could be listened to from that level instead.
Handling missed error
When this error is missed the stream is closed. So, as long as it's not "uncaught" before the promise resolves it can be handled in that way for this specific error state at least
if (body.closed || body.destroyed || body.readableEnded) {
throw ERRORS.NoContentProvided(new Error('Invalid multipart upload: stream closed prematurely'))
}Other options
I don't love any of these options. The ultimate goal would to be able to handle this error the request handler and emit an error response (e.g. 400 Bad Request). Currently the only option seems to be handling the uncaughtException at the process level