Cloudron-Pandoc-PDF-Builder/node_modules/formidable/src/plugins/multipart.js

174 lines
5.8 KiB
JavaScript

/* 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;
};
}