diff --git a/README.md b/README.md index 654af30..27b34f2 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,8 @@ http.createServer((req, res) => { * **preservePath** - _boolean_ - If paths in filenames from file parts in a `'multipart/form-data'` request shall be preserved. **Default:** `false`. + * **bufferOutput** - _boolean_ - If provided, fields part will be emitted as Buffer and not as text. **Default:** `false`. + * **limits** - _object_ - Various limits on incoming data. Valid properties are: * **fieldNameSize** - _integer_ - Max field name size (in bytes). **Default:** `100`. diff --git a/lib/index.js b/lib/index.js index 873272d..bf6e043 100644 --- a/lib/index.js +++ b/lib/index.js @@ -22,6 +22,7 @@ function getInstance(cfg) { defCharset: undefined, defParamCharset: undefined, preservePath: false, + bufferOutput: false, }; if (cfg.highWaterMark) instanceCfg.highWaterMark = cfg.highWaterMark; @@ -30,6 +31,7 @@ function getInstance(cfg) { instanceCfg.defCharset = cfg.defCharset; instanceCfg.defParamCharset = cfg.defParamCharset; instanceCfg.preservePath = cfg.preservePath; + instanceCfg.bufferOutput = cfg.bufferOutput; return new type(instanceCfg); } diff --git a/lib/types/multipart.js b/lib/types/multipart.js index cc0d7bb..2937a76 100644 --- a/lib/types/multipart.js +++ b/lib/types/multipart.js @@ -239,6 +239,7 @@ class Multipart extends Writable { : nullDecoder); const defCharset = (cfg.defCharset || 'utf8'); const preservePath = cfg.preservePath; + const bufferOutput = cfg.bufferOutput; const fileOpts = { autoDestroy: true, emitClose: true, @@ -520,17 +521,21 @@ retrydata: let data; switch (field.length) { case 0: - data = ''; + data = bufferOutput ? null : ''; break; case 1: - data = convertToUTF8(field[0], partCharset, 0); + data = bufferOutput + ? field[0] + : convertToUTF8(field[0], partCharset, 0); break; default: - data = convertToUTF8( - Buffer.concat(field, fieldSize), - partCharset, - 0 - ); + data = bufferOutput + ? Buffer.concat(field, fieldSize) + : convertToUTF8( + Buffer.concat(field, fieldSize), + partCharset, + 0 + ); } field = undefined; fieldSize = 0; diff --git a/test/test-types-multipart-buffers.js b/test/test-types-multipart-buffers.js new file mode 100644 index 0000000..cf9ef1a --- /dev/null +++ b/test/test-types-multipart-buffers.js @@ -0,0 +1,94 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const { mustCall } = require(`${__dirname}/common.js`); + +const busboy = require('../lib'); + +const input = Buffer.from([ + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="content"', + 'Content-Type: text/plain', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' +].join('\r\n')); +const boundary = '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k'; +const expected = [ + { type: 'field', + name: 'content', + val: Buffer.from('A'.repeat(1023)), + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, +]; +const bb = busboy({ + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + bufferOutput: true +}); +const results = []; + +bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); +}); + +bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); +}); + +bb.on('error', (err) => { + results.push({ error: err.message }); +}); + +bb.on('partsLimit', () => { + results.push('partsLimit'); +}); + +bb.on('filesLimit', () => { + results.push('filesLimit'); +}); + +bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); +}); + +bb.on('close', mustCall(() => { + assert.deepStrictEqual( + results, + expected, + 'Results mismatch.\n' + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(expected)}` + ); +})); + +bb.end(input);