import * as plugins from '../../plugins.js'; import { logger } from './logger.js'; import { ProxyProtocolParser as ProtocolParser, type IProxyInfo, type IProxyParseResult } from '../../protocols/proxy/index.js'; // Re-export types from protocols for backward compatibility export type { IProxyInfo, IProxyParseResult } from '../../protocols/proxy/index.js'; /** * Parser for PROXY protocol v1 (text format) * Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt * * This class now delegates to the protocol parser but adds * smartproxy-specific features like socket reading and logging */ export class ProxyProtocolParser { static readonly PROXY_V1_SIGNATURE = ProtocolParser.PROXY_V1_SIGNATURE; static readonly MAX_HEADER_LENGTH = ProtocolParser.MAX_HEADER_LENGTH; static readonly HEADER_TERMINATOR = ProtocolParser.HEADER_TERMINATOR; /** * Parse PROXY protocol v1 header from buffer * Returns proxy info and remaining data after header */ static parse(data: Buffer): IProxyParseResult { // Delegate to protocol parser return ProtocolParser.parse(data); } /** * Generate PROXY protocol v1 header */ static generate(info: IProxyInfo): Buffer { // Delegate to protocol parser return ProtocolParser.generate(info); } /** * Validate IP address format */ private static isValidIP(ip: string, protocol: 'TCP4' | 'TCP6' | 'UNKNOWN'): boolean { return ProtocolParser.isValidIP(ip, protocol); } /** * Attempt to read a complete PROXY protocol header from a socket * Returns null if no PROXY protocol detected or incomplete */ static async readFromSocket(socket: plugins.net.Socket, timeout: number = 5000): Promise { return new Promise((resolve) => { let buffer = Buffer.alloc(0); let resolved = false; const cleanup = () => { socket.removeListener('data', onData); socket.removeListener('error', onError); clearTimeout(timer); }; const timer = setTimeout(() => { if (!resolved) { resolved = true; cleanup(); resolve({ proxyInfo: null, remainingData: buffer }); } }, timeout); const onData = (chunk: Buffer) => { buffer = Buffer.concat([buffer, chunk]); // Check if we have enough data if (!buffer.toString('ascii', 0, Math.min(6, buffer.length)).startsWith(this.PROXY_V1_SIGNATURE)) { // Not PROXY protocol resolved = true; cleanup(); resolve({ proxyInfo: null, remainingData: buffer }); return; } // Try to parse try { const result = this.parse(buffer); if (result.proxyInfo) { // Successfully parsed resolved = true; cleanup(); resolve(result); } else if (buffer.length > this.MAX_HEADER_LENGTH) { // Header too long resolved = true; cleanup(); resolve({ proxyInfo: null, remainingData: buffer }); } // Otherwise continue reading } catch (error) { // Parse error logger.log('error', `PROXY protocol parse error: ${error.message}`); resolved = true; cleanup(); resolve({ proxyInfo: null, remainingData: buffer }); } }; const onError = (error: Error) => { logger.log('error', `Socket error while reading PROXY protocol: ${error.message}`); resolved = true; cleanup(); resolve({ proxyInfo: null, remainingData: buffer }); }; socket.on('data', onData); socket.on('error', onError); }); } }