diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd45fc..d0ffb65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,4 @@ Major changes since the last busboy release (0.31): * Tests were converted to Mocha (#11, #12, #22, #23) * Add isPartAFile-option, to make the file-detection configurable (#53) * Empty Parts will not hang the process (#55) +* FileStreams also provide the property `bytesRead` diff --git a/README.md b/README.md index e9ebc41..c5e2340 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ 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. * 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. * **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new non-file field found. diff --git a/benchmarks/package.json b/benchmarks/package.json index 55ed418..9dd5153 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -11,8 +11,8 @@ }, "scripts": { "install-node": "nvm install 17.2.0 && nvm install 16.13.1 && nvm install 14.18.2 && nvm install 12.22.7", - "benchmark-busboy": "node busboy/executioner.js -c 0", - "benchmark-fastify": "node busboy/executioner.js -c 1", + "benchmark-busboy": "node busboy/executioner.js -c 0 -p medium", + "benchmark-fastify": "node busboy/executioner.js -c 1 -p medium", "benchmark-all": "npm run benchmark-busboy -- -p high && npm run benchmark-fastify -- -p high", "benchmark-all-medium": "npm run benchmark-busboy -- -p medium && npm run benchmark-fastify -- -p medium", "benchmark-all-low": "npm run benchmark-busboy -- -p low && npm run benchmark-fastify -- -p low", diff --git a/lib/main.d.ts b/lib/main.d.ts index bf50a57..bb87da6 100644 --- a/lib/main.d.ts +++ b/lib/main.d.ts @@ -5,7 +5,7 @@ /// import * as http from 'http'; -import {Readable, Writable} from 'stream'; +import { Readable, Writable } from 'stream'; declare const busboy: BusboyConstructor; export default busboy @@ -49,7 +49,7 @@ export interface BusboyConfig { * Various limits on incoming data. */ limits?: - | { + | { /** * Max field name size (in bytes) * @default 100 bytes @@ -86,11 +86,22 @@ export interface BusboyConfig { */ headerPairs?: number | undefined; } - | undefined; + | undefined; } export type BusboyHeaders = { 'content-type': string } & http.IncomingHttpHeaders; +export interface BusboyFileStream extends + Readable { + + truncated: boolean; + + /** + * The number of bytes that have been read so far. + */ + bytesRead: number; +} + export interface Busboy extends Writable { addListener(event: Event, listener: BusboyEvents[Event]): this; @@ -138,7 +149,7 @@ export interface BusboyEvents { */ file: ( fieldname: string, - stream: Readable, + stream: BusboyFileStream, filename: string, transferEncoding: string, mimeType: string, diff --git a/lib/types/multipart.js b/lib/types/multipart.js index ac8b022..bbbc53a 100644 --- a/lib/types/multipart.js +++ b/lib/types/multipart.js @@ -195,12 +195,16 @@ function Multipart (boy, cfg) { onData = function (data) { if ((nsize += data.length) > fileSizeLimit) { - const extralen = (fileSizeLimit - (nsize - data.length)) + const extralen = fileSizeLimit - nsize + data.length if (extralen > 0) { file.push(data.slice(0, extralen)) } - file.emit('limit') file.truncated = true + file.bytesRead = fileSizeLimit part.removeAllListeners('data') + file.emit('limit') + return } else if (!file.push(data)) { self._pause = true } + + file.bytesRead = nsize } onEnd = function () { @@ -287,6 +291,8 @@ function FileStream (opts) { if (!(this instanceof FileStream)) { return new FileStream(opts) } ReadableStream.call(this, opts) + this.bytesRead = 0 + this.truncated = false } inherits(FileStream, ReadableStream) diff --git a/test/types-multipart.spec.js b/test/types-multipart.spec.js index b3dc23d..6e9cf4f 100644 --- a/test/types-multipart.spec.js +++ b/test/types-multipart.spec.js @@ -452,6 +452,8 @@ describe('types-multipart', () => { ++info[3] }).on('end', function () { info[2] = nb + assert(typeof (stream.bytesRead) === 'number', 'file.bytesRead is missing') + assert(stream.bytesRead === nb, 'file.bytesRead is not equal to filesize') if (stream.truncated) { ++info[3] } }) }) diff --git a/test/types/main.test-d.ts b/test/types/main.test-d.ts index f2a780b..0fe9125 100644 --- a/test/types/main.test-d.ts +++ b/test/types/main.test-d.ts @@ -1,6 +1,5 @@ -import BusboyDefault, { BusboyConstructor, BusboyConfig, BusboyHeaders, Busboy, BusboyEvents } from '../..'; +import BusboyDefault, { BusboyConstructor, BusboyConfig, BusboyHeaders, Busboy, BusboyEvents, BusboyFileStream } from '../..'; import {expectError, expectType} from "tsd"; -import {Readable} from "stream"; // test type exports type Constructor = BusboyConstructor; @@ -28,7 +27,7 @@ new BusboyDefault({ headers: { 'content-type': 'foo' }, isPartAFile: (fieldName, busboy.addListener('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname) - expectType(file); + expectType(file); expectType(filename); expectType(encoding); expectType(mimetype); @@ -58,7 +57,7 @@ busboy.on(Symbol('foo'), foo => { busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname); - expectType (file); + expectType (file); expectType (filename); expectType (encoding); expectType (mimetype); @@ -88,7 +87,7 @@ busboy.on(Symbol('foo'), foo => { busboy.once('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname); - expectType (file); + expectType (file); expectType (filename); expectType (encoding); expectType (mimetype); @@ -118,7 +117,7 @@ busboy.once(Symbol('foo'), foo => { busboy.removeListener('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname); - expectType (file); + expectType (file); expectType (filename); expectType (encoding); expectType (mimetype); @@ -148,7 +147,7 @@ busboy.removeListener(Symbol('foo'), foo => { busboy.off('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname); - expectType (file); + expectType (file); expectType (filename); expectType (encoding); expectType (mimetype); @@ -178,7 +177,7 @@ busboy.off(Symbol('foo'), foo => { busboy.prependListener('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname); - expectType (file); + expectType (file); expectType (filename); expectType (encoding); expectType (mimetype); @@ -208,7 +207,7 @@ busboy.prependListener(Symbol('foo'), foo => { busboy.prependOnceListener('file', (fieldname, file, filename, encoding, mimetype) => { expectType (fieldname); - expectType (file); + expectType (file); expectType (filename); expectType (encoding); expectType (mimetype);