Skip to content
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ _Busboy_ is a _Writable_ stream
Busboy (special) events
-----------------------

* **file**(< _string_ >fieldname, < _ReadableStream_ >stream, < _string_ >filename, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new file form field found. `transferEncoding` contains the 'Content-Transfer-Encoding' value for the file stream. `mimeType` contains the 'Content-Type' value for the file stream.
* **file**(< _string_ >fieldname, < _ReadableStream_ >stream, < _string_ >filename, < _string_ >transferEncoding, < _string_ >mimeType, < _object_ >headers) - Emitted for each new file form field found. `transferEncoding` contains the 'Content-Transfer-Encoding' value for the file stream. `mimeType` contains the 'Content-Type' value for the file stream. `headers` contains the raw headers of the part (lower-cased keys, array values, original values such as the full `Content-Type` with its parameters).
* Note: if you listen for this event, you should always handle the `stream` no matter if you care about the file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents), otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any** incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically and safely discarded (these discarded files do still count towards `files` and `parts` limits).
* If a configured file size limit was reached, `stream` will both have a boolean property `truncated` (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens.
* The property `bytesRead` informs about the number of bytes that have been read so far.
Expand All @@ -198,7 +198,7 @@ busboy.on('file', (fieldname, stream) => {
})
```

* **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new non-file field found.
* **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType, < _object_ >headers) - Emitted for each new non-file field found. `headers` contains the raw headers of the part (see `file`).

* **partsLimit**() - Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted.

Expand Down
4 changes: 2 additions & 2 deletions lib/types/multipart.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function Multipart (boy, cfg) {
cb()
}
}
boy.emit('file', fieldname, file, filename, encoding, contype)
boy.emit('file', fieldname, file, filename, encoding, contype, header)

onData = function (data) {
if ((nsize += data.length) > fileSizeLimit) {
Expand Down Expand Up @@ -245,7 +245,7 @@ function Multipart (boy, cfg) {
onEnd = function () {
curField = undefined
if (buffer.length) { buffer = decodeText(buffer, 'binary', charset) }
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype)
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype, header)
--nends
checkFinished()
}
Expand Down
47 changes: 47 additions & 0 deletions test/part-headers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict'

const Busboy = require('..')

const { test } = require('node:test')

test('field and file events expose the raw part headers', (t, done) => {
t.plan(8)
const boundary = '----busboyHeaders'
const busboy = new Busboy({
headers: { 'content-type': 'multipart/form-data; boundary=' + boundary }
})

busboy.on('field', (name, val, nameTrunc, valTrunc, encoding, mimeType, headers) => {
t.assert.strictEqual(name, 'text')
t.assert.strictEqual(mimeType, 'text/plain')
t.assert.deepStrictEqual(headers['content-type'], ['Text/Plain; charset=UTF-8'])
t.assert.deepStrictEqual(headers['x-custom'], ['a', 'b'])
})
busboy.on('file', (name, stream, filename, encoding, mimeType, headers) => {
t.assert.strictEqual(mimeType, 'text/plain')
t.assert.deepStrictEqual(headers['content-type'], ['text/plain;charset=utf-16'])
t.assert.deepStrictEqual(headers['content-disposition'], ['form-data; name="f"; filename="a.txt"'])
stream.resume()
})
busboy.on('finish', () => {
t.assert.ok(true)
done()
})

busboy.end([
'--' + boundary,
'Content-Disposition: form-data; name="text"',
'Content-Type: Text/Plain; charset=UTF-8',
'X-Custom: a',
'X-Custom: b',
'',
'hello',
'--' + boundary,
'Content-Disposition: form-data; name="f"; filename="a.txt"',
'Content-Type: text/plain;charset=utf-16',
'',
'data',
'--' + boundary + '--',
''
].join('\r\n'))
})
6 changes: 6 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export interface BusboyConfig {

export type BusboyHeaders = { 'content-type': string } & http.IncomingHttpHeaders

export type BusboyPartHeaders = Record<string, string[]>

export interface BusboyFileStream extends
Readable {

Expand Down Expand Up @@ -194,13 +196,16 @@ export interface BusboyEvents {
*
* @param listener.transferEncoding Contains the 'Content-Transfer-Encoding' value for the file stream.
* @param listener.mimeType Contains the 'Content-Type' value for the file stream.
* @param listener.headers The raw headers of the part. Keys are lower-cased and each value is an array
* (repeated headers are preserved); values are not modified, e.g. `content-type` keeps its parameters and casing.
*/
file: (
fieldname: string,
stream: BusboyFileStream,
filename: string,
transferEncoding: string,
mimeType: string,
headers: BusboyPartHeaders,
) => void;
/**
* Emitted for each new non-file field found.
Expand All @@ -212,6 +217,7 @@ export interface BusboyEvents {
valueTruncated: boolean,
transferEncoding: string,
mimeType: string,
headers: BusboyPartHeaders,
) => void;
finish: () => void;
/**
Expand Down
35 changes: 23 additions & 12 deletions types/index.tst.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable import-x/no-duplicates */
/* eslint-disable no-new */
import BusboyDefault, { Busboy, BusboyFileStream } from '.'
import BusboyDefault, { Busboy, BusboyFileStream, BusboyPartHeaders } from '.'
import { expect } from 'tstyche'
import BusboyESM from '.'

Expand Down Expand Up @@ -60,7 +60,8 @@ new BusboyDefault({
isPartAFile: (fieldName, contentType, fileName) => fileName !== undefined
})

busboy.addListener('file', (fieldname, file, filename, encoding, mimetype) => {
busboy.addListener('file', (fieldname, file, filename, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(file).type.toBe<BusboyFileStream>()
expect(filename).type.toBe<string>()
Expand All @@ -69,7 +70,8 @@ busboy.addListener('file', (fieldname, file, filename, encoding, mimetype) => {
})
busboy.addListener(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand All @@ -93,7 +95,8 @@ busboy.on(Symbol('foo'), (foo) => {
expect(foo).type.toBe<any>()
})

busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
busboy.on('file', (fieldname, file, filename, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(file).type.toBe<BusboyFileStream>()
expect(filename).type.toBe<string>()
Expand All @@ -102,7 +105,8 @@ busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
})
busboy.on(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand All @@ -126,7 +130,8 @@ busboy.on(Symbol('foo'), (foo) => {
expect(foo).type.toBe<any>()
})

busboy.once('file', (fieldname, file, filename, encoding, mimetype) => {
busboy.once('file', (fieldname, file, filename, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(file).type.toBe<BusboyFileStream>()
expect(filename).type.toBe<string>()
Expand All @@ -135,7 +140,8 @@ busboy.once('file', (fieldname, file, filename, encoding, mimetype) => {
})
busboy.once(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand Down Expand Up @@ -171,7 +177,8 @@ busboy.removeListener(
)
busboy.removeListener(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand All @@ -195,7 +202,8 @@ busboy.removeListener(Symbol('foo'), (foo) => {
expect(foo).type.toBe<any>()
})

busboy.off('file', (fieldname, file, filename, encoding, mimetype) => {
busboy.off('file', (fieldname, file, filename, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(file).type.toBe<BusboyFileStream>()
expect(filename).type.toBe<string>()
Expand All @@ -204,7 +212,8 @@ busboy.off('file', (fieldname, file, filename, encoding, mimetype) => {
})
busboy.off(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand Down Expand Up @@ -240,7 +249,8 @@ busboy.prependListener(
)
busboy.prependListener(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand Down Expand Up @@ -276,7 +286,8 @@ busboy.prependOnceListener(
)
busboy.prependOnceListener(
'field',
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype, headers) => {
expect(headers).type.toBe<BusboyPartHeaders>()
expect(fieldname).type.toBe<string>()
expect(val).type.toBe<string>()
expect(fieldnameTruncated).type.toBe<boolean>()
Expand Down
Loading