allproxy
Version:
AllProxy: MITM HTTP Debugging Tool.
315 lines • 15.5 kB
JavaScript
"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