Repository navigation
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
@fastify/[email protected]
Plugin version
No response
Node.js version
v24.18.1
Operating system
Linux
Operating system version (i.e. 20.04, 11.3, 10)
ubuntu 26.04
Description
Busboy never emits finish if any bytes are written after the closing boundary delimiter (--boundary--) in a separate write() call.
RFC 2046 allows an epilogue after the closing delimiter. In a streamed request the epilogue can arrive in a later TCP segment, so it reaches busboy as its own write() after the body has already been fully parsed.
What happens in that case:
fieldfires as expectedfinishis never emittederroris never emitted either, so the stream just hangs
If the same trailing bytes are part of the same chunk as the body, everything works. The hang only happens when they arrive in a later tick.
Steps to reproduce:
import { Busboy } from "@fastify/busboy";
import { setImmediate as nextTick } from "node:timers/promises";
const boundary = "boundary";
const busboy = new Busboy({
headers: { "content-type": `multipart/form-data; boundary=${boundary}` },
});
busboy.on("field", (name, value) => {
console.log("field:", name, "=", value);
});
async function run() {
// A complete, valid multipart body, ending with the closing delimiter `--boundary--`.
const body =
`--${boundary}\r\n` +
`Content-Disposition: form-data; name="field"\r\n` +
`\r\n` +
`value\r\n` +
`--${boundary}--\r\n`;
// Write the whole valid body first.
busboy.write(body);
// Yield once, so busboy finishes parsing and its internal Dicer ends/destroys,
// then write the trailing bytes (RFC 2046 "epilogue"). This is what a streamed
// request produces when the epilogue arrives in a later TCP segment.
await nextTick();
busboy.write("epilogue");
busboy.end();
}
const timer = setTimeout(() => {
console.log("HANG (finish never fired)");
process.exit(1);
}, 2000);
busboy.on("finish", () => {
clearTimeout(timer);
console.log("finish");
process.exit(0);
});
busboy.on("error", (err) => {
clearTimeout(timer);
console.log("error:", err.message);
process.exit(1);
});
run();Link to code that reproduces the bug
inside description
Expected Behavior
Busboy should emit finish after end(). Bytes after the closing delimiter are an epilogue and should be ignored.
If busboy intends to reject them instead, it should emit error rather than hang silently.
Activity