import * as plugins from '../../plugins.js'; import { ForwardingHandler } from './base-handler.js'; import type { IForwardConfig } from '../config/forwarding-types.js'; import { ForwardingHandlerEvents } from '../config/forwarding-types.js'; import { createIndependentSocketHandlers, setupSocketHandlers } from '../../core/utils/socket-utils.js'; /** * Handler for HTTPS passthrough (SNI forwarding without termination) */ export class HttpsPassthroughHandler extends ForwardingHandler { /** * Create a new HTTPS passthrough handler * @param config The forwarding configuration */ constructor(config: IForwardConfig) { super(config); // Validate that this is an HTTPS passthrough configuration if (config.type !== 'https-passthrough') { throw new Error(`Invalid configuration type for HttpsPassthroughHandler: ${config.type}`); } } /** * Initialize the handler * HTTPS passthrough handler doesn't need special initialization */ public async initialize(): Promise { // Basic initialization from parent class await super.initialize(); } /** * Handle a TLS/SSL socket connection by forwarding it without termination * @param clientSocket The incoming socket from the client */ public handleConnection(clientSocket: plugins.net.Socket): void { // Get the target from configuration const target = this.getTargetFromConfig(); // Log the connection const remoteAddress = clientSocket.remoteAddress || 'unknown'; const remotePort = clientSocket.remotePort || 0; this.emit(ForwardingHandlerEvents.CONNECTED, { remoteAddress, remotePort, target: `${target.host}:${target.port}` }); // Create a connection to the target server const serverSocket = plugins.net.connect(target.port, target.host); // Track data transfer for logging let bytesSent = 0; let bytesReceived = 0; // Create independent handlers for half-open connection support const { cleanupClient, cleanupServer } = createIndependentSocketHandlers( clientSocket, serverSocket, (reason) => { this.emit(ForwardingHandlerEvents.DISCONNECTED, { remoteAddress, bytesSent, bytesReceived, reason }); } ); // Setup handlers with custom timeout handling that doesn't close connections const timeout = this.getTimeout(); setupSocketHandlers(clientSocket, cleanupClient, (socket) => { // Just reset timeout, don't close socket.setTimeout(timeout); }, 'client'); setupSocketHandlers(serverSocket, cleanupServer, (socket) => { // Just reset timeout, don't close socket.setTimeout(timeout); }, 'server'); // Forward data from client to server clientSocket.on('data', (data) => { bytesSent += data.length; // Check if server socket is writable if (serverSocket.writable) { const flushed = serverSocket.write(data); // Handle backpressure if (!flushed) { clientSocket.pause(); serverSocket.once('drain', () => { clientSocket.resume(); }); } } this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { direction: 'outbound', bytes: data.length, total: bytesSent }); }); // Forward data from server to client serverSocket.on('data', (data) => { bytesReceived += data.length; // Check if client socket is writable if (clientSocket.writable) { const flushed = clientSocket.write(data); // Handle backpressure if (!flushed) { serverSocket.pause(); clientSocket.once('drain', () => { serverSocket.resume(); }); } } this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { direction: 'inbound', bytes: data.length, total: bytesReceived }); }); // Set initial timeouts - they will be reset on each timeout event clientSocket.setTimeout(timeout); serverSocket.setTimeout(timeout); } /** * Handle an HTTP request - HTTPS passthrough doesn't support HTTP * @param req The HTTP request * @param res The HTTP response */ public handleHttpRequest(_req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { // HTTPS passthrough doesn't support HTTP requests res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('HTTP not supported for this domain'); this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { statusCode: 404, headers: { 'Content-Type': 'text/plain' }, size: 'HTTP not supported for this domain'.length }); } }