Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor busboy constructor - add esm #61

Merged
merged 17 commits into from
Dec 4, 2021
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Changelog

Major changes since the last busboy release (0.3.1):
Major changes since the last busboy release (0.31):

# 1.0.0 - TBD, 2021

* Busboy is importable as ESM (#61)
* autoDestroy is by default false to avoid regressions when upgrading from node 12 to node 14 (#9)
* TypeScript types are now included in the package itself (#13)
* Non-deprecated Buffer creation is used (#8, #10)
* Error on non-number limit rather than ignoring (#7)
Expand All @@ -12,5 +14,6 @@ Major changes since the last busboy release (0.3.1):
* Using the native TextDecoder and the package 'text-decoding' for fallback if nodejs does not support the requested encoding (#50)
* Add isPartAFile-option, to make the file-detection configurable (#53)
* Empty Parts will not hang the process (#55)
* FileStreams also provide the property `bytesRead`
* FileStreams also provide the property `bytesRead` (#51)
* add and expose headerSize limit (#64)
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ Busboy methods

* **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers.

* **autoDestroy** - _boolean_ - Whether this stream should automatically call .destroy() on itself after ending. (Default: false).

* **highWaterMark** - _integer_ - highWaterMark to use for this Busboy instance (Default: WritableStream default).

* **fileHwm** - _integer_ - highWaterMark to use for file streams (Default: ReadableStream default).
Expand Down Expand Up @@ -259,6 +261,12 @@ Busboy methods

* The constructor can throw errors:

* **Unsupported content type: $type** - The `Content-Type` isn't one Busboy can parse.
* **Busboy expected an options-Object.** - Busboy expected an Object as first parameters.

* **Busboy expected an options-Object with headers-attribute.** - The first parameter is lacking of a headers-attribute.

* **Limit $limit is not a valid number** - Busboy expected the desired limit to be of type number. Busboy throws this Error to prevent a potential security issue by falling silently back to the Busboy-defaults. Potential source for this Error can be the direct use of environment variables without transforming them to the type number.
kibertoad marked this conversation as resolved.
Show resolved Hide resolved

* **Unsupported Content-Type.** - The `Content-Type` isn't one Busboy can parse.

* **Missing Content-Type** - The provided headers don't include `Content-Type` at all.
* **Missing Content-Type-header.** - The provided headers don't include `Content-Type` at all.
4 changes: 2 additions & 2 deletions lib/main.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import * as http from 'http';
import { Readable, Writable } from 'stream';

declare const busboy: BusboyConstructor;
export default busboy
export const Busboy: BusboyConstructor;
export default Busboy

export interface BusboyConfig {
/**
Expand Down
88 changes: 47 additions & 41 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
const WritableStream = require('stream').Writable
const { inherits } = require('util')

const { parseParams } = require('./utils')
const MultipartParser = require('./types/multipart')
const UrlencodedParser = require('./types/urlencoded')
const parseParams = require('./utils').parseParams

function Busboy (opts) {
Uzlopak marked this conversation as resolved.
Show resolved Hide resolved
if (!(this instanceof Busboy)) { return new Busboy(opts) }
if (opts.highWaterMark !== undefined) { WritableStream.call(this, { autoDestroy: false, highWaterMark: opts.highWaterMark }) } else { WritableStream.call(this, { autoDestroy: false }) }

if (typeof opts !== 'object') {
Uzlopak marked this conversation as resolved.
Show resolved Hide resolved
throw new TypeError('Busboy expected an options-Object.')
}
if (typeof opts.headers !== 'object') {
throw new TypeError('Busboy expected an options-Object with headers-attribute.')
}
if (typeof opts.headers['content-type'] !== 'string') {
throw new TypeError('Missing Content-Type-header.')
}

const {
headers,
...streamOptions
} = opts

this.opts = {
autoDestroy: false,
...streamOptions
}
WritableStream.call(this, this.opts)

this._done = false
this._parser = undefined
this._parser = this.getParserByHeaders(headers)
this._finished = false

this.opts = opts
if (opts.headers && typeof opts.headers['content-type'] === 'string') { this.parseHeaders(opts.headers) } else { throw new Error('Missing Content-Type') }
}
inherits(Busboy, WritableStream)

Expand All @@ -29,46 +48,33 @@ Busboy.prototype.emit = function (ev) {
WritableStream.prototype.emit.apply(this, arguments)
}

Busboy.prototype.parseHeaders = function (headers) {
this._parser = undefined
if (headers['content-type']) {
const parsed = parseParams(headers['content-type'])
let matched; let type
for (var i = 0; i < TYPES.length; ++i) { // eslint-disable-line no-var
type = TYPES[i]
if (typeof type.detect === 'function') { matched = type.detect(parsed) } else { matched = type.detect.test(parsed[0]) }
if (matched) { break }
}
if (matched) {
const cfg = {
limits: this.opts.limits,
isPartAFile: this.opts.isPartAFile,
headers: headers,
parsedConType: parsed,
highWaterMark: undefined,
fileHwm: undefined,
defCharset: undefined,
preservePath: false
}
if (this.opts.highWaterMark) { cfg.highWaterMark = this.opts.highWaterMark }
if (this.opts.fileHwm) { cfg.fileHwm = this.opts.fileHwm }
cfg.defCharset = this.opts.defCharset
cfg.preservePath = this.opts.preservePath
this._parser = type(this, cfg)
return
}
Busboy.prototype.getParserByHeaders = function (headers) {
const parsed = parseParams(headers['content-type'])

const cfg = {
defCharset: this.opts.defCharset,
fileHwm: this.opts.fileHwm,
headers: headers,
highWaterMark: this.opts.highWaterMark,
isPartAFile: this.opts.isPartAFile,
limits: this.opts.limits,
parsedConType: parsed,
preservePath: this.opts.preservePath
}
throw new Error('Unsupported content type: ' + headers['content-type'])

if (MultipartParser.detect.test(parsed[0])) {
return new MultipartParser(this, cfg)
}
if (UrlencodedParser.detect.test(parsed[0])) {
return new UrlencodedParser(this, cfg)
}
throw new Error('Unsupported Content-Type.')
}

Busboy.prototype._write = function (chunk, encoding, cb) {
if (!this._parser) { return cb(new Error('Not ready to parse. Missing Content-Type?')) }
this._parser.write(chunk, cb)
}

const TYPES = [
require('./types/multipart'),
require('./types/urlencoded')
]

module.exports = Busboy
module.exports.default = Busboy
module.exports.Busboy = Busboy
46 changes: 46 additions & 0 deletions test/busboy-constructor.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const Busboy = require('../lib/main')
const { expect } = require('chai')

describe('busboy-constructor', () => {
it('should throw an Error if no options are provided', () => {
expect(() => new Busboy()).to.throw('Busboy expected an options-Object.')
})
it('should throw an Error if options does not contain headers', () => {
expect(() => new Busboy({})).to.throw('Busboy expected an options-Object with headers-attribute.')
})
it('if busboy is called without new-operator, still creates a busboy instance', () => {
const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } })
expect(busboyInstance).to.be.instanceOf(Busboy)
})
it('should throw an Error if content-type is not set', () => {
expect(() => new Busboy({ headers: {} })).to.throw('Missing Content-Type-header.')
})
it('should throw an Error if content-type is unsupported', () => {
expect(() => new Busboy({ headers: { 'content-type': 'unsupported' } })).to.throw('Unsupported Content-Type.')
})
it('should not throw an Error if content-type is multipart', () => {
expect(() => new Busboy({ headers: { 'content-type': 'multipart/form-data' } })).to.not.throw('Unsupported Content-Type.')
})
it('should not throw an Error if content-type is urlencoded', () => {
expect(() => new Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } })).to.not.throw('Unsupported Content-Type.')
})
it('if busboy is called without stream options autoDestroy is set to false', () => {
const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } })
expect(busboyInstance._writableState.autoDestroy).to.be.equal(false)
})
it('if busboy is called with invalid value for stream option highWaterMark we should throw', () => {
expect(() => Busboy({ highWaterMark: 'not_allowed_value_for_highWaterMark', headers: { 'content-type': 'application/x-www-form-urlencoded' } })).to.throw('not_allowed_value_for_highWaterMark')
})
it('if busboy is called with stream options and autoDestroy:true, autoDestroy should be set to true', () => {
const busboyInstance = Busboy({ autoDestroy: true, headers: { 'content-type': 'application/x-www-form-urlencoded' } })
expect(busboyInstance._writableState.autoDestroy).to.be.equal(true)
})
it('busboy should be initialized with private attribute _done set as false', () => {
const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } })
expect(busboyInstance._done).to.be.equal(false)
})
it('busboy should be initialized with private attribute _finished set as false', () => {
const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } })
expect(busboyInstance._finished).to.be.equal(false)
})
})
4 changes: 4 additions & 0 deletions test/types/main.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BusboyDefault, { BusboyConstructor, BusboyConfig, BusboyHeaders, Busboy, BusboyEvents, BusboyFileStream } from '../..';
import {expectError, expectType} from "tsd";
import BusboyESM from "../..";

// test type exports
type Constructor = BusboyConstructor;
Expand All @@ -8,6 +9,9 @@ type Headers = BusboyHeaders;
type Events = BusboyEvents;
type BB = Busboy;

expectType<Busboy>(new BusboyESM({ headers: { 'content-type': 'foo' } }));
expectType<Busboy>(new Busboy({ headers: { 'content-type': 'foo' } }));

expectError(new BusboyDefault({}));
const busboy = BusboyDefault({ headers: { 'content-type': 'foo' } }); // $ExpectType Busboy
new BusboyDefault({ headers: { 'content-type': 'foo' } }); // $ExpectType Busboy
Expand Down