diff --git a/.nycrc b/.nycrc deleted file mode 100644 index f052d50..0000000 --- a/.nycrc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "all": true, - "reporter": [ - "lcovonly", - "html", - "text", - "text-summary" - ], - "exclude": [ - "bench", - "benchmarks", - "test", - "coverage", - ".eslintrc.js" - ], - "check-coverage": true, - "lines": 95, - "statements": 90, - "functions": 95, - "branches": 90 -} diff --git a/.taprc b/.taprc new file mode 100644 index 0000000..30a802b --- /dev/null +++ b/.taprc @@ -0,0 +1,4 @@ +files: + - test/**/*.test.js + +coverage: false \ No newline at end of file diff --git a/package.json b/package.json index 3288ee0..1329eb6 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "lint:everything": "npm run lint && npm run test:types", "lint:fix": "standard --fix", "lint:standard": "standard --verbose | snazzy", - "test:mocha": "mocha test", + "test:mocha": "tap", "test:types": "tsd", "test:coverage": "nyc npm run test", "test": "npm run test:mocha" @@ -37,15 +37,10 @@ "devDependencies": { "@types/node": "^20.1.0", "busboy": "^1.0.0", - "chai": "^4.3.6", - "eslint": "^8.23.0", - "eslint-config-standard": "^17.0.0", - "eslint-plugin-n": "^16.0.0", - "mocha": "^10.0.0", - "nyc": "^15.1.0", "photofinish": "^1.8.0", "snazzy": "^9.0.0", "standard": "^17.0.0", + "tap": "^16.3.8", "tsd": "^0.29.0", "typescript": "^5.0.2" }, diff --git a/test/busboy-constructor.spec.js b/test/busboy-constructor.spec.js deleted file mode 100644 index 5746323..0000000 --- a/test/busboy-constructor.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -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) - }) -}) diff --git a/test/busboy-constructor.test.js b/test/busboy-constructor.test.js new file mode 100644 index 0000000..8607789 --- /dev/null +++ b/test/busboy-constructor.test.js @@ -0,0 +1,75 @@ +'use strict' + +const Busboy = require('../lib/main') +const { test } = require('tap') + +test('busboy-constructor - should throw an Error if no options are provided', t => { + t.plan(1) + + t.throws(() => new Busboy(), new Error('Busboy expected an options-Object.')) +}) + +test('busboy-constructor - should throw an Error if options does not contain headers', t => { + t.plan(1) + + t.throws(() => new Busboy({}), new Error('Busboy expected an options-Object with headers-attribute.')) +}) + +test('busboy-constructor - if busboy is called without new-operator, still creates a busboy instance', t => { + t.plan(1) + + const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } }) + t.type(busboyInstance, Busboy) +}) + +test('busboy-constructor - should throw an Error if content-type is not set', t => { + t.plan(1) + + t.throws(() => new Busboy({ headers: {} }), new Error('Missing Content-Type-header.')) +}) + +test('busboy-constructor - should throw an Error if content-type is unsupported', t => { + t.plan(1) + + t.throws(() => new Busboy({ headers: { 'content-type': 'unsupported' } }), new Error('Unsupported Content-Type.')) +}) + +test('busboy-constructor - should not throw an Error if content-type is urlencoded', t => { + t.plan(1) + + t.doesNotThrow(() => new Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } })) +}) + +test('busboy-constructor - if busboy is called without stream options autoDestroy is set to false', t => { + t.plan(1) + + const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } }) + t.equal(busboyInstance._writableState.autoDestroy, false) +}) + +test('busboy-constructor - if busboy is called with invalid value for stream option highWaterMark we should throw', t => { + t.plan(1) + + t.throws(() => Busboy({ highWaterMark: 'not_allowed_value_for_highWaterMark', headers: { 'content-type': 'application/x-www-form-urlencoded' } }), new Error('not_allowed_value_for_highWaterMark')) +}) + +test('busboy-constructor - if busboy is called with stream options and autoDestroy:true, autoDestroy should be set to true', t => { + t.plan(1) + + const busboyInstance = Busboy({ autoDestroy: true, headers: { 'content-type': 'application/x-www-form-urlencoded' } }) + t.equal(busboyInstance._writableState.autoDestroy, true) +}) + +test('busboy-constructor - busboy should be initialized with private attribute _done set as false', t => { + t.plan(1) + + const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } }) + t.equal(busboyInstance._done, false) +}) + +test('busboy-constructor - busboy should be initialized with private attribute _finished set as false', t => { + t.plan(1) + + const busboyInstance = Busboy({ headers: { 'content-type': 'application/x-www-form-urlencoded' } }) + t.equal(busboyInstance._finished, false) +}) diff --git a/test/decoder.spec.js b/test/decoder.test.js similarity index 82% rename from test/decoder.spec.js rename to test/decoder.test.js index 99c551a..fa4ce69 100644 --- a/test/decoder.spec.js +++ b/test/decoder.test.js @@ -1,7 +1,10 @@ -const { assert, expect } = require('chai') +'use strict' + +const { test } = require('tap') const Decoder = require('../lib/utils/Decoder') -describe('Decoder', () => { +test('Decoder', t => { + const tests = [ { source: ['Hello world'], @@ -63,8 +66,13 @@ describe('Decoder', () => { expected: '5 + 5 = 10', what: 'Spaces and encoded plus' } - ].forEach((v) => { - it(v.what, () => { + ] + t.plan(tests.length + 1) + + tests.forEach((v) => { + t.test(v.what, t => { + t.plan(1) + const dec = new Decoder() let result = '' v.source.forEach(function (s) { @@ -73,16 +81,18 @@ describe('Decoder', () => { const msg = 'Decoded string mismatch.\n' + 'Saw: ' + result + '\n' + 'Expected: ' + v.expected - assert.deepEqual(result, v.expected, msg) + t.strictSame(result, v.expected, msg) }) }) - it('reset sets internal buffer to undefined', () => { + t.test('reset sets internal buffer to undefined', t => { + t.plan(2) + const dec = new Decoder() dec.write('Hello+world%2') - expect(dec.buffer).to.be.not.equal(undefined) + t.notSame(dec.buffer, undefined) dec.reset() - expect(dec.buffer).to.be.equal(undefined) + t.equal(dec.buffer, undefined) }) }) diff --git a/test/dicer-constructor.spec.js b/test/dicer-constructor.spec.js deleted file mode 100644 index b3a792b..0000000 --- a/test/dicer-constructor.spec.js +++ /dev/null @@ -1,14 +0,0 @@ -const { expect } = require('chai') -const Dicer = require('../deps/dicer/lib/Dicer') - -describe('dicer-constructor', () => { - it('should throw an Error when no options parameter is supplied to Dicer', () => { - expect(() => new Dicer()).to.throw('Boundary required') - }) - - it('without new operator a new dicer instance will be initialized', () => { - expect(Dicer({ - boundary: '----boundary' - })).to.be.instanceOf(Dicer) - }) -}) diff --git a/test/dicer-constructor.test.js b/test/dicer-constructor.test.js new file mode 100644 index 0000000..e0e6a6c --- /dev/null +++ b/test/dicer-constructor.test.js @@ -0,0 +1,22 @@ +'use strict' + +const { test } = require('tap') +const Dicer = require('../deps/dicer/lib/Dicer') + +test('dicer-constructor', t => { + t.plan(2) + + t.test('should throw an Error when no options parameter is supplied to Dicer', t => { + t.plan(1) + + t.throws(() => new Dicer(), new Error('Boundary required')) + }) + + t.test('without new operator a new dicer instance will be initialized', t => { + t.plan(1) + + t.type(Dicer({ + boundary: '----boundary' + }), Dicer) + }) +}) diff --git a/test/dicer-endfinish.spec.js b/test/dicer-endfinish.test.js similarity index 84% rename from test/dicer-endfinish.spec.js rename to test/dicer-endfinish.test.js index 3106fcd..4718076 100644 --- a/test/dicer-endfinish.spec.js +++ b/test/dicer-endfinish.test.js @@ -1,8 +1,14 @@ +'use strict' + const Dicer = require('../deps/dicer/lib/Dicer') -const { assert } = require('chai') +const { test } = require('tap') + +test('dicer-endfinish', t => { + t.plan(1) + + t.test('should properly handle finish', t => { + t.plan(4) -describe('dicer-endfinish', () => { - it('should properly handle finish', (done) => { const CRLF = '\r\n' const boundary = 'boundary' @@ -39,12 +45,12 @@ describe('dicer-endfinish', () => { setImmediate(afterWrite) } function finishListener () { - assert(firedEnd, 'Failed to end before finishing') + t.ok(firedEnd, 'end before finishing') firedFinish = true test2() } function afterWrite () { - assert(firedFinish, 'Failed to finish') + t.ok(firedFinish, 'Failed to finish') } let isPausePush = true @@ -77,9 +83,8 @@ describe('dicer-endfinish', () => { setImmediate(pauseAfterEnd) } function pauseAfterEnd () { - assert(firedPauseCallback, 'Failed to call callback after pause') - assert(firedPauseFinish, 'Failed to finish after pause') - done() + t.ok(firedPauseCallback, 'Called callback after pause') + t.ok(firedPauseFinish, 'Finish after pause') } function pauseFinish () { firedPauseFinish = true diff --git a/test/dicer-export.spec.js b/test/dicer-export.spec.js deleted file mode 100644 index 0221a94..0000000 --- a/test/dicer-export.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -const { expect } = require('chai') -const { Dicer } = require('../lib/main') - -describe('dicer-export', () => { - it('without new operator a new dicer instance will be initialized', () => { - expect(Dicer({ - boundary: '----boundary' - })).to.be.instanceOf(Dicer) - }) - - it('with new operator a new dicer instance will be initialized', () => { - expect(new Dicer({ - boundary: '----boundary' - })).to.be.instanceOf(Dicer) - }) -}) diff --git a/test/dicer-export.test.js b/test/dicer-export.test.js new file mode 100644 index 0000000..05df4e6 --- /dev/null +++ b/test/dicer-export.test.js @@ -0,0 +1,24 @@ +'use strict' + +const { test } = require('tap') +const { Dicer } = require('../lib/main') + +test('dicer-export', t => { + t.plan(2) + + t.test('without new operator a new dicer instance will be initialized', t => { + t.plan(1) + + t.type(Dicer({ + boundary: '----boundary' + }), Dicer) + }) + + t.test('with new operator a new dicer instance will be initialized', t => { + t.plan(1) + + t.type(new Dicer({ + boundary: '----boundary' + }), Dicer) + }) +}) diff --git a/test/dicer-headerparser.spec.js b/test/dicer-headerparser.test.js similarity index 92% rename from test/dicer-headerparser.spec.js rename to test/dicer-headerparser.test.js index 0ac1838..73da283 100644 --- a/test/dicer-headerparser.spec.js +++ b/test/dicer-headerparser.test.js @@ -1,12 +1,14 @@ -const { assert } = require('chai') +'use strict' + +const { test } = require('tap') const HeaderParser = require('../deps/dicer/lib/HeaderParser') -describe('dicer-headerparser', () => { +test('dicer-headerparser', t => { const DCRLF = '\r\n\r\n' const MAXED_BUFFER = Buffer.allocUnsafe(128 * 1024) - MAXED_BUFFER.fill(0x41); // 'A' + MAXED_BUFFER.fill(0x41) // 'A' - [ + const tests = [ { source: DCRLF, expected: {}, @@ -26,7 +28,7 @@ describe('dicer-headerparser', () => { cfg: { maxHeaderPairs: 0 }, - expected: { }, + expected: {}, what: 'should enforce maxHeaderPairs of 0' }, { @@ -157,8 +159,14 @@ describe('dicer-headerparser', () => { expected: {}, what: 'Max header size (multiple chunk #2)' } - ].forEach(function (v) { - it(v.what, (done) => { + ] + + t.plan(tests.length) + + tests.forEach(function (v) { + t.test(v.what, t => { + t.plan(4) + const cfg = { ...v.cfg } @@ -167,9 +175,9 @@ describe('dicer-headerparser', () => { let fired = false parser.on('header', function (header) { - assert(!fired, `${v.what}: Header event fired more than once`) + t.ok(!fired, `${v.what}: Header event fired more than once`) fired = true - assert.deepEqual(header, + t.strictSame(header, v.expected, `${v.what}: Parsed result mismatch`) }) @@ -177,8 +185,8 @@ describe('dicer-headerparser', () => { v.source.forEach(function (s) { parser.push(s) }) - assert(fired, `${v.what}: Did not receive header from parser`) - done() + t.ok(fired, `${v.what}: Did not receive header from parser`) + t.pass() }) }) }) diff --git a/test/dicer-malformed-header.spec.js b/test/dicer-malformed-header.test.js similarity index 60% rename from test/dicer-malformed-header.spec.js rename to test/dicer-malformed-header.test.js index 713cb3b..c25ccdd 100644 --- a/test/dicer-malformed-header.spec.js +++ b/test/dicer-malformed-header.test.js @@ -1,14 +1,19 @@ +'use strict' + +const { test } = require('tap') const Dicer = require('../deps/dicer/lib/Dicer') -const { expect } = require('chai') -describe('dicer-malformed-header', () => { - it('should gracefully handle headers with leading whitespace', done => { +test('dicer-malformed-header', t => { + t.plan(1) + + t.test('should gracefully handle headers with leading whitespace', t => { + t.plan(3) const d = new Dicer({ boundary: '----WebKitFormBoundaryoo6vortfDzBsDiro' }) d.on('part', function (p) { p.on('header', function (header) { - expect(header).has.property(' content-disposition') - expect(header[' content-disposition']).to.be.eql(['form-data; name="bildbeschreibung"']) + t.hasProp(header, ' content-disposition') + t.strictSame(header[' content-disposition'], ['form-data; name="bildbeschreibung"']) }) p.on('data', function (data) { }) @@ -16,7 +21,7 @@ describe('dicer-malformed-header', () => { }) }) d.on('finish', function () { - done() + t.pass() }) d.write(Buffer.from('------WebKitFormBoundaryoo6vortfDzBsDiro\r\n Content-Disposition: form-data; name="bildbeschreibung"\r\n\r\n\r\n------WebKitFormBoundaryoo6vortfDzBsDiro--')) diff --git a/test/dicer-multipart-extra-trailer.spec.js b/test/dicer-multipart-extra-trailer.spec.js deleted file mode 100644 index a089a9e..0000000 --- a/test/dicer-multipart-extra-trailer.spec.js +++ /dev/null @@ -1,131 +0,0 @@ -const Dicer = require('../deps/dicer/lib/Dicer') -const assert = require('node:assert') -const fs = require('node:fs') -const path = require('node:path') -const inspect = require('node:util').inspect - -const FIXTURES_ROOT = path.join(__dirname, 'fixtures/') - -describe('dicer-multipart-extra-trailer', () => { - [ - { - source: 'many', - opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, - chsize: 16, - nparts: 7, - what: 'Extra trailer data pushed after finished' - } - ].forEach(function (v) { - it(v.what, (done) => { - const fixtureBase = FIXTURES_ROOT + v.source - let n = 0 - const buffer = Buffer.allocUnsafe(v.chsize) - const state = { parts: [] } - - const fd = fs.openSync(fixtureBase + '/original', 'r') - - const dicer = new Dicer(v.opts) - let error - let partErrors = 0 - let finishes = 0 - let trailerEmitted = false - - dicer.on('part', function (p) { - const part = { - body: undefined, - bodylen: 0, - error: undefined, - header: undefined - } - - p.on('header', function (h) { - part.header = h - }).on('data', function (data) { - // make a copy because we are using readSync which re-uses a buffer ... - const copy = Buffer.allocUnsafe(data.length) - data.copy(copy) - data = copy - if (!part.body) { part.body = [data] } else { part.body.push(data) } - part.bodylen += data.length - }).on('error', function (err) { - part.error = err - ++partErrors - }).on('end', function () { - if (part.body) { part.body = Buffer.concat(part.body, part.bodylen) } - state.parts.push(part) - }) - }).on('error', function (err) { - error = err - }).on('trailer', function (data) { - trailerEmitted = true - assert(data.toString() === 'Extra', 'trailer should contain the extra data') - }).on('finish', function () { - assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')) - assert(trailerEmitted, makeMsg(v.what, 'should have emitted trailer')) - - if (v.dicerError) { assert(error !== undefined, makeMsg(v.what, 'Expected error')) } else { assert(error === undefined, makeMsg(v.what, 'Unexpected error')) } - - if (v.events && v.events.indexOf('part') > -1) { - assert.equal(state.parts.length, - v.nparts, - makeMsg(v.what, - 'Part count mismatch:\nActual: ' + - state.parts.length + - '\nExpected: ' + - v.nparts)) - - if (!v.npartErrors) { v.npartErrors = 0 } - assert.equal(partErrors, - v.npartErrors, - makeMsg(v.what, - 'Part errors mismatch:\nActual: ' + - partErrors + - '\nExpected: ' + - v.npartErrors)) - - for (let i = 0, header, body; i < v.nparts; ++i) { - if (fs.existsSync(fixtureBase + '/part' + (i + 1))) { - body = fs.readFileSync(fixtureBase + '/part' + (i + 1)) - if (body.length === 0) { body = undefined } - } else { body = undefined } - assert.deepEqual(state.parts[i].body, - body, - makeMsg(v.what, - 'Part #' + (i + 1) + ' body mismatch')) - if (fs.existsSync(fixtureBase + '/part' + (i + 1) + '.header')) { - header = fs.readFileSync(fixtureBase + - '/part' + (i + 1) + '.header', 'binary') - header = JSON.parse(header) - } else { header = undefined } - assert.deepEqual(state.parts[i].header, - header, - makeMsg(v.what, - 'Part #' + (i + 1) + - ' parsed header mismatch:\nActual: ' + - inspect(state.parts[i].header) + - '\nExpected: ' + - inspect(header))) - } - } - done() - }) - - while (true) { - n = fs.readSync(fd, buffer, 0, buffer.length, null) - if (n === 0) { - setTimeout(function () { - dicer.write('\r\n\r\n\r\n') - dicer.end() - }, 50) - break - } - dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)) - } - fs.closeSync(fd) - }) - }) -}) - -function makeMsg (what, msg) { - return what + ': ' + msg -} diff --git a/test/dicer-multipart-extra-trailer.test.js b/test/dicer-multipart-extra-trailer.test.js new file mode 100644 index 0000000..335605a --- /dev/null +++ b/test/dicer-multipart-extra-trailer.test.js @@ -0,0 +1,82 @@ +'use strict' + +const { test } = require('tap') +const Dicer = require('../deps/dicer/lib/Dicer') +const fs = require('fs') +const path = require('path') + +const FIXTURES_ROOT = path.join(__dirname, 'fixtures/') + +test('dicer-multipart-extra-trailer', t => { + t.plan(1) + + t.test('Extra trailer data pushed after finished', t => { + t.plan(5) + const fixtureBase = FIXTURES_ROOT + 'many' + let n = 0 + const buffer = Buffer.allocUnsafe(16) + const state = { parts: [] } + + const fd = fs.openSync(fixtureBase + '/original', 'r') + + const dicer = new Dicer({ boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }) + let error + let finishes = 0 + let trailerEmitted = false + + dicer.on('part', function (p) { + const part = { + body: undefined, + bodylen: 0, + error: undefined, + header: undefined + } + + p.on('header', function (h) { + part.header = h + }).on('data', function (data) { + // make a copy because we are using readSync which re-uses a buffer ... + const copy = Buffer.allocUnsafe(data.length) + data.copy(copy) + data = copy + if (!part.body) { part.body = [data] } else { part.body.push(data) } + part.bodylen += data.length + }).on('error', function (err) { + part.error = err + t.fail() + }).on('end', function () { + if (part.body) { part.body = Buffer.concat(part.body, part.bodylen) } + state.parts.push(part) + }) + }).on('error', function (err) { + error = err + }).on('trailer', function (data) { + trailerEmitted = true + t.equal(data.toString(), 'Extra', 'trailer should contain the extra data') + }).on('finish', function () { + t.ok(finishes++ === 0, makeMsg('Extra trailer data pushed after finished', 'finish emitted multiple times')) + t.ok(trailerEmitted, makeMsg('Extra trailer data pushed after finished', 'should have emitted trailer')) + + t.ok(error === undefined, makeMsg('Extra trailer data pushed after finished', 'Unexpected error')) + + t.pass() + }) + + while (true) { + n = fs.readSync(fd, buffer, 0, buffer.length, null) + if (n === 0) { + setTimeout(function () { + dicer.write('\r\n\r\n\r\n') + dicer.end() + }, 50) + break + } + dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)) + } + fs.closeSync(fd) + }) +}) + +function makeMsg (what, msg) { + return what + ': ' + msg +} diff --git a/test/dicer-multipart-nolisteners.spec.js b/test/dicer-multipart-nolisteners.spec.js deleted file mode 100644 index a435abc..0000000 --- a/test/dicer-multipart-nolisteners.spec.js +++ /dev/null @@ -1,199 +0,0 @@ -const Dicer = require('../deps/dicer/lib/Dicer') -const assert = require('node:assert') -const fs = require('node:fs') -const path = require('node:path') -const inspect = require('node:util').inspect - -const FIXTURES_ROOT = path.join(__dirname, 'fixtures/') - -describe('dicer-multipart-nolisteners', () => { - [ - { - source: 'many', - opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, - chsize: 16, - nparts: 0, - what: 'No preamble or part listeners' - } - ].forEach(function (v) { - it(v.what, (done) => { - const fixtureBase = path.resolve(FIXTURES_ROOT, v.source) - let n = 0 - const buffer = Buffer.allocUnsafe(v.chsize) - const state = { done: false, parts: [], preamble: undefined } - - const fd = fs.openSync(fixtureBase + '/original', 'r') - - const dicer = new Dicer(v.opts) - let error - let partErrors = 0 - let finishes = 0 - - if (v.events && v.events.indexOf('preamble') > -1) { - dicer.on('preamble', function (p) { - const preamble = { - body: undefined, - bodylen: 0, - error: undefined, - header: undefined - } - - p.on('header', function (h) { - preamble.header = h - }).on('data', function (data) { - // make a copy because we are using readSync which re-uses a buffer ... - const copy = Buffer.allocUnsafe(data.length) - data.copy(copy) - data = copy - if (!preamble.body) { preamble.body = [data] } else { preamble.body.push(data) } - preamble.bodylen += data.length - }).on('error', function (err) { - preamble.error = err - }).on('end', function () { - if (preamble.body) { preamble.body = Buffer.concat(preamble.body, preamble.bodylen) } - if (preamble.body || preamble.header) { state.preamble = preamble } - }) - }) - } - if (v.events && v.events.indexOf('part') > -1) { - dicer.on('part', function (p) { - const part = { - body: undefined, - bodylen: 0, - error: undefined, - header: undefined - } - - p.on('header', function (h) { - part.header = h - }).on('data', function (data) { - // make a copy because we are using readSync which re-uses a buffer ... - const copy = Buffer.allocUnsafe(data.length) - data.copy(copy) - data = copy - if (!part.body) { part.body = [data] } else { part.body.push(data) } - part.bodylen += data.length - }).on('error', function (err) { - part.error = err - ++partErrors - }).on('end', function () { - if (part.body) { part.body = Buffer.concat(part.body, part.bodylen) } - state.parts.push(part) - }) - }) - } - dicer.on('error', function (err) { - error = err - }).on('finish', function () { - assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')) - - if (v.dicerError) { assert(error !== undefined, makeMsg(v.what, 'Expected error')) } else { assert(error === undefined, makeMsg(v.what, `Unexpected error: ${error}`)) } - - if (v.events && v.events.indexOf('preamble') > -1) { - let preamble - if (fs.existsSync(fixtureBase + '/preamble')) { - const prebody = fs.readFileSync(fixtureBase + '/preamble') - if (prebody.length) { - preamble = { - body: prebody, - bodylen: prebody.length, - error: undefined, - header: undefined - } - } - } - if (fs.existsSync(fixtureBase + '/preamble.header')) { - const prehead = JSON.parse(fs.readFileSync(fixtureBase + - '/preamble.header', 'binary')) - if (!preamble) { - preamble = { - body: undefined, - bodylen: 0, - error: undefined, - header: prehead - } - } else { preamble.header = prehead } - } - if (fs.existsSync(fixtureBase + '/preamble.error')) { - const err = new Error(fs.readFileSync(fixtureBase + - '/preamble.error', 'binary')) - if (!preamble) { - preamble = { - body: undefined, - bodylen: 0, - error: err, - header: undefined - } - } else { preamble.error = err } - } - - assert.deepEqual(state.preamble, - preamble, - makeMsg(v.what, - 'Preamble mismatch:\nActual:' + - inspect(state.preamble) + - '\nExpected: ' + - inspect(preamble))) - } - - if (v.events && v.events.indexOf('part') > -1) { - assert.equal(state.parts.length, - v.nparts, - makeMsg(v.what, - 'Part count mismatch:\nActual: ' + - state.parts.length + - '\nExpected: ' + - v.nparts)) - - if (!v.npartErrors) { v.npartErrors = 0 } - assert.equal(partErrors, - v.npartErrors, - makeMsg(v.what, - 'Part errors mismatch:\nActual: ' + - partErrors + - '\nExpected: ' + - v.npartErrors)) - - for (let i = 0, header, body; i < v.nparts; ++i) { - if (fs.existsSync(fixtureBase + '/part' + (i + 1))) { - body = fs.readFileSync(fixtureBase + '/part' + (i + 1)) - if (body.length === 0) { body = undefined } - } else { body = undefined } - assert.deepEqual(state.parts[i].body, - body, - makeMsg(v.what, - 'Part #' + (i + 1) + ' body mismatch')) - if (fs.existsSync(fixtureBase + '/part' + (i + 1) + '.header')) { - header = fs.readFileSync(fixtureBase + - '/part' + (i + 1) + '.header', 'binary') - header = JSON.parse(header) - } else { header = undefined } - assert.deepEqual(state.parts[i].header, - header, - makeMsg(v.what, - 'Part #' + (i + 1) + - ' parsed header mismatch:\nActual: ' + - inspect(state.parts[i].header) + - '\nExpected: ' + - inspect(header))) - } - } - done() - }) - - while (true) { - n = fs.readSync(fd, buffer, 0, buffer.length, null) - if (n === 0) { - dicer.end() - break - } - dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)) - } - fs.closeSync(fd) - }) - }) -}) - -function makeMsg (what, msg) { - return what + ': ' + msg -} diff --git a/test/dicer-multipart-nolisteners.test.js b/test/dicer-multipart-nolisteners.test.js new file mode 100644 index 0000000..1e311ba --- /dev/null +++ b/test/dicer-multipart-nolisteners.test.js @@ -0,0 +1,44 @@ +'use strict' + +const Dicer = require('../deps/dicer/lib/Dicer') +const { test } = require('tap') +const fs = require('fs') +const path = require('path') + +const FIXTURES_ROOT = path.join(__dirname, 'fixtures/') + +test('dicer-multipart-nolisteners', t => { + t.plan(1) + + t.test('No preamble or part listeners', t => { + t.plan(3) + const fixtureBase = path.resolve(FIXTURES_ROOT, 'many') + let n = 0 + const buffer = Buffer.allocUnsafe(16) + + const fd = fs.openSync(fixtureBase + '/original', 'r') + + const dicer = new Dicer({ boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }) + let error + let finishes = 0 + + dicer.on('error', function (err) { + error = err + }).on('finish', function () { + t.ok(finishes++ === 0, 'finish emitted multiple times') + + t.ok(error === undefined, `Unexpected error: ${error}`) + t.pass() + }) + + while (true) { + n = fs.readSync(fd, buffer, 0, buffer.length, null) + if (n === 0) { + dicer.end() + break + } + dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)) + } + fs.closeSync(fd) + }) +}) diff --git a/test/dicer-multipart.spec.js b/test/dicer-multipart.test.js similarity index 79% rename from test/dicer-multipart.spec.js rename to test/dicer-multipart.test.js index 8f3ec94..c35c4d0 100644 --- a/test/dicer-multipart.spec.js +++ b/test/dicer-multipart.test.js @@ -1,61 +1,70 @@ +'use strict' + const Dicer = require('../deps/dicer/lib/Dicer') const assert = require('node:assert') const fs = require('node:fs') const path = require('node:path') const inspect = require('node:util').inspect +const { test } = require('tap') const FIXTURES_ROOT = path.join(__dirname, 'fixtures/') -describe('dicer-multipart', () => { - [ - { - source: 'nested', - opts: { boundary: 'AaB03x' }, - chsize: 32, - nparts: 2, - what: 'One nested multipart' - }, - { - source: 'many', - opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, - chsize: 16, - nparts: 7, - what: 'Many parts' - }, - { - source: 'many-wrongboundary', - opts: { boundary: 'LOLOLOL' }, - chsize: 8, - nparts: 0, - dicerError: true, - what: 'Many parts, wrong boundary' - }, - { - source: 'many-noend', - opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, - chsize: 16, - nparts: 7, - npartErrors: 1, - dicerError: true, - what: 'Many parts, end boundary missing, 1 file open' - }, - { - source: 'nested-full', - opts: { boundary: 'AaB03x', headerFirst: true }, - chsize: 32, - nparts: 2, - what: 'One nested multipart with preceding header' - }, - { - source: 'nested-full', - opts: { headerFirst: true }, - chsize: 32, - nparts: 2, - setBoundary: 'AaB03x', - what: 'One nested multipart with preceding header, using setBoundary' - } - ].forEach(function (v) { - it(v.what, (done) => { +test('dicer-multipart', t => { + const tests = + [ + { + source: 'nested', + opts: { boundary: 'AaB03x' }, + chsize: 32, + nparts: 2, + what: 'One nested multipart' + }, + { + source: 'many', + opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, + chsize: 16, + nparts: 7, + what: 'Many parts' + }, + { + source: 'many-wrongboundary', + opts: { boundary: 'LOLOLOL' }, + chsize: 8, + nparts: 0, + dicerError: true, + what: 'Many parts, wrong boundary' + }, + { + source: 'many-noend', + opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, + chsize: 16, + nparts: 7, + npartErrors: 1, + dicerError: true, + what: 'Many parts, end boundary missing, 1 file open' + }, + { + source: 'nested-full', + opts: { boundary: 'AaB03x', headerFirst: true }, + chsize: 32, + nparts: 2, + what: 'One nested multipart with preceding header' + }, + { + source: 'nested-full', + opts: { headerFirst: true }, + chsize: 32, + nparts: 2, + setBoundary: 'AaB03x', + what: 'One nested multipart with preceding header, using setBoundary' + } + ] + + t.plan(tests.length) + + tests.forEach(function (v) { + t.test(v.what, t => { + t.plan(1) const fixtureBase = FIXTURES_ROOT + v.source const state = { parts: [], preamble: undefined } @@ -201,7 +210,7 @@ describe('dicer-multipart', () => { '\nExpected: ' + inspect(header))) } - done() + t.pass() }) fs.createReadStream(fixtureBase + '/original').pipe(dicer) diff --git a/test/get-limit.spec.js b/test/get-limit.spec.js deleted file mode 100644 index 7586f73..0000000 --- a/test/get-limit.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -const getLimit = require('../lib/utils/getLimit') -const { assert } = require('chai') - -describe('Get limit', () => { - it('Correctly resolves limits', () => { - assert.strictEqual(getLimit(undefined, 'fieldSize', 1), 1) - assert.strictEqual(getLimit(undefined, 'fileSize', Infinity), Infinity) - - assert.strictEqual(getLimit({}, 'fieldSize', 1), 1) - assert.strictEqual(getLimit({}, 'fileSize', Infinity), Infinity) - assert.strictEqual(getLimit({ fieldSize: null }, 'fieldSize', 1), 1) - assert.strictEqual(getLimit({ fileSize: null }, 'fileSize', Infinity), Infinity) - - assert.strictEqual(getLimit({ fieldSize: 0 }, 'fieldSize', 1), 0) - assert.strictEqual(getLimit({ fileSize: 2 }, 'fileSize', 1), 2) - }) - - it('Throws an error on incorrect limits', () => { - assert.throws(function () { - getLimit({ fieldSize: '1' }, 'fieldSize', 1) - }, /Limit fieldSize is not a valid number/) - - assert.throws(function () { - getLimit({ fieldSize: NaN }, 'fieldSize', 1) - }, /Limit fieldSize is not a valid number/) - }) -}) diff --git a/test/get-limit.test.js b/test/get-limit.test.js new file mode 100644 index 0000000..76a2997 --- /dev/null +++ b/test/get-limit.test.js @@ -0,0 +1,34 @@ +'use strict' + +const getLimit = require('../lib/utils/getLimit') +const { test } = require('tap') + +test('Get limit', t => { + t.plan(2) + + t.test('Correctly resolves limits', t => { + t.plan(8) + t.strictSame(getLimit(undefined, 'fieldSize', 1), 1) + t.strictSame(getLimit(undefined, 'fileSize', Infinity), Infinity) + + t.strictSame(getLimit({}, 'fieldSize', 1), 1) + t.strictSame(getLimit({}, 'fileSize', Infinity), Infinity) + t.strictSame(getLimit({ fieldSize: null }, 'fieldSize', 1), 1) + t.strictSame(getLimit({ fileSize: null }, 'fileSize', Infinity), Infinity) + + t.strictSame(getLimit({ fieldSize: 0 }, 'fieldSize', 1), 0) + t.strictSame(getLimit({ fileSize: 2 }, 'fileSize', 1), 2) + }) + + t.test('Throws an error on incorrect limits', t => { + t.plan(2) + + t.throws(function () { + getLimit({ fieldSize: '1' }, 'fieldSize', 1) + }, new Error('Limit fieldSize is not a valid number')) + + t.throws(function () { + getLimit({ fieldSize: NaN }, 'fieldSize', 1) + }, new Error('Limit fieldSize is not a valid number')) + }) +}) diff --git a/test/multipart-stream-pause.spec.js b/test/multipart-stream-pause.spec.js deleted file mode 100644 index ce09085..0000000 --- a/test/multipart-stream-pause.spec.js +++ /dev/null @@ -1,85 +0,0 @@ -const { inspect } = require('node:util') -const { assert } = require('chai') -const Busboy = require('..') - -const BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh' - -function formDataSection (key, value) { - return Buffer.from('\r\n--' + BOUNDARY + - '\r\nContent-Disposition: form-data; name="' + - key + '"\r\n\r\n' + value) -} -function formDataFile (key, filename, contentType) { - return Buffer.concat([ - Buffer.from('\r\n--' + BOUNDARY + '\r\n'), - Buffer.from('Content-Disposition: form-data; name="' + - key + '"; filename="' + filename + '"\r\n'), - Buffer.from('Content-Type: ' + contentType + '\r\n\r\n'), - Buffer.allocUnsafe(100000) - ]) -} - -describe('multipart-stream-pause', () => { - it('processes stream correctly', (done) => { - const reqChunks = [ - Buffer.concat([ - formDataFile('file', 'file.bin', 'application/octet-stream'), - formDataSection('foo', 'foo value') - ]), - formDataSection('bar', 'bar value'), - Buffer.from('\r\n--' + BOUNDARY + '--\r\n') - ] - const busboy = new Busboy({ - headers: { - 'content-type': 'multipart/form-data; boundary=' + BOUNDARY - } - }) - let finishes = 0 - const results = [] - const expected = [ - ['file', 'file', 'file.bin', '7bit', 'application/octet-stream'], - ['field', 'foo', 'foo value', false, false, '7bit', 'text/plain'], - ['field', 'bar', 'bar value', false, false, '7bit', 'text/plain'] - ] - - busboy.on('field', function (key, val, keyTrunc, valTrunc, encoding, contype) { - results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]) - }) - busboy.on('file', function (fieldname, stream, filename, encoding, mimeType) { - results.push(['file', fieldname, filename, encoding, mimeType]) - // Simulate a pipe where the destination is pausing (perhaps due to waiting - // for file system write to finish) - setTimeout(function () { - stream.resume() - }, 10) - }) - busboy.on('finish', function () { - assert(finishes++ === 0, 'finish emitted multiple times') - assert.deepEqual(results.length, - expected.length, - 'Parsed result count mismatch. Saw ' + - results.length + - '. Expected: ' + expected.length) - - results.forEach(function (result, i) { - assert.deepEqual(result, - expected[i], - 'Result mismatch:\nParsed: ' + inspect(result) + - '\nExpected: ' + inspect(expected[i])) - }) - done() - }).on('error', function (err) { - assert(false, 'Unexpected error: ' + err.stack) - done(err) - }) - - reqChunks.forEach(function (buf) { - busboy.write(buf) - }) - busboy.end() - - process.on('exit', function () { - assert(finishes === 1, 'busboy did not finish') - }) - }) -}) diff --git a/test/multipart-stream-pause.test.js b/test/multipart-stream-pause.test.js new file mode 100644 index 0000000..856cf71 --- /dev/null +++ b/test/multipart-stream-pause.test.js @@ -0,0 +1,82 @@ +'use strict' + +const { inspect } = require('util') +const { test } = require('tap') + +const Busboy = require('..') + +const BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh' + +function formDataSection (key, value) { + return Buffer.from('\r\n--' + BOUNDARY + + '\r\nContent-Disposition: form-data; name="' + + key + '"\r\n\r\n' + value) +} +function formDataFile (key, filename, contentType) { + return Buffer.concat([ + Buffer.from('\r\n--' + BOUNDARY + '\r\n'), + Buffer.from('Content-Disposition: form-data; name="' + + key + '"; filename="' + filename + '"\r\n'), + Buffer.from('Content-Type: ' + contentType + '\r\n\r\n'), + Buffer.allocUnsafe(100000) + ]) +} + +test('multipart-stream-pause - processes stream correctly', t => { + t.plan(6) + const reqChunks = [ + Buffer.concat([ + formDataFile('file', 'file.bin', 'application/octet-stream'), + formDataSection('foo', 'foo value') + ]), + formDataSection('bar', 'bar value'), + Buffer.from('\r\n--' + BOUNDARY + '--\r\n') + ] + const busboy = new Busboy({ + headers: { + 'content-type': 'multipart/form-data; boundary=' + BOUNDARY + } + }) + let finishes = 0 + const results = [] + const expected = [ + ['file', 'file', 'file.bin', '7bit', 'application/octet-stream'], + ['field', 'foo', 'foo value', false, false, '7bit', 'text/plain'], + ['field', 'bar', 'bar value', false, false, '7bit', 'text/plain'] + ] + + busboy.on('field', function (key, val, keyTrunc, valTrunc, encoding, contype) { + results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]) + }) + busboy.on('file', function (fieldname, stream, filename, encoding, mimeType) { + results.push(['file', fieldname, filename, encoding, mimeType]) + // Simulate a pipe where the destination is pausing (perhaps due to waiting + // for file system write to finish) + setTimeout(function () { + stream.resume() + }, 10) + }) + busboy.on('finish', function () { + t.ok(finishes++ === 0, 'finish emitted multiple times') + t.strictSame(results.length, + expected.length, + 'Parsed result count mismatch. Saw ' + + results.length + + '. Expected: ' + expected.length) + + results.forEach(function (result, i) { + t.strictSame(result, + expected[i], + 'Result mismatch:\nParsed: ' + inspect(result) + + '\nExpected: ' + inspect(expected[i])) + }) + t.pass() + }).on('error', function (err) { + t.error(err) + }) + + reqChunks.forEach(function (buf) { + busboy.write(buf) + }) + busboy.end() +}) diff --git a/test/parse-params.spec.js b/test/parse-params.test.js similarity index 91% rename from test/parse-params.spec.js rename to test/parse-params.test.js index 9b5814b..eea4768 100644 --- a/test/parse-params.spec.js +++ b/test/parse-params.test.js @@ -1,9 +1,11 @@ +'use strict' + const { inspect } = require('node:util') -const { assert } = require('chai') +const { test } = require('tap') const parseParams = require('../lib/utils/parseParams') -describe('parse-params', () => { - [ +test('parse-params', t => { + const tests = [ { source: 'video/ogg', expected: ['video/ogg'], @@ -104,13 +106,19 @@ describe('parse-params', () => { expected: ['multipart/form-data', ['charset', 'utf-8'], ['boundary', '0xKhTmLbOuNdArY']], what: 'Multiple non-quoted parameters' } - ].forEach((v) => { - it(v.what, () => { + ] + + t.plan(tests.length) + + tests.forEach((v) => { + t.test(v.what, t => { + t.plan(1) + const result = parseParams(v.source) - const msg = 'parsed parameters mismatch.\n' + - 'Saw: ' + inspect(result) + '\n' + - 'Expected: ' + inspect(v.expected) - assert.deepEqual(result, v.expected, msg) + t.strictSame( + result, + v.expected, + `parsed parameters match.\nSaw: ${inspect(result)}\nExpected: ${inspect(v.expected)}`) }) }) }) diff --git a/test/streamsearch.spec.js b/test/streamsearch.test.js similarity index 54% rename from test/streamsearch.spec.js rename to test/streamsearch.test.js index 514ca31..968c7de 100644 --- a/test/streamsearch.spec.js +++ b/test/streamsearch.test.js @@ -1,20 +1,34 @@ -const { expect } = require('chai') +'use strict' + +const { test } = require('tap') const Streamsearch = require('../deps/streamsearch/sbmh') -describe('streamsearch', () => { - it('should throw an error if the needle is not a String or Buffer', () => { - expect(() => new Streamsearch(2)).to.throw('The needle has to be a String or a Buffer.') +test('streamsearch', t => { + t.plan(17) + + t.test('should throw an error if the needle is not a String or Buffer', t => { + t.plan(1) + + t.throws(() => new Streamsearch(2), new Error('The needle has to be a String or a Buffer.')) }) - it('should throw an error if the needle is an empty String', () => { - expect(() => new Streamsearch('')).to.throw('The needle cannot be an empty String/Buffer.') + t.test('should throw an error if the needle is an empty String', t => { + t.plan(1) + + t.throws(() => new Streamsearch(''), new Error('The needle cannot be an empty String/Buffer.')) }) - it('should throw an error if the needle is an empty Buffer', () => { - expect(() => new Streamsearch(Buffer.from(''))).to.throw('The needle cannot be an empty String/Buffer.') + t.test('should throw an error if the needle is an empty Buffer', t => { + t.plan(1) + + t.throws(() => new Streamsearch(Buffer.from('')), new Error('The needle cannot be an empty String/Buffer.')) }) - it('should throw an error if the needle is bigger than 256 characters', () => { - expect(() => new Streamsearch(Buffer.from(Array(257).fill('a').join('')))).to.throw('The needle cannot have a length bigger than 256.') + t.test('should throw an error if the needle is bigger than 256 characters', t => { + t.plan(1) + + t.throws(() => new Streamsearch(Buffer.from(Array(257).fill('a').join(''))), new Error('The needle cannot have a length bigger than 256.')) }) - it('should process a Buffer without a needle', (done) => { + + t.test('should process a Buffer without a needle', t => { + t.plan(5) const expected = [ [false, Buffer.from('bar hello'), 0, 9] ] @@ -25,19 +39,22 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 1) { - done() + t.pass() } }) s.push(chunks[0]) }) - it('should cast a string without a needle', (done) => { + + t.test('should cast a string without a needle', t => { + t.plan(5) + const expected = [ [false, Buffer.from('bar hello'), 0, 9] ] @@ -48,19 +65,22 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 1) { - done() + t.pass() } }) s.push(chunks[0]) }) - it('should process a chunk with a needle at the beginning', (done) => { + + t.test('should process a chunk with a needle at the beginning', t => { + t.plan(9) + const expected = [ [true, undefined, undefined, undefined], [false, Buffer.from('\r\nbar hello'), 2, 11] @@ -72,20 +92,21 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 2) { - done() + t.pass() } }) s.push(chunks[0]) }) - it('should process a chunk with a needle in the middle', (done) => { + t.test('should process a chunk with a needle in the middle', t => { + t.plan(9) const expected = [ [true, Buffer.from('bar\r\n hello'), 0, 3], [false, Buffer.from('bar\r\n hello'), 5, 11] @@ -97,20 +118,21 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 2) { - done() + t.pass() } }) s.push(chunks[0]) }) - it('should process a chunk with a needle at the end', (done) => { + t.test('should process a chunk with a needle at the end', t => { + t.plan(5) const expected = [ [true, Buffer.from('bar hello\r\n'), 0, 9] ] @@ -121,20 +143,21 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 1) { - done() + t.pass() } }) s.push(chunks[0]) }) - it('should process a chunk with multiple needle at the end', (done) => { + t.test('should process a chunk with multiple needle at the end', t => { + t.plan(9) const expected = [ [true, Buffer.from('bar hello\r\n\r\n'), 0, 9], [true, Buffer.from('bar hello\r\n\r\n'), 11, 11] @@ -146,20 +169,21 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 2) { - done() + t.pass() } }) s.push(chunks[0]) }) - it('should process two chunks without a needle', (done) => { + t.test('should process two chunks without a needle', t => { + t.plan(9) const expected = [ [false, Buffer.from('bar'), 0, 3], [false, Buffer.from('hello'), 0, 5] @@ -172,13 +196,13 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 2) { - done() + t.pass() } }) @@ -186,7 +210,8 @@ describe('streamsearch', () => { s.push(chunks[1]) }) - it('should process two chunks with an overflowing needle', (done) => { + t.test('should process two chunks with an overflowing needle', t => { + t.plan(13) const expected = [ [false, Buffer.from('bar\r'), 0, 3], [true, undefined, undefined, undefined], @@ -200,13 +225,13 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 3) { - done() + t.pass() } }) @@ -214,7 +239,9 @@ describe('streamsearch', () => { s.push(chunks[1]) }) - it('should process two chunks with a potentially overflowing needle', (done) => { + t.test('should process two chunks with a potentially overflowing needle', t => { + t.plan(13) + const expected = [ [false, Buffer.from('bar\r'), 0, 3], [false, Buffer.from('\r\0\0'), 0, 1], @@ -228,13 +255,13 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 3) { - done() + t.pass() } }) @@ -242,7 +269,9 @@ describe('streamsearch', () => { s.push(chunks[1]) }) - it('should process three chunks with a overflowing needle', (done) => { + t.test('should process three chunks with a overflowing needle', t => { + t.plan(13) + const expected = [ [false, Buffer.from('bar\r'), 0, 3], [true, undefined, undefined, undefined], @@ -257,13 +286,13 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 3) { - done() + t.pass() } }) @@ -272,7 +301,9 @@ describe('streamsearch', () => { s.push(chunks[2]) }) - it('should process four chunks with a overflowing needle', (done) => { + t.test('should process four chunks with a overflowing needle', t => { + t.plan(13) + const expected = [ [false, Buffer.from('bar\r'), 0, 3], [true, undefined, undefined, undefined], @@ -288,13 +319,13 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 3) { - done() + t.pass() } }) @@ -304,7 +335,9 @@ describe('streamsearch', () => { s.push(chunks[3]) }) - it('should process four chunks with a potentially overflowing needle', (done) => { + t.test('should process four chunks with a potentially overflowing needle', t => { + t.plan(17) + const expected = [ [false, Buffer.from('bar\r'), 0, 3], [false, Buffer.from('\r\n\0'), 0, 2], @@ -321,13 +354,13 @@ describe('streamsearch', () => { ] let i = 0 s.on('info', (isMatched, data, start, end) => { - expect(isMatched).to.be.eql(expected[i][0]) - expect(data).to.be.eql(expected[i][1]) - expect(start).to.be.eql(expected[i][2]) - expect(end).to.be.eql(expected[i][3]) + t.strictSame(isMatched, expected[i][0]) + t.strictSame(data, expected[i][1]) + t.strictSame(start, expected[i][2]) + t.strictSame(end, expected[i][3]) i++ if (i >= 4) { - done() + t.pass() } }) @@ -337,25 +370,27 @@ describe('streamsearch', () => { s.push(chunks[3]) }) - it('should reset the internal values if .reset() is called', () => { + t.test('should reset the internal values if .reset() is called', t => { + t.plan(9) + const s = new Streamsearch('test') - expect(s._lookbehind_size).to.be.eql(0) - expect(s.matches).to.be.eql(0) - expect(s._bufpos).to.be.eql(0) + t.strictSame(s._lookbehind_size, 0) + t.strictSame(s.matches, 0) + t.strictSame(s._bufpos, 0) s._lookbehind_size = 1 s._bufpos = 1 s.matches = 1 - expect(s._lookbehind_size).to.be.eql(1) - expect(s.matches).to.be.eql(1) - expect(s._bufpos).to.be.eql(1) + t.strictSame(s._lookbehind_size, 1) + t.strictSame(s.matches, 1) + t.strictSame(s._bufpos, 1) s.reset() - expect(s._lookbehind_size).to.be.eql(0) - expect(s.matches).to.be.eql(0) - expect(s._bufpos).to.be.eql(0) + t.strictSame(s._lookbehind_size, 0) + t.strictSame(s.matches, 0) + t.strictSame(s._bufpos, 0) }) }) diff --git a/test/types-multipart.spec.js b/test/types-multipart.spec.js deleted file mode 100644 index a1ab251..0000000 --- a/test/types-multipart.spec.js +++ /dev/null @@ -1,651 +0,0 @@ -const Busboy = require('..') - -const { inspect } = require('node:util') -const { assert } = require('chai') - -const EMPTY_FN = function () { -} - -describe('types-multipart', () => { - const tests = [ - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_1"', - '', - 'super beta file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', - 'Content-Type: application/octet-stream', - '', - 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], - ['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'], - ['file', 'upload_file_0', 1023, 0, '1k_a.dat', '7bit', 'application/octet-stream'], - ['file', 'upload_file_1', 1023, 0, '1k_b.dat', '7bit', 'application/octet-stream'] - ], - what: 'Fields and files' - }, - { - source: [ - ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', - 'Content-Disposition: form-data; name="cont"', - '', - 'some random content', - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', - 'Content-Disposition: form-data; name="pass"', - '', - 'some random pass', - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', - 'Content-Disposition: form-data; name="bit"', - '', - '2', - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' - ].join('\r\n') - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', - expected: [ - ['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'], - ['field', 'pass', 'some random pass', false, false, '7bit', 'text/plain'], - ['field', 'bit', '2', false, false, '7bit', 'text/plain'] - ], - what: 'Fields only' - }, - { - source: [ - '' - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', - expected: [], - shouldError: 'Unexpected end of multipart data', - what: 'No fields and no files' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - fileSize: 13, - fieldSize: 5 - }, - expected: [ - ['field', 'file_name_0', 'super', false, true, '7bit', 'text/plain'], - ['file', 'upload_file_0', 13, 2, '1k_a.dat', '7bit', 'application/octet-stream'] - ], - what: 'Fields and files (limits)' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - fields: 0 - }, - events: ['file'], - expected: [ - ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] - ], - what: 'should not emit fieldsLimit if no field was sent' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - fields: 0 - }, - events: ['file', 'fieldsLimit'], - expected: [ - ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] - ], - what: 'should respect fields limit of 0' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_1"', - '', - 'super beta file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - fields: 1 - }, - events: ['field', 'file', 'fieldsLimit'], - expected: [ - ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], - ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] - ], - what: 'should respect fields limit of 1' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - files: 0 - }, - events: ['field'], - expected: [ - ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'] - ], - what: 'should not emit filesLimit if no file was sent' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - files: 0 - }, - events: ['field', 'filesLimit'], - expected: [ - ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'] - ], - what: 'should respect fields limit of 0' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_b"; filename="1k_b.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - limits: { - files: 1 - }, - events: ['field', 'file', 'filesLimit'], - expected: [ - ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], - ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] - ], - what: 'should respect fields limit of 1' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_0"', - '', - 'super alpha file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file_name_1"', - '', - 'super beta file', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', - 'Content-Type: application/octet-stream', - '', - 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], - ['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'] - ], - events: ['field'], - what: 'Fields and (ignored) files' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="/tmp/1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\files\\1k_b.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'], - ['file', 'upload_file_1', 26, 0, '1k_b.dat', '7bit', 'application/octet-stream'], - ['file', 'upload_file_2', 26, 0, '1k_c.dat', '7bit', 'application/octet-stream'] - ], - what: 'Files with filenames containing paths' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="/absolute/1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - preservePath: true, - expected: [ - ['file', 'upload_file_0', 26, 0, '/absolute/1k_a.dat', '7bit', 'application/octet-stream'], - ['file', 'upload_file_1', 26, 0, 'C:\\absolute\\1k_b.dat', '7bit', 'application/octet-stream'], - ['file', 'upload_file_2', 26, 0, 'relative/1k_c.dat', '7bit', 'application/octet-stream'] - ], - what: 'Paths to be preserved through the preservePath option' - }, - { - source: [ - ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', - 'Content-Disposition: form-data; name="cont"', - 'Content-Type: ', - '', - 'some random content', - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', - 'Content-Disposition: ', - '', - 'some random pass', - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' - ].join('\r\n') - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', - expected: [ - ['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'] - ], - what: 'Empty content-type and empty content-disposition' - }, - { - config: { - isPartAFile: (fieldName) => (fieldName !== 'upload_file_0') - }, - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="blob"', - 'Content-Type: application/json', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['field', 'upload_file_0', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', false, false, '7bit', 'application/json'] - ], - what: 'Blob uploads should be handled as fields if isPartAFile is provided.' - }, - { - config: { - isPartAFile: (fieldName) => (fieldName !== 'upload_file_0') - }, - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="blob"', - 'Content-Type: application/json', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file"; filename*=utf-8\'\'n%C3%A4me.txt', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['field', 'upload_file_0', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', false, false, '7bit', 'application/json'], - ['file', 'file', 26, 0, 'näme.txt', '7bit', 'application/octet-stream'] - ], - what: 'Blob uploads should be handled as fields if isPartAFile is provided. Other parts should be files.' - }, - { - config: { - isPartAFile: (fieldName) => (fieldName === 'upload_file_0') - }, - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="upload_file_0"; filename="blob"', - 'Content-Type: application/json', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file"; filename*=utf-8\'\'n%C3%A4me.txt', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['file', 'upload_file_0', 26, 0, 'blob', '7bit', 'application/json'], - ['field', 'file', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', false, false, '7bit', 'application/octet-stream'] - ], - what: 'Blob uploads should be handled as files if corresponding isPartAFile is provided. Other parts should be fields.' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="file"; filename*=utf-8\'\'n%C3%A4me.txt', - 'Content-Type: application/octet-stream', - '', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['file', 'file', 26, 0, 'näme.txt', '7bit', 'application/octet-stream'] - ], - what: 'Unicode filenames' - }, - { - source: [ - ['--asdasdasdasd\r\n', - 'Content-Type: text/plain\r\n', - 'Content-Disposition: form-data; name="foo"\r\n', - '\r\n', - 'asd\r\n', - '--asdasdasdasd--' - ].join(':)') - ], - boundary: 'asdasdasdasd', - expected: [], - shouldError: 'Unexpected end of multipart data', - what: 'Stopped mid-header' - }, - { - source: [ - ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', - 'Content-Disposition: form-data; name="cont"', - 'Content-Type: application/json', - '', - '{}', - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' - ].join('\r\n') - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', - expected: [ - ['field', 'cont', '{}', false, false, '7bit', 'application/json'] - ], - what: 'content-type for fields' - }, - { - source: [ - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', - expected: [], - what: 'empty form' - }, - { - source: [ - ' ------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', - expected: [], - shouldError: 'Unexpected end of multipart data', - what: 'empty form with preceding whitespace' - }, - { - source: [ - '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' - ], - boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhYY', - expected: [], - shouldError: 'Unexpected end of multipart data', - what: 'empty form with wrong boundary (extra Y)' - }, - { - source: [ - ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="field1"', - 'content-type: text/plain; charset=utf-8', - '', - 'Aufklärung ist der Ausgang des Menschen aus seiner selbstverschuldeten Unmündigkeit.', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - 'Content-Disposition: form-data; name="field2"', - 'content-type: text/plain; charset=iso-8859-1', - '', - 'sapere aude!', - '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' - ].join('\r\n') - ], - boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', - expected: [ - ['field', 'field1', 'Aufklärung ist der Ausgang des Menschen aus seiner selbstverschuldeten Unmündigkeit.', false, false, '7bit', 'text/plain'], - ['field', 'field2', 'sapere aude!', false, false, '7bit', 'text/plain'] - ], - what: 'Fields and files' - }, - { - source: [[ - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="regsubmit"', - '', - 'yes', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="referer"', - '', - 'http://domainExample/./', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="activationauth"', - '', - '', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="seccodemodid"', - '', - 'member::register', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7--'].join('\r\n') - ], - boundary: '----WebKitFormBoundaryzca7IDMnT6QwqBp7', - expected: [ - ['field', 'regsubmit', 'yes', false, false, '7bit', 'text/plain'], - ['field', 'referer', 'http://domainExample/./', false, false, '7bit', 'text/plain'], - ['field', 'activationauth', '', false, false, '7bit', 'text/plain'], - ['field', 'seccodemodid', 'member::register', false, false, '7bit', 'text/plain'] - ], - what: 'one empty part should get ignored' - }, - { - source: [[ - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="regsubmit"', - '', - 'yes', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="referer"', - '', - 'http://domainExample/./', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="activationauth"', - '', - '', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7', - 'Content-Disposition: form-data; name="seccodemodid"', - '', - 'member::register', - '------WebKitFormBoundaryzca7IDMnT6QwqBp7--'].join('\r\n') - ], - boundary: '----WebKitFormBoundaryzca7IDMnT6QwqBp7', - expected: [ - ['field', 'regsubmit', 'yes', false, false, '7bit', 'text/plain'], - ['field', 'referer', 'http://domainExample/./', false, false, '7bit', 'text/plain'], - ['field', 'activationauth', '', false, false, '7bit', 'text/plain'], - ['field', 'seccodemodid', 'member::register', false, false, '7bit', 'text/plain'] - ], - what: 'multiple empty parts should get ignored' - } - ] - - tests.forEach((v) => { - it(v.what, (done) => { - const busboy = new Busboy({ - ...v.config, - limits: v.limits, - preservePath: v.preservePath, - headers: { - 'content-type': 'multipart/form-data; boundary=' + v.boundary - } - }) - let finishes = 0 - const results = [] - - if (v.events === undefined || v.events.indexOf('field') > -1) { - busboy.on('field', function (key, val, keyTrunc, valTrunc, encoding, contype) { - results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]) - }) - } - if (v.events === undefined || v.events.indexOf('file') > -1) { - busboy.on('file', function (fieldname, stream, filename, encoding, mimeType) { - let nb = 0 - const info = ['file', - fieldname, - nb, - 0, - filename, - encoding, - mimeType] - results.push(info) - stream.on('data', function (d) { - nb += d.length - }).on('limit', function () { - ++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] } - }) - }) - } - busboy.on('finish', function () { - assert(finishes++ === 0, 'finish emitted multiple times') - assert.deepEqual(results.length, - v.expected.length, - 'Parsed result count mismatch. Saw ' + - results.length + - '. Expected: ' + v.expected.length) - - results.forEach(function (result, i) { - assert.deepEqual(result, - v.expected[i], - 'Result mismatch:\nParsed: ' + inspect(result) + - '\nExpected: ' + inspect(v.expected[i]) - ) - }) - done() - }).on('error', function (err) { - if (!v.shouldError || v.shouldError !== err.message) { assert(false, 'Unexpected error: ' + err); done(err) } - }) - - v.source.forEach(function (s) { - busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN) - }) - busboy.end() - }) - }) -}) diff --git a/test/types-multipart.test.js b/test/types-multipart.test.js new file mode 100644 index 0000000..dc7ae88 --- /dev/null +++ b/test/types-multipart.test.js @@ -0,0 +1,678 @@ +'use strict' + +const Busboy = require('..') + +const { test } = require('tap') +const { inspect } = require('util') + +const EMPTY_FN = function () { +} + +const tests = [ + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], + ['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'], + ['file', 'upload_file_0', 1023, 0, '1k_a.dat', '7bit', 'application/octet-stream'], + ['file', 'upload_file_1', 1023, 0, '1k_b.dat', '7bit', 'application/octet-stream'] + ], + what: 'Fields and files', + plan: 11 + }, + { + source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + '', + 'some random content', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="pass"', + '', + 'some random pass', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="bit"', + '', + '2', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + ['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'], + ['field', 'pass', 'some random pass', false, false, '7bit', 'text/plain'], + ['field', 'bit', '2', false, false, '7bit', 'text/plain'] + ], + what: 'Fields only', + plan: 6 + }, + { + source: [ + '' + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [], + shouldError: 'Unexpected end of multipart data', + what: 'No fields and no files', + plan: 3 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fileSize: 13, + fieldSize: 5 + }, + expected: [ + ['field', 'file_name_0', 'super', false, true, '7bit', 'text/plain'], + ['file', 'upload_file_0', 13, 2, '1k_a.dat', '7bit', 'application/octet-stream'] + ], + what: 'Fields and files (limits)', + plan: 7 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fields: 0 + }, + events: ['file'], + expected: [ + ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] + ], + what: 'should not emit fieldsLimit if no field was sent', + plan: 6 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fields: 0 + }, + events: ['file', 'fieldsLimit'], + expected: [ + ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] + ], + what: 'should respect fields limit of 0', + plan: 6 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fields: 1 + }, + events: ['field', 'file', 'fieldsLimit'], + expected: [ + ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], + ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] + ], + what: 'should respect fields limit of 7', + plan: 7 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 0 + }, + events: ['field'], + expected: [ + ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'] + ], + what: 'should not emit filesLimit if no file was sent', + plan: 4 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 0 + }, + events: ['field', 'filesLimit'], + expected: [ + ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'] + ], + what: 'should respect fields limit of 0', + plan: 4 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_b"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 1 + }, + events: ['field', 'file', 'filesLimit'], + expected: [ + ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], + ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'] + ], + what: 'should respect fields limit of 1', + plan: 7 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], + ['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'] + ], + events: ['field'], + what: 'Fields and (ignored) files', + plan: 5 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="/tmp/1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\files\\1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'], + ['file', 'upload_file_1', 26, 0, '1k_b.dat', '7bit', 'application/octet-stream'], + ['file', 'upload_file_2', 26, 0, '1k_c.dat', '7bit', 'application/octet-stream'] + ], + what: 'Files with filenames containing paths', + plan: 12 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="/absolute/1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + preservePath: true, + expected: [ + ['file', 'upload_file_0', 26, 0, '/absolute/1k_a.dat', '7bit', 'application/octet-stream'], + ['file', 'upload_file_1', 26, 0, 'C:\\absolute\\1k_b.dat', '7bit', 'application/octet-stream'], + ['file', 'upload_file_2', 26, 0, 'relative/1k_c.dat', '7bit', 'application/octet-stream'] + ], + what: 'Paths to be preserved through the preservePath option', + plan: 12 + }, + { + source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + 'Content-Type: ', + '', + 'some random content', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: ', + '', + 'some random pass', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + ['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'] + ], + what: 'Empty content-type and empty content-disposition', + plan: 4 + }, + { + config: { + isPartAFile: (fieldName) => (fieldName !== 'upload_file_0') + }, + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="blob"', + 'Content-Type: application/json', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['field', 'upload_file_0', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', false, false, '7bit', 'application/json'] + ], + what: 'Blob uploads should be handled as fields if isPartAFile is provided.', + plan: 4 + }, + { + config: { + isPartAFile: (fieldName) => (fieldName !== 'upload_file_0') + }, + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="blob"', + 'Content-Type: application/json', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file"; filename*=utf-8\'\'n%C3%A4me.txt', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['field', 'upload_file_0', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', false, false, '7bit', 'application/json'], + ['file', 'file', 26, 0, 'näme.txt', '7bit', 'application/octet-stream'] + ], + what: 'Blob uploads should be handled as fields if isPartAFile is provided. Other parts should be files.', + plan: 7 + }, + { + config: { + isPartAFile: (fieldName) => (fieldName === 'upload_file_0') + }, + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="upload_file_0"; filename="blob"', + 'Content-Type: application/json', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file"; filename*=utf-8\'\'n%C3%A4me.txt', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['file', 'upload_file_0', 26, 0, 'blob', '7bit', 'application/json'], + ['field', 'file', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', false, false, '7bit', 'application/octet-stream'] + ], + what: 'Blob uploads sould be handled as files if corresponding isPartAFile is provided. Other parts should be fields.', + plan: 7 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file"; filename*=utf-8\'\'n%C3%A4me.txt', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['file', 'file', 26, 0, 'näme.txt', '7bit', 'application/octet-stream'] + ], + what: 'Unicode filenames', + plan: 6 + }, + { + source: [ + ['--asdasdasdasd\r\n', + 'Content-Type: text/plain\r\n', + 'Content-Disposition: form-data; name="foo"\r\n', + '\r\n', + 'asd\r\n', + '--asdasdasdasd--' + ].join(':)') + ], + boundary: 'asdasdasdasd', + expected: [], + shouldError: 'Unexpected end of multipart data', + what: 'Stopped mid-header', + plan: 3 + }, + { + source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + 'Content-Type: application/json', + '', + '{}', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + ['field', 'cont', '{}', false, false, '7bit', 'application/json'] + ], + what: 'content-type for fields', + plan: 4 + }, + { + source: [ + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [], + what: 'empty form', + plan: 3 + }, + { + source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="field1"', + 'content-type: text/plain; charset=utf-8', + '', + 'Aufklärung ist der Ausgang des Menschen aus seiner selbstverschuldeten Unmündigkeit.', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="field2"', + 'content-type: text/plain; charset=iso-8859-1', + '', + 'sapere aude!', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + ['field', 'field1', 'Aufklärung ist der Ausgang des Menschen aus seiner selbstverschuldeten Unmündigkeit.', false, false, '7bit', 'text/plain'], + ['field', 'field2', 'sapere aude!', false, false, '7bit', 'text/plain'] + ], + what: 'Fields and files', + plan: 5 + }, + { + source: [[ + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="regsubmit"', + '', + 'yes', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="referer"', + '', + 'http://domainExample/./', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="activationauth"', + '', + '', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="seccodemodid"', + '', + 'member::register', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7--'].join('\r\n') + ], + boundary: '----WebKitFormBoundaryzca7IDMnT6QwqBp7', + expected: [ + ['field', 'regsubmit', 'yes', false, false, '7bit', 'text/plain'], + ['field', 'referer', 'http://domainExample/./', false, false, '7bit', 'text/plain'], + ['field', 'activationauth', '', false, false, '7bit', 'text/plain'], + ['field', 'seccodemodid', 'member::register', false, false, '7bit', 'text/plain'] + ], + what: 'one empty part should get ignored', + plan: 7 + }, + { + source: [ + ' ------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [], + shouldError: 'Unexpected end of multipart data', + what: 'empty form with preceding whitespace', + plan: 3 + }, + { + source: [ + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhYY', + expected: [], + shouldError: 'Unexpected end of multipart data', + what: 'empty form with wrong boundary (extra Y)', + plan: 3 + }, + { + source: [[ + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="regsubmit"', + '', + 'yes', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="referer"', + '', + 'http://domainExample/./', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="activationauth"', + '', + '', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7', + 'Content-Disposition: form-data; name="seccodemodid"', + '', + 'member::register', + '------WebKitFormBoundaryzca7IDMnT6QwqBp7--'].join('\r\n') + ], + boundary: '----WebKitFormBoundaryzca7IDMnT6QwqBp7', + expected: [ + ['field', 'regsubmit', 'yes', false, false, '7bit', 'text/plain'], + ['field', 'referer', 'http://domainExample/./', false, false, '7bit', 'text/plain'], + ['field', 'activationauth', '', false, false, '7bit', 'text/plain'], + ['field', 'seccodemodid', 'member::register', false, false, '7bit', 'text/plain'] + ], + what: 'multiple empty parts should get ignored', + plan: 7 + } +] + +tests.forEach((v) => { + test(v.what, t => { + t.plan(v.plan) + const busboy = new Busboy({ + ...v.config, + limits: v.limits, + preservePath: v.preservePath, + headers: { + 'content-type': 'multipart/form-data; boundary=' + v.boundary + } + }) + let finishes = 0 + const results = [] + + if (v.events === undefined || v.events.indexOf('field') > -1) { + busboy.on('field', function (key, val, keyTrunc, valTrunc, encoding, contype) { + results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]) + }) + } + if (v.events === undefined || v.events.indexOf('file') > -1) { + busboy.on('file', function (fieldname, stream, filename, encoding, mimeType) { + let nb = 0 + const info = ['file', + fieldname, + nb, + 0, + filename, + encoding, + mimeType] + results.push(info) + stream.on('data', function (d) { + nb += d.length + }).on('limit', function () { + ++info[3] + }).on('end', function () { + info[2] = nb + t.ok(typeof (stream.bytesRead) === 'number', 'file.bytesRead is missing') + t.ok(stream.bytesRead === nb, 'file.bytesRead is not equal to filesize') + if (stream.truncated) { ++info[3] } + }) + }) + } + busboy.on('finish', function () { + t.ok(finishes++ === 0, 'finish emitted multiple times') + t.equal(results.length, + v.expected.length, + 'Parsed result count mismatch. Saw ' + + results.length + + '. Expected: ' + v.expected.length) + + results.forEach(function (result, i) { + t.strictSame(result, + v.expected[i], + 'Result mismatch:\nParsed: ' + inspect(result) + + '\nExpected: ' + inspect(v.expected[i]) + ) + }) + t.pass() + }).on('error', function (err) { + if (!v.shouldError || v.shouldError !== err.message) { t.error(err) } + }) + + v.source.forEach(function (s) { + busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN) + }) + busboy.end() + }) +}) diff --git a/test/types-urlencoded.spec.js b/test/types-urlencoded.test.js similarity index 55% rename from test/types-urlencoded.spec.js rename to test/types-urlencoded.test.js index b0ad8b0..73cc286 100644 --- a/test/types-urlencoded.spec.js +++ b/test/types-urlencoded.test.js @@ -1,6 +1,8 @@ -const { inspect } = require('node:util') -const { assert } = require('chai') +'use strict' + +const { inspect } = require('util') const Busboy = require('..') +const { test } = require('tap') const EMPTY_FN = function () { } @@ -9,182 +11,200 @@ const tests = [ { source: ['foo'], expected: [['foo', '', false, false]], - what: 'Unassigned value' + what: 'Unassigned value', + plan: 4 }, { source: ['foo=bar'], expected: [['foo', 'bar', false, false]], - what: 'Assigned value' + what: 'Assigned value', + plan: 4 }, { source: ['foo&bar=baz'], expected: [['foo', '', false, false], ['bar', 'baz', false, false]], - what: 'Unassigned and assigned value' + what: 'Unassigned and assigned value', + plan: 5 }, { source: ['foo=bar&baz'], expected: [['foo', 'bar', false, false], ['baz', '', false, false]], - what: 'Assigned and unassigned value' + what: 'Assigned and unassigned value', + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [['foo', 'bar', false, false], ['baz', 'bla', false, false]], - what: 'Two assigned values' + what: 'Two assigned values', + plan: 5 }, { source: ['foo&bar'], expected: [['foo', '', false, false], ['bar', '', false, false]], - what: 'Two unassigned values' + what: 'Two unassigned values', + plan: 5 }, { source: ['foo&bar&'], expected: [['foo', '', false, false], ['bar', '', false, false]], - what: 'Two unassigned values and ampersand' + what: 'Two unassigned values and ampersand', + plan: 5 }, { source: ['foo=bar+baz%2Bquux'], expected: [['foo', 'bar baz+quux', false, false]], - what: 'Assigned value with (plus) space' + what: 'Assigned value with (plus) space', + plan: 4 }, { source: ['foo=bar%20baz%21'], expected: [['foo', 'bar baz!', false, false]], - what: 'Assigned value with encoded bytes' + what: 'Assigned value with encoded bytes', + plan: 4 }, { source: ['foo%20bar=baz%20bla%21'], expected: [['foo bar', 'baz bla!', false, false]], - what: 'Assigned value with encoded bytes #2' + what: 'Assigned value with encoded bytes #2', + plan: 4 }, { source: ['foo=bar%20baz%21&num=1000'], expected: [['foo', 'bar baz!', false, false], ['num', '1000', false, false]], - what: 'Two assigned values, one with encoded bytes' + what: 'Two assigned values, one with encoded bytes', + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [], what: 'Limits: zero fields', - limits: { fields: 0 } + limits: { fields: 0 }, + plan: 3 }, { source: ['foo=bar&baz=bla'], expected: [['foo', 'bar', false, false]], what: 'Limits: one field', - limits: { fields: 1 } + limits: { fields: 1 }, + plan: 4 }, { source: ['foo=bar&baz=bla'], expected: [['foo', 'bar', false, false], ['baz', 'bla', false, false]], what: 'Limits: field part lengths match limits', - limits: { fieldNameSize: 3, fieldSize: 3 } + limits: { fieldNameSize: 3, fieldSize: 3 }, + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [['fo', 'bar', true, false], ['ba', 'bla', true, false]], what: 'Limits: truncated field name', - limits: { fieldNameSize: 2 } + limits: { fieldNameSize: 2 }, + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [['foo', 'ba', false, true], ['baz', 'bl', false, true]], what: 'Limits: truncated field value', - limits: { fieldSize: 2 } + limits: { fieldSize: 2 }, + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [['fo', 'ba', true, true], ['ba', 'bl', true, true]], what: 'Limits: truncated field name and value', - limits: { fieldNameSize: 2, fieldSize: 2 } + limits: { fieldNameSize: 2, fieldSize: 2 }, + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [['fo', '', true, true], ['ba', '', true, true]], what: 'Limits: truncated field name and zero value limit', - limits: { fieldNameSize: 2, fieldSize: 0 } + limits: { fieldNameSize: 2, fieldSize: 0 }, + plan: 5 }, { source: ['foo=bar&baz=bla'], expected: [['', '', true, true], ['', '', true, true]], what: 'Limits: truncated zero field name and zero value limit', - limits: { fieldNameSize: 0, fieldSize: 0 } + limits: { fieldNameSize: 0, fieldSize: 0 }, + plan: 5 }, { source: ['&'], expected: [], - what: 'Ampersand' + what: 'Ampersand', + plan: 3 }, { source: ['&&&&&'], expected: [], - what: 'Many ampersands' + what: 'Many ampersands', + plan: 3 }, { source: ['='], expected: [['', '', false, false]], - what: 'Assigned value, empty name and value' + what: 'Assigned value, empty name and value', + plan: 4 }, { source: [''], expected: [], - what: 'Nothing' + what: 'Nothing', + plan: 3 } ] -describe('types urlencoded', () => { - tests.forEach((v) => { - it(v.what, (done) => { - const busboy = new Busboy({ - limits: v.limits, - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=utf-8' - } - }) - let finishes = 0 - const results = [] +tests.forEach((v) => { + test(v.what, t => { + t.plan(v.plan || 20) + const busboy = new Busboy({ + limits: v.limits, + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8' + } + }) + let finishes = 0 + const results = [] - busboy.on('field', function (key, val, keyTrunc, valTrunc) { - results.push([key, val, keyTrunc, valTrunc]) - }) - busboy.on('file', function () { - throw new Error('Unexpected file') - }) - busboy.on('finish', function () { - assert(finishes++ === 0, 'finish emitted multiple times') - assert.deepEqual(results.length, - v.expected.length, - 'Parsed result count mismatch. Saw ' + - results.length + - '. Expected: ' + v.expected.length) + busboy.on('field', function (key, val, keyTrunc, valTrunc) { + results.push([key, val, keyTrunc, valTrunc]) + }) + busboy.on('file', function () { + throw new Error('Unexpected file') + }) + busboy.on('finish', function () { + t.ok(finishes++ === 0, 'finish emitted multiple times') + t.equal(results.length, v.expected.length) - let i = 0 - results.forEach(function (result) { - assert.deepEqual(result, - v.expected[i], - 'Result mismatch:\nParsed: ' + inspect(result) + + let i = 0 + results.forEach(function (result) { + t.strictSame(result, + v.expected[i], + 'Result mismatch:\nParsed: ' + inspect(result) + '\nExpected: ' + inspect(v.expected[i]) - ) - ++i - }) - done() + ) + ++i }) + t.pass() + }) - v.source.forEach(function (s) { - busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN) - }) - busboy.end() + v.source.forEach(function (s) { + busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN) }) + busboy.end() }) })