UNPKG

allproxy

Version:

AllProxy: MITM HTTP Debugging Tool.

315 lines 15.5 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 }); const url_1 = __importDefault(require("url")); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const Global_1 = __importDefault(require("./Global")); const ProxyConfig_1 = __importDefault(require("../../common/ProxyConfig")); const HttpMessage_1 = __importDefault(require("./HttpMessage")); const querystring_1 = __importDefault(require("querystring")); const Listen_1 = __importDefault(require("./Listen")); const GenerateCertKey_1 = __importDefault(require("./GenerateCertKey")); const Zlib_1 = require("./Zlib"); const intercept_1 = __importDefault(require("../../intercept")); const AllProxyApp_1 = __importDefault(require("./AllProxyApp")); const ConsoleLog_1 = __importDefault(require("./ConsoleLog")); // Hop-by-hop headers. These are removed when sent to the backend. // As of RFC 7230, hop-by-hop headers are required to appear in the // Connection header field. These are the headers defined by the // obsoleted RFC 2616 (section 13.5.1) and are used for backward // compatibility. var hopHeaders = [ "connection", "proxy-connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", ]; class HttpOrHttpsServer { constructor(proxyType, protocol, reverseProxyHostname = null, reverseProxyPort = null) { this.proxyType = 'forward'; this.reverseProxyHostname = null; this.reverseProxyPort = null; this.ephemeralPort = 0; this.server = null; this.resolvePromise = () => 1; this.promise = new Promise((resolve) => { this.resolvePromise = resolve; }); this.proxyType = proxyType; this.protocol = protocol; if (reverseProxyHostname) { this.reverseProxyHostname = reverseProxyHostname; } if (reverseProxyPort) { this.reverseProxyPort = reverseProxyPort; } } destructor() { this.server && this.server.close(); } start(listenPort) { return __awaiter(this, void 0, void 0, function* () { if (this.protocol === 'https:') { const certKey = yield (0, GenerateCertKey_1.default)(this.reverseProxyHostname); this.server = https_1.default.createServer(certKey, this.onRequest.bind(this)); Global_1.default.socketIoManager.addHttpServer(this.server); } else { this.server = http_1.default.createServer(this.onRequest.bind(this)); this.server.keepAliveTimeout = 0; Global_1.default.socketIoManager.addHttpServer(this.server); } yield (0, Listen_1.default)('HttpOrHttpsServer', this.server, listenPort); // assign port number this.ephemeralPort = this.server.address().port; this.resolvePromise(0); }); } waitForServerToStart() { return __awaiter(this, void 0, void 0, function* () { yield this.promise; }); } getEphemeralPort() { return this.ephemeralPort; } onRequest(clientReq, clientRes) { return __awaiter(this, void 0, void 0, function* () { clientReq.on('error', function (error) { console.error('HttpOrHttpsServer clientReq error', JSON.stringify(error, null, 2)); }); // eslint-disable-next-line node/no-deprecated-api const reqUrl = url_1.default.parse(clientReq.url ? clientReq.url : ''); // Request is from AllProxy app? if ((0, AllProxyApp_1.default)(clientReq, clientRes, reqUrl)) { return; } // Proxy is blocked when a public hostname is specified if (Global_1.default.proxyIsBlocked) { //console.log('Discarding HTTP request from ' + clientReq.socket?.remoteAddress + ' ' + clientReq.url); return; } ConsoleLog_1.default.info('HttpOrHttpsServer onRequest', reqUrl.path); const clientHostName = yield Global_1.default.resolveIp(clientReq.socket.remoteAddress); const sequenceNumber = Global_1.default.nextSequenceNumber(); const remoteAddress = clientReq.socket.remoteAddress; let proxyConfig = Global_1.default.socketIoManager.findProxyConfigMatchingURL(this.protocol, clientHostName, reqUrl, this.proxyType); // Always proxy forward proxy requests if (proxyConfig === undefined) { // Forward proxy? if (reqUrl.protocol !== null) { proxyConfig = new ProxyConfig_1.default(); proxyConfig.path = reqUrl.pathname; proxyConfig.protocol = reqUrl.protocol; proxyConfig.hostname = reqUrl.hostname; proxyConfig.port = reqUrl.port === null ? reqUrl.protocol === 'http:' ? 80 : 443 : +reqUrl.port; } else if (this.protocol === 'https:' && this.reverseProxyHostname != null) { proxyConfig = new ProxyConfig_1.default(); proxyConfig.path = reqUrl.pathname; proxyConfig.protocol = this.protocol; proxyConfig.hostname = this.reverseProxyHostname; proxyConfig.port = this.reverseProxyPort; proxyConfig.isSecure = true; } } ConsoleLog_1.default.debug('HttpOrHttpsServer - ProxyConfig:', proxyConfig); // URLs for requests proxied from terminal (e.g., https_proxy=localhost:8888) do not include schema and hostname let urlWithHostname = clientReq.url; if (this.proxyType === 'forward' && urlWithHostname.startsWith('/')) { urlWithHostname = this.protocol + "//" + this.reverseProxyHostname + urlWithHostname; } const httpMessage = new HttpMessage_1.default(this.protocol, proxyConfig, sequenceNumber, remoteAddress, clientReq.method, urlWithHostname, clientReq.headers); if (proxyConfig === undefined) { const msg = 'No matching proxy configuration found for ' + reqUrl.pathname; clientRes.writeHead(404, msg); clientRes.end(); httpMessage.emitMessageToBrowser(msg); } else { const requestBodyPromise = getReqBody(clientReq); httpMessage.emitMessageToBrowser(''); // No request body received yet this.proxyRequest(reqUrl, clientReq, clientRes, httpMessage, proxyConfig, requestBodyPromise); } function getReqBody(clientReq) { return new Promise(resolve => { // eslint-disable-next-line no-unreachable let requestBody = ''; let rawData = ''; clientReq.on('data', function (chunk) { rawData += chunk; }); // eslint-disable-next-line no-unreachable clientReq.on('end', function () { return __awaiter(this, void 0, void 0, function* () { try { requestBody = JSON.parse(rawData); } catch (e) { const contentType = clientReq.headers['content-type']; if (contentType && contentType.indexOf('application/x-www-form-urlencoded') !== -1) { requestBody = querystring_1.default.parse(rawData); } else { requestBody = rawData; } } resolve(requestBody); }); }); }); } }); } proxyRequest(reqUrl, clientReq, clientRes, httpMessage, proxyConfig, requestBodyPromise) { return __awaiter(this, void 0, void 0, function* () { const headers = clientReq.headers; let { hostname, port } = reqUrl.protocol !== null ? reqUrl : proxyConfig; // Override hostname and port? if (this.reverseProxyHostname !== null) { hostname = this.reverseProxyHostname; port = this.reverseProxyPort; } if (!port) { port = this.protocol === 'https:' ? 443 : 80; } headers.host = hostname + ':' + port; const options = { protocol: this.protocol, hostname, port, path: clientReq.url, method: clientReq.method, headers }; ConsoleLog_1.default.debug('HttpOrHttpsServer proxy request options', options); let retryCount = 0; const MAX_RETRIES = 5; doProxy(this.protocol); function doProxy(protocol) { const proxyReq = protocol === 'https:' ? https_1.default.request(options, handleResponse) : http_1.default.request(options, handleResponse); proxyReq.on('error', function (error) { return __awaiter(this, void 0, void 0, function* () { proxyReq.destroy(); if (error.code === 'EAI_AGAIN' && retryCount++ < MAX_RETRIES) { ConsoleLog_1.default.info(`Retry ${retryCount} for ${options.hostname}`); setTimeout(doProxy, retryCount * 1000, protocol); } else { console.error('Proxy connect error', JSON.stringify(error, null, 2), 'config:', proxyConfig); const requestBody = yield requestBodyPromise; httpMessage.emitMessageToBrowser(requestBody, 503, {}, { error, 'allproxy-config': proxyConfig }); clientRes.writeHead(503); clientRes.write(JSON.stringify(error)); clientRes.end(); } }); }); clientReq.pipe(proxyReq, { end: true }); } function handleResponse(proxyRes) { return __awaiter(this, void 0, void 0, function* () { /** * Forward the response back to the client */ for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) { const key = proxyRes.rawHeaders[i]; if (hopHeaders.indexOf(key) !== -1) continue; const value = proxyRes.rawHeaders[i + 1]; clientRes.setHeader(key, value); } if ((clientReq.method === 'DELETE' || clientReq.method === 'PUT') && proxyRes.statusCode && proxyRes.statusCode < 400) { clientRes.removeHeader('Connection'); // Don't send keepalive } clientRes.writeHead(proxyRes.statusCode, proxyRes.statusMessage); const chunks = []; /** * Forward the response back to the client */ proxyRes.on('data', function (chunk) { chunks.push(chunk); }); proxyRes.on('end', () => __awaiter(this, void 0, void 0, function* () { const resBody = getResBody(proxyRes.headers, chunks); const requestBody = yield requestBodyPromise; let message = yield httpMessage.buildMessage(requestBody, proxyRes.statusCode, proxyRes.headers, resBody); if (isApplicationJson(proxyRes.headers)) { let modified = false; if (Global_1.default.socketIoManager.isBreakpointEnabled()) { message = yield Global_1.default.socketIoManager.handleBreakpoint(message); modified = message.modified; } if (typeof message.responseBody === 'object') { const newJson = (0, intercept_1.default)(clientReq, message.responseBody); if (newJson) { message.responseBody = newJson; modified = true; } } if (modified) { ConsoleLog_1.default.info('InterceptJsonResponse changed the JSON body'); let buffer = Buffer.from(JSON.stringify(message.responseBody)); buffer = (0, Zlib_1.compressResponse)(proxyRes.headers, buffer); chunks.splice(0, chunks.length); clientRes.write(buffer); } } // If the response was not modified above, write chunks hear for (const chunk of chunks) { clientRes.write(chunk); } clientRes.end(); httpMessage.emitMessageToBrowser2(message); })); }); } }); } } exports.default = HttpOrHttpsServer; function isApplicationJson(headers) { const ct = headers['content-type']; if (ct && ct.indexOf('application/json') !== -1) { return true; } return false; } function getResBody(headers, chunks) { if (chunks.length === 0) return ''; let resBuffer = Buffer.concat(chunks); resBuffer = (0, Zlib_1.decompressResponse)(headers, resBuffer); const resString = resBuffer.toString(); let resBody = resString; if (isApplicationJson(headers)) { try { resBody = JSON.parse(resString); // assume JSON } catch (e) { } } return resBody; } //# sourceMappingURL=HttpOrHttpsServer.js.map