#!/usr/bin/env node import net from "node:net"; const [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2); const proxyPort = Number.parseInt(proxyPortRaw || "", 10); const targetPort = Number.parseInt(targetPortRaw || "", 10); if (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) { console.error("hwlab git-mirror proxy-connect: invalid ProxyCommand arguments"); process.exit(64); } let settled = false; let tunnelEstablished = false; function finish(code, message) { if (settled) return; settled = true; if (message) console.error("hwlab git-mirror proxy-connect: " + message); process.exit(code); } const socket = net.createConnection({ host: proxyHost, port: proxyPort }); let buffer = Buffer.alloc(0); socket.setTimeout(15000, () => { socket.destroy(); finish(65, "timeout connecting via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort); }); socket.on("connect", () => socket.write("CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1\r\nHost: " + targetHost + ":" + targetPort + "\r\nProxy-Connection: Keep-Alive\r\n\r\n")); socket.on("error", (error) => finish(tunnelEstablished ? 69 : 66, (tunnelEstablished ? "tunnel socket error: " : "tcp error connecting to proxy: ") + (error && error.message ? error.message : String(error)))); socket.on("close", () => { if (!tunnelEstablished) finish(68, "proxy closed before CONNECT completed via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort); else finish(0); }); function onData(chunk) { buffer = Buffer.concat([buffer, chunk]); const headerEnd = buffer.indexOf("\r\n\r\n"); if (headerEnd === -1 && buffer.length < 8192) return; if (headerEnd === -1) { socket.destroy(); finish(68, "proxy response header exceeded 8192 bytes before CONNECT status via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort); return; } const head = buffer.slice(0, headerEnd + 4).toString("latin1"); const statusLine = head.split("\r\n", 1)[0] || ""; const statusCode = Number.parseInt(statusLine.split(" ")[1] || "", 10); if (!statusLine.startsWith("HTTP/1.") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) { const safeStatus = statusLine.replace(/[^\x20-\x7e]/g, "?").slice(0, 160); socket.destroy(); finish(67, "proxy CONNECT failed via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort + ": " + safeStatus); return; } socket.off("data", onData); socket.setTimeout(0); tunnelEstablished = true; const rest = buffer.slice(headerEnd + 4); if (rest.length) process.stdout.write(rest); process.stdin.on("error", () => {}); process.stdout.on("error", () => {}); process.stdin.pipe(socket); socket.pipe(process.stdout); } socket.on("data", onData);