pax_global_header 0000666 0000000 0000000 00000000064 14227530204 0014511 g ustar 00root root 0000000 0000000 52 comment=9aadb7afbcb8c70c81c93b1018313c1b1835afb0 busboy-1.6.0/ 0000775 0000000 0000000 00000000000 14227530204 0013020 5 ustar 00root root 0000000 0000000 busboy-1.6.0/.eslintrc.js 0000664 0000000 0000000 00000000111 14227530204 0015250 0 ustar 00root root 0000000 0000000 'use strict'; module.exports = { extends: '@mscdex/eslint-config', }; busboy-1.6.0/.github/ 0000775 0000000 0000000 00000000000 14227530204 0014360 5 ustar 00root root 0000000 0000000 busboy-1.6.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14227530204 0016415 5 ustar 00root root 0000000 0000000 busboy-1.6.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000001006 14227530204 0017530 0 ustar 00root root 0000000 0000000 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 busboy-1.6.0/.github/workflows/lint.yml 0000664 0000000 0000000 00000000727 14227530204 0020114 0 ustar 00root root 0000000 0000000 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 busboy-1.6.0/LICENSE 0000664 0000000 0000000 00000002053 14227530204 0014025 0 ustar 00root root 0000000 0000000 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. busboy-1.6.0/README.md 0000664 0000000 0000000 00000017366 14227530204 0014314 0 ustar 00root root 0000000 0000000 # 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(`
`); } }).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. busboy-1.6.0/bench/ 0000775 0000000 0000000 00000000000 14227530204 0014077 5 ustar 00root root 0000000 0000000 busboy-1.6.0/bench/bench-multipart-fields-100mb-big.js 0000664 0000000 0000000 00000006152 14227530204 0022357 0 ustar 00root root 0000000 0000000 '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); } busboy-1.6.0/bench/bench-multipart-fields-100mb-small.js 0000664 0000000 0000000 00000006141 14227530204 0022724 0 ustar 00root root 0000000 0000000 '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); } busboy-1.6.0/bench/bench-multipart-files-100mb-big.js 0000664 0000000 0000000 00000006425 14227530204 0022216 0 ustar 00root root 0000000 0000000 '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); } busboy-1.6.0/bench/bench-multipart-files-100mb-small.js 0000664 0000000 0000000 00000006414 14227530204 0022563 0 ustar 00root root 0000000 0000000 '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); } busboy-1.6.0/bench/bench-urlencoded-fields-100pairs-small.js 0000664 0000000 0000000 00000004243 14227530204 0023550 0 ustar 00root root 0000000 0000000 '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); } busboy-1.6.0/bench/bench-urlencoded-fields-900pairs-small-alt.js 0000664 0000000 0000000 00000003420 14227530204 0024332 0 ustar 00root root 0000000 0000000 '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); } busboy-1.6.0/lib/ 0000775 0000000 0000000 00000000000 14227530204 0013566 5 ustar 00root root 0000000 0000000 busboy-1.6.0/lib/index.js 0000664 0000000 0000000 00000003050 14227530204 0015231 0 ustar 00root root 0000000 0000000 '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); }; busboy-1.6.0/lib/types/ 0000775 0000000 0000000 00000000000 14227530204 0014732 5 ustar 00root root 0000000 0000000 busboy-1.6.0/lib/types/multipart.js 0000664 0000000 0000000 00000045051 14227530204 0017316 0 ustar 00root root 0000000 0000000 '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; busboy-1.6.0/lib/types/urlencoded.js 0000664 0000000 0000000 00000024721 14227530204 0017422 0 ustar 00root root 0000000 0000000 '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; busboy-1.6.0/lib/utils.js 0000664 0000000 0000000 00000037534 14227530204 0015300 0 ustar 00root root 0000000 0000000 '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, }; busboy-1.6.0/package.json 0000664 0000000 0000000 00000001452 14227530204 0015310 0 ustar 00root root 0000000 0000000 { "name": "busboy", "version": "1.6.0", "author": "Brian White