whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
163 lines (155 loc) • 4.49 kB
JavaScript
require('../util/patch');
var express = require('express');
var bodyParser = require('body-parser');
var multer = require('multer2');
var fs = require('fs');
var path = require('path');
var Buffer = require('safe-buffer').Buffer;
var util = require('./util');
var extractSaz = require('./extract-saz');
var generateSaz = require('./generate-saz');
var getServer = require('hagent').getServer;
var SESSIONS_FILE_RE = /\.(txt|json|saz)$/i;
var TEMP_FILES_PATH;
var LIMIT_SIZE = 1024 * 1024 * 128;
var MAX_TEMP_SIZE = 1024 * 1024 * 10;
var TEMP_FILE_RE = /^[\da-f]{64}$/;
var storage = multer.memoryStorage();
var upload = multer({
storage: storage,
fieldSize: LIMIT_SIZE
});
function statRetry(filepath, callback, retry) {
fs.stat(filepath, function(e, stat) {
if (!e || retry) {
return callback(e, stat);
}
statRetry(filepath, callback, true);
});
}
function writeRetry(filepath, data, callback, retry) {
fs.writeFile(filepath, data, function(e) {
if (!e || retry) {
return callback(e);
}
writeRetry(filepath, data, callback, true);
});
}
function writeFile(filepath, data, callback) {
statRetry(filepath, function(e, stat) {
if (!stat || !stat.isFile()) {
return writeRetry(filepath, data, callback);
}
callback();
});
}
function getContent(options) {
var value = options.value;
if (value && typeof value === 'string') {
return value;
}
var base64 = options.base64;
if (base64) {
try {
return Buffer.from(base64, 'base64');
} catch(e) {}
}
return '';
}
function sessionsHandler() {
var app = express();
app.use(function (req, res, next) {
req.on('error', abort);
res.on('error', abort);
function abort() {
res.destroy();
}
next();
});
app.get('/cgi-bin/sessions/get-temp-file', function(req, res) {
var filename = req.query.filename;
if (!TEMP_FILE_RE.test(filename)) {
return res.json({ ec: 0 });
}
filename = path.join(TEMP_FILES_PATH, filename);
statRetry(filename, function(err, stat) {
if (err ? err.code === 'ENOENT' : !stat.isFile()) {
return res.json({ ec: 0 });
}
if (err) {
return res.json({ ec: 2, em: err.message || 'Error' });
}
if (stat.size > MAX_TEMP_SIZE) {
return res.json({ ec: 0, em: 'File is too large to load.' });
}
fs.readFile(filename, function(e, data) {
if (e) {
return res.json({ ec: 2, em: e.message || 'Error' });
}
res.json({ ec: 0, value: data + '' });
});
});
});
app.use('/cgi-bin/sessions/create-temp-file',
bodyParser.json({ limit: 1024 * 1024 * 72 }),
function(req, res) {
var value = getContent(req.body);
var filename = util.getHexHash(value);
writeFile(path.join(TEMP_FILES_PATH, filename), value, function(e) {
if (e) {
return res.json({ ec: 2, em: e.message });
}
res.json({ ec: 0, filepath: 'temp/' + filename });
});
}
);
app.use(
'/cgi-bin/sessions/import',
upload.single('importSessions'),
function (req, res) {
var file = req.file;
var suffix;
if (file && SESSIONS_FILE_RE.test(file.originalname)) {
suffix = RegExp.$1.toLowerCase();
}
if (!suffix || !Buffer.isBuffer(file.buffer)) {
return res.json([]);
}
if (suffix !== 'saz') {
var sessions = util.parseJSON(file.buffer + '');
return res.json(Array.isArray(sessions) ? sessions : []);
}
try {
extractSaz(file.buffer, res.json.bind(res));
} catch (e) {
res.status(500).send(e.stack);
}
}
);
app.use(bodyParser.urlencoded({ extended: true, limit: LIMIT_SIZE }));
app.use(bodyParser.json());
app.use('/cgi-bin/sessions/export', function (req, res) {
var body = req.body;
var type = body.exportFileType;
var download = function(content) {
res.attachment(util.getFilename(type, body.exportFilename)).send(content || body.sessions);
};
if (type !== 'Fiddler') {
return download();
}
generateSaz(body, function(e, body) {
if (e) {
return res.status(500).send(e.stack);
}
download(body);
});
});
return app;
}
module.exports = function (options, callback) {
TEMP_FILES_PATH = options.TEMP_FILES_PATH;
getServer(function (server, port) {
server.on('request', sessionsHandler());
callback(null, { port: port });
});
};