dalao-proxy
Version:
An expandable HTTP proxy based on the plug-in system for frontend developers with request caching request mock and development!
171 lines (149 loc) • 4.93 kB
JavaScript
const BodyParser = module.exports;
const { isDebugMode } = require('../utils');
const querystring = require('querystring');
/**
* Parse raw request body
* @param {String} contentType request content type
* @param {String} rawBuffer request raw body
* @param {Object} [opt]
* @param {Function} [opt.errorHandler] parse error handler
* @param {Boolean} [opt.appendRawFormData] whether append raw form data in field `_raw`. default: false
* @param {Boolean} [opt.noRawFileData] whether collect raw file data. default: false
*/
BodyParser.parse = function parse(contentType, rawBuffer, opt) {
const {
errorHandler,
appendRawFormData,
noRawFileData
} = opt || {};
if (!rawBuffer.length || !contentType) return;
let body = {};
try {
if (/application\/x-www-form-urlencoded/.test(contentType)) {
body = querystring.parse(rawBuffer.toString());
}
else if (/application\/json/.test(contentType)) {
body = JSON.parse(rawBuffer.toString());
}
else if (/multipart\/form-data/.test(contentType)) {
body = parseFormData(contentType, rawBuffer, { appendRawFormData, noRawFileData });
}
} catch (error) {
if (isDebugMode) {
console.error(error);
}
errorHandler && errorHandler(error);
}
return body;
};
/**
* parse form data
* @param {String} contentType
* @param {Buffer} rawBuffer
* @param {Object} [opt]
* @param {Boolean} [opt.appendRawFormData] whether append raw form data in field `_raw`. default: false
* @param {Boolean} [opt.noRawFileData] whether collect raw file data. default: false
*/
function parseFormData(contentType, rawBuffer, opt) {
const { appendRawFormData, noRawFileData } = opt || {};
const boundary = Buffer.from('--' + contentType.match(/boundary=(\S+)$/)[1]);
const parts = [];
let index = 0,
lastSameIndex = 0;
// split
while (index <= rawBuffer.length - boundary.length - 2) {
const isSame = boundary.compare(rawBuffer, index, index + boundary.length) === 0;
if (isSame) {
if (index) {
const part = rawBuffer.slice(lastSameIndex + boundary.length + 1, index);
parts.push(part);
}
lastSameIndex = index;
index += boundary.length;
}
else {
index++;
}
}
const body = {};
const rawBody = {};
parts.forEach(part => {
const {
head,
rawHeadBuffer,
rawBodyBuffer
} = splitHeadAndBody(part);
const field = getHeadFieldValue(head, "name");
let value, rawValue;
if (getHeadFieldValue(head, "filename")) {
// is file
const fileName = getHeadFieldValue(head, "filename");
const contentType = getContentType(head);
value = {
name: fileName,
type: contentType,
size: Buffer.byteLength(rawBodyBuffer),
rawBuffer: rawBodyBuffer
};
if (noRawFileData) {
delete value.rawBuffer;
}
rawValue = {
head: rawHeadBuffer,
body: rawBodyBuffer,
isFile: true
};
}
else {
// is plain text
value = rawBodyBuffer.toString();
rawValue = {
head: rawHeadBuffer,
body: rawBodyBuffer,
isFile: false
};
}
body[field] = value;
rawBody[field] = rawValue;
});
if (appendRawFormData) {
body._raw = rawBody;
}
return body;
function splitHeadAndBody(part) {
let isCRLF = true;
let seperator = part.indexOf('\r\n\r\n');
if (seperator === -1) {
seperator = part.indexOf('\n\n');
isCRLF = false;
}
if (seperator === -1) {
throw new Error('Parsing form data error: can\'t split head and body');
}
const head = part.slice(1, seperator);
const body = part.slice(seperator + (isCRLF ? 4 : 2), part.length - (isCRLF ? 2 : 1));
return {
head: head.toString(),
rawHeadBuffer: head,
rawBodyBuffer: body
};
}
function getHeadFieldValue(head, field) {
const regExp = new RegExp(`\\b${field}="(.+?)"`);
return _getMatchedValue(head, regExp);
}
function getContentType(head) {
return _getMatchedValue(head, new RegExp("Content-Type:\\s?(\\S+)"));
}
/**
* @param {String} head
* @param {RegExp} regExp
*/
function _getMatchedValue(head, regExp) {
let matched, value;
if (matched = head.match(regExp)) {
value = matched[1];
}
return value;
}
}