Files
2026-10-03 12:21:15 +08:00

613 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var qs = require('querystring');
var iconv = require('iconv-lite');
var net = require('net');
var util = require('../util');
var extend = require('extend');
var mime = require('mime');
var pluginMgr = require('../plugins');
var config = require('../config');
var handleWeinre = require('./weinre');
var handleLog = require('./log');
var Transform = require('pipestream').Transform;
var ReplacePatternTransform = util.ReplacePatternTransform;
var ReplaceStringTransform = util.ReplaceStringTransform;
var FileWriterTransform = util.FileWriterTransform;
var JSON_RE = /{[\w\W]*}|\[[\w\W]*\]/;
var BUOUNDARY_RE = /boundary=(?:"([^"]+)"|([^;]+))/i;
var MAX_REQ_SIZE = 1024 * 1024 * (config.strict ? 1 : 2); // 2MB
var BIG_MAX_REQ_SIZE = 1024 * 1024 * 16;
var MAX_HEADER_SIZE = 1024 * 8;
var CRLF = Buffer.from('\r\n');
var CR = CRLF[0];
var LF = CRLF[1];
var LINE = Buffer.from('-')[0];
var UPLOAD_CTN_SEP = Buffer.from('\r\n\r\n');
var CTN_DIS = 'Content-Disposition: form-data; name="';
var clientIpKey = config.CLIENT_IP_HEADER;
var REQ_TYPE = {
urlencoded: 'application/x-www-form-urlencoded',
form: 'application/x-www-form-urlencoded',
json: 'application/json',
xml: 'application/xml',
text: 'text/plain',
upload: 'multipart/form-data',
multipart: 'multipart/form-data',
defaultType: 'application/octet-stream'
};
var NAME_RE = /name=(?:"([^"]+)"|([^;]+))/i;
function getName(firstLine) {
if (!NAME_RE.test(firstLine)) {
return null;
}
var name = RegExp.$1;
if (name) {
return name;
}
name = RegExp.$2;
if (name[0] === '\'') {
var lastIndex = name.length - 1;
if (name[lastIndex] === '\'') {
return name.substring(1, lastIndex);
}
}
return name;
}
function toMultipart(name, value) {
if (value === undefined) {
return null;
}
if (value == null) {
value = '';
}
if (typeof value == 'object') {
var filename = value.filename || value.name;
var base64 = value.base64;
var type = value.type;
filename = filename == null ? name : filename + '';
value = value.content || value.value || '';
if (value && typeof value === 'object') {
try {
value = JSON.stringify(value, null, ' ') || '';
} catch (e) {}
} else if (base64) {
try {
base64 = Buffer.from(base64, 'base64');
value = '';
} catch (e) {
base64 = '';
}
}
if (!util.isString(type)) {
type = '';
} else if (type.indexOf('/') === -1) {
type = mime.lookup(type, type);
}
value = Buffer.from(CTN_DIS + name + '"; filename="' + filename
+ '"\r\nContent-Type: ' + (type || mime.lookup(filename)) + '\r\n\r\n' + value);
return base64 ? Buffer.concat([value, base64]) : value;
}
return Buffer.from(CTN_DIS + name + '"\r\n\r\n' + value);
}
/**
* 处理请求数据
*
* @param req:method、body、headers,top,bottom,speed、delay,charset, timeout
* @param data
*/
function handleReq(req, data, reqRules, delType) {
extend(req.headers, data.headers);
if (reqRules.reqType) {
var newType = util.getMatcherValue(reqRules.reqType).split(';');
var type = newType[0];
newType[0] =
!type || type.indexOf('/') != -1
? type
: REQ_TYPE[type] || util.lookupType(type);
req.headers['content-type'] = util.getNewType(newType.join(';'), req.headers);
}
util.setCharset(req.headers, data.charset, delType.reqType, delType.reqCharset);
if (!util.hasRequestBody(req.method)) {
delete data.top;
delete data.bottom;
delete data.body;
delete req.headers['content-length'];
} else {
util.removeReqBody(req, data);
if (data.top || data.bottom || data.body) {
delete req.headers['content-length'];
req._hasInjectBody = true;
}
}
util.isWhistleTransformData(data) &&
req.addZipTransform(new util.WhistleTransform(data, req));
var opList = util.parseHeaderReplace(req.rules.headerReplace).req;
if (opList) {
var host = req.headers.host;
var xff = req.headers[clientIpKey];
var clientId = req.headers[config.CLIENT_ID_HEADER];
util.handleHeaderReplace(req.headers, opList);
if (req.headers.host !== host) {
req._customHost = req.headers.host;
}
var newXFF = req.headers[clientIpKey];
if (xff !== newXFF && net.isIP(newXFF)) {
req._customXFF = newXFF;
}
if (clientId !== req.headers[config.CLIENT_ID_HEADER]) {
req._customClientId = req.headers[config.CLIENT_ID_HEADER];
}
}
}
function handleAuth(data, authObj) {
var auth = util.getAuthBasic(authObj);
if (auth) {
util.setHeader(data, (authObj.proxy ? 'proxy-' : '') + 'authorization', auth);
}
}
function handleParams(req, params, urlParams, enableBigData) {
params = util.isEmptyObject(params) ? null : params;
var delProps = util.parseDelReqBody(req);
var hasBody;
var buffer;
var len = 0;
if (params || delProps) {
var maxReqSize = enableBigData || util.isEnable(req, 'reqMergeBigData') ? BIG_MAX_REQ_SIZE : MAX_REQ_SIZE;
var transform;
var headers = req.headers;
var isJson = util.isJSONContent(req);
if (isJson || util.isUrlEncoded(req)) {
delete headers['content-length'];
transform = new Transform();
var interrupt;
transform._transform = function (chunk, _, callback) {
if (chunk) {
if (!interrupt) {
len += chunk.length;
buffer = buffer || [];
buffer.push(chunk);
chunk = null;
if (len > maxReqSize) {
interrupt = true;
chunk = Buffer.concat(buffer, len);
buffer = null;
}
}
} else if (buffer && buffer.length) {
buffer = util.formatBuffer(buffer, len);
var body;
var isGBK = !util.isUtf8(buffer);
if (isGBK) {
try {
body = iconv.decode(buffer, 'GB18030');
} catch (e) {}
}
if (!body) {
body = buffer + '';
}
if (isJson) {
body = body.replace(JSON_RE, function (json) {
var obj = util.parseRawJson(json);
if (obj) {
if (params) {
obj = extend(true, obj, params);
}
util.deleteProps(obj, delProps);
json = JSON.stringify(obj);
}
return json;
});
} else {
body = util.replaceQueryString(body, params, delProps);
}
if (isGBK) {
try {
body = iconv.encode(body, 'GB18030');
} catch (e) {}
} else {
chunk = util.toBuffer(body);
}
buffer = null;
} else if (!interrupt && params) {
util.deleteProps(params, delProps);
var data = isJson ? JSON.stringify(params) : qs.stringify(params);
chunk = util.toBuffer(data);
}
callback(null, chunk);
};
req.addZipTransform(transform);
hasBody = true;
} else if (util.isMultipart(req) && BUOUNDARY_RE.test(headers['content-type'])) {
delete headers['content-length'];
var boundaryStr = '--' + (RegExp.$1 || RegExp.$2);
var startBoundary = Buffer.from(boundaryStr + '\r\n');
var boundary = Buffer.from('\r\n' + boundaryStr);
var sepBoundary = Buffer.from('\r\n' + boundaryStr + '\r\n');
var endBoudary = Buffer.from('\r\n' + boundaryStr + '--');
var length = startBoundary.length;
var endLength = sepBoundary.length;
var minCtnLen = length + endLength;
var preBuffer;
var ended;
var lastChunk;
var badMultipart;
var pass;
var ignore;
var result;
transform = new Transform();
if (delProps) {
params = params || delProps;
Object.keys(delProps).forEach(function(key) {
params[key] = undefined;
});
}
var getRestData = function() {
var rest;
Object.keys(params).forEach(function(name) {
var part = toMultipart(name, params[name]);
if (part) {
if (rest) {
rest.push(sepBoundary);
} else {
rest = [];
}
rest.push(part);
}
});
if (!rest) {
return null;
}
return rest.length < 2 ? rest[0] : Buffer.concat(rest);
};
var pushResult = function(buf, addSep) {
if (buf && buf.length) {
result = result || [];
if (addSep) {
if (startBoundary) {
result.unshift(startBoundary);
startBoundary = null;
} else {
result.push(sepBoundary);
}
}
result.push(buf);
}
};
var getResult = function() {
if (ended) {
pushResult(getRestData(), true);
}
if (!result) {
return null;
}
if (ended && !pass && result.length) {
result.push(endBoudary);
}
if (result.length < 2) {
return result[0];
}
return Buffer.concat(result);
};
var getChunk = function() {
result = null;
while(!ended && buffer.length >= endLength) {
var index = buffer.indexOf(boundary);
var isMatched = index !== -1;
if (isMatched) {
var first = buffer[index + length];
var sec = buffer[index + length + 1];
ended = first === LINE && sec === LINE;
isMatched = ended || (first === CR && sec === LF);
}
if (ignore || pass) {
if (!isMatched) {
index = -endLength + 1;
if (pass) {
pushResult(buffer.slice(0, index));
}
buffer = buffer.slice(index);
return getResult();
}
if (pass) {
pushResult(buffer.slice(0, index));
}
buffer = buffer.slice(index + endLength);
ignore = false;
pass = false;
} else {
var part;
var ctnIndex;
var name;
if (isMatched) {
part = buffer.slice(0, index);
ctnIndex = part.indexOf(UPLOAD_CTN_SEP);
if (ctnIndex !== -1) {
name = getName(buffer.slice(0, ctnIndex) + '');
if (name != null && (name in params)) {
part = toMultipart(name, params[name]);
params[name] = undefined;
}
}
pushResult(part, true);
buffer = buffer.slice(index + endLength);
} else {
ctnIndex = buffer.indexOf(UPLOAD_CTN_SEP);
if (ctnIndex !== -1) {
name = getName(buffer.slice(0, ctnIndex) + '');
pass = name == null || !(name in params);
index = -endLength + 1;
if (pass) {
pushResult(buffer.slice(0, index), true);
} else {
pushResult(toMultipart(name, params[name]), true);
params[name] = undefined;
ignore = true;
}
buffer = buffer.slice(index);
} else {
if (buffer.length > MAX_HEADER_SIZE) {
badMultipart = true;
pass = true;
pushResult(buffer, true);
}
return getResult();
}
}
}
}
if (pass && lastChunk) {
pushResult(buffer);
}
return getResult();
};
var getCustomData = function() {
var chunk = lastChunk ? getRestData() : null;
return chunk && Buffer.concat([startBoundary, chunk, endBoudary]);
};
transform._transform = function (chunk, _, callback) {
if (ended) {
return callback();
}
if (badMultipart) {
return callback(null, chunk);
}
lastChunk = !chunk;
if (chunk && preBuffer !== null) {
preBuffer = preBuffer ? Buffer.concat([preBuffer, chunk]) : chunk;
if (preBuffer.length <= length) {
return callback(null, lastChunk ? preBuffer : null);
}
if (!util.startWithList(preBuffer, startBoundary)) {
lastChunk = true;
if (chunk = getCustomData()) {
ended = true;
} else {
badMultipart = true;
chunk = preBuffer;
}
return callback(null, chunk);
}
chunk = preBuffer.slice(length);
preBuffer = null;
}
buffer = buffer && chunk ? Buffer.concat([buffer, chunk]) : (buffer || chunk);
if (!buffer || buffer.length < minCtnLen) {
return callback(null, getCustomData());
}
callback(null, getChunk());
};
req.addZipTransform(transform);
hasBody = true;
}
var matcher = req.rules.params && req.rules.params.matcher;
if (matcher && !matcher.indexOf(' params://')) {
hasBody = true;
}
}
var _params = hasBody ? null : params;
if (_params || urlParams) {
req._urlParams =
urlParams && _params ? extend(_params, urlParams) : _params || urlParams;
}
}
function handleReplace(req, replacement) {
if (!util.hasRequestBody(req) || !replacement) {
return;
}
var type = req.headers['content-type'];
type = util.isUrlEncoded(req) ? 'FORM' : util.getContentType(type);
if (!type || type == 'IMG') {
return;
}
Object.keys(replacement).forEach(function (pattern) {
var value = replacement[pattern];
if (
util.isOriginalRegExp(pattern) &&
(pattern = util.toOriginalRegExp(pattern))
) {
req.addTextTransform(new ReplacePatternTransform(pattern, value));
} else if (pattern) {
req.addTextTransform(new ReplaceStringTransform(pattern, value));
}
});
}
module.exports = function (req, res, next) {
handleWeinre(req, res);
handleLog(req, res);
var reqRules = req.rules;
var authObj = util.getAuthByRules(reqRules);
var reqMerge = reqRules.params;
util.parseRuleJson(
[
reqRules.reqHeaders,
reqRules.reqCookies,
authObj ? null : reqRules.auth,
reqMerge,
reqRules.reqCors,
reqRules.reqReplace,
reqRules.urlReplace,
reqRules.urlParams
],
function (
headers,
cookies,
auth,
params,
cors,
replacement,
urlReplace,
urlParams
) {
var data = {};
if (headers) {
data.headers = extend(data.headers || {}, headers);
}
if (data.body && typeof data.body !== 'string') {
try {
data.body = JSON.stringify(data.body);
} catch (e) {}
}
if (data.headers) {
data.headers = util.lowerCaseify(data.headers, req.rawHeaderNames);
req._customHost = data.headers.host;
req._customXFF = data.headers[clientIpKey];
req._customClientId = data.headers[config.CLIENT_ID_HEADER];
if (typeof data.headers['content-type'] !== 'string') {
delete data.headers['content-type'];
}
}
if (reqRules.method) {
data.method = util.getMatcherValue(reqRules.method);
}
if (reqRules.reqCharset) {
data.charset = util.getMatcherValue(reqRules.reqCharset);
}
if (reqRules.referer) {
util.setHeader(data, 'referer', util.getMatcherValue(reqRules.referer));
}
if (reqRules.ua) {
util.setHeader(data, 'user-agent', util.getMatcherValue(reqRules.ua));
}
var reqSpeed = util.getMatcherValue(reqRules.reqSpeed);
reqSpeed = reqSpeed && parseFloat(reqSpeed);
if (reqSpeed > 0) {
data.speed = reqSpeed;
}
var cookie = data.headers && data.headers.cookie;
if (typeof cookie !== 'string') {
if (Array.isArray(cookie)) {
cookie = cookie.join('; ');
} else {
cookie = req.headers.cookie;
}
}
util.setReqCookies(data, cookies, cookie, req);
handleAuth(data, auth || authObj);
util.setReqCors(data, cors);
req.method = util.getMethod(data.method || req.method);
var bodyList = [
reqRules.reqBody,
reqRules.reqPrepend,
reqRules.reqAppend
];
util.getRuleValue(
bodyList,
function (reqBody, reqPrepend, reqAppend) {
if (reqBody != null) {
data.body = reqBody || util.EMPTY_BUFFER;
}
if (reqPrepend) {
data.top = reqPrepend;
}
if (reqAppend) {
data.bottom = reqAppend;
}
var delType = {};
var dProps = util.parseDelQuery(req, delType);
var queryProps = dProps.query;
var pathProps = dProps.paths;
handleReq(req, data, reqRules, delType);
handleParams(req, params, urlParams, reqMerge && reqMerge.lineProps.enableBigData);
if (req._urlParams || urlReplace || pathProps || queryProps || req._delQueryString) {
var options = req.options;
var isUrl = util.isUrl(options.href);
var newUrl = isUrl ? options.href : req.fullUrl;
if (req._urlParams) {
newUrl = util.replaceUrlQueryString(newUrl, req._urlParams);
}
newUrl = util.parsePathReplace(newUrl, urlReplace, pathProps) || newUrl;
newUrl = util.deleteQuery(newUrl, queryProps, req._delQueryString);
if (newUrl !== options.href) {
if (isUrl) {
req.options = util.parseUrl(newUrl);
} else {
req._realUrl = newUrl;
}
}
}
util.removeUnsupportsHeaders(req.headers);
util.disableReqProps(req);
handleReplace(req, replacement);
var reqWriter = util.hasRequestBody(req)
? util.getWriteFilePath(reqRules.reqWrite)
: null;
util.getFileWriters(
[reqWriter, util.getWriteFilePath(reqRules.reqWriteRaw)],
function (writer, rawWriter) {
if (writer) {
req.addZipTransform(
new FileWriterTransform(writer, req, false, false, true)
);
}
if (rawWriter) {
req.addZipTransform(
new FileWriterTransform(rawWriter, req, true, false, true)
);
}
pluginMgr.postStats(req);
next();
},
util.isEnable(req, 'forceReqWrite')
);
},
!util.hasRequestBody(req.method),
null,
null,
req
);
},
req
);
};