summaryrefslogtreecommitdiffstats
path: root/fastify-busboy/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--fastify-busboy/test/busboy-constructor.test.js75
-rw-r--r--fastify-busboy/test/decoder.test.js98
-rw-r--r--fastify-busboy/test/dicer-constructor.test.js22
-rw-r--r--fastify-busboy/test/dicer-endfinish.test.js96
-rw-r--r--fastify-busboy/test/dicer-export.test.js24
-rw-r--r--fastify-busboy/test/dicer-headerparser.test.js192
-rw-r--r--fastify-busboy/test/dicer-malformed-header.test.js29
-rw-r--r--fastify-busboy/test/dicer-multipart-extra-trailer.test.js82
-rw-r--r--fastify-busboy/test/dicer-multipart-nolisteners.test.js44
-rw-r--r--fastify-busboy/test/dicer-multipart.test.js223
-rw-r--r--fastify-busboy/test/fixtures/many-noend/original31
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part11
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part1.header1
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part20
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part2.header1
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part30
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part3.header1
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part40
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part4.header1
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part53
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part5.header1
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part61
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part6.header1
-rw-r--r--fastify-busboy/test/fixtures/many-noend/part7.header2
-rw-r--r--fastify-busboy/test/fixtures/many-wrongboundary/original32
-rw-r--r--fastify-busboy/test/fixtures/many-wrongboundary/preamble33
-rw-r--r--fastify-busboy/test/fixtures/many-wrongboundary/preamble.error1
-rw-r--r--fastify-busboy/test/fixtures/many/original32
-rw-r--r--fastify-busboy/test/fixtures/many/part11
-rw-r--r--fastify-busboy/test/fixtures/many/part1.header1
-rw-r--r--fastify-busboy/test/fixtures/many/part20
-rw-r--r--fastify-busboy/test/fixtures/many/part2.header1
-rw-r--r--fastify-busboy/test/fixtures/many/part30
-rw-r--r--fastify-busboy/test/fixtures/many/part3.header1
-rw-r--r--fastify-busboy/test/fixtures/many/part40
-rw-r--r--fastify-busboy/test/fixtures/many/part4.header1
-rw-r--r--fastify-busboy/test/fixtures/many/part53
-rw-r--r--fastify-busboy/test/fixtures/many/part5.header1
-rw-r--r--fastify-busboy/test/fixtures/many/part60
-rw-r--r--fastify-busboy/test/fixtures/many/part6.header2
-rw-r--r--fastify-busboy/test/fixtures/many/part71
-rw-r--r--fastify-busboy/test/fixtures/many/part7.header1
-rw-r--r--fastify-busboy/test/fixtures/nested-full/original24
-rw-r--r--fastify-busboy/test/fixtures/nested-full/part11
-rw-r--r--fastify-busboy/test/fixtures/nested-full/part1.header1
-rw-r--r--fastify-busboy/test/fixtures/nested-full/part212
-rw-r--r--fastify-busboy/test/fixtures/nested-full/part2.header2
-rw-r--r--fastify-busboy/test/fixtures/nested-full/preamble.header2
-rw-r--r--fastify-busboy/test/fixtures/nested/original21
-rw-r--r--fastify-busboy/test/fixtures/nested/part11
-rw-r--r--fastify-busboy/test/fixtures/nested/part1.header1
-rw-r--r--fastify-busboy/test/fixtures/nested/part212
-rw-r--r--fastify-busboy/test/fixtures/nested/part2.header2
-rw-r--r--fastify-busboy/test/get-limit.test.js34
-rw-r--r--fastify-busboy/test/multipart-stream-pause.test.js82
-rw-r--r--fastify-busboy/test/parse-params.test.js124
-rw-r--r--fastify-busboy/test/streamsearch.test.js396
-rw-r--r--fastify-busboy/test/types-multipart.test.js678
-rw-r--r--fastify-busboy/test/types-urlencoded.test.js210
-rw-r--r--fastify-busboy/test/types/dicer.test-d.ts81
-rw-r--r--fastify-busboy/test/types/main.test-d.ts241
61 files changed, 2965 insertions, 0 deletions
diff --git a/fastify-busboy/test/busboy-constructor.test.js b/fastify-busboy/test/busboy-constructor.test.js
new file mode 100644
index 0000000..8607789
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/decoder.test.js b/fastify-busboy/test/decoder.test.js
new file mode 100644
index 0000000..fa4ce69
--- /dev/null
+++ b/fastify-busboy/test/decoder.test.js
@@ -0,0 +1,98 @@
+'use strict'
+
+const { test } = require('tap')
+const Decoder = require('../lib/utils/Decoder')
+
+test('Decoder', t => {
+ const tests =
+ [
+ {
+ source: ['Hello world'],
+ expected: 'Hello world',
+ what: 'No encoded bytes'
+ },
+ {
+ source: ['Hello%20world'],
+ expected: 'Hello world',
+ what: 'One full encoded byte'
+ },
+ {
+ source: ['Hello%20world%21'],
+ expected: 'Hello world!',
+ what: 'Two full encoded bytes'
+ },
+ {
+ source: ['Hello%', '20world'],
+ expected: 'Hello world',
+ what: 'One full encoded byte split #1'
+ },
+ {
+ source: ['Hello%2', '0world'],
+ expected: 'Hello world',
+ what: 'One full encoded byte split #2'
+ },
+ {
+ source: ['Hello%20', 'world'],
+ expected: 'Hello world',
+ what: 'One full encoded byte (concat)'
+ },
+ {
+ source: ['Hello%2Qworld'],
+ expected: 'Hello%2Qworld',
+ what: 'Malformed encoded byte #1'
+ },
+ {
+ source: ['Hello%world'],
+ expected: 'Hello%world',
+ what: 'Malformed encoded byte #2'
+ },
+ {
+ source: ['Hello+world'],
+ expected: 'Hello world',
+ what: 'Plus to space'
+ },
+ {
+ source: ['Hello+world%21'],
+ expected: 'Hello world!',
+ what: 'Plus and encoded byte'
+ },
+ {
+ source: ['5%2B5%3D10'],
+ expected: '5+5=10',
+ what: 'Encoded plus'
+ },
+ {
+ source: ['5+%2B+5+%3D+10'],
+ expected: '5 + 5 = 10',
+ what: 'Spaces and encoded plus'
+ }
+ ]
+ 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) {
+ result += dec.write(s)
+ })
+ const msg = 'Decoded string mismatch.\n' +
+ 'Saw: ' + result + '\n' +
+ 'Expected: ' + v.expected
+ t.strictSame(result, v.expected, msg)
+ })
+ })
+
+ t.test('reset sets internal buffer to undefined', t => {
+ t.plan(2)
+
+ const dec = new Decoder()
+ dec.write('Hello+world%2')
+
+ t.notSame(dec.buffer, undefined)
+ dec.reset()
+ t.equal(dec.buffer, undefined)
+ })
+})
diff --git a/fastify-busboy/test/dicer-constructor.test.js b/fastify-busboy/test/dicer-constructor.test.js
new file mode 100644
index 0000000..e0e6a6c
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/dicer-endfinish.test.js b/fastify-busboy/test/dicer-endfinish.test.js
new file mode 100644
index 0000000..4718076
--- /dev/null
+++ b/fastify-busboy/test/dicer-endfinish.test.js
@@ -0,0 +1,96 @@
+'use strict'
+
+const Dicer = require('../deps/dicer/lib/Dicer')
+const { test } = require('tap')
+
+test('dicer-endfinish', t => {
+ t.plan(1)
+
+ t.test('should properly handle finish', t => {
+ t.plan(4)
+
+ const CRLF = '\r\n'
+ const boundary = 'boundary'
+
+ const writeSep = '--' + boundary
+
+ const writePart = [
+ writeSep,
+ 'Content-Type: text/plain',
+ 'Content-Length: 0'
+ ].join(CRLF) +
+ CRLF + CRLF +
+ 'some data' + CRLF
+
+ const writeEnd = '--' + CRLF
+
+ let firedEnd = false
+ let firedFinish = false
+
+ const dicer = new Dicer({ boundary })
+ dicer.on('part', partListener)
+ dicer.on('finish', finishListener)
+ dicer.write(writePart + writeSep)
+
+ function partListener (partReadStream) {
+ partReadStream.on('data', function () { })
+ partReadStream.on('end', partEndListener)
+ }
+ function partEndListener () {
+ firedEnd = true
+ setImmediate(afterEnd)
+ }
+ function afterEnd () {
+ dicer.end(writeEnd)
+ setImmediate(afterWrite)
+ }
+ function finishListener () {
+ t.ok(firedEnd, 'end before finishing')
+ firedFinish = true
+ test2()
+ }
+ function afterWrite () {
+ t.ok(firedFinish, 'Failed to finish')
+ }
+
+ let isPausePush = true
+
+ let firedPauseCallback = false
+ let firedPauseFinish = false
+
+ let dicer2 = null
+
+ function test2 () {
+ dicer2 = new Dicer({ boundary })
+ dicer2.on('part', pausePartListener)
+ dicer2.on('finish', pauseFinish)
+ dicer2.write(writePart + writeSep, 'utf8', pausePartCallback)
+ setImmediate(pauseAfterWrite)
+ }
+ function pausePartListener (partReadStream) {
+ partReadStream.on('data', function () { })
+ partReadStream.on('end', function () { })
+ const realPush = partReadStream.push
+ partReadStream.push = function fakePush () {
+ realPush.apply(partReadStream, arguments)
+ if (!isPausePush) { return true }
+ isPausePush = false
+ return false
+ }
+ }
+ function pauseAfterWrite () {
+ dicer2.end(writeEnd)
+ setImmediate(pauseAfterEnd)
+ }
+ function pauseAfterEnd () {
+ t.ok(firedPauseCallback, 'Called callback after pause')
+ t.ok(firedPauseFinish, 'Finish after pause')
+ }
+ function pauseFinish () {
+ firedPauseFinish = true
+ }
+ function pausePartCallback () {
+ firedPauseCallback = true
+ }
+ })
+})
diff --git a/fastify-busboy/test/dicer-export.test.js b/fastify-busboy/test/dicer-export.test.js
new file mode 100644
index 0000000..05df4e6
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/dicer-headerparser.test.js b/fastify-busboy/test/dicer-headerparser.test.js
new file mode 100644
index 0000000..73da283
--- /dev/null
+++ b/fastify-busboy/test/dicer-headerparser.test.js
@@ -0,0 +1,192 @@
+'use strict'
+
+const { test } = require('tap')
+const HeaderParser = require('../deps/dicer/lib/HeaderParser')
+
+test('dicer-headerparser', t => {
+ const DCRLF = '\r\n\r\n'
+ const MAXED_BUFFER = Buffer.allocUnsafe(128 * 1024)
+ MAXED_BUFFER.fill(0x41) // 'A'
+
+ const tests = [
+ {
+ source: DCRLF,
+ expected: {},
+ what: 'No header'
+ },
+ {
+ source: ['Content-Type:\t text/plain',
+ 'Content-Length:0'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'], 'content-length': ['0'] },
+ what: 'Value spacing'
+ },
+ {
+ source: ['Content-Type:\t text/plain',
+ 'Content-Length:0'
+ ].join('\r\n') + DCRLF,
+ cfg: {
+ maxHeaderPairs: 0
+ },
+ expected: {},
+ what: 'should enforce maxHeaderPairs of 0'
+ },
+ {
+ source: ['Content-Type:\t text/plain',
+ 'Content-Length:0'
+ ].join('\r\n') + DCRLF,
+ cfg: {
+ maxHeaderPairs: 1
+ },
+ expected: { 'content-type': [' text/plain'] },
+ what: 'should enforce maxHeaderPairs of 1'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'Foo:\r\n bar\r\n baz'
+ ].join('\r\n') + DCRLF,
+ expected: {},
+ cfg: {
+ maxHeaderSize: 0
+ },
+ what: 'should enforce maxHeaderSize of 0'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'Foo:\r\n bar\r\n baz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plai'] },
+ cfg: {
+ maxHeaderSize: 25
+ },
+ what: 'should enforce maxHeaderSize of 25'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'Foo:\r\n bar\r\n baz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'] },
+ cfg: {
+ maxHeaderSize: 31
+ },
+ what: 'should enforce maxHeaderSize of 31 and ignore the second header'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'Foo:\r\n bar\r\n baz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'], foo: [''] },
+ cfg: {
+ maxHeaderSize: 32
+ },
+ what: 'should enforce maxHeaderSize of 32 and only add key of second header'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'Foo:\r\n bar\r\n baz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'], foo: ['\r'] },
+ cfg: {
+ maxHeaderSize: 33
+ },
+ what: 'should enforce maxHeaderSize of 32 and get only first character of second pair'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ ' : '
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain : '] },
+ what: 'should not break if invalid header pair (colon exists but empty key and value) is provided'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'FoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobaz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'] },
+ what: 'should not break if invalid header pair (no distinctive colon) is provided'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ ':FoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobazFoobaz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'] },
+ what: 'should not break if invalid header pair (no key) is provided'
+ },
+ {
+ source: ['Content-Type:\t text/plain',
+ 'Content-Length:0'
+ ].join('\r\n') + DCRLF,
+ cfg: {
+ maxHeaderPairs: 2
+ },
+ expected: { 'content-type': [' text/plain'], 'content-length': ['0'] },
+ what: 'should enforce maxHeaderPairs of 2'
+ },
+ {
+ source: ['Content-Type:\r\n text/plain',
+ 'Foo:\r\n bar\r\n baz'
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [' text/plain'], foo: [' bar baz'] },
+ what: 'Folded values'
+ },
+ {
+ source: [
+ 'Foo: bar',
+ 'Foo: baz'
+ ].join('\r\n') + DCRLF,
+ expected: { foo: ['bar', 'baz'] },
+ what: 'Folded values'
+ },
+ {
+ source: ['Content-Type:',
+ 'Foo: '
+ ].join('\r\n') + DCRLF,
+ expected: { 'content-type': [''], foo: [''] },
+ what: 'Empty values'
+ },
+ {
+ source: MAXED_BUFFER.toString('ascii') + DCRLF,
+ expected: {},
+ what: 'Max header size (single chunk)'
+ },
+ {
+ source: ['ABCDEFGHIJ', MAXED_BUFFER.toString('ascii'), DCRLF],
+ expected: {},
+ what: 'Max header size (multiple chunks #1)'
+ },
+ {
+ source: [MAXED_BUFFER.toString('ascii'), MAXED_BUFFER.toString('ascii'), DCRLF],
+ expected: {},
+ what: 'Max header size (multiple chunk #2)'
+ }
+ ]
+
+ t.plan(tests.length)
+
+ tests.forEach(function (v) {
+ t.test(v.what, t => {
+ t.plan(4)
+
+ const cfg = {
+ ...v.cfg
+ }
+
+ const parser = Object.keys(cfg).length ? new HeaderParser(cfg) : new HeaderParser()
+ let fired = false
+
+ parser.on('header', function (header) {
+ t.ok(!fired, `${v.what}: Header event fired more than once`)
+ fired = true
+ t.strictSame(header,
+ v.expected,
+ `${v.what}: Parsed result mismatch`)
+ })
+ if (!Array.isArray(v.source)) { v.source = [v.source] }
+ v.source.forEach(function (s) {
+ parser.push(s)
+ })
+ t.ok(fired, `${v.what}: Did not receive header from parser`)
+ t.pass()
+ })
+ })
+})
diff --git a/fastify-busboy/test/dicer-malformed-header.test.js b/fastify-busboy/test/dicer-malformed-header.test.js
new file mode 100644
index 0000000..c25ccdd
--- /dev/null
+++ b/fastify-busboy/test/dicer-malformed-header.test.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const { test } = require('tap')
+const Dicer = require('../deps/dicer/lib/Dicer')
+
+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) {
+ t.hasProp(header, ' content-disposition')
+ t.strictSame(header[' content-disposition'], ['form-data; name="bildbeschreibung"'])
+ })
+ p.on('data', function (data) {
+ })
+ p.on('end', function () {
+ })
+ })
+ d.on('finish', function () {
+ 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/fastify-busboy/test/dicer-multipart-extra-trailer.test.js b/fastify-busboy/test/dicer-multipart-extra-trailer.test.js
new file mode 100644
index 0000000..335605a
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/dicer-multipart-nolisteners.test.js b/fastify-busboy/test/dicer-multipart-nolisteners.test.js
new file mode 100644
index 0000000..1e311ba
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/dicer-multipart.test.js b/fastify-busboy/test/dicer-multipart.test.js
new file mode 100644
index 0000000..c35c4d0
--- /dev/null
+++ b/fastify-busboy/test/dicer-multipart.test.js
@@ -0,0 +1,223 @@
+'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/')
+
+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 }
+
+ const dicer = new Dicer(v.opts)
+ let error
+ let partErrors = 0
+ let finishes = 0
+
+ dicer.on('preamble', function (p) {
+ const preamble = {
+ body: undefined,
+ bodylen: 0,
+ error: undefined,
+ header: undefined
+ }
+
+ p.on('header', function (h) {
+ preamble.header = h
+ if (v.setBoundary) { dicer.setBoundary(v.setBoundary) }
+ }).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 }
+ })
+ })
+ 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) {
+ 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('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)) }
+
+ 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)))
+
+ 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)))
+ }
+ t.pass()
+ })
+
+ fs.createReadStream(fixtureBase + '/original').pipe(dicer)
+ })
+ })
+})
+
+function makeMsg (what, msg) {
+ return what + ': ' + msg
+}
diff --git a/fastify-busboy/test/fixtures/many-noend/original b/fastify-busboy/test/fixtures/many-noend/original
new file mode 100644
index 0000000..ad9f0cc
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/original
@@ -0,0 +1,31 @@
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="_method"
+
+put
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[blog]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[public_email]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[interests]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[bio]"
+
+hello
+
+"quote"
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="commit"
+
+Save
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="media"; filename=""
+Content-Type: application/octet-stream
+
+
diff --git a/fastify-busboy/test/fixtures/many-noend/part1 b/fastify-busboy/test/fixtures/many-noend/part1
new file mode 100644
index 0000000..a232311
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part1
@@ -0,0 +1 @@
+put \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part1.header b/fastify-busboy/test/fixtures/many-noend/part1.header
new file mode 100644
index 0000000..5e6bbe5
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part1.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"_method\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part2 b/fastify-busboy/test/fixtures/many-noend/part2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part2
diff --git a/fastify-busboy/test/fixtures/many-noend/part2.header b/fastify-busboy/test/fixtures/many-noend/part2.header
new file mode 100644
index 0000000..5b53966
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part2.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[blog]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part3 b/fastify-busboy/test/fixtures/many-noend/part3
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part3
diff --git a/fastify-busboy/test/fixtures/many-noend/part3.header b/fastify-busboy/test/fixtures/many-noend/part3.header
new file mode 100644
index 0000000..579e16e
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part3.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[public_email]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part4 b/fastify-busboy/test/fixtures/many-noend/part4
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part4
diff --git a/fastify-busboy/test/fixtures/many-noend/part4.header b/fastify-busboy/test/fixtures/many-noend/part4.header
new file mode 100644
index 0000000..b41be09
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part4.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[interests]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part5 b/fastify-busboy/test/fixtures/many-noend/part5
new file mode 100644
index 0000000..f2bb979
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part5
@@ -0,0 +1,3 @@
+hello
+
+"quote" \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part5.header b/fastify-busboy/test/fixtures/many-noend/part5.header
new file mode 100644
index 0000000..92e417f
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part5.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[bio]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part6 b/fastify-busboy/test/fixtures/many-noend/part6
new file mode 100644
index 0000000..f0f5479
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part6
@@ -0,0 +1 @@
+Save \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part6.header b/fastify-busboy/test/fixtures/many-noend/part6.header
new file mode 100644
index 0000000..65a68a9
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part6.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"commit\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-noend/part7.header b/fastify-busboy/test/fixtures/many-noend/part7.header
new file mode 100644
index 0000000..25171e8
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-noend/part7.header
@@ -0,0 +1,2 @@
+{"content-disposition": ["form-data; name=\"media\"; filename=\"\""],
+ "content-type": ["application/octet-stream"]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-wrongboundary/original b/fastify-busboy/test/fixtures/many-wrongboundary/original
new file mode 100644
index 0000000..859770c
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-wrongboundary/original
@@ -0,0 +1,32 @@
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="_method"
+
+put
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[blog]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[public_email]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[interests]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[bio]"
+
+hello
+
+"quote"
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="media"; filename=""
+Content-Type: application/octet-stream
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="commit"
+
+Save
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-wrongboundary/preamble b/fastify-busboy/test/fixtures/many-wrongboundary/preamble
new file mode 100644
index 0000000..6e4bcc6
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-wrongboundary/preamble
@@ -0,0 +1,33 @@
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="_method"
+
+put
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[blog]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[public_email]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[interests]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[bio]"
+
+hello
+
+"quote"
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="media"; filename=""
+Content-Type: application/octet-stream
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="commit"
+
+Save
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many-wrongboundary/preamble.error b/fastify-busboy/test/fixtures/many-wrongboundary/preamble.error
new file mode 100644
index 0000000..15f4c89
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many-wrongboundary/preamble.error
@@ -0,0 +1 @@
+Preamble terminated early due to unexpected end of multipart data \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/original b/fastify-busboy/test/fixtures/many/original
new file mode 100644
index 0000000..779c5cb
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/original
@@ -0,0 +1,32 @@
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="_method"
+
+put
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[blog]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[public_email]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[interests]"
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="profile[bio]"
+
+hello
+
+"quote"
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="media"; filename=""
+Content-Type: application/octet-stream
+
+
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR
+Content-Disposition: form-data; name="commit"
+
+Save
+------WebKitFormBoundaryWLHCs9qmcJJoyjKR--Extra \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part1 b/fastify-busboy/test/fixtures/many/part1
new file mode 100644
index 0000000..a232311
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part1
@@ -0,0 +1 @@
+put \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part1.header b/fastify-busboy/test/fixtures/many/part1.header
new file mode 100644
index 0000000..5e6bbe5
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part1.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"_method\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part2 b/fastify-busboy/test/fixtures/many/part2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part2
diff --git a/fastify-busboy/test/fixtures/many/part2.header b/fastify-busboy/test/fixtures/many/part2.header
new file mode 100644
index 0000000..5b53966
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part2.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[blog]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part3 b/fastify-busboy/test/fixtures/many/part3
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part3
diff --git a/fastify-busboy/test/fixtures/many/part3.header b/fastify-busboy/test/fixtures/many/part3.header
new file mode 100644
index 0000000..579e16e
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part3.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[public_email]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part4 b/fastify-busboy/test/fixtures/many/part4
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part4
diff --git a/fastify-busboy/test/fixtures/many/part4.header b/fastify-busboy/test/fixtures/many/part4.header
new file mode 100644
index 0000000..b41be09
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part4.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[interests]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part5 b/fastify-busboy/test/fixtures/many/part5
new file mode 100644
index 0000000..f2bb979
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part5
@@ -0,0 +1,3 @@
+hello
+
+"quote" \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part5.header b/fastify-busboy/test/fixtures/many/part5.header
new file mode 100644
index 0000000..92e417f
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part5.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"profile[bio]\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part6 b/fastify-busboy/test/fixtures/many/part6
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part6
diff --git a/fastify-busboy/test/fixtures/many/part6.header b/fastify-busboy/test/fixtures/many/part6.header
new file mode 100644
index 0000000..25171e8
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part6.header
@@ -0,0 +1,2 @@
+{"content-disposition": ["form-data; name=\"media\"; filename=\"\""],
+ "content-type": ["application/octet-stream"]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part7 b/fastify-busboy/test/fixtures/many/part7
new file mode 100644
index 0000000..f0f5479
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part7
@@ -0,0 +1 @@
+Save \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/many/part7.header b/fastify-busboy/test/fixtures/many/part7.header
new file mode 100644
index 0000000..65a68a9
--- /dev/null
+++ b/fastify-busboy/test/fixtures/many/part7.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"commit\""]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested-full/original b/fastify-busboy/test/fixtures/nested-full/original
new file mode 100644
index 0000000..3044550
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested-full/original
@@ -0,0 +1,24 @@
+User-Agent: foo bar baz
+Content-Type: multipart/form-data; boundary=AaB03x
+
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="files"
+Content-Type: multipart/mixed, boundary=BbC04y
+
+--BbC04y
+Content-Disposition: attachment; filename="file.txt"
+Content-Type: text/plain
+
+contents
+--BbC04y
+Content-Disposition: attachment; filename="flowers.jpg"
+Content-Type: image/jpeg
+Content-Transfer-Encoding: binary
+
+contents
+--BbC04y--
+--AaB03x-- \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested-full/part1 b/fastify-busboy/test/fixtures/nested-full/part1
new file mode 100644
index 0000000..ba0e162
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested-full/part1
@@ -0,0 +1 @@
+bar \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested-full/part1.header b/fastify-busboy/test/fixtures/nested-full/part1.header
new file mode 100644
index 0000000..03bd093
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested-full/part1.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"foo\""]}
diff --git a/fastify-busboy/test/fixtures/nested-full/part2 b/fastify-busboy/test/fixtures/nested-full/part2
new file mode 100644
index 0000000..2d4deb5
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested-full/part2
@@ -0,0 +1,12 @@
+--BbC04y
+Content-Disposition: attachment; filename="file.txt"
+Content-Type: text/plain
+
+contents
+--BbC04y
+Content-Disposition: attachment; filename="flowers.jpg"
+Content-Type: image/jpeg
+Content-Transfer-Encoding: binary
+
+contents
+--BbC04y-- \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested-full/part2.header b/fastify-busboy/test/fixtures/nested-full/part2.header
new file mode 100644
index 0000000..bbe4513
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested-full/part2.header
@@ -0,0 +1,2 @@
+{"content-disposition": ["form-data; name=\"files\""],
+ "content-type": ["multipart/mixed, boundary=BbC04y"]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested-full/preamble.header b/fastify-busboy/test/fixtures/nested-full/preamble.header
new file mode 100644
index 0000000..2815341
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested-full/preamble.header
@@ -0,0 +1,2 @@
+{"user-agent": ["foo bar baz"],
+ "content-type": ["multipart/form-data; boundary=AaB03x"]} \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested/original b/fastify-busboy/test/fixtures/nested/original
new file mode 100644
index 0000000..380f451
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested/original
@@ -0,0 +1,21 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="files"
+Content-Type: multipart/mixed, boundary=BbC04y
+
+--BbC04y
+Content-Disposition: attachment; filename="file.txt"
+Content-Type: text/plain
+
+contents
+--BbC04y
+Content-Disposition: attachment; filename="flowers.jpg"
+Content-Type: image/jpeg
+Content-Transfer-Encoding: binary
+
+contents
+--BbC04y--
+--AaB03x-- \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested/part1 b/fastify-busboy/test/fixtures/nested/part1
new file mode 100644
index 0000000..ba0e162
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested/part1
@@ -0,0 +1 @@
+bar \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested/part1.header b/fastify-busboy/test/fixtures/nested/part1.header
new file mode 100644
index 0000000..03bd093
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested/part1.header
@@ -0,0 +1 @@
+{"content-disposition": ["form-data; name=\"foo\""]}
diff --git a/fastify-busboy/test/fixtures/nested/part2 b/fastify-busboy/test/fixtures/nested/part2
new file mode 100644
index 0000000..2d4deb5
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested/part2
@@ -0,0 +1,12 @@
+--BbC04y
+Content-Disposition: attachment; filename="file.txt"
+Content-Type: text/plain
+
+contents
+--BbC04y
+Content-Disposition: attachment; filename="flowers.jpg"
+Content-Type: image/jpeg
+Content-Transfer-Encoding: binary
+
+contents
+--BbC04y-- \ No newline at end of file
diff --git a/fastify-busboy/test/fixtures/nested/part2.header b/fastify-busboy/test/fixtures/nested/part2.header
new file mode 100644
index 0000000..bbe4513
--- /dev/null
+++ b/fastify-busboy/test/fixtures/nested/part2.header
@@ -0,0 +1,2 @@
+{"content-disposition": ["form-data; name=\"files\""],
+ "content-type": ["multipart/mixed, boundary=BbC04y"]} \ No newline at end of file
diff --git a/fastify-busboy/test/get-limit.test.js b/fastify-busboy/test/get-limit.test.js
new file mode 100644
index 0000000..76a2997
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/multipart-stream-pause.test.js b/fastify-busboy/test/multipart-stream-pause.test.js
new file mode 100644
index 0000000..856cf71
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/parse-params.test.js b/fastify-busboy/test/parse-params.test.js
new file mode 100644
index 0000000..eea4768
--- /dev/null
+++ b/fastify-busboy/test/parse-params.test.js
@@ -0,0 +1,124 @@
+'use strict'
+
+const { inspect } = require('node:util')
+const { test } = require('tap')
+const parseParams = require('../lib/utils/parseParams')
+
+test('parse-params', t => {
+ const tests = [
+ {
+ source: 'video/ogg',
+ expected: ['video/ogg'],
+ what: 'No parameters'
+ },
+ {
+ source: 'video/ogg;',
+ expected: ['video/ogg'],
+ what: 'No parameters (with separator)'
+ },
+ {
+ source: 'video/ogg; ',
+ expected: ['video/ogg'],
+ what: 'No parameters (with separator followed by whitespace)'
+ },
+ {
+ source: ';video/ogg',
+ expected: ['', 'video/ogg'],
+ what: 'Empty parameter'
+ },
+ {
+ source: 'video/*',
+ expected: ['video/*'],
+ what: 'Subtype with asterisk'
+ },
+ {
+ source: 'text/plain; encoding=utf8',
+ expected: ['text/plain', ['encoding', 'utf8']],
+ what: 'Unquoted'
+ },
+ {
+ source: 'text/plain; encoding=',
+ expected: ['text/plain', ['encoding', '']],
+ what: 'Unquoted empty string'
+ },
+ {
+ source: 'text/plain; encoding="utf8"',
+ expected: ['text/plain', ['encoding', 'utf8']],
+ what: 'Quoted'
+ },
+ {
+ source: 'text/plain; greeting="hello \\"world\\""',
+ expected: ['text/plain', ['greeting', 'hello "world"']],
+ what: 'Quotes within quoted'
+ },
+ {
+ source: 'text/plain; encoding=""',
+ expected: ['text/plain', ['encoding', '']],
+ what: 'Quoted empty string'
+ },
+ {
+ source: 'text/plain; encoding="utf8";\t foo=bar;test',
+ expected: ['text/plain', ['encoding', 'utf8'], ['foo', 'bar'], 'test'],
+ what: 'Multiple params with various spacing'
+ },
+ {
+ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates",
+ expected: ['text/plain', ['filename', '£ rates']],
+ what: 'Extended parameter (RFC 5987) with language'
+ },
+ {
+ source: "text/plain; filename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates",
+ expected: ['text/plain', ['filename', '£ and € rates']],
+ what: 'Extended parameter (RFC 5987) without language'
+ },
+ {
+ source: "text/plain; filename*=utf-8''%E6%B5%8B%E8%AF%95%E6%96%87%E6%A1%A3",
+ expected: ['text/plain', ['filename', '测试文档']],
+ what: 'Extended parameter (RFC 5987) without language #2'
+ },
+ {
+ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates",
+ expected: ['text/plain', ['filename', '£ rates'], ['altfilename', '£ and € rates']],
+ what: 'Multiple extended parameters (RFC 5987) with mixed charsets'
+ },
+ {
+ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename=\"foobarbaz\"",
+ expected: ['text/plain', ['filename', '£ rates'], ['altfilename', 'foobarbaz']],
+ what: 'Mixed regular and extended parameters (RFC 5987)'
+ },
+ {
+ source: "text/plain; filename=\"foobarbaz\"; altfilename*=iso-8859-1'en'%A3%20rates",
+ expected: ['text/plain', ['filename', 'foobarbaz'], ['altfilename', '£ rates']],
+ what: 'Mixed regular and extended parameters (RFC 5987) #2'
+ },
+ {
+ source: 'text/plain; filename="C:\\folder\\test.png"',
+ expected: ['text/plain', ['filename', 'C:\\folder\\test.png']],
+ what: 'Unescaped backslashes should be considered backslashes'
+ },
+ {
+ source: 'text/plain; filename="John \\"Magic\\" Smith.png"',
+ expected: ['text/plain', ['filename', 'John "Magic" Smith.png']],
+ what: 'Escaped double-quotes should be considered double-quotes'
+ },
+ {
+ source: 'multipart/form-data; charset=utf-8; boundary=0xKhTmLbOuNdArY',
+ expected: ['multipart/form-data', ['charset', 'utf-8'], ['boundary', '0xKhTmLbOuNdArY']],
+ what: 'Multiple non-quoted parameters'
+ }
+ ]
+
+ t.plan(tests.length)
+
+ tests.forEach((v) => {
+ t.test(v.what, t => {
+ t.plan(1)
+
+ const result = parseParams(v.source)
+ t.strictSame(
+ result,
+ v.expected,
+ `parsed parameters match.\nSaw: ${inspect(result)}\nExpected: ${inspect(v.expected)}`)
+ })
+ })
+})
diff --git a/fastify-busboy/test/streamsearch.test.js b/fastify-busboy/test/streamsearch.test.js
new file mode 100644
index 0000000..968c7de
--- /dev/null
+++ b/fastify-busboy/test/streamsearch.test.js
@@ -0,0 +1,396 @@
+'use strict'
+
+const { test } = require('tap')
+const Streamsearch = require('../deps/streamsearch/sbmh')
+
+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.'))
+ })
+ 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.'))
+ })
+ 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.'))
+ })
+ 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.'))
+ })
+
+ t.test('should process a Buffer without a needle', t => {
+ t.plan(5)
+ const expected = [
+ [false, Buffer.from('bar hello'), 0, 9]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar hello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ })
+
+ t.test('should cast a string without a needle', t => {
+ t.plan(5)
+
+ const expected = [
+ [false, Buffer.from('bar hello'), 0, 9]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ 'bar hello'
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ })
+
+ 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]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('\r\nbar hello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ })
+
+ 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]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar\r\n hello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ })
+
+ 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]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar hello\r\n')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ })
+
+ 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]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar hello\r\n\r\n')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ })
+
+ 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]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar'),
+ Buffer.from('hello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ s.push(chunks[1])
+ })
+
+ 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],
+ [false, Buffer.from('\nhello'), 1, 6]
+ ]
+ const needle = '\r\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar\r'),
+ Buffer.from('\nhello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ s.push(chunks[1])
+ })
+
+ 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],
+ [false, Buffer.from('\n\r\nhello'), 0, 8]
+ ]
+ const needle = '\r\n\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar\r'),
+ Buffer.from('\n\r\nhello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ s.push(chunks[1])
+ })
+
+ 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],
+ [false, Buffer.from('\nhello'), 1, 6]
+ ]
+ const needle = '\r\n\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar\r'),
+ Buffer.from('\n'),
+ Buffer.from('\nhello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ s.push(chunks[1])
+ s.push(chunks[2])
+ })
+
+ 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],
+ [false, Buffer.from('hello'), 0, 5]
+ ]
+ const needle = '\r\n\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar\r'),
+ Buffer.from('\n'),
+ Buffer.from('\n'),
+ Buffer.from('hello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ s.push(chunks[1])
+ s.push(chunks[2])
+ s.push(chunks[3])
+ })
+
+ 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],
+ [false, Buffer.from('\r\n\0'), 0, 1],
+ [false, Buffer.from('hello'), 0, 5]
+ ]
+ const needle = '\r\n\n'
+ const s = new Streamsearch(needle)
+ const chunks = [
+ Buffer.from('bar\r'),
+ Buffer.from('\n'),
+ Buffer.from('\r'),
+ Buffer.from('hello')
+ ]
+ let i = 0
+ s.on('info', (isMatched, data, start, end) => {
+ 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) {
+ t.pass()
+ }
+ })
+
+ s.push(chunks[0])
+ s.push(chunks[1])
+ s.push(chunks[2])
+ s.push(chunks[3])
+ })
+
+ t.test('should reset the internal values if .reset() is called', t => {
+ t.plan(9)
+
+ const s = new Streamsearch('test')
+
+ 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
+
+ t.strictSame(s._lookbehind_size, 1)
+ t.strictSame(s.matches, 1)
+ t.strictSame(s._bufpos, 1)
+
+ s.reset()
+
+ t.strictSame(s._lookbehind_size, 0)
+ t.strictSame(s.matches, 0)
+ t.strictSame(s._bufpos, 0)
+ })
+})
diff --git a/fastify-busboy/test/types-multipart.test.js b/fastify-busboy/test/types-multipart.test.js
new file mode 100644
index 0000000..dc7ae88
--- /dev/null
+++ b/fastify-busboy/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/fastify-busboy/test/types-urlencoded.test.js b/fastify-busboy/test/types-urlencoded.test.js
new file mode 100644
index 0000000..73cc286
--- /dev/null
+++ b/fastify-busboy/test/types-urlencoded.test.js
@@ -0,0 +1,210 @@
+'use strict'
+
+const { inspect } = require('util')
+const Busboy = require('..')
+const { test } = require('tap')
+
+const EMPTY_FN = function () {
+}
+
+const tests = [
+ {
+ source: ['foo'],
+ expected: [['foo', '', false, false]],
+ what: 'Unassigned value',
+ plan: 4
+ },
+ {
+ source: ['foo=bar'],
+ expected: [['foo', 'bar', false, false]],
+ what: 'Assigned value',
+ plan: 4
+ },
+ {
+ source: ['foo&bar=baz'],
+ expected: [['foo', '', false, false],
+ ['bar', 'baz', false, false]],
+ what: 'Unassigned and assigned value',
+ plan: 5
+ },
+ {
+ source: ['foo=bar&baz'],
+ expected: [['foo', 'bar', false, false],
+ ['baz', '', false, false]],
+ 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',
+ plan: 5
+ },
+ {
+ source: ['foo&bar'],
+ expected: [['foo', '', false, false],
+ ['bar', '', false, false]],
+ what: 'Two unassigned values',
+ plan: 5
+ },
+ {
+ source: ['foo&bar&'],
+ expected: [['foo', '', false, false],
+ ['bar', '', false, false]],
+ 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',
+ plan: 4
+ },
+ {
+ source: ['foo=bar%20baz%21'],
+ expected: [['foo', 'bar baz!', false, false]],
+ 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',
+ 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',
+ plan: 5
+ },
+ {
+ source: ['foo=bar&baz=bla'],
+ expected: [],
+ what: 'Limits: zero fields',
+ limits: { fields: 0 },
+ plan: 3
+ },
+ {
+ source: ['foo=bar&baz=bla'],
+ expected: [['foo', 'bar', false, false]],
+ what: 'Limits: one field',
+ 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 },
+ plan: 5
+ },
+ {
+ source: ['foo=bar&baz=bla'],
+ expected: [['fo', 'bar', true, false],
+ ['ba', 'bla', true, false]],
+ what: 'Limits: truncated field name',
+ 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 },
+ 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 },
+ 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 },
+ 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 },
+ plan: 5
+ },
+ {
+ source: ['&'],
+ expected: [],
+ what: 'Ampersand',
+ plan: 3
+ },
+ {
+ source: ['&&&&&'],
+ expected: [],
+ what: 'Many ampersands',
+ plan: 3
+ },
+ {
+ source: ['='],
+ expected: [['', '', false, false]],
+ what: 'Assigned value, empty name and value',
+ plan: 4
+ },
+ {
+ source: [''],
+ expected: [],
+ what: 'Nothing',
+ plan: 3
+ }
+]
+
+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 () {
+ t.ok(finishes++ === 0, 'finish emitted multiple times')
+ t.equal(results.length, v.expected.length)
+
+ let i = 0
+ results.forEach(function (result) {
+ t.strictSame(result,
+ v.expected[i],
+ 'Result mismatch:\nParsed: ' + inspect(result) +
+ '\nExpected: ' + inspect(v.expected[i])
+ )
+ ++i
+ })
+ t.pass()
+ })
+
+ v.source.forEach(function (s) {
+ busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN)
+ })
+ busboy.end()
+ })
+})
diff --git a/fastify-busboy/test/types/dicer.test-d.ts b/fastify-busboy/test/types/dicer.test-d.ts
new file mode 100644
index 0000000..466c1e1
--- /dev/null
+++ b/fastify-busboy/test/types/dicer.test-d.ts
@@ -0,0 +1,81 @@
+import { Dicer } from "../../lib/main";
+import * as fs from "fs";
+import * as stream from "stream";
+
+function testDicerSyntax() {
+ const opts: Dicer.Config = {
+ boundary: "testing",
+ };
+ const dicer = new Dicer(opts);
+ const opts2: Dicer.Config = {
+ headerFirst: true,
+ maxHeaderPairs: 1,
+ };
+ const opts3: Dicer.Config = {
+ boundary: "more-testing",
+ headerFirst: false,
+ maxHeaderPairs: 8,
+ };
+ dicer.setBoundary("new-testing-boundary");
+ dicer.on("part", handleDicerPartStream);
+ dicer.on("finish", () => {
+ console.log("dicer parsing finished");
+ });
+ dicer.on("preamble", part => {
+ console.log("dicer preamble to new part");
+ });
+ dicer.on("trailer", data => {
+ console.log(`dicer trailing data found: ${data.length} bytes`);
+ });
+ dicer.on("close", () => {
+ console.log("dicer close");
+ });
+ dicer.on("drain", () => {
+ console.log("dicer drain");
+ });
+ dicer.on("error", err => {
+ console.error(`dicer error: ${err.message || JSON.stringify(err)}`);
+ });
+ dicer.on("finish", () => {
+ console.log("dicer finish");
+ });
+ dicer.on("pipe", (src: stream.Readable) => {
+ console.log("dicer pipe");
+ });
+ dicer.on("unpipe", (src: stream.Readable) => {
+ console.log("dicer unpipe");
+ });
+ const inputFileStream = fs.createReadStream("in-test-file.txt");
+ inputFileStream.pipe(dicer);
+}
+/**
+ * Handle a part found by a Dicer parser
+ *
+ * @param part Part found
+ */
+function handleDicerPartStream(part: Dicer.PartStream) {
+ console.log("dicer part found");
+ const outputFileStream = fs.createWriteStream("out-test-file.txt");
+ part.on("readable", () => {
+ console.log("part readable");
+ });
+ part.on("header", header => {
+ console.log(`part header found:\n${JSON.stringify(header)}`);
+ });
+ part.on("data", () => {
+ console.log("part data");
+ });
+ part.on("finish", () => {
+ console.log("part finished");
+ });
+ part.on("error", err => {
+ console.error(`part error: ${err.message || JSON.stringify(err)}`);
+ });
+ part.on("end", () => {
+ console.log("part ended");
+ });
+ part.on("close", () => {
+ console.log("part closed");
+ });
+ part.pipe(outputFileStream);
+} \ No newline at end of file
diff --git a/fastify-busboy/test/types/main.test-d.ts b/fastify-busboy/test/types/main.test-d.ts
new file mode 100644
index 0000000..fb58b3f
--- /dev/null
+++ b/fastify-busboy/test/types/main.test-d.ts
@@ -0,0 +1,241 @@
+import BusboyDefault, { BusboyConstructor, BusboyConfig, BusboyHeaders, Busboy, BusboyEvents, BusboyFileStream } from '../..';
+import {expectError, expectType} from "tsd";
+import BusboyESM from "../..";
+
+// test type exports
+type Constructor = BusboyConstructor;
+type Config = BusboyConfig;
+type Headers = BusboyHeaders;
+type Events = BusboyEvents;
+type BB = Busboy;
+
+expectType<Busboy>(new BusboyESM({ headers: { 'content-type': 'foo' } }));
+expectType<Busboy>(new Busboy({ headers: { 'content-type': 'foo' } }));
+
+expectError(new BusboyDefault({}));
+const busboy = BusboyDefault({ headers: { 'content-type': 'foo' } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, highWaterMark: 1000 }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, fileHwm: 1000 }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, defCharset: 'utf8' }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, preservePath: true }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { fieldNameSize: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { fieldSize: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { fields: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { fileSize: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { files: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { parts: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { headerPairs: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, limits: { headerSize: 200 } }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, isPartAFile: (fieldName, contentType, fileName) => fieldName === 'my-special-field' || fileName !== 'not-so-special.txt' }); // $ExpectType Busboy
+new BusboyDefault({ headers: { 'content-type': 'foo' }, isPartAFile: (fieldName, contentType, fileName) => fileName !== undefined }); // $ExpectType Busboy
+
+busboy.addListener('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname)
+ expectType<BusboyFileStream>(file);
+ expectType<string>(filename);
+ expectType<string>(encoding);
+ expectType<string>(mimetype);
+});
+busboy.addListener('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.addListener('partsLimit', () => {});
+busboy.addListener('filesLimit', () => {});
+busboy.addListener('fieldsLimit', () => {});
+busboy.addListener('error', e => {
+ expectType<unknown> (e);
+});
+busboy.addListener('finish', () => {});
+// test fallback
+busboy.on('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.on(Symbol('foo'), foo => {
+ expectType<any>(foo);
+});
+
+busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<BusboyFileStream> (file);
+ expectType<string> (filename);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.on('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.on('partsLimit', () => {});
+busboy.on('filesLimit', () => {});
+busboy.on('fieldsLimit', () => {});
+busboy.on('error', e => {
+ expectType<unknown> (e);
+});
+busboy.on('finish', () => {});
+// test fallback
+busboy.on('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.on(Symbol('foo'), foo => {
+ expectType<any> (foo);
+});
+
+busboy.once('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<BusboyFileStream> (file);
+ expectType<string> (filename);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.once('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.once('partsLimit', () => {});
+busboy.once('filesLimit', () => {});
+busboy.once('fieldsLimit', () => {});
+busboy.once('error', e => {
+ expectType<unknown> (e);
+});
+busboy.once('finish', () => {});
+// test fallback
+busboy.once('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.once(Symbol('foo'), foo => {
+ expectType<any> (foo);
+});
+
+busboy.removeListener('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<BusboyFileStream> (file);
+ expectType<string> (filename);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.removeListener('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.removeListener('partsLimit', () => {});
+busboy.removeListener('filesLimit', () => {});
+busboy.removeListener('fieldsLimit', () => {});
+busboy.removeListener('error', e => {
+ expectType<unknown> (e);
+});
+busboy.removeListener('finish', () => {});
+// test fallback
+busboy.removeListener('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.removeListener(Symbol('foo'), foo => {
+ expectType<any> (foo);
+});
+
+busboy.off('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<BusboyFileStream> (file);
+ expectType<string> (filename);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.off('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.off('partsLimit', () => {});
+busboy.off('filesLimit', () => {});
+busboy.off('fieldsLimit', () => {});
+busboy.off('error', e => {
+ expectType<unknown> (e);
+});
+busboy.off('finish', () => {});
+// test fallback
+busboy.off('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.off(Symbol('foo'), foo => {
+ expectType<any> (foo);
+});
+
+busboy.prependListener('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<BusboyFileStream> (file);
+ expectType<string> (filename);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.prependListener('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.prependListener('partsLimit', () => {});
+busboy.prependListener('filesLimit', () => {});
+busboy.prependListener('fieldsLimit', () => {});
+busboy.prependListener('error', e => {
+ expectType<unknown> (e);
+});
+busboy.prependListener('finish', () => {});
+// test fallback
+busboy.prependListener('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.prependListener(Symbol('foo'), foo => {
+ expectType<any> (foo);
+});
+
+busboy.prependOnceListener('file', (fieldname, file, filename, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<BusboyFileStream> (file);
+ expectType<string> (filename);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.prependOnceListener('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
+ expectType<string> (fieldname);
+ expectType<string> (val);
+ expectType<boolean> (fieldnameTruncated);
+ expectType<boolean> (valTruncated);
+ expectType<string> (encoding);
+ expectType<string> (mimetype);
+});
+busboy.prependOnceListener('partsLimit', () => {});
+busboy.prependOnceListener('filesLimit', () => {});
+busboy.prependOnceListener('fieldsLimit', () => {});
+busboy.prependOnceListener('error', e => {
+ expectType<unknown> (e);
+});
+busboy.prependOnceListener('finish', () => {});
+// test fallback
+busboy.prependOnceListener('foo', foo => {
+ expectType<any> (foo);
+});
+busboy.prependOnceListener(Symbol('foo'), foo => {
+ expectType<any> (foo);
+});