diff options
Diffstat (limited to 'together/node_modules/formidable/src/plugins/multipart.js')
-rw-r--r-- | together/node_modules/formidable/src/plugins/multipart.js | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/together/node_modules/formidable/src/plugins/multipart.js b/together/node_modules/formidable/src/plugins/multipart.js new file mode 100644 index 0000000..ba07373 --- /dev/null +++ b/together/node_modules/formidable/src/plugins/multipart.js @@ -0,0 +1,173 @@ +/* eslint-disable no-underscore-dangle */ + +'use strict'; + +const { Stream } = require('stream'); +const MultipartParser = require('../parsers/Multipart'); +const errors = require('../FormidableError.js'); + +const { FormidableError } = errors; + +// the `options` is also available through the `options` / `formidable.options` +module.exports = function plugin(formidable, options) { + // the `this` context is always formidable, as the first argument of a plugin + // but this allows us to customize/test each plugin + + /* istanbul ignore next */ + const self = this || formidable; + + // NOTE: we (currently) support both multipart/form-data and multipart/related + const multipart = /multipart/i.test(self.headers['content-type']); + + if (multipart) { + const m = self.headers['content-type'].match( + /boundary=(?:"([^"]+)"|([^;]+))/i, + ); + if (m) { + const initMultipart = createInitMultipart(m[1] || m[2]); + initMultipart.call(self, self, options); // lgtm [js/superfluous-trailing-arguments] + } else { + const err = new FormidableError( + 'bad content-type header, no multipart boundary', + errors.missingMultipartBoundary, + 400, + ); + self._error(err); + } + } +}; + +// Note that it's a good practice (but it's up to you) to use the `this.options` instead +// of the passed `options` (second) param, because when you decide +// to test the plugin you can pass custom `this` context to it (and so `this.options`) +function createInitMultipart(boundary) { + return function initMultipart() { + this.type = 'multipart'; + + const parser = new MultipartParser(this.options); + let headerField; + let headerValue; + let part; + + parser.initWithBoundary(boundary); + + // eslint-disable-next-line max-statements, consistent-return + parser.on('data', ({ name, buffer, start, end }) => { + if (name === 'partBegin') { + part = new Stream(); + part.readable = true; + part.headers = {}; + part.name = null; + part.originalFilename = null; + part.mimetype = null; + + part.transferEncoding = this.options.encoding; + part.transferBuffer = ''; + + headerField = ''; + headerValue = ''; + } else if (name === 'headerField') { + headerField += buffer.toString(this.options.encoding, start, end); + } else if (name === 'headerValue') { + headerValue += buffer.toString(this.options.encoding, start, end); + } else if (name === 'headerEnd') { + headerField = headerField.toLowerCase(); + part.headers[headerField] = headerValue; + + // matches either a quoted-string or a token (RFC 2616 section 19.5.1) + const m = headerValue.match( + // eslint-disable-next-line no-useless-escape + /\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i, + ); + if (headerField === 'content-disposition') { + if (m) { + part.name = m[2] || m[3] || ''; + } + + part.originalFilename = this._getFileName(headerValue); + } else if (headerField === 'content-type') { + part.mimetype = headerValue; + } else if (headerField === 'content-transfer-encoding') { + part.transferEncoding = headerValue.toLowerCase(); + } + + headerField = ''; + headerValue = ''; + } else if (name === 'headersEnd') { + switch (part.transferEncoding) { + case 'binary': + case '7bit': + case '8bit': + case 'utf-8': { + const dataPropagation = (ctx) => { + if (ctx.name === 'partData') { + part.emit('data', ctx.buffer.slice(ctx.start, ctx.end)); + } + }; + const dataStopPropagation = (ctx) => { + if (ctx.name === 'partEnd') { + part.emit('end'); + parser.off('data', dataPropagation); + parser.off('data', dataStopPropagation); + } + }; + parser.on('data', dataPropagation); + parser.on('data', dataStopPropagation); + break; + } + case 'base64': { + const dataPropagation = (ctx) => { + if (ctx.name === 'partData') { + part.transferBuffer += ctx.buffer + .slice(ctx.start, ctx.end) + .toString('ascii'); + + /* + four bytes (chars) in base64 converts to three bytes in binary + encoding. So we should always work with a number of bytes that + can be divided by 4, it will result in a number of buytes that + can be divided vy 3. + */ + const offset = parseInt(part.transferBuffer.length / 4, 10) * 4; + part.emit( + 'data', + Buffer.from( + part.transferBuffer.substring(0, offset), + 'base64', + ), + ); + part.transferBuffer = part.transferBuffer.substring(offset); + } + }; + const dataStopPropagation = (ctx) => { + if (ctx.name === 'partEnd') { + part.emit('data', Buffer.from(part.transferBuffer, 'base64')); + part.emit('end'); + parser.off('data', dataPropagation); + parser.off('data', dataStopPropagation); + } + }; + parser.on('data', dataPropagation); + parser.on('data', dataStopPropagation); + break; + } + default: + return this._error( + new FormidableError( + 'unknown transfer-encoding', + errors.unknownTransferEncoding, + 501, + ), + ); + } + + this.onPart(part); + } else if (name === 'end') { + this.ended = true; + this._maybeEnd(); + } + }); + + this._parser = parser; + }; +} |