UNPKG

allproxy

Version:

AllProxy: MITM HTTP Debugging Tool.

616 lines 27.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.socketIoManager = exports.BATCH_SIZE = void 0; const socket_io_1 = __importDefault(require("socket.io")); const TcpProxy_1 = __importDefault(require("./TcpProxy")); const LogProxy_1 = __importDefault(require("./LogProxy")); const fs_1 = __importDefault(require("fs")); const ProxyConfig_1 = require("../../common/ProxyConfig"); const net_1 = __importDefault(require("net")); const Ping_1 = __importDefault(require("./Ping")); const Resend_1 = __importDefault(require("./Resend")); const GrpcProxy_1 = __importDefault(require("./GrpcProxy")); const Paths_1 = __importDefault(require("./Paths")); const Global_1 = __importDefault(require("./Global")); const ConsoleLog_1 = __importDefault(require("./ConsoleLog")); const BrowserLauncher_1 = __importDefault(require("./BrowserLauncher")); const APFileSystem_1 = __importDefault(require("./APFileSystem")); const app_1 = require("../../app"); const child_process_1 = require("child_process"); const path_1 = __importDefault(require("path")); const FileLineMatcher_1 = __importDefault(require("./FileLineMatcher")); const { rgPath } = require('@vscode/ripgrep'); const jqPath = './node_modules/node-jq/bin/jq'; const USE_HTTP2 = true; const CONFIG_JSON = Paths_1.default.configJson(); const CACHE_SOCKET_ID = 'cache'; exports.BATCH_SIZE = 100; // windows size - maximum outstanding messages const MAX_OUT = 2; // two message batches let breakpointQueue = []; class SocketIoInfo { constructor(socket, configs) { this.socket = undefined; this.configs = []; this.breakpointEnabled = false; this.seqNum = 0; this.messagesOut = 0; this.queuedMessages = []; this.socket = socket; this.configs = configs; } } ; class SocketIoManager { constructor() { this.socketIoMap = new Map(); this.resolveQueue = []; this.activateConfig(this.getConfig()); exports.socketIoManager = this; } clientEndedSocket() { for (const socketId in this.socketIoMap) { this.closeAnyServersWithSocket(socketId); this.socketIoMap.delete(socketId); } } defaultConfig() { // proxy all http by default return [ { isSecure: false, protocol: 'browser:', path: '/', hostname: '', port: 0, recording: true, hostReachable: true, comment: '' } ]; } getConfig() { const configs = fs_1.default.existsSync(CONFIG_JSON) ? JSON.parse(fs_1.default.readFileSync(CONFIG_JSON).toString()).configs : this.defaultConfig(); let modified = false; for (const config of configs) { if (config.protocol === 'proxy:') { config.protocol = 'browser:'; modified = true; } } modified && this.saveConfig(configs); return configs; } updateHostReachable() { return new Promise((resolve) => { const configs = this.getConfig(); this.resolveQueue.push(resolve); if (this.resolveQueue.length > 1) return; let count = 0; const done = () => { if (++count === configs.length) { // const queueCount = this.resolveQueue.length let func; // eslint-disable-next-line no-cond-assign while (func = this.resolveQueue.pop()) { func(configs); } } }; configs.forEach(config => { if (config.protocol === 'browser:' || config.protocol === 'log:') { config.hostReachable = true; done(); } else { config.hostReachable = false; setTimeout(() => __awaiter(this, void 0, void 0, function* () { const pingSuccessful = yield Ping_1.default.host(config.hostname); if (!pingSuccessful) { done(); } else { const socket = net_1.default.connect(config.port, config.hostname, () => { config.hostReachable = true; socket.end(); done(); }); socket.on('error', (_err) => { done(); socket.end(); }); } })); } }); }); } saveConfig(proxyConfigs) { // Cache the config, to configure the proxy on the next start up prior // to receiving the config from the browser. fs_1.default.writeFileSync(CONFIG_JSON, JSON.stringify({ configs: proxyConfigs }, null, 2)); } addHttpServer(httpServer) { ConsoleLog_1.default.debug('SocketIoManager add Server'); const server = new socket_io_1.default.Server(httpServer); server.on('connection', (socket) => this._socketConnection(socket)); } _socketConnection(socket) { return __awaiter(this, void 0, void 0, function* () { ConsoleLog_1.default.debug('SocketIoManager on connection'); const config = this.getConfig(); socket.emit('port config', Global_1.default.portConfig); // send port config to browser socket.emit('proxy config', config); // send config to browser socket.on('ping', (pingReply) => { pingReply(); }); socket.on('ostype', (os, urlPath, ipInfo) => { if (ipInfo) { if (process.env.FILE_SYSTEM_LOG === '1') { console.log(urlPath); console.log(os); // { // ipAddress: '64.118.12.153', // continentCode: 'NA', // continentName: 'North America', // countryCode: 'US', // countryName: 'United States', // stateProvCode: 'MN', // stateProv: 'Minnesota', // city: 'Underwood' // } ipInfo.date = getDateNow(); ipInfo.os = os; ipInfo.app = urlPath; console.log(JSON.stringify(ipInfo)); socket.handshake.url = urlPath; socket.handshake.address = ipInfo.ipAddress; } } (0, app_1.setOsBinaries)(os); }); socket.on('get install type', (callback) => { var type = 'Electron'; let headless = process.env.HEADLESS; if (headless) { if (process.env.npm_command === 'start') { type = 'GitHub'; } else { type = 'NPM'; } } callback(type); }); socket.on('proxy config', (proxyConfigs) => { ConsoleLog_1.default.info(`${Paths_1.default.configJson()}:\n`, proxyConfigs); this.saveConfig(proxyConfigs); // Make sure all matching connection based servers are closed. for (const proxyConfig of proxyConfigs) { if (proxyConfig._server) { this.closeAnyServerWithPort(proxyConfig.port); } } this.activateConfig(proxyConfigs, socket); // this.updateHostReachable(); }); socket.on('resend', (forwardProxy, method, url, message, body) => { (0, Resend_1.default)(forwardProxy, method, url, message, body); }); socket.on('breakpoint', (enable) => { const socketIoInfo = this.socketIoMap.get(socket.id); if (socketIoInfo) { //ConsoleLog.info('breakpoint', enable); socketIoInfo.breakpointEnabled = enable; } }); socket.on('detect browsers', (callback) => { // Running in docker container if (Global_1.default.inDockerContainer) { callback([]); } else { BrowserLauncher_1.default.detect() .then((browsers) => { callback(browsers); }) .catch(e => { console.log('Error detecting browsers:', e); callback([]); }); } }); socket.on('launch browser', (browser) => { BrowserLauncher_1.default.launch(browser); }); socket.on('is file in downloads', (fileName, callback) => { callback(fs_1.default.existsSync(process.env.HOME + '/Downloads/' + fileName)); }); socket.on('read file', (fileName, operator, filters, maxLines, callback) => __awaiter(this, void 0, void 0, function* () { const filePath = "'" + process.env.HOME + "" + path_1.default.sep + 'Downloads' + path_1.default.sep + fileName + "'"; const rg = rgPath; let cmd = ''; if (filters.length === 0) { cmd = rg + ' -F -m ' + maxLines + ' "" ' + filePath; } else { if (operator === 'and') { for (let i = 0; i < filters.length; ++i) { const filter = `'${filters[i]}'`; if (i === filters.length - 1) { cmd += i === 0 ? rg + ' -F -m ' + maxLines + ' ' + filter + ' ' + filePath : ' | ' + rg + ' -F -m ' + maxLines + ' ' + filter; } else { cmd += i === 0 ? rg + ' -F ' + filter + ' ' + filePath : ' | ' + rg + ' -F ' + filter; } } } else { cmd += rg + ' -m ' + maxLines + " -e '" + filters.join('|') + "' " + filePath; //console.log(cmd); } } exports.socketIoManager.emitStatusToBrowser(socket, 'Executing ripgrep: ' + cmd); const result = yield ripgrep(cmd, filters, socket); const result2 = result.join(); callback(result2.toString().split('\n')); })); socket.on('sort file', (fileName, callback) => __awaiter(this, void 0, void 0, function* () { const downloads = process.env.HOME + "" + path_1.default.sep + 'Downloads'; const filePath = downloads + path_1.default.sep + fileName; const tempFilePath = downloads + path_1.default.sep + fileName + '-temp'; let cmd = ''; cmd = jqPath + ` -sc 'sort_by( .ts_millis )[]' '${filePath}' > '${tempFilePath}'`; exports.socketIoManager.emitStatusToBrowser(socket, 'Sorting: ' + cmd); yield execCommand(cmd, socket); fs_1.default.rmSync(filePath); fs_1.default.renameSync(tempFilePath, filePath); callback(); })); socket.on('json field exists', (fileName, jsonField, callback) => __awaiter(this, void 0, void 0, function* () { const downloads = process.env.HOME + "" + path_1.default.sep + 'Downloads'; const rg = rgPath; const filter = `'"${jsonField}":'`; let cmd = rg + ' -F -m 1 ' + filter + ' ' + downloads + path_1.default.sep + fileName; const data = yield ripgrep(cmd, [], socket, 10 * 1000); //console.log('json field exists?', jsonField, data.length > 0); callback(data.length > 0); })); socket.on('is sorted', (fileName, timeFieldName, callback) => { const matcher = new FileLineMatcher_1.default(socket, fileName); matcher.setTimeFilter(timeFieldName, new Date(), new Date()); callback(matcher.isSorted()); }); socket.on('file line matcher', (fileName, timeFieldName, startTime, endTime, operator, filters, maxLines, callback) => { //console.log('file line matcher', fileName, timeFieldName, startTime, endTime, operator, filters, maxLines); const matcher = new FileLineMatcher_1.default(socket, fileName); matcher.setTimeFilter(timeFieldName, new Date(startTime), new Date(endTime)); matcher.setFilters(filters); matcher.setOperator(operator); matcher.setMaxLines(maxLines); const lines = matcher.read(); callback(lines); }); socket.on('disconnect', () => { this.closeAnyServersWithSocket(socket.id); this.socketIoMap.delete(socket.id); }); socket.on('error', (e) => { console.error('error', e); this.closeAnyServersWithSocket(socket.id); this.socketIoMap.delete(socket.id); }); const apFileSystem = new APFileSystem_1.default(socket); yield apFileSystem.listen(); }); } activateConfig(proxyConfigs, socket) { return __awaiter(this, void 0, void 0, function* () { ConsoleLog_1.default.debug('SocketIoManager.activateConfig'); for (const proxyConfig of proxyConfigs) { if (proxyConfig.protocol === 'log:') { // eslint-disable-next-line no-new new LogProxy_1.default(proxyConfig); } else if (proxyConfig.protocol === 'grpc:' && USE_HTTP2) { GrpcProxy_1.default.reverseProxy(proxyConfig); } else if (proxyConfig.protocol !== 'http:' && proxyConfig.protocol !== 'https:' && proxyConfig.protocol !== 'browser:') { // eslint-disable-next-line no-new new TcpProxy_1.default(proxyConfig); } } this.socketIoMap.set(socket ? socket.id : CACHE_SOCKET_ID, new SocketIoInfo((socket || undefined), proxyConfigs)); if (socket !== undefined) { this.closeAnyServersWithSocket(CACHE_SOCKET_ID); this.socketIoMap.delete(CACHE_SOCKET_ID); } }); } // Close 'any:' protocol servers that are running for the browser owning the socket closeAnyServersWithSocket(socketId) { this.socketIoMap.forEach((socketInfo, key) => { if (socketId && key !== socketId) return; for (const proxyConfig of socketInfo.configs) { if (proxyConfig.protocol === 'log:') { LogProxy_1.default.destructor(proxyConfig); } if (proxyConfig.protocol === 'grpc:') { GrpcProxy_1.default.destructor(proxyConfig); } else if (proxyConfig._server) { TcpProxy_1.default.destructor(proxyConfig); } } }); } // Close 'any:' protocol servers the specified listening port closeAnyServerWithPort(port) { this.socketIoMap.forEach((socketInfo, _key) => { for (const proxyConfig of socketInfo.configs) { if (proxyConfig._server && proxyConfig.port === port) { if (proxyConfig.protocol === 'grpc:' && USE_HTTP2) { GrpcProxy_1.default.destructor(proxyConfig); } else { TcpProxy_1.default.destructor(proxyConfig); } } } }); } isMatch(needle, haystack) { if (needle.indexOf('.*') !== -1) { const match = haystack.match(needle); return match !== null && match.length > 0; } else { return haystack.startsWith(needle); } } /** * Find proxy config matching URL * @params protocol * @params clientHostName * @param {*} reqUrl * @param isForwardProxy * @returns ProxyConfig */ findProxyConfigMatchingURL(protocol, clientHostName, reqUrl, proxyType = 'reverse') { const reqUrlPath = reqUrl.pathname.replace(/\/\//g, '/'); const isForwardProxy = proxyType === 'forward'; let matchingProxyConfig; // Find matching proxy configuration this.socketIoMap.forEach((socketInfo, _key) => { for (const proxyConfig of socketInfo.configs) { if (proxyConfig.protocol !== protocol && proxyConfig.protocol !== 'browser:') continue; if ((this.isMatch(proxyConfig.path, reqUrlPath) || this.isMatch(proxyConfig.path, clientHostName + reqUrlPath)) && isForwardProxy === (proxyConfig.protocol === 'browser:')) { if (matchingProxyConfig === undefined || proxyConfig.path.length > matchingProxyConfig.path.length) { matchingProxyConfig = proxyConfig; } } } }); return matchingProxyConfig; } findGrpcProxyConfig(hostname, port) { let matchingProxyConfig; // Find matching proxy configuration this.socketIoMap.forEach((socketInfo, _key) => { for (const proxyConfig of socketInfo.configs) { if (proxyConfig.protocol === 'grpc:' && proxyConfig.hostname === hostname && proxyConfig.port === port) { ConsoleLog_1.default.debug(`findGrpcProxy(${hostname}, ${port}) return:`, proxyConfig); matchingProxyConfig = proxyConfig; } } }); return matchingProxyConfig; } /** * Emit message to browser. * @param {*} message * @param {*} proxyConfig */ emitMessageToBrowser(messageType, message, inProxyConfig) { let queueCount = 0; const isDynamic = inProxyConfig === undefined || inProxyConfig.comment === ProxyConfig_1.DYNAMICALLY_ADDED; const emittedSocketId = {}; message.type = messageType; const path = inProxyConfig ? inProxyConfig.path : ''; let emitted = false; this.socketIoMap.forEach((socketInfo, socketId) => { for (const proxyConfig of socketInfo.configs) { if (inProxyConfig === undefined || isDynamic || (proxyConfig.path === path && inProxyConfig.protocol === proxyConfig.protocol)) { if (proxyConfig.protocol === 'log:' && inProxyConfig !== proxyConfig) continue; if (emittedSocketId[socketId]) continue; if (!proxyConfig.recording) { if (proxyConfig.protocol !== 'log:') { ConsoleLog_1.default.info('Record is disabled for protocol ', proxyConfig.protocol); } continue; } message.proxyConfig = isDynamic ? inProxyConfig : proxyConfig; // Remove _server: net.Server if (message.proxyConfig && message.proxyConfig._server) { const pc = message.proxyConfig; const server = pc._server; delete pc._server; message.proxyConfig = Object.assign({}, pc); pc._server = server; } if (socketInfo.socket) { if (socketInfo.queuedMessages.length > 0) { socketInfo.queuedMessages.push(message); queueCount += socketInfo.queuedMessages.length; } else { queueCount += this.emitMessageWithFlowControl([message], socketInfo, socketId); } emittedSocketId[socketId] = true; emitted = true; } } } }); if (!emitted) { // console.error(message.sequenceNumber, 'no browser socket to emit to', message.url) } return queueCount; } emitMessageWithFlowControl(messages, socketInfo, socketId) { if (socketInfo.messagesOut >= MAX_OUT) { socketInfo.queuedMessages = socketInfo.queuedMessages.concat(messages); } else { if (socketInfo.socket) { ++socketInfo.seqNum; ++socketInfo.messagesOut; socketInfo.socket.emit('reqResJson', messages, socketInfo.queuedMessages.length, // callback: (_response) => { --socketInfo.messagesOut; ConsoleLog_1.default.info(`out=${socketInfo.messagesOut}`, `sent=${messages.length}`, `queued=${socketInfo.queuedMessages.length}`, `(${_response})`); const count = Math.min(exports.BATCH_SIZE, socketInfo.queuedMessages.length); if (count > 0) { this.emitMessageWithFlowControl(socketInfo.queuedMessages.splice(0, count), socketInfo, socketId); } }); } } return socketInfo.queuedMessages.length; } isBreakpointEnabled() { let enabled = false; this.socketIoMap.forEach((socketInfo, _socketId) => { if (socketInfo.breakpointEnabled) { enabled = true; } }); return enabled; } handleBreakpoint(message) { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => { let socket; this.socketIoMap.forEach((socketInfo, _socketId) => { if (socketInfo.breakpointEnabled) { socket = socketInfo.socket; message.proxyConfig = socketInfo.configs[0]; } }); // Breakpoint found? if (socket) { breakpointQueue.push({ message, socket, resolve }); // Only one breakpoint inprogress? if (breakpointQueue.length === 1) { handleBreakpoints(); function handleBreakpoints() { const bpMessage = breakpointQueue[0]; bpMessage.socket.emit('breakpoint', bpMessage.message, (message2) => { bpMessage.resolve(message2); breakpointQueue.shift(); if (breakpointQueue.length > 0) { handleBreakpoints(); } }); } } } else { resolve(message); } }); }); } emitStatusToBrowser(socket, message) { socket.emit('status dialog', message); } emitErrorToBrowser(socket, message) { socket.emit('error dialog', message); } } exports.default = SocketIoManager; function execCommand(command, socket) { return __awaiter(this, void 0, void 0, function* () { //console.log(command); return yield new Promise(resolve => { const tokens = command.split(' '); const p = (0, child_process_1.spawn)(tokens[0], tokens.slice(1), { shell: true }); p.stderr.on('data', (data) => { console.error('spawn error', data.toString()); exports.socketIoManager.emitErrorToBrowser(socket, command + ': ' + data.toString()); resolve(); }); p.on('exit', () => { resolve(); }); }); }); } function ripgrep(command, filters, socket, timeout) { return __awaiter(this, void 0, void 0, function* () { //console.log(command); let result = []; let size = 0; let progressTime = Date.now(); return yield new Promise(resolve => { const tokens = command.split(' '); const p = (0, child_process_1.spawn)(tokens[0], tokens.slice(1), { shell: true }); p.stdout.on('data', (data) => { result.push(data.toString()); if (timeout === undefined) { size += data.length; if (Date.now() - progressTime >= 1000 * 1) { exports.socketIoManager.emitStatusToBrowser(socket, size + ' bytes match filters: ' + filters); progressTime = Date.now(); } } }); p.stderr.on('data', (data) => { console.error('ripgrep error', data.toString()); exports.socketIoManager.emitErrorToBrowser(socket, command + ': ' + data.toString()); resolve(result); }); p.on('exit', () => { resolve(result); }); }); }); } function getDateNow() { // return json.sequenceNumber; // used for testing only const date = new Date(); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); const seconds = date.getSeconds().toString().padStart(2, '0'); const msecs = (date.getMilliseconds() / 1000).toFixed(3).toString().replace('0.', ''); return `${date.toDateString()} ${hours}:${minutes}:${seconds}.${msecs}`; } //# sourceMappingURL=SocketIoManager.js.map