UNPKG

proxy-chain

Version:

Node.js implementation of a proxy server (think Squid) with support for SSL, authentication, upstream proxy chaining, and protocol tunneling.

91 lines 3.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.chainSocks = void 0; const node_url_1 = require("node:url"); const socks_1 = require("socks"); const statuses_1 = require("./statuses"); const count_target_bytes_1 = require("./utils/count_target_bytes"); const socksProtocolToVersionNumber = (protocol) => { switch (protocol) { case 'socks4:': case 'socks4a:': return 4; default: return 5; } }; /** * Client -> Apify (CONNECT) -> Upstream (SOCKS) -> Web * Client <- Apify (CONNECT) <- Upstream (SOCKS) <- Web */ const chainSocks = async ({ request, sourceSocket, head, server, handlerOpts, }) => { const { proxyChainId } = sourceSocket; const { hostname, port, username, password } = handlerOpts.upstreamProxyUrlParsed; const proxy = { host: hostname, port: Number(port), type: socksProtocolToVersionNumber(handlerOpts.upstreamProxyUrlParsed.protocol), userId: decodeURIComponent(username), password: decodeURIComponent(password), }; if (head && head.length > 0) { // HTTP/1.1 has no defined semantics when sending payload along with CONNECT and servers can reject the request. // HTTP/2 only says that subsequent DATA frames must be transferred after HEADERS has been sent. // HTTP/3 says that all DATA frames should be transferred (implies pre-HEADERS data). // // Let's go with the HTTP/3 behavior. // There are also clients that send payload along with CONNECT to save milliseconds apparently. // Beware of upstream proxy servers that send out valid CONNECT responses with diagnostic data such as IPs! sourceSocket.unshift(head); } const url = new node_url_1.URL(`connect://${request.url}`); const destination = { port: Number(url.port), host: url.hostname, }; let targetSocket; try { const client = await socks_1.SocksClient.createConnection({ proxy, command: 'connect', destination, }); targetSocket = client.socket; sourceSocket.write(`HTTP/1.1 200 Connection Established\r\n\r\n`); } catch (error) { const socksError = error; server.log(proxyChainId, `Failed to connect to upstream SOCKS proxy ${socksError.stack}`); sourceSocket.end((0, statuses_1.createCustomStatusHttpResponse)((0, statuses_1.socksErrorMessageToStatusCode)(socksError.message), socksError.message)); return; } (0, count_target_bytes_1.countTargetBytes)(sourceSocket, targetSocket); sourceSocket.pipe(targetSocket); targetSocket.pipe(sourceSocket); // Once target socket closes forcibly, the source socket gets paused. // We need to enable flowing, otherwise the socket would remain open indefinitely. // Nothing would consume the data, we just want to close the socket. targetSocket.on('close', () => { sourceSocket.resume(); if (sourceSocket.writable) { sourceSocket.end(); } }); // Same here. sourceSocket.on('close', () => { targetSocket.resume(); if (targetSocket.writable) { targetSocket.end(); } }); targetSocket.on('error', (error) => { server.log(proxyChainId, `Chain SOCKS Destination Socket Error: ${error.stack}`); sourceSocket.destroy(); }); sourceSocket.on('error', (error) => { server.log(proxyChainId, `Chain SOCKS Source Socket Error: ${error.stack}`); targetSocket.destroy(); }); }; exports.chainSocks = chainSocks; //# sourceMappingURL=chain_socks.js.map