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
8 changes: 8 additions & 0 deletions lib/types/multipart.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,14 @@ function Multipart (boy, cfg) {
}

Multipart.prototype.write = function (chunk, cb) {
// After the closing delimiter Dicer is no longer writable. A later
// chunk can still carry an RFC 2046 epilogue; ignore it so the write
// callback still runs instead of hanging on a destroyed parser.
if (!this.parser.writable) {
cb()
return
}

const r = this.parser.write(chunk)
if (r && !this._pause) {
cb()
Expand Down
67 changes: 67 additions & 0 deletions test/types-multipart-epilogue.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use strict'

const { test } = require('node:test')
const { setImmediate: nextTick } = require('node:timers/promises')
const Busboy = require('..')

function multipartBody (boundary, extra) {
return '--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="field"\r\n' +
'\r\n' +
'value\r\n' +
'--' + boundary + '--\r\n' +
(extra || '')
}

test('emits finish when an RFC 2046 epilogue arrives in a later write', async (t) => {
const boundary = 'boundary'
const busboy = new Busboy({
headers: { 'content-type': 'multipart/form-data; boundary=' + boundary }
})

const fields = []
busboy.on('field', (name, value) => fields.push([name, value]))

const finished = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('finish was never emitted'))
}, 1000)
busboy.once('finish', () => {
clearTimeout(timer)
resolve()
})
busboy.once('error', (err) => {
clearTimeout(timer)
reject(err)
})
})

busboy.write(Buffer.from(multipartBody(boundary), 'utf8'))
await nextTick()
busboy.write(Buffer.from('epilogue', 'utf8'))
busboy.end()

await finished
t.assert.deepStrictEqual(fields, [['field', 'value']])
})

test('still emits finish when the epilogue is in the same chunk as the closing delimiter', async (t) => {
const boundary = 'boundary'
const busboy = new Busboy({
headers: { 'content-type': 'multipart/form-data; boundary=' + boundary }
})

const fields = []
busboy.on('field', (name, value) => fields.push([name, value]))

const finished = new Promise((resolve, reject) => {
busboy.once('finish', resolve)
busboy.once('error', reject)
})

busboy.write(Buffer.from(multipartBody(boundary, 'epilogue'), 'utf8'))
busboy.end()

await finished
t.assert.deepStrictEqual(fields, [['field', 'value']])
})