fastify / fastify/busboy

`finish` is never emitted when bytes arrive after the closing boundary in a separate write

Open
#228 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
139
Forks
30
PR merge metrics
No merged PRs in 30d

Description

### Prerequisites

- [x] I have written a descriptive issue title
- [x] I have searched existing issues to ensure the bug has not already been reported

### Fastify version

@fastify/busboy@3.2.0

### 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:

- `field` fires as expected
- `finish` is never emitted
- `error` is 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:

```js
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.

Contributor guide

Open the contributing guide

Research direction

Start at the Busboy write/end entry points and the Dicer lifecycle described in the reproduction, focusing on what happens when a later write contains epilogue bytes. Add a regression test for the separate-write case and verify that end() emits finish without emitting error.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.