summaryrefslogtreecommitdiff
path: root/node_modules/busboy
diff options
context:
space:
mode:
authorsowgro <tpoke.ferrari@gmail.com>2023-09-02 19:12:47 -0400
committersowgro <tpoke.ferrari@gmail.com>2023-09-02 19:12:47 -0400
commite4450c8417624b71d779cb4f41692538f9165e10 (patch)
treeb70826542223ecdf8a7a259f61b0a1abb8a217d8 /node_modules/busboy
downloadsowbot3-e4450c8417624b71d779cb4f41692538f9165e10.tar.gz
sowbot3-e4450c8417624b71d779cb4f41692538f9165e10.tar.bz2
sowbot3-e4450c8417624b71d779cb4f41692538f9165e10.zip
first commit
Diffstat (limited to 'node_modules/busboy')
-rw-r--r--node_modules/busboy/.eslintrc.js5
-rw-r--r--node_modules/busboy/.github/workflows/ci.yml24
-rw-r--r--node_modules/busboy/.github/workflows/lint.yml23
-rw-r--r--node_modules/busboy/LICENSE19
-rw-r--r--node_modules/busboy/README.md191
-rw-r--r--node_modules/busboy/bench/bench-multipart-fields-100mb-big.js149
-rw-r--r--node_modules/busboy/bench/bench-multipart-fields-100mb-small.js143
-rw-r--r--node_modules/busboy/bench/bench-multipart-files-100mb-big.js154
-rw-r--r--node_modules/busboy/bench/bench-multipart-files-100mb-small.js148
-rw-r--r--node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js101
-rw-r--r--node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js84
-rw-r--r--node_modules/busboy/lib/index.js57
-rw-r--r--node_modules/busboy/lib/types/multipart.js653
-rw-r--r--node_modules/busboy/lib/types/urlencoded.js350
-rw-r--r--node_modules/busboy/lib/utils.js596
-rw-r--r--node_modules/busboy/package.json22
-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
22 files changed, 4585 insertions, 0 deletions
diff --git a/node_modules/busboy/.eslintrc.js b/node_modules/busboy/.eslintrc.js
new file mode 100644
index 0000000..be9311d
--- /dev/null
+++ b/node_modules/busboy/.eslintrc.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ extends: '@mscdex/eslint-config',
+};
diff --git a/node_modules/busboy/.github/workflows/ci.yml b/node_modules/busboy/.github/workflows/ci.yml
new file mode 100644
index 0000000..799bae0
--- /dev/null
+++ b/node_modules/busboy/.github/workflows/ci.yml
@@ -0,0 +1,24 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches: [ master ]
+
+jobs:
+ tests-linux:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: [10.16.0, 10.x, 12.x, 14.x, 16.x]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Install module
+ run: npm install
+ - name: Run tests
+ run: npm test
diff --git a/node_modules/busboy/.github/workflows/lint.yml b/node_modules/busboy/.github/workflows/lint.yml
new file mode 100644
index 0000000..9f9e1f5
--- /dev/null
+++ b/node_modules/busboy/.github/workflows/lint.yml
@@ -0,0 +1,23 @@
+name: lint
+
+on:
+ pull_request:
+ push:
+ branches: [ master ]
+
+env:
+ NODE_VERSION: 16.x
+
+jobs:
+ lint-js:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ env.NODE_VERSION }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ - name: Install ESLint + ESLint configs/plugins
+ run: npm install --only=dev
+ - name: Lint files
+ run: npm run lint
diff --git a/node_modules/busboy/LICENSE b/node_modules/busboy/LICENSE
new file mode 100644
index 0000000..290762e
--- /dev/null
+++ b/node_modules/busboy/LICENSE
@@ -0,0 +1,19 @@
+Copyright Brian White. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE. \ No newline at end of file
diff --git a/node_modules/busboy/README.md b/node_modules/busboy/README.md
new file mode 100644
index 0000000..654af30
--- /dev/null
+++ b/node_modules/busboy/README.md
@@ -0,0 +1,191 @@
+# Description
+
+A node.js module for parsing incoming HTML form data.
+
+Changes (breaking or otherwise) in v1.0.0 can be found [here](https://github.com/mscdex/busboy/issues/266).
+
+# Requirements
+
+* [node.js](http://nodejs.org/) -- v10.16.0 or newer
+
+
+# Install
+
+ npm install busboy
+
+
+# Examples
+
+* Parsing (multipart) with default options:
+
+```js
+const http = require('http');
+
+const busboy = require('busboy');
+
+http.createServer((req, res) => {
+ if (req.method === 'POST') {
+ console.log('POST request');
+ const bb = busboy({ headers: req.headers });
+ bb.on('file', (name, file, info) => {
+ const { filename, encoding, mimeType } = info;
+ console.log(
+ `File [${name}]: filename: %j, encoding: %j, mimeType: %j`,
+ filename,
+ encoding,
+ mimeType
+ );
+ file.on('data', (data) => {
+ console.log(`File [${name}] got ${data.length} bytes`);
+ }).on('close', () => {
+ console.log(`File [${name}] done`);
+ });
+ });
+ bb.on('field', (name, val, info) => {
+ console.log(`Field [${name}]: value: %j`, val);
+ });
+ bb.on('close', () => {
+ console.log('Done parsing form!');
+ res.writeHead(303, { Connection: 'close', Location: '/' });
+ res.end();
+ });
+ req.pipe(bb);
+ } else if (req.method === 'GET') {
+ res.writeHead(200, { Connection: 'close' });
+ res.end(`
+ <html>
+ <head></head>
+ <body>
+ <form method="POST" enctype="multipart/form-data">
+ <input type="file" name="filefield"><br />
+ <input type="text" name="textfield"><br />
+ <input type="submit">
+ </form>
+ </body>
+ </html>
+ `);
+ }
+}).listen(8000, () => {
+ console.log('Listening for requests');
+});
+
+// Example output:
+//
+// Listening for requests
+// < ... form submitted ... >
+// POST request
+// File [filefield]: filename: "logo.jpg", encoding: "binary", mime: "image/jpeg"
+// File [filefield] got 11912 bytes
+// Field [textfield]: value: "testing! :-)"
+// File [filefield] done
+// Done parsing form!
+```
+
+* Save all incoming files to disk:
+
+```js
+const { randomFillSync } = require('crypto');
+const fs = require('fs');
+const http = require('http');
+const os = require('os');
+const path = require('path');
+
+const busboy = require('busboy');
+
+const random = (() => {
+ const buf = Buffer.alloc(16);
+ return () => randomFillSync(buf).toString('hex');
+})();
+
+http.createServer((req, res) => {
+ if (req.method === 'POST') {
+ const bb = busboy({ headers: req.headers });
+ bb.on('file', (name, file, info) => {
+ const saveTo = path.join(os.tmpdir(), `busboy-upload-${random()}`);
+ file.pipe(fs.createWriteStream(saveTo));
+ });
+ bb.on('close', () => {
+ res.writeHead(200, { 'Connection': 'close' });
+ res.end(`That's all folks!`);
+ });
+ req.pipe(bb);
+ return;
+ }
+ res.writeHead(404);
+ res.end();
+}).listen(8000, () => {
+ console.log('Listening for requests');
+});
+```
+
+
+# API
+
+## Exports
+
+`busboy` exports a single function:
+
+**( _function_ )**(< _object_ >config) - Creates and returns a new _Writable_ form parser stream.
+
+* Valid `config` properties:
+
+ * **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers.
+
+ * **highWaterMark** - _integer_ - highWaterMark to use for the parser stream. **Default:** node's _stream.Writable_ default.
+
+ * **fileHwm** - _integer_ - highWaterMark to use for individual file streams. **Default:** node's _stream.Readable_ default.
+
+ * **defCharset** - _string_ - Default character set to use when one isn't defined. **Default:** `'utf8'`.
+
+ * **defParamCharset** - _string_ - For multipart forms, the default character set to use for values of part header parameters (e.g. filename) that are not extended parameters (that contain an explicit charset). **Default:** `'latin1'`.
+
+ * **preservePath** - _boolean_ - If paths in filenames from file parts in a `'multipart/form-data'` request shall be preserved. **Default:** `false`.
+
+ * **limits** - _object_ - Various limits on incoming data. Valid properties are:
+
+ * **fieldNameSize** - _integer_ - Max field name size (in bytes). **Default:** `100`.
+
+ * **fieldSize** - _integer_ - Max field value size (in bytes). **Default:** `1048576` (1MB).
+
+ * **fields** - _integer_ - Max number of non-file fields. **Default:** `Infinity`.
+
+ * **fileSize** - _integer_ - For multipart forms, the max file size (in bytes). **Default:** `Infinity`.
+
+ * **files** - _integer_ - For multipart forms, the max number of file fields. **Default:** `Infinity`.
+
+ * **parts** - _integer_ - For multipart forms, the max number of parts (fields + files). **Default:** `Infinity`.
+
+ * **headerPairs** - _integer_ - For multipart forms, the max number of header key-value pairs to parse. **Default:** `2000` (same as node's http module).
+
+This function can throw exceptions if there is something wrong with the values in `config`. For example, if the Content-Type in `headers` is missing entirely, is not a supported type, or is missing the boundary for `'multipart/form-data'` requests.
+
+## (Special) Parser stream events
+
+* **file**(< _string_ >name, < _Readable_ >stream, < _object_ >info) - Emitted for each new file found. `name` contains the form field name. `stream` is a _Readable_ stream containing the file's data. No transformations/conversions (e.g. base64 to raw binary) are done on the file's data. `info` contains the following properties:
+
+ * `filename` - _string_ - If supplied, this contains the file's filename. **WARNING:** You should almost _never_ use this value as-is (especially if you are using `preservePath: true` in your `config`) as it could contain malicious input. You are better off generating your own (safe) filenames, or at the very least using a hash of the filename.
+
+ * `encoding` - _string_ - The file's `'Content-Transfer-Encoding'` value.
+
+ * `mimeType` - _string_ - The file's `'Content-Type'` value.
+
+ **Note:** If you listen for this event, you should always consume the `stream` whether you care about its contents or not (you can simply do `stream.resume();` if you want to discard/skip the contents), otherwise the `'finish'`/`'close'` event will never fire on the busboy parser stream.
+ However, if you aren't accepting files, you can either simply not listen for the `'file'` event at all or set `limits.files` to `0`, and any/all files will be automatically skipped (these skipped files will still count towards any configured `limits.files` and `limits.parts` limits though).
+
+ **Note:** If a configured `limits.fileSize` limit was reached for a file, `stream` will both have a boolean property `truncated` set to `true` (best checked at the end of the stream) and emit a `'limit'` event to notify you when this happens.
+
+* **field**(< _string_ >name, < _string_ >value, < _object_ >info) - Emitted for each new non-file field found. `name` contains the form field name. `value` contains the string value of the field. `info` contains the following properties:
+
+ * `nameTruncated` - _boolean_ - Whether `name` was truncated or not (due to a configured `limits.fieldNameSize` limit)
+
+ * `valueTruncated` - _boolean_ - Whether `value` was truncated or not (due to a configured `limits.fieldSize` limit)
+
+ * `encoding` - _string_ - The field's `'Content-Transfer-Encoding'` value.
+
+ * `mimeType` - _string_ - The field's `'Content-Type'` value.
+
+* **partsLimit**() - Emitted when the configured `limits.parts` limit has been reached. No more `'file'` or `'field'` events will be emitted.
+
+* **filesLimit**() - Emitted when the configured `limits.files` limit has been reached. No more `'file'` events will be emitted.
+
+* **fieldsLimit**() - Emitted when the configured `limits.fields` limit has been reached. No more `'field'` events will be emitted.
diff --git a/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js b/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js
new file mode 100644
index 0000000..ef15729
--- /dev/null
+++ b/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js
@@ -0,0 +1,149 @@
+'use strict';
+
+function createMultipartBuffers(boundary, sizes) {
+ const bufs = [];
+ for (let i = 0; i < sizes.length; ++i) {
+ const mb = sizes[i] * 1024 * 1024;
+ bufs.push(Buffer.from([
+ `--${boundary}`,
+ `content-disposition: form-data; name="field${i + 1}"`,
+ '',
+ '0'.repeat(mb),
+ '',
+ ].join('\r\n')));
+ }
+ bufs.push(Buffer.from([
+ `--${boundary}--`,
+ '',
+ ].join('\r\n')));
+ return bufs;
+}
+
+const boundary = '-----------------------------168072824752491622650073';
+const buffers = createMultipartBuffers(boundary, [
+ 10,
+ 10,
+ 10,
+ 20,
+ 50,
+]);
+const calls = {
+ partBegin: 0,
+ headerField: 0,
+ headerValue: 0,
+ headerEnd: 0,
+ headersEnd: 0,
+ partData: 0,
+ partEnd: 0,
+ end: 0,
+};
+
+const moduleName = process.argv[2];
+switch (moduleName) {
+ case 'busboy': {
+ const busboy = require('busboy');
+
+ const parser = busboy({
+ limits: {
+ fieldSizeLimit: Infinity,
+ },
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ },
+ });
+ parser.on('field', (name, val, info) => {
+ ++calls.partBegin;
+ ++calls.partData;
+ ++calls.partEnd;
+ }).on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+ break;
+ }
+
+ case 'formidable': {
+ const { MultipartParser } = require('formidable');
+
+ const parser = new MultipartParser();
+ parser.initWithBoundary(boundary);
+ parser.on('data', ({ name }) => {
+ ++calls[name];
+ if (name === 'end')
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+
+ break;
+ }
+
+ case 'multiparty': {
+ const { Readable } = require('stream');
+
+ const { Form } = require('multiparty');
+
+ const form = new Form({
+ maxFieldsSize: Infinity,
+ maxFields: Infinity,
+ maxFilesSize: Infinity,
+ autoFields: false,
+ autoFiles: false,
+ });
+
+ const req = new Readable({ read: () => {} });
+ req.headers = {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ };
+
+ function hijack(name, fn) {
+ const oldFn = form[name];
+ form[name] = function() {
+ fn();
+ return oldFn.apply(this, arguments);
+ };
+ }
+
+ hijack('onParseHeaderField', () => {
+ ++calls.headerField;
+ });
+ hijack('onParseHeaderValue', () => {
+ ++calls.headerValue;
+ });
+ hijack('onParsePartBegin', () => {
+ ++calls.partBegin;
+ });
+ hijack('onParsePartData', () => {
+ ++calls.partData;
+ });
+ hijack('onParsePartEnd', () => {
+ ++calls.partEnd;
+ });
+
+ form.on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ }).on('part', (p) => p.resume());
+
+ console.time(moduleName);
+ form.parse(req);
+ for (const buf of buffers)
+ req.push(buf);
+ req.push(null);
+
+ break;
+ }
+
+ default:
+ if (moduleName === undefined)
+ console.error('Missing parser module name');
+ else
+ console.error(`Invalid parser module name: ${moduleName}`);
+ process.exit(1);
+}
diff --git a/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js b/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js
new file mode 100644
index 0000000..f32d421
--- /dev/null
+++ b/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js
@@ -0,0 +1,143 @@
+'use strict';
+
+function createMultipartBuffers(boundary, sizes) {
+ const bufs = [];
+ for (let i = 0; i < sizes.length; ++i) {
+ const mb = sizes[i] * 1024 * 1024;
+ bufs.push(Buffer.from([
+ `--${boundary}`,
+ `content-disposition: form-data; name="field${i + 1}"`,
+ '',
+ '0'.repeat(mb),
+ '',
+ ].join('\r\n')));
+ }
+ bufs.push(Buffer.from([
+ `--${boundary}--`,
+ '',
+ ].join('\r\n')));
+ return bufs;
+}
+
+const boundary = '-----------------------------168072824752491622650073';
+const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1));
+const calls = {
+ partBegin: 0,
+ headerField: 0,
+ headerValue: 0,
+ headerEnd: 0,
+ headersEnd: 0,
+ partData: 0,
+ partEnd: 0,
+ end: 0,
+};
+
+const moduleName = process.argv[2];
+switch (moduleName) {
+ case 'busboy': {
+ const busboy = require('busboy');
+
+ const parser = busboy({
+ limits: {
+ fieldSizeLimit: Infinity,
+ },
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ },
+ });
+ parser.on('field', (name, val, info) => {
+ ++calls.partBegin;
+ ++calls.partData;
+ ++calls.partEnd;
+ }).on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+ break;
+ }
+
+ case 'formidable': {
+ const { MultipartParser } = require('formidable');
+
+ const parser = new MultipartParser();
+ parser.initWithBoundary(boundary);
+ parser.on('data', ({ name }) => {
+ ++calls[name];
+ if (name === 'end')
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+
+ break;
+ }
+
+ case 'multiparty': {
+ const { Readable } = require('stream');
+
+ const { Form } = require('multiparty');
+
+ const form = new Form({
+ maxFieldsSize: Infinity,
+ maxFields: Infinity,
+ maxFilesSize: Infinity,
+ autoFields: false,
+ autoFiles: false,
+ });
+
+ const req = new Readable({ read: () => {} });
+ req.headers = {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ };
+
+ function hijack(name, fn) {
+ const oldFn = form[name];
+ form[name] = function() {
+ fn();
+ return oldFn.apply(this, arguments);
+ };
+ }
+
+ hijack('onParseHeaderField', () => {
+ ++calls.headerField;
+ });
+ hijack('onParseHeaderValue', () => {
+ ++calls.headerValue;
+ });
+ hijack('onParsePartBegin', () => {
+ ++calls.partBegin;
+ });
+ hijack('onParsePartData', () => {
+ ++calls.partData;
+ });
+ hijack('onParsePartEnd', () => {
+ ++calls.partEnd;
+ });
+
+ form.on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ }).on('part', (p) => p.resume());
+
+ console.time(moduleName);
+ form.parse(req);
+ for (const buf of buffers)
+ req.push(buf);
+ req.push(null);
+
+ break;
+ }
+
+ default:
+ if (moduleName === undefined)
+ console.error('Missing parser module name');
+ else
+ console.error(`Invalid parser module name: ${moduleName}`);
+ process.exit(1);
+}
diff --git a/node_modules/busboy/bench/bench-multipart-files-100mb-big.js b/node_modules/busboy/bench/bench-multipart-files-100mb-big.js
new file mode 100644
index 0000000..b46bdee
--- /dev/null
+++ b/node_modules/busboy/bench/bench-multipart-files-100mb-big.js
@@ -0,0 +1,154 @@
+'use strict';
+
+function createMultipartBuffers(boundary, sizes) {
+ const bufs = [];
+ for (let i = 0; i < sizes.length; ++i) {
+ const mb = sizes[i] * 1024 * 1024;
+ bufs.push(Buffer.from([
+ `--${boundary}`,
+ `content-disposition: form-data; name="file${i + 1}"; `
+ + `filename="random${i + 1}.bin"`,
+ 'content-type: application/octet-stream',
+ '',
+ '0'.repeat(mb),
+ '',
+ ].join('\r\n')));
+ }
+ bufs.push(Buffer.from([
+ `--${boundary}--`,
+ '',
+ ].join('\r\n')));
+ return bufs;
+}
+
+const boundary = '-----------------------------168072824752491622650073';
+const buffers = createMultipartBuffers(boundary, [
+ 10,
+ 10,
+ 10,
+ 20,
+ 50,
+]);
+const calls = {
+ partBegin: 0,
+ headerField: 0,
+ headerValue: 0,
+ headerEnd: 0,
+ headersEnd: 0,
+ partData: 0,
+ partEnd: 0,
+ end: 0,
+};
+
+const moduleName = process.argv[2];
+switch (moduleName) {
+ case 'busboy': {
+ const busboy = require('busboy');
+
+ const parser = busboy({
+ limits: {
+ fieldSizeLimit: Infinity,
+ },
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ },
+ });
+ parser.on('file', (name, stream, info) => {
+ ++calls.partBegin;
+ stream.on('data', (chunk) => {
+ ++calls.partData;
+ }).on('end', () => {
+ ++calls.partEnd;
+ });
+ }).on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+ break;
+ }
+
+ case 'formidable': {
+ const { MultipartParser } = require('formidable');
+
+ const parser = new MultipartParser();
+ parser.initWithBoundary(boundary);
+ parser.on('data', ({ name }) => {
+ ++calls[name];
+ if (name === 'end')
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+
+ break;
+ }
+
+ case 'multiparty': {
+ const { Readable } = require('stream');
+
+ const { Form } = require('multiparty');
+
+ const form = new Form({
+ maxFieldsSize: Infinity,
+ maxFields: Infinity,
+ maxFilesSize: Infinity,
+ autoFields: false,
+ autoFiles: false,
+ });
+
+ const req = new Readable({ read: () => {} });
+ req.headers = {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ };
+
+ function hijack(name, fn) {
+ const oldFn = form[name];
+ form[name] = function() {
+ fn();
+ return oldFn.apply(this, arguments);
+ };
+ }
+
+ hijack('onParseHeaderField', () => {
+ ++calls.headerField;
+ });
+ hijack('onParseHeaderValue', () => {
+ ++calls.headerValue;
+ });
+ hijack('onParsePartBegin', () => {
+ ++calls.partBegin;
+ });
+ hijack('onParsePartData', () => {
+ ++calls.partData;
+ });
+ hijack('onParsePartEnd', () => {
+ ++calls.partEnd;
+ });
+
+ form.on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ }).on('part', (p) => p.resume());
+
+ console.time(moduleName);
+ form.parse(req);
+ for (const buf of buffers)
+ req.push(buf);
+ req.push(null);
+
+ break;
+ }
+
+ default:
+ if (moduleName === undefined)
+ console.error('Missing parser module name');
+ else
+ console.error(`Invalid parser module name: ${moduleName}`);
+ process.exit(1);
+}
diff --git a/node_modules/busboy/bench/bench-multipart-files-100mb-small.js b/node_modules/busboy/bench/bench-multipart-files-100mb-small.js
new file mode 100644
index 0000000..46b5dff
--- /dev/null
+++ b/node_modules/busboy/bench/bench-multipart-files-100mb-small.js
@@ -0,0 +1,148 @@
+'use strict';
+
+function createMultipartBuffers(boundary, sizes) {
+ const bufs = [];
+ for (let i = 0; i < sizes.length; ++i) {
+ const mb = sizes[i] * 1024 * 1024;
+ bufs.push(Buffer.from([
+ `--${boundary}`,
+ `content-disposition: form-data; name="file${i + 1}"; `
+ + `filename="random${i + 1}.bin"`,
+ 'content-type: application/octet-stream',
+ '',
+ '0'.repeat(mb),
+ '',
+ ].join('\r\n')));
+ }
+ bufs.push(Buffer.from([
+ `--${boundary}--`,
+ '',
+ ].join('\r\n')));
+ return bufs;
+}
+
+const boundary = '-----------------------------168072824752491622650073';
+const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1));
+const calls = {
+ partBegin: 0,
+ headerField: 0,
+ headerValue: 0,
+ headerEnd: 0,
+ headersEnd: 0,
+ partData: 0,
+ partEnd: 0,
+ end: 0,
+};
+
+const moduleName = process.argv[2];
+switch (moduleName) {
+ case 'busboy': {
+ const busboy = require('busboy');
+
+ const parser = busboy({
+ limits: {
+ fieldSizeLimit: Infinity,
+ },
+ headers: {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ },
+ });
+ parser.on('file', (name, stream, info) => {
+ ++calls.partBegin;
+ stream.on('data', (chunk) => {
+ ++calls.partData;
+ }).on('end', () => {
+ ++calls.partEnd;
+ });
+ }).on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+ break;
+ }
+
+ case 'formidable': {
+ const { MultipartParser } = require('formidable');
+
+ const parser = new MultipartParser();
+ parser.initWithBoundary(boundary);
+ parser.on('data', ({ name }) => {
+ ++calls[name];
+ if (name === 'end')
+ console.timeEnd(moduleName);
+ });
+
+ console.time(moduleName);
+ for (const buf of buffers)
+ parser.write(buf);
+
+ break;
+ }
+
+ case 'multiparty': {
+ const { Readable } = require('stream');
+
+ const { Form } = require('multiparty');
+
+ const form = new Form({
+ maxFieldsSize: Infinity,
+ maxFields: Infinity,
+ maxFilesSize: Infinity,
+ autoFields: false,
+ autoFiles: false,
+ });
+
+ const req = new Readable({ read: () => {} });
+ req.headers = {
+ 'content-type': `multipart/form-data; boundary=${boundary}`,
+ };
+
+ function hijack(name, fn) {
+ const oldFn = form[name];
+ form[name] = function() {
+ fn();
+ return oldFn.apply(this, arguments);
+ };
+ }
+
+ hijack('onParseHeaderField', () => {
+ ++calls.headerField;
+ });
+ hijack('onParseHeaderValue', () => {
+ ++calls.headerValue;
+ });
+ hijack('onParsePartBegin', () => {
+ ++calls.partBegin;
+ });
+ hijack('onParsePartData', () => {
+ ++calls.partData;
+ });
+ hijack('onParsePartEnd', () => {
+ ++calls.partEnd;
+ });
+
+ form.on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ }).on('part', (p) => p.resume());
+
+ console.time(moduleName);
+ form.parse(req);
+ for (const buf of buffers)
+ req.push(buf);
+ req.push(null);
+
+ break;
+ }
+
+ default:
+ if (moduleName === undefined)
+ console.error('Missing parser module name');
+ else
+ console.error(`Invalid parser module name: ${moduleName}`);
+ process.exit(1);
+}
diff --git a/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js b/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js
new file mode 100644
index 0000000..5c337df
--- /dev/null
+++ b/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js
@@ -0,0 +1,101 @@
+'use strict';
+
+const buffers = [
+ Buffer.from(
+ (new Array(100)).fill('').map((_, i) => `key${i}=value${i}`).join('&')
+ ),
+];
+const calls = {
+ field: 0,
+ end: 0,
+};
+
+let n = 3e3;
+
+const moduleName = process.argv[2];
+switch (moduleName) {
+ case 'busboy': {
+ const busboy = require('busboy');
+
+ console.time(moduleName);
+ (function next() {
+ const parser = busboy({
+ limits: {
+ fieldSizeLimit: Infinity,
+ },
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
+ },
+ });
+ parser.on('field', (name, val, info) => {
+ ++calls.field;
+ }).on('close', () => {
+ ++calls.end;
+ if (--n === 0)
+ console.timeEnd(moduleName);
+ else
+ process.nextTick(next);
+ });
+
+ for (const buf of buffers)
+ parser.write(buf);
+ parser.end();
+ })();
+ break;
+ }
+
+ case 'formidable': {
+ const QuerystringParser =
+ require('formidable/src/parsers/Querystring.js');
+
+ console.time(moduleName);
+ (function next() {
+ const parser = new QuerystringParser();
+ parser.on('data', (obj) => {
+ ++calls.field;
+ }).on('end', () => {
+ ++calls.end;
+ if (--n === 0)
+ console.timeEnd(moduleName);
+ else
+ process.nextTick(next);
+ });
+
+ for (const buf of buffers)
+ parser.write(buf);
+ parser.end();
+ })();
+ break;
+ }
+
+ case 'formidable-streaming': {
+ const QuerystringParser =
+ require('formidable/src/parsers/StreamingQuerystring.js');
+
+ console.time(moduleName);
+ (function next() {
+ const parser = new QuerystringParser();
+ parser.on('data', (obj) => {
+ ++calls.field;
+ }).on('end', () => {
+ ++calls.end;
+ if (--n === 0)
+ console.timeEnd(moduleName);
+ else
+ process.nextTick(next);
+ });
+
+ for (const buf of buffers)
+ parser.write(buf);
+ parser.end();
+ })();
+ break;
+ }
+
+ default:
+ if (moduleName === undefined)
+ console.error('Missing parser module name');
+ else
+ console.error(`Invalid parser module name: ${moduleName}`);
+ process.exit(1);
+}
diff --git a/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js b/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js
new file mode 100644
index 0000000..1f5645c
--- /dev/null
+++ b/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js
@@ -0,0 +1,84 @@
+'use strict';
+
+const buffers = [
+ Buffer.from(
+ (new Array(900)).fill('').map((_, i) => `key${i}=value${i}`).join('&')
+ ),
+];
+const calls = {
+ field: 0,
+ end: 0,
+};
+
+const moduleName = process.argv[2];
+switch (moduleName) {
+ case 'busboy': {
+ const busboy = require('busboy');
+
+ console.time(moduleName);
+ const parser = busboy({
+ limits: {
+ fieldSizeLimit: Infinity,
+ },
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
+ },
+ });
+ parser.on('field', (name, val, info) => {
+ ++calls.field;
+ }).on('close', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ for (const buf of buffers)
+ parser.write(buf);
+ parser.end();
+ break;
+ }
+
+ case 'formidable': {
+ const QuerystringParser =
+ require('formidable/src/parsers/Querystring.js');
+
+ console.time(moduleName);
+ const parser = new QuerystringParser();
+ parser.on('data', (obj) => {
+ ++calls.field;
+ }).on('end', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ for (const buf of buffers)
+ parser.write(buf);
+ parser.end();
+ break;
+ }
+
+ case 'formidable-streaming': {
+ const QuerystringParser =
+ require('formidable/src/parsers/StreamingQuerystring.js');
+
+ console.time(moduleName);
+ const parser = new QuerystringParser();
+ parser.on('data', (obj) => {
+ ++calls.field;
+ }).on('end', () => {
+ ++calls.end;
+ console.timeEnd(moduleName);
+ });
+
+ for (const buf of buffers)
+ parser.write(buf);
+ parser.end();
+ break;
+ }
+
+ default:
+ if (moduleName === undefined)
+ console.error('Missing parser module name');
+ else
+ console.error(`Invalid parser module name: ${moduleName}`);
+ process.exit(1);
+}
diff --git a/node_modules/busboy/lib/index.js b/node_modules/busboy/lib/index.js
new file mode 100644
index 0000000..873272d
--- /dev/null
+++ b/node_modules/busboy/lib/index.js
@@ -0,0 +1,57 @@
+'use strict';
+
+const { parseContentType } = require('./utils.js');
+
+function getInstance(cfg) {
+ const headers = cfg.headers;
+ const conType = parseContentType(headers['content-type']);
+ if (!conType)
+ throw new Error('Malformed content type');
+
+ for (const type of TYPES) {
+ const matched = type.detect(conType);
+ if (!matched)
+ continue;
+
+ const instanceCfg = {
+ limits: cfg.limits,
+ headers,
+ conType,
+ highWaterMark: undefined,
+ fileHwm: undefined,
+ defCharset: undefined,
+ defParamCharset: undefined,
+ preservePath: false,
+ };
+ if (cfg.highWaterMark)
+ instanceCfg.highWaterMark = cfg.highWaterMark;
+ if (cfg.fileHwm)
+ instanceCfg.fileHwm = cfg.fileHwm;
+ instanceCfg.defCharset = cfg.defCharset;
+ instanceCfg.defParamCharset = cfg.defParamCharset;
+ instanceCfg.preservePath = cfg.preservePath;
+ return new type(instanceCfg);
+ }
+
+ throw new Error(`Unsupported content type: ${headers['content-type']}`);
+}
+
+// Note: types are explicitly listed here for easier bundling
+// See: https://github.com/mscdex/busboy/issues/121
+const TYPES = [
+ require('./types/multipart'),
+ require('./types/urlencoded'),
+].filter(function(typemod) { return typeof typemod.detect === 'function'; });
+
+module.exports = (cfg) => {
+ if (typeof cfg !== 'object' || cfg === null)
+ cfg = {};
+
+ if (typeof cfg.headers !== 'object'
+ || cfg.headers === null
+ || typeof cfg.headers['content-type'] !== 'string') {
+ throw new Error('Missing Content-Type');
+ }
+
+ return getInstance(cfg);
+};
diff --git a/node_modules/busboy/lib/types/multipart.js b/node_modules/busboy/lib/types/multipart.js
new file mode 100644
index 0000000..cc0d7bb
--- /dev/null
+++ b/node_modules/busboy/lib/types/multipart.js
@@ -0,0 +1,653 @@
+'use strict';
+
+const { Readable, Writable } = require('stream');
+
+const StreamSearch = require('streamsearch');
+
+const {
+ basename,
+ convertToUTF8,
+ getDecoder,
+ parseContentType,
+ parseDisposition,
+} = require('../utils.js');
+
+const BUF_CRLF = Buffer.from('\r\n');
+const BUF_CR = Buffer.from('\r');
+const BUF_DASH = Buffer.from('-');
+
+function noop() {}
+
+const MAX_HEADER_PAIRS = 2000; // From node
+const MAX_HEADER_SIZE = 16 * 1024; // From node (its default value)
+
+const HPARSER_NAME = 0;
+const HPARSER_PRE_OWS = 1;
+const HPARSER_VALUE = 2;
+class HeaderParser {
+ constructor(cb) {
+ this.header = Object.create(null);
+ this.pairCount = 0;
+ this.byteCount = 0;
+ this.state = HPARSER_NAME;
+ this.name = '';
+ this.value = '';
+ this.crlf = 0;
+ this.cb = cb;
+ }
+
+ reset() {
+ this.header = Object.create(null);
+ this.pairCount = 0;
+ this.byteCount = 0;
+ this.state = HPARSER_NAME;
+ this.name = '';
+ this.value = '';
+ this.crlf = 0;
+ }
+
+ push(chunk, pos, end) {
+ let start = pos;
+ while (pos < end) {
+ switch (this.state) {
+ case HPARSER_NAME: {
+ let done = false;
+ for (; pos < end; ++pos) {
+ if (this.byteCount === MAX_HEADER_SIZE)
+ return -1;
+ ++this.byteCount;
+ const code = chunk[pos];
+ if (TOKEN[code] !== 1) {
+ if (code !== 58/* ':' */)
+ return -1;
+ this.name += chunk.latin1Slice(start, pos);
+ if (this.name.length === 0)
+ return -1;
+ ++pos;
+ done = true;
+ this.state = HPARSER_PRE_OWS;
+ break;
+ }
+ }
+ if (!done) {
+ this.name += chunk.latin1Slice(start, pos);
+ break;
+ }
+ // FALLTHROUGH
+ }
+ case HPARSER_PRE_OWS: {
+ // Skip optional whitespace
+ let done = false;
+ for (; pos < end; ++pos) {
+ if (this.byteCount === MAX_HEADER_SIZE)
+ return -1;
+ ++this.byteCount;
+ const code = chunk[pos];
+ if (code !== 32/* ' ' */ && code !== 9/* '\t' */) {
+ start = pos;
+ done = true;
+ this.state = HPARSER_VALUE;
+ break;
+ }
+ }
+ if (!done)
+ break;
+ // FALLTHROUGH
+ }
+ case HPARSER_VALUE:
+ switch (this.crlf) {
+ case 0: // Nothing yet
+ for (; pos < end; ++pos) {
+ if (this.byteCount === MAX_HEADER_SIZE)
+ return -1;
+ ++this.byteCount;
+ const code = chunk[pos];
+ if (FIELD_VCHAR[code] !== 1) {
+ if (code !== 13/* '\r' */)
+ return -1;
+ ++this.crlf;
+ break;
+ }
+ }
+ this.value += chunk.latin1Slice(start, pos++);
+ break;
+ case 1: // Received CR
+ if (this.byteCount === MAX_HEADER_SIZE)
+ return -1;
+ ++this.byteCount;
+ if (chunk[pos++] !== 10/* '\n' */)
+ return -1;
+ ++this.crlf;
+ break;
+ case 2: { // Received CR LF
+ if (this.byteCount === MAX_HEADER_SIZE)
+ return -1;
+ ++this.byteCount;
+ const code = chunk[pos];
+ if (code === 32/* ' ' */ || code === 9/* '\t' */) {
+ // Folded value
+ start = pos;
+ this.crlf = 0;
+ } else {
+ if (++this.pairCount < MAX_HEADER_PAIRS) {
+ this.name = this.name.toLowerCase();
+ if (this.header[this.name] === undefined)
+ this.header[this.name] = [this.value];
+ else
+ this.header[this.name].push(this.value);
+ }
+ if (code === 13/* '\r' */) {
+ ++this.crlf;
+ ++pos;
+ } else {
+ // Assume start of next header field name
+ start = pos;
+ this.crlf = 0;
+ this.state = HPARSER_NAME;
+ this.name = '';
+ this.value = '';
+ }
+ }
+ break;
+ }
+ case 3: { // Received CR LF CR
+ if (this.byteCount === MAX_HEADER_SIZE)
+ return -1;
+ ++this.byteCount;
+ if (chunk[pos++] !== 10/* '\n' */)
+ return -1;
+ // End of header
+ const header = this.header;
+ this.reset();
+ this.cb(header);
+ return pos;
+ }
+ }
+ break;
+ }
+ }
+
+ return pos;
+ }
+}
+
+class FileStream extends Readable {
+ constructor(opts, owner) {
+ super(opts);
+ this.truncated = false;
+ this._readcb = null;
+ this.once('end', () => {
+ // We need to make sure that we call any outstanding _writecb() that is
+ // associated with this file so that processing of the rest of the form
+ // can continue. This may not happen if the file stream ends right after
+ // backpressure kicks in, so we force it here.
+ this._read();
+ if (--owner._fileEndsLeft === 0 && owner._finalcb) {
+ const cb = owner._finalcb;
+ owner._finalcb = null;
+ // Make sure other 'end' event handlers get a chance to be executed
+ // before busboy's 'finish' event is emitted
+ process.nextTick(cb);
+ }
+ });
+ }
+ _read(n) {
+ const cb = this._readcb;
+ if (cb) {
+ this._readcb = null;
+ cb();
+ }
+ }
+}
+
+const ignoreData = {
+ push: (chunk, pos) => {},
+ destroy: () => {},
+};
+
+function callAndUnsetCb(self, err) {
+ const cb = self._writecb;
+ self._writecb = null;
+ if (err)
+ self.destroy(err);
+ else if (cb)
+ cb();
+}
+
+function nullDecoder(val, hint) {
+ return val;
+}
+
+class Multipart extends Writable {
+ constructor(cfg) {
+ const streamOpts = {
+ autoDestroy: true,
+ emitClose: true,
+ highWaterMark: (typeof cfg.highWaterMark === 'number'
+ ? cfg.highWaterMark
+ : undefined),
+ };
+ super(streamOpts);
+
+ if (!cfg.conType.params || typeof cfg.conType.params.boundary !== 'string')
+ throw new Error('Multipart: Boundary not found');
+
+ const boundary = cfg.conType.params.boundary;
+ const paramDecoder = (typeof cfg.defParamCharset === 'string'
+ && cfg.defParamCharset
+ ? getDecoder(cfg.defParamCharset)
+ : nullDecoder);
+ const defCharset = (cfg.defCharset || 'utf8');
+ const preservePath = cfg.preservePath;
+ const fileOpts = {
+ autoDestroy: true,
+ emitClose: true,
+ highWaterMark: (typeof cfg.fileHwm === 'number'
+ ? cfg.fileHwm
+ : undefined),
+ };
+
+ const limits = cfg.limits;
+ const fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
+ ? limits.fieldSize
+ : 1 * 1024 * 1024);
+ const fileSizeLimit = (limits && typeof limits.fileSize === 'number'
+ ? limits.fileSize
+ : Infinity);
+ const filesLimit = (limits && typeof limits.files === 'number'
+ ? limits.files
+ : Infinity);
+ const fieldsLimit = (limits && typeof limits.fields === 'number'
+ ? limits.fields
+ : Infinity);
+ const partsLimit = (limits && typeof limits.parts === 'number'
+ ? limits.parts
+ : Infinity);
+
+ let parts = -1; // Account for initial boundary
+ let fields = 0;
+ let files = 0;
+ let skipPart = false;
+
+ this._fileEndsLeft = 0;
+ this._fileStream = undefined;
+ this._complete = false;
+ let fileSize = 0;
+
+ let field;
+ let fieldSize = 0;
+ let partCharset;
+ let partEncoding;
+ let partType;
+ let partName;
+ let partTruncated = false;
+
+ let hitFilesLimit = false;
+ let hitFieldsLimit = false;
+
+ this._hparser = null;
+ const hparser = new HeaderParser((header) => {
+ this._hparser = null;
+ skipPart = false;
+
+ partType = 'text/plain';
+ partCharset = defCharset;
+ partEncoding = '7bit';
+ partName = undefined;
+ partTruncated = false;
+
+ let filename;
+ if (!header['content-disposition']) {
+ skipPart = true;
+ return;
+ }
+
+ const disp = parseDisposition(header['content-disposition'][0],
+ paramDecoder);
+ if (!disp || disp.type !== 'form-data') {
+ skipPart = true;
+ return;
+ }
+
+ if (disp.params) {
+ if (disp.params.name)
+ partName = disp.params.name;
+
+ if (disp.params['filename*'])
+ filename = disp.params['filename*'];
+ else if (disp.params.filename)
+ filename = disp.params.filename;
+
+ if (filename !== undefined && !preservePath)
+ filename = basename(filename);
+ }
+
+ if (header['content-type']) {
+ const conType = parseContentType(header['content-type'][0]);
+ if (conType) {
+ partType = `${conType.type}/${conType.subtype}`;
+ if (conType.params && typeof conType.params.charset === 'string')
+ partCharset = conType.params.charset.toLowerCase();
+ }
+ }
+
+ if (header['content-transfer-encoding'])
+ partEncoding = header['content-transfer-encoding'][0].toLowerCase();
+
+ if (partType === 'application/octet-stream' || filename !== undefined) {
+ // File
+
+ if (files === filesLimit) {
+ if (!hitFilesLimit) {
+ hitFilesLimit = true;
+ this.emit('filesLimit');
+ }
+ skipPart = true;
+ return;
+ }
+ ++files;
+
+ if (this.listenerCount('file') === 0) {
+ skipPart = true;
+ return;
+ }
+
+ fileSize = 0;
+ this._fileStream = new FileStream(fileOpts, this);
+ ++this._fileEndsLeft;
+ this.emit(
+ 'file',
+ partName,
+ this._fileStream,
+ { filename,
+ encoding: partEncoding,
+ mimeType: partType }
+ );
+ } else {
+ // Non-file
+
+ if (fields === fieldsLimit) {
+ if (!hitFieldsLimit) {
+ hitFieldsLimit = true;
+ this.emit('fieldsLimit');
+ }
+ skipPart = true;
+ return;
+ }
+ ++fields;
+
+ if (this.listenerCount('field') === 0) {
+ skipPart = true;
+ return;
+ }
+
+ field = [];
+ fieldSize = 0;
+ }
+ });
+
+ let matchPostBoundary = 0;
+ const ssCb = (isMatch, data, start, end, isDataSafe) => {
+retrydata:
+ while (data) {
+ if (this._hparser !== null) {
+ const ret = this._hparser.push(data, start, end);
+ if (ret === -1) {
+ this._hparser = null;
+ hparser.reset();
+ this.emit('error', new Error('Malformed part header'));
+ break;
+ }
+ start = ret;
+ }
+
+ if (start === end)
+ break;
+
+ if (matchPostBoundary !== 0) {
+ if (matchPostBoundary === 1) {
+ switch (data[start]) {
+ case 45: // '-'
+ // Try matching '--' after boundary
+ matchPostBoundary = 2;
+ ++start;
+ break;
+ case 13: // '\r'
+ // Try matching CR LF before header
+ matchPostBoundary = 3;
+ ++start;
+ break;
+ default:
+ matchPostBoundary = 0;
+ }
+ if (start === end)
+ return;
+ }
+
+ if (matchPostBoundary === 2) {
+ matchPostBoundary = 0;
+ if (data[start] === 45/* '-' */) {
+ // End of multipart data
+ this._complete = true;
+ this._bparser = ignoreData;
+ return;
+ }
+ // We saw something other than '-', so put the dash we consumed
+ // "back"
+ const writecb = this._writecb;
+ this._writecb = noop;
+ ssCb(false, BUF_DASH, 0, 1, false);
+ this._writecb = writecb;
+ } else if (matchPostBoundary === 3) {
+ matchPostBoundary = 0;
+ if (data[start] === 10/* '\n' */) {
+ ++start;
+ if (parts >= partsLimit)
+ break;
+ // Prepare the header parser
+ this._hparser = hparser;
+ if (start === end)
+ break;
+ // Process the remaining data as a header
+ continue retrydata;
+ } else {
+ // We saw something other than LF, so put the CR we consumed
+ // "back"
+ const writecb = this._writecb;
+ this._writecb = noop;
+ ssCb(false, BUF_CR, 0, 1, false);
+ this._writecb = writecb;
+ }
+ }
+ }
+
+ if (!skipPart) {
+ if (this._fileStream) {
+ let chunk;
+ const actualLen = Math.min(end - start, fileSizeLimit - fileSize);
+ if (!isDataSafe) {
+ chunk = Buffer.allocUnsafe(actualLen);
+ data.copy(chunk, 0, start, start + actualLen);
+ } else {
+ chunk = data.slice(start, start + actualLen);
+ }
+
+ fileSize += chunk.length;
+ if (fileSize === fileSizeLimit) {
+ if (chunk.length > 0)
+ this._fileStream.push(chunk);
+ this._fileStream.emit('limit');
+ this._fileStream.truncated = true;
+ skipPart = true;
+ } else if (!this._fileStream.push(chunk)) {
+ if (this._writecb)
+ this._fileStream._readcb = this._writecb;
+ this._writecb = null;
+ }
+ } else if (field !== undefined) {
+ let chunk;
+ const actualLen = Math.min(
+ end - start,
+ fieldSizeLimit - fieldSize
+ );
+ if (!isDataSafe) {
+ chunk = Buffer.allocUnsafe(actualLen);
+ data.copy(chunk, 0, start, start + actualLen);
+ } else {
+ chunk = data.slice(start, start + actualLen);
+ }
+
+ fieldSize += actualLen;
+ field.push(chunk);
+ if (fieldSize === fieldSizeLimit) {
+ skipPart = true;
+ partTruncated = true;
+ }
+ }
+ }
+
+ break;
+ }
+
+ if (isMatch) {
+ matchPostBoundary = 1;
+
+ if (this._fileStream) {
+ // End the active file stream if the previous part was a file
+ this._fileStream.push(null);
+ this._fileStream = null;
+ } else if (field !== undefined) {
+ let data;
+ switch (field.length) {
+ case 0:
+ data = '';
+ break;
+ case 1:
+ data = convertToUTF8(field[0], partCharset, 0);
+ break;
+ default:
+ data = convertToUTF8(
+ Buffer.concat(field, fieldSize),
+ partCharset,
+ 0
+ );
+ }
+ field = undefined;
+ fieldSize = 0;
+ this.emit(
+ 'field',
+ partName,
+ data,
+ { nameTruncated: false,
+ valueTruncated: partTruncated,
+ encoding: partEncoding,
+ mimeType: partType }
+ );
+ }
+
+ if (++parts === partsLimit)
+ this.emit('partsLimit');
+ }
+ };
+ this._bparser = new StreamSearch(`\r\n--${boundary}`, ssCb);
+
+ this._writecb = null;
+ this._finalcb = null;
+
+ // Just in case there is no preamble
+ this.write(BUF_CRLF);
+ }
+
+ static detect(conType) {
+ return (conType.type === 'multipart' && conType.subtype === 'form-data');
+ }
+
+ _write(chunk, enc, cb) {
+ this._writecb = cb;
+ this._bparser.push(chunk, 0);
+ if (this._writecb)
+ callAndUnsetCb(this);
+ }
+
+ _destroy(err, cb) {
+ this._hparser = null;
+ this._bparser = ignoreData;
+ if (!err)
+ err = checkEndState(this);
+ const fileStream = this._fileStream;
+ if (fileStream) {
+ this._fileStream = null;
+ fileStream.destroy(err);
+ }
+ cb(err);
+ }
+
+ _final(cb) {
+ this._bparser.destroy();
+ if (!this._complete)
+ return cb(new Error('Unexpected end of form'));
+ if (this._fileEndsLeft)
+ this._finalcb = finalcb.bind(null, this, cb);
+ else
+ finalcb(this, cb);
+ }
+}
+
+function finalcb(self, cb, err) {
+ if (err)
+ return cb(err);
+ err = checkEndState(self);
+ cb(err);
+}
+
+function checkEndState(self) {
+ if (self._hparser)
+ return new Error('Malformed part header');
+ const fileStream = self._fileStream;
+ if (fileStream) {
+ self._fileStream = null;
+ fileStream.destroy(new Error('Unexpected end of file'));
+ }
+ if (!self._complete)
+ return new Error('Unexpected end of form');
+}
+
+const TOKEN = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+
+const FIELD_VCHAR = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+];
+
+module.exports = Multipart;
diff --git a/node_modules/busboy/lib/types/urlencoded.js b/node_modules/busboy/lib/types/urlencoded.js
new file mode 100644
index 0000000..5c463a2
--- /dev/null
+++ b/node_modules/busboy/lib/types/urlencoded.js
@@ -0,0 +1,350 @@
+'use strict';
+
+const { Writable } = require('stream');
+
+const { getDecoder } = require('../utils.js');
+
+class URLEncoded extends Writable {
+ constructor(cfg) {
+ const streamOpts = {
+ autoDestroy: true,
+ emitClose: true,
+ highWaterMark: (typeof cfg.highWaterMark === 'number'
+ ? cfg.highWaterMark
+ : undefined),
+ };
+ super(streamOpts);
+
+ let charset = (cfg.defCharset || 'utf8');
+ if (cfg.conType.params && typeof cfg.conType.params.charset === 'string')
+ charset = cfg.conType.params.charset;
+
+ this.charset = charset;
+
+ const limits = cfg.limits;
+ this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
+ ? limits.fieldSize
+ : 1 * 1024 * 1024);
+ this.fieldsLimit = (limits && typeof limits.fields === 'number'
+ ? limits.fields
+ : Infinity);
+ this.fieldNameSizeLimit = (
+ limits && typeof limits.fieldNameSize === 'number'
+ ? limits.fieldNameSize
+ : 100
+ );
+
+ this._inKey = true;
+ this._keyTrunc = false;
+ this._valTrunc = false;
+ this._bytesKey = 0;
+ this._bytesVal = 0;
+ this._fields = 0;
+ this._key = '';
+ this._val = '';
+ this._byte = -2;
+ this._lastPos = 0;
+ this._encode = 0;
+ this._decoder = getDecoder(charset);
+ }
+
+ static detect(conType) {
+ return (conType.type === 'application'
+ && conType.subtype === 'x-www-form-urlencoded');
+ }
+
+ _write(chunk, enc, cb) {
+ if (this._fields >= this.fieldsLimit)
+ return cb();
+
+ let i = 0;
+ const len = chunk.length;
+ this._lastPos = 0;
+
+ // Check if we last ended mid-percent-encoded byte
+ if (this._byte !== -2) {
+ i = readPctEnc(this, chunk, i, len);
+ if (i === -1)
+ return cb(new Error('Malformed urlencoded form'));
+ if (i >= len)
+ return cb();
+ if (this._inKey)
+ ++this._bytesKey;
+ else
+ ++this._bytesVal;
+ }
+
+main:
+ while (i < len) {
+ if (this._inKey) {
+ // Parsing key
+
+ i = skipKeyBytes(this, chunk, i, len);
+
+ while (i < len) {
+ switch (chunk[i]) {
+ case 61: // '='
+ if (this._lastPos < i)
+ this._key += chunk.latin1Slice(this._lastPos, i);
+ this._lastPos = ++i;
+ this._key = this._decoder(this._key, this._encode);
+ this._encode = 0;
+ this._inKey = false;
+ continue main;
+ case 38: // '&'
+ if (this._lastPos < i)
+ this._key += chunk.latin1Slice(this._lastPos, i);
+ this._lastPos = ++i;
+ this._key = this._decoder(this._key, this._encode);
+ this._encode = 0;
+ if (this._bytesKey > 0) {
+ this.emit(
+ 'field',
+ this._key,
+ '',
+ { nameTruncated: this._keyTrunc,
+ valueTruncated: false,
+ encoding: this.charset,
+ mimeType: 'text/plain' }
+ );
+ }
+ this._key = '';
+ this._val = '';
+ this._keyTrunc = false;
+ this._valTrunc = false;
+ this._bytesKey = 0;
+ this._bytesVal = 0;
+ if (++this._fields >= this.fieldsLimit) {
+ this.emit('fieldsLimit');
+ return cb();
+ }
+ continue;
+ case 43: // '+'
+ if (this._lastPos < i)
+ this._key += chunk.latin1Slice(this._lastPos, i);
+ this._key += ' ';
+ this._lastPos = i + 1;
+ break;
+ case 37: // '%'
+ if (this._encode === 0)
+ this._encode = 1;
+ if (this._lastPos < i)
+ this._key += chunk.latin1Slice(this._lastPos, i);
+ this._lastPos = i + 1;
+ this._byte = -1;
+ i = readPctEnc(this, chunk, i + 1, len);
+ if (i === -1)
+ return cb(new Error('Malformed urlencoded form'));
+ if (i >= len)
+ return cb();
+ ++this._bytesKey;
+ i = skipKeyBytes(this, chunk, i, len);
+ continue;
+ }
+ ++i;
+ ++this._bytesKey;
+ i = skipKeyBytes(this, chunk, i, len);
+ }
+ if (this._lastPos < i)
+ this._key += chunk.latin1Slice(this._lastPos, i);
+ } else {
+ // Parsing value
+
+ i = skipValBytes(this, chunk, i, len);
+
+ while (i < len) {
+ switch (chunk[i]) {
+ case 38: // '&'
+ if (this._lastPos < i)
+ this._val += chunk.latin1Slice(this._lastPos, i);
+ this._lastPos = ++i;
+ this._inKey = true;
+ this._val = this._decoder(this._val, this._encode);
+ this._encode = 0;
+ if (this._bytesKey > 0 || this._bytesVal > 0) {
+ this.emit(
+ 'field',
+ this._key,
+ this._val,
+ { nameTruncated: this._keyTrunc,
+ valueTruncated: this._valTrunc,
+ encoding: this.charset,
+ mimeType: 'text/plain' }
+ );
+ }
+ this._key = '';
+ this._val = '';
+ this._keyTrunc = false;
+ this._valTrunc = false;
+ this._bytesKey = 0;
+ this._bytesVal = 0;
+ if (++this._fields >= this.fieldsLimit) {
+ this.emit('fieldsLimit');
+ return cb();
+ }
+ continue main;
+ case 43: // '+'
+ if (this._lastPos < i)
+ this._val += chunk.latin1Slice(this._lastPos, i);
+ this._val += ' ';
+ this._lastPos = i + 1;
+ break;
+ case 37: // '%'
+ if (this._encode === 0)
+ this._encode = 1;
+ if (this._lastPos < i)
+ this._val += chunk.latin1Slice(this._lastPos, i);
+ this._lastPos = i + 1;
+ this._byte = -1;
+ i = readPctEnc(this, chunk, i + 1, len);
+ if (i === -1)
+ return cb(new Error('Malformed urlencoded form'));
+ if (i >= len)
+ return cb();
+ ++this._bytesVal;
+ i = skipValBytes(this, chunk, i, len);
+ continue;
+ }
+ ++i;
+ ++this._bytesVal;
+ i = skipValBytes(this, chunk, i, len);
+ }
+ if (this._lastPos < i)
+ this._val += chunk.latin1Slice(this._lastPos, i);
+ }
+ }
+
+ cb();
+ }
+
+ _final(cb) {
+ if (this._byte !== -2)
+ return cb(new Error('Malformed urlencoded form'));
+ if (!this._inKey || this._bytesKey > 0 || this._bytesVal > 0) {
+ if (this._inKey)
+ this._key = this._decoder(this._key, this._encode);
+ else
+ this._val = this._decoder(this._val, this._encode);
+ this.emit(
+ 'field',
+ this._key,
+ this._val,
+ { nameTruncated: this._keyTrunc,
+ valueTruncated: this._valTrunc,
+ encoding: this.charset,
+ mimeType: 'text/plain' }
+ );
+ }
+ cb();
+ }
+}
+
+function readPctEnc(self, chunk, pos, len) {
+ if (pos >= len)
+ return len;
+
+ if (self._byte === -1) {
+ // We saw a '%' but no hex characters yet
+ const hexUpper = HEX_VALUES[chunk[pos++]];
+ if (hexUpper === -1)
+ return -1;
+
+ if (hexUpper >= 8)
+ self._encode = 2; // Indicate high bits detected
+
+ if (pos < len) {
+ // Both hex characters are in this chunk
+ const hexLower = HEX_VALUES[chunk[pos++]];
+ if (hexLower === -1)
+ return -1;
+
+ if (self._inKey)
+ self._key += String.fromCharCode((hexUpper << 4) + hexLower);
+ else
+ self._val += String.fromCharCode((hexUpper << 4) + hexLower);
+
+ self._byte = -2;
+ self._lastPos = pos;
+ } else {
+ // Only one hex character was available in this chunk
+ self._byte = hexUpper;
+ }
+ } else {
+ // We saw only one hex character so far
+ const hexLower = HEX_VALUES[chunk[pos++]];
+ if (hexLower === -1)
+ return -1;
+
+ if (self._inKey)
+ self._key += String.fromCharCode((self._byte << 4) + hexLower);
+ else
+ self._val += String.fromCharCode((self._byte << 4) + hexLower);
+
+ self._byte = -2;
+ self._lastPos = pos;
+ }
+
+ return pos;
+}
+
+function skipKeyBytes(self, chunk, pos, len) {
+ // Skip bytes if we've truncated
+ if (self._bytesKey > self.fieldNameSizeLimit) {
+ if (!self._keyTrunc) {
+ if (self._lastPos < pos)
+ self._key += chunk.latin1Slice(self._lastPos, pos - 1);
+ }
+ self._keyTrunc = true;
+ for (; pos < len; ++pos) {
+ const code = chunk[pos];
+ if (code === 61/* '=' */ || code === 38/* '&' */)
+ break;
+ ++self._bytesKey;
+ }
+ self._lastPos = pos;
+ }
+
+ return pos;
+}
+
+function skipValBytes(self, chunk, pos, len) {
+ // Skip bytes if we've truncated
+ if (self._bytesVal > self.fieldSizeLimit) {
+ if (!self._valTrunc) {
+ if (self._lastPos < pos)
+ self._val += chunk.latin1Slice(self._lastPos, pos - 1);
+ }
+ self._valTrunc = true;
+ for (; pos < len; ++pos) {
+ if (chunk[pos] === 38/* '&' */)
+ break;
+ ++self._bytesVal;
+ }
+ self._lastPos = pos;
+ }
+
+ return pos;
+}
+
+/* eslint-disable no-multi-spaces */
+const HEX_VALUES = [
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+];
+/* eslint-enable no-multi-spaces */
+
+module.exports = URLEncoded;
diff --git a/node_modules/busboy/lib/utils.js b/node_modules/busboy/lib/utils.js
new file mode 100644
index 0000000..8274f6c
--- /dev/null
+++ b/node_modules/busboy/lib/utils.js
@@ -0,0 +1,596 @@
+'use strict';
+
+function parseContentType(str) {
+ if (str.length === 0)
+ return;
+
+ const params = Object.create(null);
+ let i = 0;
+
+ // Parse type
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ if (code !== 47/* '/' */ || i === 0)
+ return;
+ break;
+ }
+ }
+ // Check for type without subtype
+ if (i === str.length)
+ return;
+
+ const type = str.slice(0, i).toLowerCase();
+
+ // Parse subtype
+ const subtypeStart = ++i;
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ // Make sure we have a subtype
+ if (i === subtypeStart)
+ return;
+
+ if (parseContentTypeParams(str, i, params) === undefined)
+ return;
+ break;
+ }
+ }
+ // Make sure we have a subtype
+ if (i === subtypeStart)
+ return;
+
+ const subtype = str.slice(subtypeStart, i).toLowerCase();
+
+ return { type, subtype, params };
+}
+
+function parseContentTypeParams(str, i, params) {
+ while (i < str.length) {
+ // Consume whitespace
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
+ break;
+ }
+
+ // Ended on whitespace
+ if (i === str.length)
+ break;
+
+ // Check for malformed parameter
+ if (str.charCodeAt(i++) !== 59/* ';' */)
+ return;
+
+ // Consume whitespace
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
+ break;
+ }
+
+ // Ended on whitespace (malformed)
+ if (i === str.length)
+ return;
+
+ let name;
+ const nameStart = i;
+ // Parse parameter name
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ if (code !== 61/* '=' */)
+ return;
+ break;
+ }
+ }
+
+ // No value (malformed)
+ if (i === str.length)
+ return;
+
+ name = str.slice(nameStart, i);
+ ++i; // Skip over '='
+
+ // No value (malformed)
+ if (i === str.length)
+ return;
+
+ let value = '';
+ let valueStart;
+ if (str.charCodeAt(i) === 34/* '"' */) {
+ valueStart = ++i;
+ let escaping = false;
+ // Parse quoted value
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code === 92/* '\\' */) {
+ if (escaping) {
+ valueStart = i;
+ escaping = false;
+ } else {
+ value += str.slice(valueStart, i);
+ escaping = true;
+ }
+ continue;
+ }
+ if (code === 34/* '"' */) {
+ if (escaping) {
+ valueStart = i;
+ escaping = false;
+ continue;
+ }
+ value += str.slice(valueStart, i);
+ break;
+ }
+ if (escaping) {
+ valueStart = i - 1;
+ escaping = false;
+ }
+ // Invalid unescaped quoted character (malformed)
+ if (QDTEXT[code] !== 1)
+ return;
+ }
+
+ // No end quote (malformed)
+ if (i === str.length)
+ return;
+
+ ++i; // Skip over double quote
+ } else {
+ valueStart = i;
+ // Parse unquoted value
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ // No value (malformed)
+ if (i === valueStart)
+ return;
+ break;
+ }
+ }
+ value = str.slice(valueStart, i);
+ }
+
+ name = name.toLowerCase();
+ if (params[name] === undefined)
+ params[name] = value;
+ }
+
+ return params;
+}
+
+function parseDisposition(str, defDecoder) {
+ if (str.length === 0)
+ return;
+
+ const params = Object.create(null);
+ let i = 0;
+
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ if (parseDispositionParams(str, i, params, defDecoder) === undefined)
+ return;
+ break;
+ }
+ }
+
+ const type = str.slice(0, i).toLowerCase();
+
+ return { type, params };
+}
+
+function parseDispositionParams(str, i, params, defDecoder) {
+ while (i < str.length) {
+ // Consume whitespace
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
+ break;
+ }
+
+ // Ended on whitespace
+ if (i === str.length)
+ break;
+
+ // Check for malformed parameter
+ if (str.charCodeAt(i++) !== 59/* ';' */)
+ return;
+
+ // Consume whitespace
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
+ break;
+ }
+
+ // Ended on whitespace (malformed)
+ if (i === str.length)
+ return;
+
+ let name;
+ const nameStart = i;
+ // Parse parameter name
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ if (code === 61/* '=' */)
+ break;
+ return;
+ }
+ }
+
+ // No value (malformed)
+ if (i === str.length)
+ return;
+
+ let value = '';
+ let valueStart;
+ let charset;
+ //~ let lang;
+ name = str.slice(nameStart, i);
+ if (name.charCodeAt(name.length - 1) === 42/* '*' */) {
+ // Extended value
+
+ const charsetStart = ++i;
+ // Parse charset name
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (CHARSET[code] !== 1) {
+ if (code !== 39/* '\'' */)
+ return;
+ break;
+ }
+ }
+
+ // Incomplete charset (malformed)
+ if (i === str.length)
+ return;
+
+ charset = str.slice(charsetStart, i);
+ ++i; // Skip over the '\''
+
+ //~ const langStart = ++i;
+ // Parse language name
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code === 39/* '\'' */)
+ break;
+ }
+
+ // Incomplete language (malformed)
+ if (i === str.length)
+ return;
+
+ //~ lang = str.slice(langStart, i);
+ ++i; // Skip over the '\''
+
+ // No value (malformed)
+ if (i === str.length)
+ return;
+
+ valueStart = i;
+
+ let encode = 0;
+ // Parse value
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (EXTENDED_VALUE[code] !== 1) {
+ if (code === 37/* '%' */) {
+ let hexUpper;
+ let hexLower;
+ if (i + 2 < str.length
+ && (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1
+ && (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) {
+ const byteVal = (hexUpper << 4) + hexLower;
+ value += str.slice(valueStart, i);
+ value += String.fromCharCode(byteVal);
+ i += 2;
+ valueStart = i + 1;
+ if (byteVal >= 128)
+ encode = 2;
+ else if (encode === 0)
+ encode = 1;
+ continue;
+ }
+ // '%' disallowed in non-percent encoded contexts (malformed)
+ return;
+ }
+ break;
+ }
+ }
+
+ value += str.slice(valueStart, i);
+ value = convertToUTF8(value, charset, encode);
+ if (value === undefined)
+ return;
+ } else {
+ // Non-extended value
+
+ ++i; // Skip over '='
+
+ // No value (malformed)
+ if (i === str.length)
+ return;
+
+ if (str.charCodeAt(i) === 34/* '"' */) {
+ valueStart = ++i;
+ let escaping = false;
+ // Parse quoted value
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (code === 92/* '\\' */) {
+ if (escaping) {
+ valueStart = i;
+ escaping = false;
+ } else {
+ value += str.slice(valueStart, i);
+ escaping = true;
+ }
+ continue;
+ }
+ if (code === 34/* '"' */) {
+ if (escaping) {
+ valueStart = i;
+ escaping = false;
+ continue;
+ }
+ value += str.slice(valueStart, i);
+ break;
+ }
+ if (escaping) {
+ valueStart = i - 1;
+ escaping = false;
+ }
+ // Invalid unescaped quoted character (malformed)
+ if (QDTEXT[code] !== 1)
+ return;
+ }
+
+ // No end quote (malformed)
+ if (i === str.length)
+ return;
+
+ ++i; // Skip over double quote
+ } else {
+ valueStart = i;
+ // Parse unquoted value
+ for (; i < str.length; ++i) {
+ const code = str.charCodeAt(i);
+ if (TOKEN[code] !== 1) {
+ // No value (malformed)
+ if (i === valueStart)
+ return;
+ break;
+ }
+ }
+ value = str.slice(valueStart, i);
+ }
+
+ value = defDecoder(value, 2);
+ if (value === undefined)
+ return;
+ }
+
+ name = name.toLowerCase();
+ if (params[name] === undefined)
+ params[name] = value;
+ }
+
+ return params;
+}
+
+function getDecoder(charset) {
+ let lc;
+ while (true) {
+ switch (charset) {
+ case 'utf-8':
+ case 'utf8':
+ return decoders.utf8;
+ case 'latin1':
+ case 'ascii': // TODO: Make these a separate, strict decoder?
+ case 'us-ascii':
+ case 'iso-8859-1':
+ case 'iso8859-1':
+ case 'iso88591':
+ case 'iso_8859-1':
+ case 'windows-1252':
+ case 'iso_8859-1:1987':
+ case 'cp1252':
+ case 'x-cp1252':
+ return decoders.latin1;
+ case 'utf16le':
+ case 'utf-16le':
+ case 'ucs2':
+ case 'ucs-2':
+ return decoders.utf16le;
+ case 'base64':
+ return decoders.base64;
+ default:
+ if (lc === undefined) {
+ lc = true;
+ charset = charset.toLowerCase();
+ continue;
+ }
+ return decoders.other.bind(charset);
+ }
+ }
+}
+
+const decoders = {
+ utf8: (data, hint) => {
+ if (data.length === 0)
+ return '';
+ if (typeof data === 'string') {
+ // If `data` never had any percent-encoded bytes or never had any that
+ // were outside of the ASCII range, then we can safely just return the
+ // input since UTF-8 is ASCII compatible
+ if (hint < 2)
+ return data;
+
+ data = Buffer.from(data, 'latin1');
+ }
+ return data.utf8Slice(0, data.length);
+ },
+
+ latin1: (data, hint) => {
+ if (data.length === 0)
+ return '';
+ if (typeof data === 'string')
+ return data;
+ return data.latin1Slice(0, data.length);
+ },
+
+ utf16le: (data, hint) => {
+ if (data.length === 0)
+ return '';
+ if (typeof data === 'string')
+ data = Buffer.from(data, 'latin1');
+ return data.ucs2Slice(0, data.length);
+ },
+
+ base64: (data, hint) => {
+ if (data.length === 0)
+ return '';
+ if (typeof data === 'string')
+ data = Buffer.from(data, 'latin1');
+ return data.base64Slice(0, data.length);
+ },
+
+ other: (data, hint) => {
+ if (data.length === 0)
+ return '';
+ if (typeof data === 'string')
+ data = Buffer.from(data, 'latin1');
+ try {
+ const decoder = new TextDecoder(this);
+ return decoder.decode(data);
+ } catch {}
+ },
+};
+
+function convertToUTF8(data, charset, hint) {
+ const decode = getDecoder(charset);
+ if (decode)
+ return decode(data, hint);
+}
+
+function basename(path) {
+ if (typeof path !== 'string')
+ return '';
+ for (let i = path.length - 1; i >= 0; --i) {
+ switch (path.charCodeAt(i)) {
+ case 0x2F: // '/'
+ case 0x5C: // '\'
+ path = path.slice(i + 1);
+ return (path === '..' || path === '.' ? '' : path);
+ }
+ }
+ return (path === '..' || path === '.' ? '' : path);
+}
+
+const TOKEN = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+
+const QDTEXT = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+];
+
+const CHARSET = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+
+const EXTENDED_VALUE = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+
+/* eslint-disable no-multi-spaces */
+const HEX_VALUES = [
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+];
+/* eslint-enable no-multi-spaces */
+
+module.exports = {
+ basename,
+ convertToUTF8,
+ getDecoder,
+ parseContentType,
+ parseDisposition,
+};
diff --git a/node_modules/busboy/package.json b/node_modules/busboy/package.json
new file mode 100644
index 0000000..ac2577f
--- /dev/null
+++ b/node_modules/busboy/package.json
@@ -0,0 +1,22 @@
+{ "name": "busboy",
+ "version": "1.6.0",
+ "author": "Brian White <mscdex@mscdex.net>",
+ "description": "A streaming parser for HTML form data for node.js",
+ "main": "./lib/index.js",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "devDependencies": {
+ "@mscdex/eslint-config": "^1.1.0",
+ "eslint": "^7.32.0"
+ },
+ "scripts": {
+ "test": "node test/test.js",
+ "lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test bench",
+ "lint:fix": "npm run lint -- --fix"
+ },
+ "engines": { "node": ">=10.16.0" },
+ "keywords": [ "uploads", "forms", "multipart", "form-data" ],
+ "licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/busboy/raw/master/LICENSE" } ],
+ "repository": { "type": "git", "url": "http://github.com/mscdex/busboy.git" }
+}
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;
+ }
+}