AikidoSec-safe-chain/packages/safe-chain/src/registryProxy/tunnelRequestHandler.js
2025-11-03 13:57:29 +01:00

147 lines
4.1 KiB
JavaScript

import * as net from "net";
import { ui } from "../environment/userInteraction.js";
/**
* @param {import("http").IncomingMessage} req
* @param {import("http").ServerResponse} clientSocket
* @param {Buffer} head
*
* @returns {void}
*/
export function tunnelRequest(req, clientSocket, head) {
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
if (httpsProxy) {
// If an HTTPS proxy is set, tunnel the request via the proxy
// This is the system proxy, not the safe-chain proxy
// The package manager will run via the safe-chain proxy
// The safe-chain proxy will then send the request to the system proxy
// Typical flow: package manager -> safe-chain proxy -> system proxy -> destination
// There are 2 processes involved in this:
// 1. Safe-chain process: has HTTPS_PROXY set to system proxy
// 2. Package manager process: has HTTPS_PROXY set to safe-chain proxy
tunnelRequestViaProxy(req, clientSocket, head, httpsProxy);
} else {
tunnelRequestToDestination(req, clientSocket, head);
}
}
/**
* @param {import("http").IncomingMessage} req
* @param {import("http").ServerResponse} clientSocket
* @param {Buffer} head
*
* @returns {void}
*/
function tunnelRequestToDestination(req, clientSocket, head) {
const { port, hostname } = new URL(`http://${req.url}`);
const serverSocket = net.connect(
Number.parseInt(port) || 443,
hostname,
() => {
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
serverSocket.write(head);
serverSocket.pipe(clientSocket);
clientSocket.pipe(serverSocket);
}
);
clientSocket.on("error", () => {
// This can happen if the client TCP socket sends RST instead of FIN.
// Not subscribing to 'error' event will cause node to throw and crash.
if (serverSocket.writable) {
serverSocket.end();
}
});
serverSocket.on("error", (err) => {
ui.writeError(
`Safe-chain: error connecting to ${hostname}:${port} - ${err.message}`
);
if (clientSocket.writable) {
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
}
});
}
/**
* @param {import("http").IncomingMessage} req
* @param {import("http").ServerResponse} clientSocket
* @param {Buffer} head
* @param {string} proxyUrl
*/
function tunnelRequestViaProxy(req, clientSocket, head, proxyUrl) {
const { port, hostname } = new URL(`http://${req.url}`);
const proxy = new URL(proxyUrl);
// Connect to proxy server
const proxySocket = net.connect({
host: proxy.hostname,
port: Number.parseInt(proxy.port) || 80,
});
proxySocket.on("connect", () => {
// Send CONNECT request to proxy
const connectRequest = [
`CONNECT ${hostname}:${port || 443} HTTP/1.1`,
`Host: ${hostname}:${port || 443}`,
"",
"",
].join("\r\n");
proxySocket.write(connectRequest);
});
let isConnected = false;
proxySocket.once("data", (data) => {
const response = data.toString();
// Check if CONNECT succeeded (HTTP/1.1 200)
if (response.startsWith("HTTP/1.1 200")) {
isConnected = true;
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
proxySocket.write(head);
proxySocket.pipe(clientSocket);
clientSocket.pipe(proxySocket);
} else {
ui.writeError(
`Safe-chain: proxy CONNECT failed: ${response.split("\r\n")[0]}`
);
if (clientSocket.writable) {
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
}
if (proxySocket.writable) {
proxySocket.end();
}
}
});
proxySocket.on("error", (err) => {
if (!isConnected) {
ui.writeError(
`Safe-chain: error connecting to proxy ${proxy.hostname}:${
proxy.port || 8080
} - ${err.message}`
);
if (clientSocket.writable) {
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
}
} else {
ui.writeError(
`Safe-chain: proxy socket error after connection - ${err.message}`
);
if (clientSocket.writable) {
clientSocket.end();
}
}
});
clientSocket.on("error", () => {
if (proxySocket.writable) {
proxySocket.end();
}
});
}