From d53a7ed956d29a0f3ab5c900a828f5198de20ead Mon Sep 17 00:00:00 2001 From: Greg Leppert Date: Sat, 18 Feb 2023 22:16:28 -0500 Subject: [PATCH 1/5] Initial request and response parsing working Still need to account for chunked responses. Also need to understand why `setResponse` is only working when the `request` is instantiated in `setRequestSocket`, necessitating the ugly need for `this._setResponseResolver`. It seems like this should work when instantiated in `setResponse` where it can call the promise's `resolve()` directly, but it does not. --- core/Session.js | 57 +++++++++++++------------------ core/onConnectedClientHandling.js | 6 ++-- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/core/Session.js b/core/Session.js index 8ee4fab..3f8cf04 100644 --- a/core/Session.js +++ b/core/Session.js @@ -1,8 +1,9 @@ const tls = require('tls'); -const {EVENTS, DEFAULT_KEYS, STRINGS} = require('../lib/constants'); const parseDataToObject = require('../lib/parseDataToObject'); +const net = require('net'); +const {EVENTS, DEFAULT_KEYS} = require('../lib/constants'); +const {request, createServer} = require('http'); const {CLOSE, DATA, ERROR} = EVENTS; -const {BLANK, CRLF, LF, SEPARATOR} = STRINGS; /** * Write data of given socket @@ -40,8 +41,6 @@ class Session extends Object { this.user = null; this.authenticated = false; this.isHttps = false; - this._request = {}; - this._response = {}; } /** @@ -84,6 +83,23 @@ class Session extends Object { return this.authenticated; } + setRequest(data) { + return new Promise((resolve) => { + createServer() + .on('connect', resolve) + .on('request', resolve) + .emit('connection', this._srcMirror) + this._srcMirror.emit('data', data) + }).then(request => this.request = request) + } + + setResponse(data) { + return new Promise((resolve) => { + this._setResponseResolver = resolve + this._dstMirror.emit('data', data) + }).then(response => this.response = response) + } + /** * Set the socket that will receive response * @param {net.Socket} socket @@ -91,6 +107,7 @@ class Session extends Object { */ setResponseSocket(socket) { this._src = socket; + this._srcMirror = new net.Socket() return this; } @@ -101,6 +118,8 @@ class Session extends Object { */ setRequestSocket(socket) { this._dst = socket; + this._dstMirror = new net.Socket() + request({ createConnection: () => this._dstMirror }, response => { this._setResponseResolver(response) }); return this; } @@ -112,36 +131,6 @@ class Session extends Object { return this._id; } - set request(buffer) { - const parsedRequest = parseDataToObject(buffer); - if (parsedRequest.headers) { - this._request = parsedRequest; - } - return this._request; - } - - get request() { - return this._request; - } - - set response(buffer) { - // const indexOfChunkEnd = buffer.toString().indexOf(LF + CRLF); - // this._response.complete = indexOfChunkEnd; //TODO find a way to recognize last chunk - - const parsedResponse = parseDataToObject(buffer, true, !!this._response.body); - if (this._response.body - && parsedResponse.body) { - parsedResponse.body = this._response.body + parsedResponse.body; - } - this._response = {...this._response, ...parsedResponse}; - - return this._response; - } - - get response() { - return this._response; - } - /** * @param {string} username * @returns {Session} diff --git a/core/onConnectedClientHandling.js b/core/onConnectedClientHandling.js index d218d00..971242a 100644 --- a/core/onConnectedClientHandling.js +++ b/core/onConnectedClientHandling.js @@ -84,7 +84,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect async function onDataFromUpstream(dataFromUpStream) { const thisTunnel = bridgedConnections[remoteID]; if (thisTunnel) { - thisTunnel.response = dataFromUpStream; + await thisTunnel.setResponse(dataFromUpStream); const responseData = isFunction(injectResponse) ? await injectResponse(dataFromUpStream, thisTunnel) @@ -102,7 +102,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect async function onDirectConnectionOpen(srcData) { const thisTunnel = bridgedConnections[remoteID]; if (thisTunnel) { - thisTunnel.request = srcData; + await thisTunnel.setRequest(srcData); const requestData = isFunction(injectData) ? await injectData(srcData, thisTunnel) @@ -249,7 +249,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect */ async function onDataFromClient(data) { const thisTunnel = bridgedConnections[remoteID]; - thisTunnel.request = data; + await thisTunnel.setRequest(data) const dataString = data.toString(); From a37dba744d97663d7fa5f6746e5eae88c1b8d21f Mon Sep 17 00:00:00 2001 From: Greg Leppert Date: Sun, 19 Feb 2023 07:15:23 -0500 Subject: [PATCH 2/5] Restructure parsing setup --- core/Session.js | 25 +++++++++++++------------ core/onConnectedClientHandling.js | 8 ++++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/core/Session.js b/core/Session.js index 3f8cf04..36945f3 100644 --- a/core/Session.js +++ b/core/Session.js @@ -83,20 +83,19 @@ class Session extends Object { return this.authenticated; } - setRequest(data) { + parseAndSetRequest(data) { return new Promise((resolve) => { - createServer() - .on('connect', resolve) - .on('request', resolve) - .emit('connection', this._srcMirror) - this._srcMirror.emit('data', data) + this._requestParsingServer + .once('connect', resolve) + .once('request', resolve) + this._requestParsingSocket.emit('data', data) }).then(request => this.request = request) } - setResponse(data) { + parseAndSetResponse(data) { return new Promise((resolve) => { - this._setResponseResolver = resolve - this._dstMirror.emit('data', data) + this._responseParsingServer.once('response', resolve) + this._responseParsingSocket.emit('data', data) }).then(response => this.response = response) } @@ -107,7 +106,9 @@ class Session extends Object { */ setResponseSocket(socket) { this._src = socket; - this._srcMirror = new net.Socket() + this._requestParsingSocket = new net.Socket() + this._requestParsingServer = createServer() + this._requestParsingServer.emit('connection', this._requestParsingSocket) return this; } @@ -118,8 +119,8 @@ class Session extends Object { */ setRequestSocket(socket) { this._dst = socket; - this._dstMirror = new net.Socket() - request({ createConnection: () => this._dstMirror }, response => { this._setResponseResolver(response) }); + this._responseParsingSocket = new net.Socket() + this._responseParsingServer = request({ createConnection: () => this._responseParsingSocket }); return this; } diff --git a/core/onConnectedClientHandling.js b/core/onConnectedClientHandling.js index 971242a..873fe6d 100644 --- a/core/onConnectedClientHandling.js +++ b/core/onConnectedClientHandling.js @@ -84,7 +84,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect async function onDataFromUpstream(dataFromUpStream) { const thisTunnel = bridgedConnections[remoteID]; if (thisTunnel) { - await thisTunnel.setResponse(dataFromUpStream); + await thisTunnel.parseAndSetResponse(dataFromUpStream); const responseData = isFunction(injectResponse) ? await injectResponse(dataFromUpStream, thisTunnel) @@ -102,7 +102,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect async function onDirectConnectionOpen(srcData) { const thisTunnel = bridgedConnections[remoteID]; if (thisTunnel) { - await thisTunnel.setRequest(srcData); + await thisTunnel.parseAndSetRequest(srcData); const requestData = isFunction(injectData) ? await injectData(srcData, thisTunnel) @@ -249,7 +249,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect */ async function onDataFromClient(data) { const thisTunnel = bridgedConnections[remoteID]; - await thisTunnel.setRequest(data) + await thisTunnel.parseAndSetRequest(data); const dataString = data.toString(); @@ -306,4 +306,4 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect .on(CLOSE, onClose) .on(EXIT, onClose) ); -}; \ No newline at end of file +}; From 6ee4f0f0f697308f4e72872a8aa50c166fd8cea0 Mon Sep 17 00:00:00 2001 From: Greg Leppert Date: Sun, 19 Feb 2023 15:23:55 -0500 Subject: [PATCH 3/5] Trying out async getters --- core/Session.js | 54 ++++++++++++++++++++----------- core/onConnectedClientHandling.js | 14 +++----- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/core/Session.js b/core/Session.js index 36945f3..4fafbf8 100644 --- a/core/Session.js +++ b/core/Session.js @@ -1,5 +1,4 @@ const tls = require('tls'); -const parseDataToObject = require('../lib/parseDataToObject'); const net = require('net'); const {EVENTS, DEFAULT_KEYS} = require('../lib/constants'); const {request, createServer} = require('http'); @@ -83,20 +82,24 @@ class Session extends Object { return this.authenticated; } - parseAndSetRequest(data) { - return new Promise((resolve) => { - this._requestParsingServer - .once('connect', resolve) - .once('request', resolve) - this._requestParsingSocket.emit('data', data) - }).then(request => this.request = request) + _requestPromise = new Promise(resolve => this._requestPromiseResolve = resolve) + + get request() { + return this._requestPromise + } + + set request(val) { + this._requestPromiseResolve(val) + } + + _responsePromise = new Promise(resolve => this._responsePromiseResolve = resolve) + + get response() { + return this._responsePromise } - parseAndSetResponse(data) { - return new Promise((resolve) => { - this._responseParsingServer.once('response', resolve) - this._responseParsingSocket.emit('data', data) - }).then(response => this.response = response) + set response(val) { + this._responsePromiseResolve(val) } /** @@ -106,9 +109,17 @@ class Session extends Object { */ setResponseSocket(socket) { this._src = socket; - this._requestParsingSocket = new net.Socket() - this._requestParsingServer = createServer() - this._requestParsingServer.emit('connection', this._requestParsingSocket) + const mirror = new net.Socket() + this._src.prependListener('data', data => { + if(!this.request || this.request.complete) { + this._requestPromise = new Promise(resolve => this._requestPromiseResolve = resolve) + } + mirror.emit('data', data) + }) + createServer() + .on('connect', request => this.request = request) + .on('request', request => this.request = request) + .emit('connection', mirror) return this; } @@ -119,8 +130,15 @@ class Session extends Object { */ setRequestSocket(socket) { this._dst = socket; - this._responseParsingSocket = new net.Socket() - this._responseParsingServer = request({ createConnection: () => this._responseParsingSocket }); + const mirror = new net.Socket() + this._dst.prependListener('data', data => { + if(!this.response || this.response.complete){ + this._responsePromise = new Promise(resolve => this._responsePromiseResolve = resolve) + } + mirror.emit('data', data) + }) + request({ createConnection: () => mirror }) + .on('response', response => this.response = response); return this; } diff --git a/core/onConnectedClientHandling.js b/core/onConnectedClientHandling.js index 873fe6d..7849c40 100644 --- a/core/onConnectedClientHandling.js +++ b/core/onConnectedClientHandling.js @@ -84,8 +84,6 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect async function onDataFromUpstream(dataFromUpStream) { const thisTunnel = bridgedConnections[remoteID]; if (thisTunnel) { - await thisTunnel.parseAndSetResponse(dataFromUpStream); - const responseData = isFunction(injectResponse) ? await injectResponse(dataFromUpStream, thisTunnel) : dataFromUpStream; @@ -102,8 +100,6 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect async function onDirectConnectionOpen(srcData) { const thisTunnel = bridgedConnections[remoteID]; if (thisTunnel) { - await thisTunnel.parseAndSetRequest(srcData); - const requestData = isFunction(injectData) ? await injectData(srcData, thisTunnel) : srcData; @@ -161,13 +157,13 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect /** * @param {Error} connectionError */ - function onTunnelHTTPConnectionOpen(connectionError) { + async function onTunnelHTTPConnectionOpen(connectionError) { if (connectionError) { return onClose(connectionError); } if (connectionOpt.credentials) { - const headers = thisTunnel.request.headers; + const { headers } = await thisTunnel.request; const basedCredentials = Buffer.from(connectionOpt.credentials) .toString('base64'); //converting to base64 headers[PROXY_AUTH.toLowerCase()] = PROXY_AUTH_BASIC + BLANK + basedCredentials; @@ -189,7 +185,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect } if (connectionOpt.upstreamed) { if (connectionOpt.credentials) { - const headers = thisTunnel.request.headers; + const { headers } = await thisTunnel.request; const basedCredentials = Buffer.from(connectionOpt.credentials).toString('base64'); //converting to base64 headers[PROXY_AUTH.toLowerCase()] = PROXY_AUTH_BASIC + BLANK + basedCredentials; const newData = rebuildHeaders(headers, data); @@ -249,13 +245,11 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect */ async function onDataFromClient(data) { const thisTunnel = bridgedConnections[remoteID]; - await thisTunnel.parseAndSetRequest(data); - const dataString = data.toString(); try { if (dataString && dataString.length > 0) { - const headers = thisTunnel.request.headers; + const { headers } = await thisTunnel.request; const split = dataString.split(CRLF); if (isFunction(auth) From f7afa00918393d0e2a17e4305b36bf5ba902018f Mon Sep 17 00:00:00 2001 From: Greg Leppert Date: Wed, 22 Feb 2023 12:11:49 +0100 Subject: [PATCH 4/5] Remove old parseDataToObject lib --- lib/parseDataToObject.js | 59 ---------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 lib/parseDataToObject.js diff --git a/lib/parseDataToObject.js b/lib/parseDataToObject.js deleted file mode 100644 index 45cba95..0000000 --- a/lib/parseDataToObject.js +++ /dev/null @@ -1,59 +0,0 @@ -const {STRINGS} = require('./constants'); -const {BLANK, CRLF, SEPARATOR} = STRINGS; - -/** - * - * @param data - * @param isResponse - Is a ResponseMessage. - * @param chunked - Is response chunked. - * @returns {Object} HTTP-Message - {method,path,version,headers:{},body} - */ -module.exports = function parseDataToObject(data, isResponse = false, chunked = false) { - //TODO make secure - const dataString = data.toString(); - const splitAt = dataString.indexOf(CRLF + CRLF); - const infoObject = {}; - - if (!chunked) { - const [headers, body] = [dataString.slice(0, splitAt), dataString.slice(splitAt + 1)]; - const headerRows = headers.split(CRLF, 50); - - for (let i = 0; i < headerRows.length; i++) { - const headerRow = headerRows[i]; - if (i === 0) { //first row contain method, path and type - const firstSplitIndex = headerRow.indexOf(BLANK); - const [method, pathWithVersion] = [headerRow.slice(0, firstSplitIndex), headerRow.slice(firstSplitIndex + 1)]; - const secondSplitIndex = pathWithVersion.indexOf(BLANK); - const [path, version] = [pathWithVersion.slice(0, secondSplitIndex), pathWithVersion.slice(secondSplitIndex + 1)]; - if (isResponse) { - infoObject.version = method; - infoObject.statusCode = path; - infoObject.statusText = version; - } - else { - infoObject.method = method; - infoObject.path = path; - infoObject.version = version; - } - } - else { - infoObject.headers = infoObject.headers || {}; - const splitIndexRow = headerRow.indexOf(SEPARATOR); - const [attribute, value] = [headerRow.slice(0, splitIndexRow), headerRow.slice(splitIndexRow + 1)]; - if (attribute && value) { - const lowerAttribute = attribute.trim().toLowerCase(); - infoObject.headers[lowerAttribute] = value.trim(); - } - } - } - if (body) { - infoObject.body = body; - } - } - else { - // data is only the chunked body - infoObject.body = dataString; - } - - return infoObject; -}; From 7992474578143c5ac82091b16c622cb1c20d0a0b Mon Sep 17 00:00:00 2001 From: Greg Leppert Date: Sat, 25 Feb 2023 23:53:59 +0100 Subject: [PATCH 5/5] Only parse req/resp if not HTTPS or is CONNECT --- core/Session.js | 35 +++++++++++++++++---------- core/onConnectedClientHandling.js | 39 ++++++++++++++----------------- lib/getFirstHeaderRow.js | 10 ++++++++ lib/isRequestOfMethodType.js | 10 ++++++++ 4 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 lib/getFirstHeaderRow.js create mode 100644 lib/isRequestOfMethodType.js diff --git a/core/Session.js b/core/Session.js index 4fafbf8..7e07513 100644 --- a/core/Session.js +++ b/core/Session.js @@ -1,8 +1,11 @@ +const isRequestOfMethodType = require('../lib/isRequestOfMethodType'); + const tls = require('tls'); const net = require('net'); -const {EVENTS, DEFAULT_KEYS} = require('../lib/constants'); const {request, createServer} = require('http'); +const {EVENTS, DEFAULT_KEYS, HTTP_METHODS} = require('../lib/constants'); const {CLOSE, DATA, ERROR} = EVENTS; +const {CONNECT} = HTTP_METHODS; /** * Write data of given socket @@ -111,10 +114,14 @@ class Session extends Object { this._src = socket; const mirror = new net.Socket() this._src.prependListener('data', data => { - if(!this.request || this.request.complete) { - this._requestPromise = new Promise(resolve => this._requestPromiseResolve = resolve) + if (!this.request || this.request.complete) { + this._requestPromise = new Promise(resolve => this._requestPromiseResolve = resolve) + } + if (!this.isHttps || isRequestOfMethodType(CONNECT, data)) { + mirror.emit('data', data) + } else { + this._requestPromiseResolve() } - mirror.emit('data', data) }) createServer() .on('connect', request => this.request = request) @@ -131,14 +138,18 @@ class Session extends Object { setRequestSocket(socket) { this._dst = socket; const mirror = new net.Socket() - this._dst.prependListener('data', data => { - if(!this.response || this.response.complete){ - this._responsePromise = new Promise(resolve => this._responsePromiseResolve = resolve) - } - mirror.emit('data', data) - }) - request({ createConnection: () => mirror }) - .on('response', response => this.response = response); + if (!this.isHttps) { + this._dst.prependListener('data', data => { + if (!this.response || this.response.complete) { + this._responsePromise = new Promise(resolve => this._responsePromiseResolve = resolve) + } + mirror.emit('data', data); + }) + request({ createConnection: () => mirror }) + .on('response', response => this.response = response); + } else { + this._responsePromiseResolve() + } return this; } diff --git a/core/onConnectedClientHandling.js b/core/onConnectedClientHandling.js index 7849c40..109c9ee 100644 --- a/core/onConnectedClientHandling.js +++ b/core/onConnectedClientHandling.js @@ -5,6 +5,8 @@ const getConnectionOptions = require('./getConnectionOptions'); const rebuildHeaders = require('../lib/rebuildHeaders'); const isFunction = require('../lib/isFunction'); const usingUpstreamToProxy = require('../lib/usingUpstreamToProxy'); +const getFirstHeaderRow = require('../lib/getFirstHeaderRow'); +const isRequestOfMethodType = require('../lib/isRequestOfMethodType'); const { EVENTS, @@ -13,7 +15,7 @@ const { } = require('../lib/constants'); const {CLOSE, DATA, ERROR, EXIT} = EVENTS; -const {ETIMEDOUT, ENOTFOUND, EPIPE, EPROTO} = ERROR_CODES; +const {ETIMEDOUT, ENOTFOUND, EPIPE} = ERROR_CODES; const {CONNECT} = HTTP_METHODS; const {AUTH_REQUIRED, OK, NOT_OK, TIMED_OUT, NOT_FOUND} = HTTP_RESPONSES; const {BLANK, CRLF, EMPTY, SEPARATOR, PROXY_AUTH, PROXY_AUTH_BASIC} = STRINGS; @@ -129,9 +131,9 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect * @param {boolean} isConnectMethod - false as default. * @returns Promise{boolean|{host: string, port: number, protocol: string, credentials: string, upstreamed: boolean}} */ - async function prepareTunnel(data, firstHeaderRow, isConnectMethod = false) { + async function prepareTunnel(data, isConnectMethod = false) { const thisTunnel = bridgedConnections[remoteID]; - const upstreamHost = firstHeaderRow.split(BLANK)[1]; + const upstreamHost = getFirstHeaderRow(data).toString().split(BLANK)[1]; const initOpt = getConnectionOptions(false, upstreamHost); thisTunnel.setTunnelOpt(initOpt); //settings opt before callback @@ -220,19 +222,17 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect } /** - * @param {Array} split * @param {buffer} data */ - function handleProxyTunnel(split, data) { - const firstHeaderRow = split[0]; + function handleProxyTunnel(data) { const thisTunnel = bridgedConnections[remoteID]; + const isConnectMethod = isRequestOfMethodType(CONNECT, data); - if (~firstHeaderRow.indexOf(CONNECT)) { //managing HTTP-Tunnel(upstream) & HTTPs - return prepareTunnel(data, firstHeaderRow, true); + if (isConnectMethod) { //managing HTTP-Tunnel(upstream) & HTTPs + return prepareTunnel(data, true); } - else if (firstHeaderRow.indexOf(CONNECT) === -1 - && !thisTunnel._dst) { // managing http - return prepareTunnel(data, firstHeaderRow); + else if (!isConnectMethod && !thisTunnel._dst) { // managing http + return prepareTunnel(data); } else if (thisTunnel && thisTunnel._dst) { return onDirectConnectionOpen(data); @@ -244,16 +244,13 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect * @returns {Promise} */ async function onDataFromClient(data) { - const thisTunnel = bridgedConnections[remoteID]; - const dataString = data.toString(); - try { - if (dataString && dataString.length > 0) { - const { headers } = await thisTunnel.request; - const split = dataString.split(CRLF); - - if (isFunction(auth) + if (data.length > 0) { + const thisTunnel = bridgedConnections[remoteID]; + if (isRequestOfMethodType(CONNECT, data) + && isFunction(auth) && !thisTunnel.isAuthenticated()) { + const { headers } = await thisTunnel.request; const proxyAuth = headers[PROXY_AUTH.toLowerCase()]; if (proxyAuth) { const credentials = proxyAuth @@ -270,7 +267,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect if (isLogged) { thisTunnel.setUserAuthentication(username); - return handleProxyTunnel(split, data); + return handleProxyTunnel(data); } else { //return auth-error and close all @@ -283,7 +280,7 @@ module.exports = function onConnectedClientHandling(clientSocket, bridgedConnect } } else { - return handleProxyTunnel(split, data); + return handleProxyTunnel(data); } } } diff --git a/lib/getFirstHeaderRow.js b/lib/getFirstHeaderRow.js new file mode 100644 index 0000000..16aa026 --- /dev/null +++ b/lib/getFirstHeaderRow.js @@ -0,0 +1,10 @@ +const {STRINGS} = require('./constants'); +const {CRLF} = STRINGS; + +/** + * @param {buffer} data + * @returns {buffer} + */ +module.exports = function getFirstHeaderRow(buffer) { + return buffer.subarray(0, buffer.indexOf(CRLF)); +}; diff --git a/lib/isRequestOfMethodType.js b/lib/isRequestOfMethodType.js new file mode 100644 index 0000000..0d8ec84 --- /dev/null +++ b/lib/isRequestOfMethodType.js @@ -0,0 +1,10 @@ +const getFirstHeaderRow = require('./getFirstHeaderRow'); + +/** + * @param {string} method + * @param {buffer} data + * @returns {boolean} + */ +module.exports = function isRequestOfMethodType(method, buffer) { + return getFirstHeaderRow(buffer).indexOf(method) > -1; +};