whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
279 lines (273 loc) • 8.09 kB
JavaScript
var AdmZip = require('adm-zip');
var parseString = require('xml2js').parseString;
var parseUrl = require('../util/parse-url-safe');
var common = require('../util/common');
var util = require('./util');
function getMetaAttrs(meta) {
meta = meta && meta.Session;
if (!meta) {
return {};
}
var result = meta.SessionTimers && meta.SessionTimers[0];
result = (result && result.$) || {};
var SessionFlag =
meta.SessionFlags &&
meta.SessionFlags[0] &&
meta.SessionFlags[0].SessionFlag;
if (Array.isArray(SessionFlag)) {
SessionFlag.forEach(function (flag) {
flag = flag && flag.$;
if (!flag || typeof flag.N !== 'string') {
return;
}
result[flag.N] = flag.V || '';
});
}
return result;
}
function parseMetaInfo(result) {
var req = result.req;
if (!req) {
return false;
}
var port;
if (/^[^:/]+:\/\//.test(req.url)) {
var options = parseUrl(req.url);
if (!req.headers.host) {
req.headers.host = options.host;
}
req.isHttps = /^https:/i.test(req.url);
port = options.port || (req.isHttps ? 443 : 80);
req.url = options.path;
} else if (typeof req.headers.host !== 'string') {
req.headers.host = '';
}
var meta = getMetaAttrs(result.meta);
if (!result.startTime) {
var startTime = (result.startTime =
new Date(meta.ClientConnected).getTime() || 0);
if (meta.DNSTime >= 0) {
startTime = result.dnsTime = +startTime + +meta.DNSTime;
}
if (meta.ClientDoneRequest) {
var requestTime = new Date(meta.ClientDoneRequest).getTime() || 0;
startTime = result.requestTime = Math.max(startTime, requestTime);
}
if (meta.ClientBeginResponse) {
var responseTime = new Date(meta.ClientBeginResponse).getTime() || 0;
startTime = result.responseTime = Math.max(startTime, responseTime);
}
if (meta.ClientDoneResponse) {
var endTime = new Date(meta.ClientDoneResponse).getTime() || 0;
result.endTime = Math.max(endTime, startTime);
}
}
if (meta['x-ttfb'] >= 0) {
result.ttfb = +meta['x-ttfb'];
}
result.rules = result.rules || {};
var res = (result.res = result.res || {});
result.hostIp = res.ip = util.removeIPV6Prefix(meta['x-hostip']);
res.port = meta['x-serverport'] || port;
result.clientIp = req.ip = util.removeIPV6Prefix(meta['x-clientip']);
var clientPort = meta['x-clientport'];
if (clientPort) {
req.port = clientPort;
}
var comment = meta['ui-comments'];
if (comment && typeof comment === 'string') {
result.customData = result.customData || {};
result.customData.comment = comment;
}
var size = meta['x-transfer-size'] || meta['x-responsebodytransferlength'];
if (typeof size === 'string') {
size = parseInt(size.replace(/\s*,\s*/g, ''), 10);
}
if (size > -1) {
res.size = size;
}
if (req.method === 'CONNECT') {
result.url = req.url;
result.isHttps = true;
} else {
result.url =
'http' + (req.isHttps ? 's' : '') + '://' + req.headers.host + req.url;
if (/\bwebsocket\b/i.test(req.headers.upgrade)) {
result.url = result.url.replace(/^http/, 'ws');
}
}
}
function sortKeys(cur, next) {
return parseInt(cur, 10) - parseInt(next, 10);
}
module.exports = function (buffer, cb) {
var zip = new AdmZip(buffer);
var zipEntries = zip.getEntries();
var sessions = {};
var count = 0;
var execCallback = function () {
if (count <= 0) {
var result = [];
var wsLen = 0;
Object.keys(sessions)
.sort(sortKeys)
.forEach(function (key) {
var session = sessions[key];
if (session.req && session.meta) {
if (parseMetaInfo(session) !== false) {
result.push(session);
}
}
if (session._url) {
session.url = session._url;
delete session._url;
}
if (session.trailers && session.res) {
session.res.trailers = session.trailers;
session.res.rawTrailerNames = session.rawTrailerNames;
delete session.trailers;
delete session.rawTrailerNames;
}
var framesData = session.framesData;
if (framesData) {
delete session.framesData;
++wsLen;
util.parseFrames(session.res, framesData, function (frames) {
session.frames = frames;
if (--wsLen === 0) {
wsLen = -1;
cb(result);
}
});
}
});
if (wsLen === 0) {
wsLen = -1;
cb(result);
}
}
};
zipEntries.forEach(function (entry) {
if (entry.isDirectory) {
return;
}
var entryName = entry.entryName;
var filename = entryName.substring(4);
var dashIndex = filename.lastIndexOf('_');
if (dashIndex <= 0) {
return;
}
var index = filename.substring(0, dashIndex);
filename = filename.substring(dashIndex + 1).toLowerCase();
if (
['c.txt', 'm.xml', 's.txt', 'w.txt', 'whistle.json'].indexOf(filename) ===
-1
) {
return;
}
var content = zip.readFile(entryName);
if (!content) {
return;
}
var result = (sessions[index] = sessions[index] || {});
++count;
if (filename === 'c.txt') {
util.getReq(content, function (req) {
setImmediate(function () {
result.req = req;
--count;
execCallback();
});
});
} else if (filename === 'm.xml') {
parseString(content, function (err, meta) {
setImmediate(function () {
result.meta = meta;
--count;
execCallback();
});
});
} else if (filename === 'whistle.json') {
setImmediate(function () {
--count;
var data = util.parseJSON(String(content));
if (data) {
if (typeof data.realUrl === 'string') {
result.realUrl = data.realUrl;
}
if (common.isUrl(data.url)) {
result._url = data.url;
}
if (data.rules) {
result.rules = data.rules;
}
if (data.frames) {
result.frames = data.frames;
}
if (data.fwdHost) {
result.fwdHost = data.fwdHost;
}
if (data.useHttp) {
result.useHttp = true;
}
if (data.sniPlugin) {
result.sniPlugin = data.sniPlugin;
}
if (data.httpsTime > 0) {
result.httpsTime = data.httpsTime;
}
if (data.useH2) {
result.useH2 = true;
}
if (data.mark) {
result.mark = true;
}
if (data.captureError) {
result.captureError = true;
}
if (data.reqError) {
result.reqError = true;
}
if (data.resError) {
result.resError = true;
}
var times = data.times;
if (times) {
result.startTime = times.startTime;
result.dnsTime = times.dnsTime;
result.requestTime = times.requestTime;
result.responseTime = times.responseTime;
result.endTime = times.endTime;
}
result.trailers = data.trailers;
result.rawTrailerNames = data.rawTrailerNames;
if (data.version) {
result.version = data.version;
}
if (data.nodeVersion) {
result.nodeVersion = data.nodeVersion;
}
if (data.customData) {
result.customData = data.customData;
}
}
execCallback();
});
} else if (filename === 'w.txt') {
setImmediate(function () {
result.framesData = content;
--count;
execCallback();
});
} else {
util.getRes(content, function (res) {
setImmediate(function () {
result.res = res;
--count;
execCallback();
});
});
}
});
execCallback();
};