summaryrefslogtreecommitdiff
path: root/node_modules/busboy/test
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/busboy/test')
-rw-r--r--node_modules/busboy/test/common.js109
-rw-r--r--node_modules/busboy/test/test-types-multipart-charsets.js94
-rw-r--r--node_modules/busboy/test/test-types-multipart-stream-pause.js102
-rw-r--r--node_modules/busboy/test/test-types-multipart.js1053
-rw-r--r--node_modules/busboy/test/test-types-urlencoded.js488
-rw-r--r--node_modules/busboy/test/test.js20
6 files changed, 1866 insertions, 0 deletions
diff --git a/node_modules/busboy/test/common.js b/node_modules/busboy/test/common.js
new file mode 100644
index 0000000..fb82ad8
--- /dev/null
+++ b/node_modules/busboy/test/common.js
@@ -0,0 +1,109 @@
+'use strict';
+
+const assert = require('assert');
+const { inspect } = require('util');
+
+const mustCallChecks = [];
+
+function noop() {}
+
+function runCallChecks(exitCode) {
+ if (exitCode !== 0) return;
+
+ const failed = mustCallChecks.filter((context) => {
+ if ('minimum' in context) {
+ context.messageSegment = `at least ${context.minimum}`;
+ return context.actual < context.minimum;
+ }
+ context.messageSegment = `exactly ${context.exact}`;
+ return context.actual !== context.exact;
+ });
+
+ failed.forEach((context) => {
+ console.error('Mismatched %s function calls. Expected %s, actual %d.',
+ context.name,
+ context.messageSegment,
+ context.actual);
+ console.error(context.stack.split('\n').slice(2).join('\n'));
+ });
+
+ if (failed.length)
+ process.exit(1);
+}
+
+function mustCall(fn, exact) {
+ return _mustCallInner(fn, exact, 'exact');
+}
+
+function mustCallAtLeast(fn, minimum) {
+ return _mustCallInner(fn, minimum, 'minimum');
+}
+
+function _mustCallInner(fn, criteria = 1, field) {
+ if (process._exiting)
+ throw new Error('Cannot use common.mustCall*() in process exit handler');
+
+ if (typeof fn === 'number') {
+ criteria = fn;
+ fn = noop;
+ } else if (fn === undefined) {
+ fn = noop;
+ }
+
+ if (typeof criteria !== 'number')
+ throw new TypeError(`Invalid ${field} value: ${criteria}`);
+
+ const context = {
+ [field]: criteria,
+ actual: 0,
+ stack: inspect(new Error()),
+ name: fn.name || '<anonymous>'
+ };
+
+ // Add the exit listener only once to avoid listener leak warnings
+ if (mustCallChecks.length === 0)
+ process.on('exit', runCallChecks);
+
+ mustCallChecks.push(context);
+
+ function wrapped(...args) {
+ ++context.actual;
+ return fn.call(this, ...args);
+ }
+ // TODO: remove origFn?
+ wrapped.origFn = fn;
+
+ return wrapped;
+}
+
+function getCallSite(top) {
+ const originalStackFormatter = Error.prepareStackTrace;
+ Error.prepareStackTrace = (err, stack) =>
+ `${stack[0].getFileName()}:${stack[0].getLineNumber()}`;
+ const err = new Error();
+ Error.captureStackTrace(err, top);
+ // With the V8 Error API, the stack is not formatted until it is accessed
+ // eslint-disable-next-line no-unused-expressions
+ err.stack;
+ Error.prepareStackTrace = originalStackFormatter;
+ return err.stack;
+}
+
+function mustNotCall(msg) {
+ const callSite = getCallSite(mustNotCall);
+ return function mustNotCall(...args) {
+ args = args.map(inspect).join(', ');
+ const argsInfo = (args.length > 0
+ ? `\ncalled with arguments: ${args}`
+ : '');
+ assert.fail(
+ `${msg || 'function should not have been called'} at ${callSite}`
+ + argsInfo);
+ };
+}
+
+module.exports = {
+ mustCall,
+ mustCallAtLeast,
+ mustNotCall,
+};
diff --git a/node_modules/busboy/test/test-types-multipart-charsets.js b/node_modules/busboy/test/test-types-multipart-charsets.js
new file mode 100644
index 0000000..ed9c38a
--- /dev/null
+++ b/node_modules/busboy/test/test-types-multipart-charsets.js
@@ -0,0 +1,94 @@
+'use strict';
+
+const assert = require('assert');
+const { inspect } = require('util');
+
+const { mustCall } = require(`${__dirname}/common.js`);
+
+const busboy = require('..');
+
+const input = Buffer.from([
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="テスト.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'A'.repeat(1023),
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
+].join('\r\n'));
+const boundary = '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k';
+const expected = [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('A'.repeat(1023)),
+ info: {
+ filename: 'テスト.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+];
+const bb = busboy({
+ defParamCharset: 'utf8',
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ }
+});
+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);
diff --git a/node_modules/busboy/test/test-types-multipart-stream-pause.js b/node_modules/busboy/test/test-types-multipart-stream-pause.js
new file mode 100644
index 0000000..df7268a
--- /dev/null
+++ b/node_modules/busboy/test/test-types-multipart-stream-pause.js
@@ -0,0 +1,102 @@
+'use strict';
+
+const assert = require('assert');
+const { randomFillSync } = require('crypto');
+const { inspect } = require('util');
+
+const busboy = require('..');
+
+const { mustCall } = require('./common.js');
+
+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) {
+ const buf = Buffer.allocUnsafe(100000);
+ 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`),
+ randomFillSync(buf)
+ ]);
+}
+
+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 bb = busboy({
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${BOUNDARY}`
+ }
+});
+const expected = [
+ { type: 'file',
+ name: 'file',
+ info: {
+ filename: 'file.bin',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ },
+ { type: 'field',
+ name: 'foo',
+ val: 'foo value',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'field',
+ name: 'bar',
+ val: 'bar value',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+];
+const results = [];
+
+bb.on('field', (name, val, info) => {
+ results.push({ type: 'field', name, val, info });
+});
+
+bb.on('file', (name, stream, info) => {
+ results.push({ type: 'file', name, info });
+ // Simulate a pipe where the destination is pausing (perhaps due to waiting
+ // for file system write to finish)
+ setTimeout(() => {
+ stream.resume();
+ }, 10);
+});
+
+bb.on('close', mustCall(() => {
+ assert.deepStrictEqual(
+ results,
+ expected,
+ 'Results mismatch.\n'
+ + `Parsed: ${inspect(results)}\n`
+ + `Expected: ${inspect(expected)}`
+ );
+}));
+
+for (const chunk of reqChunks)
+ bb.write(chunk);
+bb.end();
diff --git a/node_modules/busboy/test/test-types-multipart.js b/node_modules/busboy/test/test-types-multipart.js
new file mode 100644
index 0000000..9755642
--- /dev/null
+++ b/node_modules/busboy/test/test-types-multipart.js
@@ -0,0 +1,1053 @@
+'use strict';
+
+const assert = require('assert');
+const { inspect } = require('util');
+
+const busboy = require('..');
+
+const active = new Map();
+
+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',
+ '',
+ 'A'.repeat(1023),
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_1"; filename="1k_b.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'B'.repeat(1023),
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'field',
+ name: 'file_name_0',
+ val: 'super alpha file',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'field',
+ name: 'file_name_1',
+ val: 'super beta file',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('A'.repeat(1023)),
+ info: {
+ filename: '1k_a.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_1',
+ data: Buffer.from('B'.repeat(1023)),
+ info: {
+ filename: '1k_b.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Fields and files'
+ },
+ { source: [
+ ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ 'Content-Disposition: form-data; name="cont"',
+ '',
+ 'some random content',
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ 'Content-Disposition: form-data; name="pass"',
+ '',
+ 'some random pass',
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ 'Content-Disposition: form-data; name=bit',
+ '',
+ '2',
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
+ ].join('\r\n')
+ ],
+ boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ expected: [
+ { type: 'field',
+ name: 'cont',
+ val: 'some random content',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'field',
+ name: 'pass',
+ val: 'some random pass',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'field',
+ name: 'bit',
+ val: '2',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ ],
+ what: 'Fields only'
+ },
+ { source: [
+ ''
+ ],
+ boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ expected: [
+ { error: 'Unexpected end of form' },
+ ],
+ what: 'No fields and no files'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; name="file_name_0"',
+ '',
+ 'super alpha file',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="1k_a.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ limits: {
+ fileSize: 13,
+ fieldSize: 5
+ },
+ expected: [
+ { type: 'field',
+ name: 'file_name_0',
+ val: 'super',
+ info: {
+ nameTruncated: false,
+ valueTruncated: true,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('ABCDEFGHIJKLM'),
+ info: {
+ filename: '1k_a.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: true,
+ },
+ ],
+ what: 'Fields and files (limits)'
+ },
+ { 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
+ },
+ expected: [
+ { type: 'field',
+ name: 'file_name_0',
+ val: 'super alpha file',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ 'filesLimit',
+ ],
+ what: 'Fields and files (limits: 0 files)'
+ },
+ { 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',
+ '',
+ 'A'.repeat(1023),
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_1"; filename="1k_b.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'B'.repeat(1023),
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'field',
+ name: 'file_name_0',
+ val: 'super alpha file',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ { type: 'field',
+ name: 'file_name_1',
+ val: 'super beta file',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ ],
+ events: ['field'],
+ what: 'Fields and (ignored) files'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="/tmp/1k_a.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_1"; filename="C:\\files\\1k_b.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_2"; filename="relative/1k_c.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: '1k_a.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_1',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: '1k_b.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_2',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: '1k_c.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Files with filenames containing paths'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="/absolute/1k_a.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_2"; filename="relative/1k_c.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ preservePath: true,
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: '/absolute/1k_a.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_1',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: 'C:\\absolute\\1k_b.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_2',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: 'relative/1k_c.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Paths to be preserved through the preservePath option'
+ },
+ { source: [
+ ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ 'Content-Disposition: form-data; name="cont"',
+ 'Content-Type: ',
+ '',
+ 'some random content',
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ 'Content-Disposition: ',
+ '',
+ 'some random pass',
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
+ ].join('\r\n')
+ ],
+ boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ expected: [
+ { type: 'field',
+ name: 'cont',
+ val: 'some random content',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ ],
+ what: 'Empty content-type and empty content-disposition'
+ },
+ { 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: [
+ { type: 'file',
+ name: 'file',
+ data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
+ info: {
+ filename: 'näme.txt',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Unicode filenames'
+ },
+ { source: [
+ ['--asdasdasdasd\r\n',
+ 'Content-Type: text/plain\r\n',
+ 'Content-Disposition: form-data; name="foo"\r\n',
+ '\r\n',
+ 'asd\r\n',
+ '--asdasdasdasd--'
+ ].join(':)')
+ ],
+ boundary: 'asdasdasdasd',
+ expected: [
+ { error: 'Malformed part header' },
+ { error: 'Unexpected end of form' },
+ ],
+ what: 'Stopped mid-header'
+ },
+ { source: [
+ ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ 'Content-Disposition: form-data; name="cont"',
+ 'Content-Type: application/json',
+ '',
+ '{}',
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
+ ].join('\r\n')
+ ],
+ boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ expected: [
+ { type: 'field',
+ name: 'cont',
+ val: '{}',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'application/json',
+ },
+ },
+ ],
+ what: 'content-type for fields'
+ },
+ { source: [
+ '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
+ ],
+ boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
+ expected: [],
+ what: 'empty form'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name=upload_file_0; filename="1k_a.dat"',
+ 'Content-Type: application/octet-stream',
+ 'Content-Transfer-Encoding: binary',
+ '',
+ '',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.alloc(0),
+ info: {
+ filename: '1k_a.dat',
+ encoding: 'binary',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ err: 'Unexpected end of form',
+ },
+ { error: 'Unexpected end of form' },
+ ],
+ what: 'Stopped mid-file #1'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name=upload_file_0; filename="1k_a.dat"',
+ 'Content-Type: application/octet-stream',
+ '',
+ 'a',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('a'),
+ info: {
+ filename: '1k_a.dat',
+ encoding: '7bit',
+ mimeType: 'application/octet-stream',
+ },
+ limited: false,
+ err: 'Unexpected end of form',
+ },
+ { error: 'Unexpected end of form' },
+ ],
+ what: 'Stopped mid-file #2'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="notes.txt"',
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'a',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('a'),
+ info: {
+ filename: 'notes.txt',
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Text file with charset'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="notes.txt"',
+ 'Content-Type: ',
+ ' text/plain; charset=utf8',
+ '',
+ 'a',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('a'),
+ info: {
+ filename: 'notes.txt',
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Folded header value'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'a',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [],
+ what: 'No Content-Disposition'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; name="file_name_0"',
+ '',
+ 'a'.repeat(64 * 1024),
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="notes.txt"',
+ 'Content-Type: ',
+ ' text/plain; charset=utf8',
+ '',
+ 'bc',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ limits: {
+ fieldSize: Infinity,
+ },
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('bc'),
+ info: {
+ filename: 'notes.txt',
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ ],
+ events: [ 'file' ],
+ what: 'Skip field parts if no listener'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; name="file_name_0"',
+ '',
+ 'a',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="notes.txt"',
+ 'Content-Type: ',
+ ' text/plain; charset=utf8',
+ '',
+ 'bc',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ limits: {
+ parts: 1,
+ },
+ expected: [
+ { type: 'field',
+ name: 'file_name_0',
+ val: 'a',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ 'partsLimit',
+ ],
+ what: 'Parts limit'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; name="file_name_0"',
+ '',
+ 'a',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; name="file_name_1"',
+ '',
+ 'b',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ limits: {
+ fields: 1,
+ },
+ expected: [
+ { type: 'field',
+ name: 'file_name_0',
+ val: 'a',
+ info: {
+ nameTruncated: false,
+ valueTruncated: false,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ },
+ 'fieldsLimit',
+ ],
+ what: 'Fields limit'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="notes.txt"',
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'ab',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_1"; filename="notes2.txt"',
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'cd',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ limits: {
+ files: 1,
+ },
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('ab'),
+ info: {
+ filename: 'notes.txt',
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ 'filesLimit',
+ ],
+ what: 'Files limit'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + `name="upload_file_0"; filename="${'a'.repeat(64 * 1024)}.txt"`,
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'ab',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_1"; filename="notes2.txt"',
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'cd',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { error: 'Malformed part header' },
+ { type: 'file',
+ name: 'upload_file_1',
+ data: Buffer.from('cd'),
+ info: {
+ filename: 'notes2.txt',
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Oversized part header'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + 'name="upload_file_0"; filename="notes.txt"',
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'a'.repeat(31) + '\r',
+ ].join('\r\n'),
+ 'b'.repeat(40),
+ '\r\n-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ fileHwm: 32,
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('a'.repeat(31) + '\r' + 'b'.repeat(40)),
+ info: {
+ filename: 'notes.txt',
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Lookbehind data should not stall file streams'
+ },
+ { source: [
+ ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + `name="upload_file_0"; filename="${'a'.repeat(8 * 1024)}.txt"`,
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'ab',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + `name="upload_file_1"; filename="${'b'.repeat(8 * 1024)}.txt"`,
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'cd',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ 'Content-Disposition: form-data; '
+ + `name="upload_file_2"; filename="${'c'.repeat(8 * 1024)}.txt"`,
+ 'Content-Type: text/plain; charset=utf8',
+ '',
+ 'ef',
+ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
+ ].join('\r\n')
+ ],
+ boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
+ expected: [
+ { type: 'file',
+ name: 'upload_file_0',
+ data: Buffer.from('ab'),
+ info: {
+ filename: `${'a'.repeat(8 * 1024)}.txt`,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_1',
+ data: Buffer.from('cd'),
+ info: {
+ filename: `${'b'.repeat(8 * 1024)}.txt`,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ { type: 'file',
+ name: 'upload_file_2',
+ data: Buffer.from('ef'),
+ info: {
+ filename: `${'c'.repeat(8 * 1024)}.txt`,
+ encoding: '7bit',
+ mimeType: 'text/plain',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Header size limit should be per part'
+ },
+ { source: [
+ '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee\r\n',
+ 'Content-Type: application/gzip\r\n'
+ + 'Content-Encoding: gzip\r\n'
+ + 'Content-Disposition: form-data; name=batch-1; filename=batch-1'
+ + '\r\n\r\n',
+ '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee--',
+ ],
+ boundary: 'd1bf46b3-aa33-4061-b28d-6c5ced8b08ee',
+ expected: [
+ { type: 'file',
+ name: 'batch-1',
+ data: Buffer.alloc(0),
+ info: {
+ filename: 'batch-1',
+ encoding: '7bit',
+ mimeType: 'application/gzip',
+ },
+ limited: false,
+ },
+ ],
+ what: 'Empty part'
+ },
+];
+
+for (const test of tests) {
+ active.set(test, 1);
+
+ const { what, boundary, events, limits, preservePath, fileHwm } = test;
+ const bb = busboy({
+ fileHwm,
+ limits,
+ preservePath,
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ }
+ });
+ const results = [];
+
+ if (events === undefined || events.includes('field')) {
+ bb.on('field', (name, val, info) => {
+ results.push({ type: 'field', name, val, info });
+ });
+ }
+
+ if (events === undefined || events.includes('file')) {
+ 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', () => {
+ active.delete(test);
+
+ assert.deepStrictEqual(
+ results,
+ test.expected,
+ `[${what}] Results mismatch.\n`
+ + `Parsed: ${inspect(results)}\n`
+ + `Expected: ${inspect(test.expected)}`
+ );
+ });
+
+ for (const src of test.source) {
+ const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
+ bb.write(buf);
+ }
+ bb.end();
+}
+
+// Byte-by-byte versions
+for (let test of tests) {
+ test = { ...test };
+ test.what += ' (byte-by-byte)';
+ active.set(test, 1);
+
+ const { what, boundary, events, limits, preservePath, fileHwm } = test;
+ const bb = busboy({
+ fileHwm,
+ limits,
+ preservePath,
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ }
+ });
+ const results = [];
+
+ if (events === undefined || events.includes('field')) {
+ bb.on('field', (name, val, info) => {
+ results.push({ type: 'field', name, val, info });
+ });
+ }
+
+ if (events === undefined || events.includes('file')) {
+ 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', () => {
+ active.delete(test);
+
+ assert.deepStrictEqual(
+ results,
+ test.expected,
+ `[${what}] Results mismatch.\n`
+ + `Parsed: ${inspect(results)}\n`
+ + `Expected: ${inspect(test.expected)}`
+ );
+ });
+
+ for (const src of test.source) {
+ const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
+ for (let i = 0; i < buf.length; ++i)
+ bb.write(buf.slice(i, i + 1));
+ }
+ bb.end();
+}
+
+{
+ let exception = false;
+ process.once('uncaughtException', (ex) => {
+ exception = true;
+ throw ex;
+ });
+ process.on('exit', () => {
+ if (exception || active.size === 0)
+ return;
+ process.exitCode = 1;
+ console.error('==========================');
+ console.error(`${active.size} test(s) did not finish:`);
+ console.error('==========================');
+ console.error(Array.from(active.keys()).map((v) => v.what).join('\n'));
+ });
+}
diff --git a/node_modules/busboy/test/test-types-urlencoded.js b/node_modules/busboy/test/test-types-urlencoded.js
new file mode 100644
index 0000000..c35962b
--- /dev/null
+++ b/node_modules/busboy/test/test-types-urlencoded.js
@@ -0,0 +1,488 @@
+'use strict';
+
+const assert = require('assert');
+const { transcode } = require('buffer');
+const { inspect } = require('util');
+
+const busboy = require('..');
+
+const active = new Map();
+
+const tests = [
+ { source: ['foo'],
+ expected: [
+ ['foo',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Unassigned value'
+ },
+ { source: ['foo=bar'],
+ expected: [
+ ['foo',
+ 'bar',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Assigned value'
+ },
+ { source: ['foo&bar=baz'],
+ expected: [
+ ['foo',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['bar',
+ 'baz',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Unassigned and assigned value'
+ },
+ { source: ['foo=bar&baz'],
+ expected: [
+ ['foo',
+ 'bar',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['baz',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Assigned and unassigned value'
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['foo',
+ 'bar',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['baz',
+ 'bla',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Two assigned values'
+ },
+ { source: ['foo&bar'],
+ expected: [
+ ['foo',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['bar',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Two unassigned values'
+ },
+ { source: ['foo&bar&'],
+ expected: [
+ ['foo',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['bar',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Two unassigned values and ampersand'
+ },
+ { source: ['foo+1=bar+baz%2Bquux'],
+ expected: [
+ ['foo 1',
+ 'bar baz+quux',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Assigned key and value with (plus) space'
+ },
+ { source: ['foo=bar%20baz%21'],
+ expected: [
+ ['foo',
+ 'bar baz!',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Assigned value with encoded bytes'
+ },
+ { source: ['foo%20bar=baz%20bla%21'],
+ expected: [
+ ['foo bar',
+ 'baz bla!',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Assigned value with encoded bytes #2'
+ },
+ { source: ['foo=bar%20baz%21&num=1000'],
+ expected: [
+ ['foo',
+ 'bar baz!',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['num',
+ '1000',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Two assigned values, one with encoded bytes'
+ },
+ { source: [
+ Array.from(transcode(Buffer.from('foo'), 'utf8', 'utf16le')).map(
+ (n) => `%${n.toString(16).padStart(2, '0')}`
+ ).join(''),
+ '=',
+ Array.from(transcode(Buffer.from('😀!'), 'utf8', 'utf16le')).map(
+ (n) => `%${n.toString(16).padStart(2, '0')}`
+ ).join(''),
+ ],
+ expected: [
+ ['foo',
+ '😀!',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'UTF-16LE',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ charset: 'UTF-16LE',
+ what: 'Encoded value with multi-byte charset'
+ },
+ { source: [
+ 'foo=<',
+ Array.from(transcode(Buffer.from('©:^þ'), 'utf8', 'latin1')).map(
+ (n) => `%${n.toString(16).padStart(2, '0')}`
+ ).join(''),
+ ],
+ expected: [
+ ['foo',
+ '<©:^þ',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'ISO-8859-1',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ charset: 'ISO-8859-1',
+ what: 'Encoded value with single-byte, ASCII-compatible, non-UTF8 charset'
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [],
+ what: 'Limits: zero fields',
+ limits: { fields: 0 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['foo',
+ 'bar',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: one field',
+ limits: { fields: 1 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['foo',
+ 'bar',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['baz',
+ 'bla',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: field part lengths match limits',
+ limits: { fieldNameSize: 3, fieldSize: 3 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['fo',
+ 'bar',
+ { nameTruncated: true,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['ba',
+ 'bla',
+ { nameTruncated: true,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: truncated field name',
+ limits: { fieldNameSize: 2 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['foo',
+ 'ba',
+ { nameTruncated: false,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['baz',
+ 'bl',
+ { nameTruncated: false,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: truncated field value',
+ limits: { fieldSize: 2 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['fo',
+ 'ba',
+ { nameTruncated: true,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['ba',
+ 'bl',
+ { nameTruncated: true,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: truncated field name and value',
+ limits: { fieldNameSize: 2, fieldSize: 2 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['fo',
+ '',
+ { nameTruncated: true,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['ba',
+ '',
+ { nameTruncated: true,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: truncated field name and zero value limit',
+ limits: { fieldNameSize: 2, fieldSize: 0 }
+ },
+ { source: ['foo=bar&baz=bla'],
+ expected: [
+ ['',
+ '',
+ { nameTruncated: true,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ['',
+ '',
+ { nameTruncated: true,
+ valueTruncated: true,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Limits: truncated zero field name and zero value limit',
+ limits: { fieldNameSize: 0, fieldSize: 0 }
+ },
+ { source: ['&'],
+ expected: [],
+ what: 'Ampersand'
+ },
+ { source: ['&&&&&'],
+ expected: [],
+ what: 'Many ampersands'
+ },
+ { source: ['='],
+ expected: [
+ ['',
+ '',
+ { nameTruncated: false,
+ valueTruncated: false,
+ encoding: 'utf-8',
+ mimeType: 'text/plain' },
+ ],
+ ],
+ what: 'Assigned value, empty name and value'
+ },
+ { source: [''],
+ expected: [],
+ what: 'Nothing'
+ },
+];
+
+for (const test of tests) {
+ active.set(test, 1);
+
+ const { what } = test;
+ const charset = test.charset || 'utf-8';
+ const bb = busboy({
+ limits: test.limits,
+ headers: {
+ 'content-type': `application/x-www-form-urlencoded; charset=${charset}`,
+ },
+ });
+ const results = [];
+
+ bb.on('field', (key, val, info) => {
+ results.push([key, val, info]);
+ });
+
+ bb.on('file', () => {
+ throw new Error(`[${what}] Unexpected file`);
+ });
+
+ bb.on('close', () => {
+ active.delete(test);
+
+ assert.deepStrictEqual(
+ results,
+ test.expected,
+ `[${what}] Results mismatch.\n`
+ + `Parsed: ${inspect(results)}\n`
+ + `Expected: ${inspect(test.expected)}`
+ );
+ });
+
+ for (const src of test.source) {
+ const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
+ bb.write(buf);
+ }
+ bb.end();
+}
+
+// Byte-by-byte versions
+for (let test of tests) {
+ test = { ...test };
+ test.what += ' (byte-by-byte)';
+ active.set(test, 1);
+
+ const { what } = test;
+ const charset = test.charset || 'utf-8';
+ const bb = busboy({
+ limits: test.limits,
+ headers: {
+ 'content-type': `application/x-www-form-urlencoded; charset="${charset}"`,
+ },
+ });
+ const results = [];
+
+ bb.on('field', (key, val, info) => {
+ results.push([key, val, info]);
+ });
+
+ bb.on('file', () => {
+ throw new Error(`[${what}] Unexpected file`);
+ });
+
+ bb.on('close', () => {
+ active.delete(test);
+
+ assert.deepStrictEqual(
+ results,
+ test.expected,
+ `[${what}] Results mismatch.\n`
+ + `Parsed: ${inspect(results)}\n`
+ + `Expected: ${inspect(test.expected)}`
+ );
+ });
+
+ for (const src of test.source) {
+ const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
+ for (let i = 0; i < buf.length; ++i)
+ bb.write(buf.slice(i, i + 1));
+ }
+ bb.end();
+}
+
+{
+ let exception = false;
+ process.once('uncaughtException', (ex) => {
+ exception = true;
+ throw ex;
+ });
+ process.on('exit', () => {
+ if (exception || active.size === 0)
+ return;
+ process.exitCode = 1;
+ console.error('==========================');
+ console.error(`${active.size} test(s) did not finish:`);
+ console.error('==========================');
+ console.error(Array.from(active.keys()).map((v) => v.what).join('\n'));
+ });
+}
diff --git a/node_modules/busboy/test/test.js b/node_modules/busboy/test/test.js
new file mode 100644
index 0000000..d0380f2
--- /dev/null
+++ b/node_modules/busboy/test/test.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const { spawnSync } = require('child_process');
+const { readdirSync } = require('fs');
+const { join } = require('path');
+
+const files = readdirSync(__dirname).sort();
+for (const filename of files) {
+ if (filename.startsWith('test-')) {
+ const path = join(__dirname, filename);
+ console.log(`> Running ${filename} ...`);
+ const result = spawnSync(`${process.argv0} ${path}`, {
+ shell: true,
+ stdio: 'inherit',
+ windowsHide: true
+ });
+ if (result.status !== 0)
+ process.exitCode = 1;
+ }
+}