feat(protocols): refactor protocol utilities into centralized protocols module
This commit is contained in:
		| @@ -1,5 +1,5 @@ | |||||||
| { | { | ||||||
|   "expiryDate": "2025-10-18T13:15:48.916Z", |   "expiryDate": "2025-10-19T22:36:33.093Z", | ||||||
|   "issueDate": "2025-07-20T13:15:48.916Z", |   "issueDate": "2025-07-21T22:36:33.093Z", | ||||||
|   "savedAt": "2025-07-20T13:15:48.916Z" |   "savedAt": "2025-07-21T22:36:33.094Z" | ||||||
| } | } | ||||||
| @@ -1,5 +1,14 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-07-21 - 21.1.0 - feat(protocols) | ||||||
|  | Refactor protocol utilities into centralized protocols module | ||||||
|  |  | ||||||
|  | - Moved TLS utilities from `ts/tls/` to `ts/protocols/tls/` | ||||||
|  | - Created centralized protocol modules for HTTP, WebSocket, Proxy, and TLS | ||||||
|  | - Core utilities now delegate to protocol modules for parsing and utilities | ||||||
|  | - Maintains backward compatibility through re-exports in original locations | ||||||
|  | - Improves code organization and separation of concerns | ||||||
|  |  | ||||||
| ## 2025-07-22 - 21.0.0 - BREAKING_CHANGE(forwarding) | ## 2025-07-22 - 21.0.0 - BREAKING_CHANGE(forwarding) | ||||||
| Remove legacy forwarding module | Remove legacy forwarding module | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@push.rocks/smartproxy", |   "name": "@push.rocks/smartproxy", | ||||||
|   "version": "21.0.0", |   "version": "21.1.0", | ||||||
|   "private": false, |   "private": false, | ||||||
|   "description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.", |   "description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.", | ||||||
|   "main": "dist_ts/index.js", |   "main": "dist_ts/index.js", | ||||||
|   | |||||||
| @@ -1,161 +1,44 @@ | |||||||
| import * as plugins from '../../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import { logger } from './logger.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 | ||||||
|  * Interface representing parsed PROXY protocol information | export type { IProxyInfo, IProxyParseResult } from '../../protocols/proxy/index.js'; | ||||||
|  */ |  | ||||||
| export interface IProxyInfo { |  | ||||||
|   protocol: 'TCP4' | 'TCP6' | 'UNKNOWN'; |  | ||||||
|   sourceIP: string; |  | ||||||
|   sourcePort: number; |  | ||||||
|   destinationIP: string; |  | ||||||
|   destinationPort: number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Interface for parse result including remaining data |  | ||||||
|  */ |  | ||||||
| export interface IProxyParseResult { |  | ||||||
|   proxyInfo: IProxyInfo | null; |  | ||||||
|   remainingData: Buffer; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Parser for PROXY protocol v1 (text format) |  * Parser for PROXY protocol v1 (text format) | ||||||
|  * Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt |  * 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 { | export class ProxyProtocolParser { | ||||||
|   static readonly PROXY_V1_SIGNATURE = 'PROXY '; |   static readonly PROXY_V1_SIGNATURE = ProtocolParser.PROXY_V1_SIGNATURE; | ||||||
|   static readonly MAX_HEADER_LENGTH = 107; // Max length for v1 header |   static readonly MAX_HEADER_LENGTH = ProtocolParser.MAX_HEADER_LENGTH; | ||||||
|   static readonly HEADER_TERMINATOR = '\r\n'; |   static readonly HEADER_TERMINATOR = ProtocolParser.HEADER_TERMINATOR; | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Parse PROXY protocol v1 header from buffer |    * Parse PROXY protocol v1 header from buffer | ||||||
|    * Returns proxy info and remaining data after header |    * Returns proxy info and remaining data after header | ||||||
|    */ |    */ | ||||||
|   static parse(data: Buffer): IProxyParseResult { |   static parse(data: Buffer): IProxyParseResult { | ||||||
|     // Check if buffer starts with PROXY signature |     // Delegate to protocol parser | ||||||
|     if (!data.toString('ascii', 0, 6).startsWith(this.PROXY_V1_SIGNATURE)) { |     return ProtocolParser.parse(data); | ||||||
|       return { |  | ||||||
|         proxyInfo: null, |  | ||||||
|         remainingData: data |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Find header terminator |  | ||||||
|     const headerEndIndex = data.indexOf(this.HEADER_TERMINATOR); |  | ||||||
|     if (headerEndIndex === -1) { |  | ||||||
|       // Header incomplete, need more data |  | ||||||
|       if (data.length > this.MAX_HEADER_LENGTH) { |  | ||||||
|         // Header too long, invalid |  | ||||||
|         throw new Error('PROXY protocol header exceeds maximum length'); |  | ||||||
|       } |  | ||||||
|       return { |  | ||||||
|         proxyInfo: null, |  | ||||||
|         remainingData: data |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Extract header line |  | ||||||
|     const headerLine = data.toString('ascii', 0, headerEndIndex); |  | ||||||
|     const remainingData = data.slice(headerEndIndex + 2); // Skip \r\n |  | ||||||
|      |  | ||||||
|     // Parse header |  | ||||||
|     const parts = headerLine.split(' '); |  | ||||||
|      |  | ||||||
|     if (parts.length < 2) { |  | ||||||
|       throw new Error(`Invalid PROXY protocol header format: ${headerLine}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     const [signature, protocol] = parts; |  | ||||||
|      |  | ||||||
|     // Validate protocol |  | ||||||
|     if (!['TCP4', 'TCP6', 'UNKNOWN'].includes(protocol)) { |  | ||||||
|       throw new Error(`Invalid PROXY protocol: ${protocol}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // For UNKNOWN protocol, ignore addresses |  | ||||||
|     if (protocol === 'UNKNOWN') { |  | ||||||
|       return { |  | ||||||
|         proxyInfo: { |  | ||||||
|           protocol: 'UNKNOWN', |  | ||||||
|           sourceIP: '', |  | ||||||
|           sourcePort: 0, |  | ||||||
|           destinationIP: '', |  | ||||||
|           destinationPort: 0 |  | ||||||
|         }, |  | ||||||
|         remainingData |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // For TCP4/TCP6, we need all 6 parts |  | ||||||
|     if (parts.length !== 6) { |  | ||||||
|       throw new Error(`Invalid PROXY protocol header format: ${headerLine}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     const [, , srcIP, dstIP, srcPort, dstPort] = parts; |  | ||||||
|      |  | ||||||
|     // Validate and parse ports |  | ||||||
|     const sourcePort = parseInt(srcPort, 10); |  | ||||||
|     const destinationPort = parseInt(dstPort, 10); |  | ||||||
|      |  | ||||||
|     if (isNaN(sourcePort) || sourcePort < 0 || sourcePort > 65535) { |  | ||||||
|       throw new Error(`Invalid source port: ${srcPort}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     if (isNaN(destinationPort) || destinationPort < 0 || destinationPort > 65535) { |  | ||||||
|       throw new Error(`Invalid destination port: ${dstPort}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Validate IP addresses |  | ||||||
|     const protocolType = protocol as 'TCP4' | 'TCP6' | 'UNKNOWN'; |  | ||||||
|     if (!this.isValidIP(srcIP, protocolType)) { |  | ||||||
|       throw new Error(`Invalid source IP for ${protocol}: ${srcIP}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     if (!this.isValidIP(dstIP, protocolType)) { |  | ||||||
|       throw new Error(`Invalid destination IP for ${protocol}: ${dstIP}`); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     return { |  | ||||||
|       proxyInfo: { |  | ||||||
|         protocol: protocol as 'TCP4' | 'TCP6', |  | ||||||
|         sourceIP: srcIP, |  | ||||||
|         sourcePort, |  | ||||||
|         destinationIP: dstIP, |  | ||||||
|         destinationPort |  | ||||||
|       }, |  | ||||||
|       remainingData |  | ||||||
|     }; |  | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Generate PROXY protocol v1 header |    * Generate PROXY protocol v1 header | ||||||
|    */ |    */ | ||||||
|   static generate(info: IProxyInfo): Buffer { |   static generate(info: IProxyInfo): Buffer { | ||||||
|     if (info.protocol === 'UNKNOWN') { |     // Delegate to protocol parser | ||||||
|       return Buffer.from(`PROXY UNKNOWN\r\n`, 'ascii'); |     return ProtocolParser.generate(info); | ||||||
|     } |  | ||||||
|      |  | ||||||
|     const header = `PROXY ${info.protocol} ${info.sourceIP} ${info.destinationIP} ${info.sourcePort} ${info.destinationPort}\r\n`; |  | ||||||
|      |  | ||||||
|     if (header.length > this.MAX_HEADER_LENGTH) { |  | ||||||
|       throw new Error('Generated PROXY protocol header exceeds maximum length'); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     return Buffer.from(header, 'ascii'); |  | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Validate IP address format |    * Validate IP address format | ||||||
|    */ |    */ | ||||||
|   private static isValidIP(ip: string, protocol: 'TCP4' | 'TCP6' | 'UNKNOWN'): boolean { |   private static isValidIP(ip: string, protocol: 'TCP4' | 'TCP6' | 'UNKNOWN'): boolean { | ||||||
|     if (protocol === 'TCP4') { |     return ProtocolParser.isValidIP(ip, protocol); | ||||||
|       return plugins.net.isIPv4(ip); |  | ||||||
|     } else if (protocol === 'TCP6') { |  | ||||||
|       return plugins.net.isIPv6(ip); |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
| /** | /** | ||||||
|  * WebSocket utility functions |  * WebSocket utility functions | ||||||
|  |  *  | ||||||
|  |  * This module provides smartproxy-specific WebSocket utilities | ||||||
|  |  * and re-exports protocol utilities from the protocols module | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| /** | // Import and re-export from protocols | ||||||
|  * Type for WebSocket RawData that can be different types in different environments | import { getMessageSize as protocolGetMessageSize, toBuffer as protocolToBuffer } from '../../protocols/websocket/index.js'; | ||||||
|  * This matches the ws library's type definition | export type { RawData } from '../../protocols/websocket/index.js'; | ||||||
|  */ |  | ||||||
| export type RawData = Buffer | ArrayBuffer | Buffer[] | any; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Get the length of a WebSocket message regardless of its type |  * Get the length of a WebSocket message regardless of its type | ||||||
| @@ -15,35 +16,9 @@ export type RawData = Buffer | ArrayBuffer | Buffer[] | any; | |||||||
|  * @param data - The data message from WebSocket (could be any RawData type) |  * @param data - The data message from WebSocket (could be any RawData type) | ||||||
|  * @returns The length of the data in bytes |  * @returns The length of the data in bytes | ||||||
|  */ |  */ | ||||||
| export function getMessageSize(data: RawData): number { | export function getMessageSize(data: import('../../protocols/websocket/index.js').RawData): number { | ||||||
|   if (typeof data === 'string') { |   // Delegate to protocol implementation | ||||||
|     // For string data, get the byte length |   return protocolGetMessageSize(data); | ||||||
|     return Buffer.from(data, 'utf8').length; |  | ||||||
|   } else if (data instanceof Buffer) { |  | ||||||
|     // For Node.js Buffer |  | ||||||
|     return data.length; |  | ||||||
|   } else if (data instanceof ArrayBuffer) { |  | ||||||
|     // For ArrayBuffer |  | ||||||
|     return data.byteLength; |  | ||||||
|   } else if (Array.isArray(data)) { |  | ||||||
|     // For array of buffers, sum their lengths |  | ||||||
|     return data.reduce((sum, chunk) => { |  | ||||||
|       if (chunk instanceof Buffer) { |  | ||||||
|         return sum + chunk.length; |  | ||||||
|       } else if (chunk instanceof ArrayBuffer) { |  | ||||||
|         return sum + chunk.byteLength; |  | ||||||
|       } |  | ||||||
|       return sum; |  | ||||||
|     }, 0); |  | ||||||
|   } else { |  | ||||||
|     // For other types, try to determine the size or return 0 |  | ||||||
|     try { |  | ||||||
|       return Buffer.from(data).length; |  | ||||||
|     } catch (e) { |  | ||||||
|       console.warn('Could not determine message size', e); |  | ||||||
|       return 0; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -52,30 +27,7 @@ export function getMessageSize(data: RawData): number { | |||||||
|  * @param data - The data message from WebSocket (could be any RawData type) |  * @param data - The data message from WebSocket (could be any RawData type) | ||||||
|  * @returns A Buffer containing the data |  * @returns A Buffer containing the data | ||||||
|  */ |  */ | ||||||
| export function toBuffer(data: RawData): Buffer { | export function toBuffer(data: import('../../protocols/websocket/index.js').RawData): Buffer { | ||||||
|   if (typeof data === 'string') { |   // Delegate to protocol implementation | ||||||
|     return Buffer.from(data, 'utf8'); |   return protocolToBuffer(data); | ||||||
|   } else if (data instanceof Buffer) { |  | ||||||
|     return data; |  | ||||||
|   } else if (data instanceof ArrayBuffer) { |  | ||||||
|     return Buffer.from(data); |  | ||||||
|   } else if (Array.isArray(data)) { |  | ||||||
|     // For array of buffers, concatenate them |  | ||||||
|     return Buffer.concat(data.map(chunk => { |  | ||||||
|       if (chunk instanceof Buffer) { |  | ||||||
|         return chunk; |  | ||||||
|       } else if (chunk instanceof ArrayBuffer) { |  | ||||||
|         return Buffer.from(chunk); |  | ||||||
|       } |  | ||||||
|       return Buffer.from(chunk); |  | ||||||
|     })); |  | ||||||
|   } else { |  | ||||||
|     // For other types, try to convert to Buffer or return empty Buffer |  | ||||||
|     try { |  | ||||||
|       return Buffer.from(data); |  | ||||||
|     } catch (e) { |  | ||||||
|       console.warn('Could not convert message to Buffer', e); |  | ||||||
|       return Buffer.alloc(0); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| @@ -8,10 +8,12 @@ import type { IDetectionResult, IDetectionOptions, IConnectionInfo } from '../mo | |||||||
| import { readUInt16BE, readUInt24BE, BufferAccumulator } from '../utils/buffer-utils.js'; | import { readUInt16BE, readUInt24BE, BufferAccumulator } from '../utils/buffer-utils.js'; | ||||||
| import { tlsVersionToString } from '../utils/parser-utils.js'; | import { tlsVersionToString } from '../utils/parser-utils.js'; | ||||||
|  |  | ||||||
| // Import existing TLS utilities | // Import from protocols | ||||||
| import { TlsUtils, TlsRecordType, TlsHandshakeType, TlsExtensionType } from '../../tls/utils/tls-utils.js'; | import { TlsRecordType, TlsHandshakeType, TlsExtensionType } from '../../protocols/tls/index.js'; | ||||||
| import { SniExtraction } from '../../tls/sni/sni-extraction.js'; |  | ||||||
| import { ClientHelloParser } from '../../tls/sni/client-hello-parser.js'; | // Import TLS utilities for SNI extraction from protocols | ||||||
|  | import { SniExtraction } from '../../protocols/tls/sni/sni-extraction.js'; | ||||||
|  | import { ClientHelloParser } from '../../protocols/tls/sni/client-hello-parser.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * TLS detector implementation |  * TLS detector implementation | ||||||
| @@ -92,7 +94,7 @@ export class TlsDetector implements IProtocolDetector { | |||||||
|             protocol: 'tls', |             protocol: 'tls', | ||||||
|             connectionInfo, |             connectionInfo, | ||||||
|             remainingBuffer: buffer.length > totalRecordLength  |             remainingBuffer: buffer.length > totalRecordLength  | ||||||
|               ? buffer.slice(totalRecordLength)  |               ? buffer.subarray(totalRecordLength)  | ||||||
|               : undefined, |               : undefined, | ||||||
|             isComplete: true |             isComplete: true | ||||||
|           }; |           }; | ||||||
| @@ -114,7 +116,7 @@ export class TlsDetector implements IProtocolDetector { | |||||||
|       connectionInfo, |       connectionInfo, | ||||||
|       isComplete: true, |       isComplete: true, | ||||||
|       remainingBuffer: buffer.length > recordLength + 5  |       remainingBuffer: buffer.length > recordLength + 5  | ||||||
|         ? buffer.slice(recordLength + 5)  |         ? buffer.subarray(recordLength + 5)  | ||||||
|         : undefined |         : undefined | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| @@ -185,7 +187,7 @@ export class TlsDetector implements IProtocolDetector { | |||||||
|       offset++; |       offset++; | ||||||
|        |        | ||||||
|       if (offset + protoLength <= data.length) { |       if (offset + protoLength <= data.length) { | ||||||
|         const protocol = data.slice(offset, offset + protoLength).toString('ascii'); |         const protocol = data.subarray(offset, offset + protoLength).toString('ascii'); | ||||||
|         protocols.push(protocol); |         protocols.push(protocol); | ||||||
|         offset += protoLength; |         offset += protoLength; | ||||||
|       } else { |       } else { | ||||||
|   | |||||||
| @@ -2,6 +2,9 @@ | |||||||
|  * Buffer manipulation utilities for protocol detection |  * Buffer manipulation utilities for protocol detection | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | // Import from protocols | ||||||
|  | import { HttpParser } from '../../protocols/http/index.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * BufferAccumulator class for handling fragmented data |  * BufferAccumulator class for handling fragmented data | ||||||
|  */ |  */ | ||||||
| @@ -101,33 +104,8 @@ export function findSequence(buffer: Buffer, sequence: Buffer, startOffset = 0): | |||||||
|  * Extract a line from buffer (up to CRLF or LF) |  * Extract a line from buffer (up to CRLF or LF) | ||||||
|  */ |  */ | ||||||
| export function extractLine(buffer: Buffer, startOffset = 0): { line: string; nextOffset: number } | null { | export function extractLine(buffer: Buffer, startOffset = 0): { line: string; nextOffset: number } | null { | ||||||
|   let lineEnd = -1; |   // Delegate to protocol parser | ||||||
|   let skipBytes = 1; |   return HttpParser.extractLine(buffer, startOffset); | ||||||
|    |  | ||||||
|   // Look for CRLF first |  | ||||||
|   const crlfPos = findSequence(buffer, Buffer.from('\r\n'), startOffset); |  | ||||||
|   if (crlfPos !== -1) { |  | ||||||
|     lineEnd = crlfPos; |  | ||||||
|     skipBytes = 2; |  | ||||||
|   } else { |  | ||||||
|     // Look for LF only |  | ||||||
|     for (let i = startOffset; i < buffer.length; i++) { |  | ||||||
|       if (buffer[i] === 0x0A) { // LF |  | ||||||
|         lineEnd = i; |  | ||||||
|         break; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   if (lineEnd === -1) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   const line = buffer.slice(startOffset, lineEnd).toString('utf8'); |  | ||||||
|   return { |  | ||||||
|     line, |  | ||||||
|     nextOffset: lineEnd + skipBytes |  | ||||||
|   }; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -158,17 +136,6 @@ export function safeSlice(buffer: Buffer, start: number, end?: number): Buffer { | |||||||
|  * Check if buffer contains printable ASCII |  * Check if buffer contains printable ASCII | ||||||
|  */ |  */ | ||||||
| export function isPrintableAscii(buffer: Buffer, length?: number): boolean { | export function isPrintableAscii(buffer: Buffer, length?: number): boolean { | ||||||
|   const checkLength = length || buffer.length; |   // Delegate to protocol parser | ||||||
|    |   return HttpParser.isPrintableAscii(buffer, length); | ||||||
|   for (let i = 0; i < checkLength && i < buffer.length; i++) { |  | ||||||
|     const byte = buffer[i]; |  | ||||||
|     // Check if byte is printable ASCII (0x20-0x7E) or tab/newline/carriage return |  | ||||||
|     if (byte < 0x20 || byte > 0x7E) { |  | ||||||
|       if (byte !== 0x09 && byte !== 0x0A && byte !== 0x0D) { |  | ||||||
|         return false; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   return true; |  | ||||||
| } | } | ||||||
| @@ -1,20 +1,14 @@ | |||||||
| /** | /** | ||||||
|  * Parser utilities for protocol detection |  * Parser utilities for protocol detection | ||||||
|  |  * Now delegates to protocol modules for actual parsing | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import type { THttpMethod, TTlsVersion } from '../models/detection-types.js'; | import type { THttpMethod, TTlsVersion } from '../models/detection-types.js'; | ||||||
|  | import { HttpParser, HTTP_METHODS, HTTP_VERSIONS } from '../../protocols/http/index.js'; | ||||||
|  | import { tlsVersionToString as protocolTlsVersionToString } from '../../protocols/tls/index.js'; | ||||||
|  |  | ||||||
| /** | // Re-export constants for backward compatibility | ||||||
|  * Valid HTTP methods | export { HTTP_METHODS, HTTP_VERSIONS }; | ||||||
|  */ |  | ||||||
| export const HTTP_METHODS: THttpMethod[] = [ |  | ||||||
|   'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE' |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * HTTP version strings |  | ||||||
|  */ |  | ||||||
| export const HTTP_VERSIONS = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2', 'HTTP/3']; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Parse HTTP request line |  * Parse HTTP request line | ||||||
| @@ -24,118 +18,60 @@ export function parseHttpRequestLine(line: string): { | |||||||
|   path: string; |   path: string; | ||||||
|   version: string; |   version: string; | ||||||
| } | null { | } | null { | ||||||
|   const parts = line.trim().split(' '); |   // Delegate to protocol parser | ||||||
|    |   const result = HttpParser.parseRequestLine(line); | ||||||
|   if (parts.length !== 3) { |   return result ? { | ||||||
|     return null; |     method: result.method as THttpMethod, | ||||||
|   } |     path: result.path, | ||||||
|    |     version: result.version | ||||||
|   const [method, path, version] = parts; |   } : null; | ||||||
|    |  | ||||||
|   // Validate method |  | ||||||
|   if (!HTTP_METHODS.includes(method as THttpMethod)) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   // Validate version |  | ||||||
|   if (!version.startsWith('HTTP/')) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   return { |  | ||||||
|     method: method as THttpMethod, |  | ||||||
|     path, |  | ||||||
|     version |  | ||||||
|   }; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Parse HTTP header line |  * Parse HTTP header line | ||||||
|  */ |  */ | ||||||
| export function parseHttpHeader(line: string): { name: string; value: string } | null { | export function parseHttpHeader(line: string): { name: string; value: string } | null { | ||||||
|   const colonIndex = line.indexOf(':'); |   // Delegate to protocol parser | ||||||
|    |   return HttpParser.parseHeaderLine(line); | ||||||
|   if (colonIndex === -1) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   const name = line.slice(0, colonIndex).trim(); |  | ||||||
|   const value = line.slice(colonIndex + 1).trim(); |  | ||||||
|    |  | ||||||
|   if (!name) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   return { name, value }; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Parse HTTP headers from lines |  * Parse HTTP headers from lines | ||||||
|  */ |  */ | ||||||
| export function parseHttpHeaders(lines: string[]): Record<string, string> { | export function parseHttpHeaders(lines: string[]): Record<string, string> { | ||||||
|   const headers: Record<string, string> = {}; |   // Delegate to protocol parser | ||||||
|    |   return HttpParser.parseHeaders(lines); | ||||||
|   for (const line of lines) { |  | ||||||
|     const header = parseHttpHeader(line); |  | ||||||
|     if (header) { |  | ||||||
|       // Convert header names to lowercase for consistency |  | ||||||
|       headers[header.name.toLowerCase()] = header.value; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   return headers; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Convert TLS version bytes to version string |  * Convert TLS version bytes to version string | ||||||
|  */ |  */ | ||||||
| export function tlsVersionToString(major: number, minor: number): TTlsVersion | null { | export function tlsVersionToString(major: number, minor: number): TTlsVersion | null { | ||||||
|   if (major === 0x03) { |   // Delegate to protocol parser | ||||||
|     switch (minor) { |   return protocolTlsVersionToString(major, minor) as TTlsVersion; | ||||||
|       case 0x00: return 'SSLv3'; |  | ||||||
|       case 0x01: return 'TLSv1.0'; |  | ||||||
|       case 0x02: return 'TLSv1.1'; |  | ||||||
|       case 0x03: return 'TLSv1.2'; |  | ||||||
|       case 0x04: return 'TLSv1.3'; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return null; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Extract domain from Host header value |  * Extract domain from Host header value | ||||||
|  */ |  */ | ||||||
| export function extractDomainFromHost(hostHeader: string): string { | export function extractDomainFromHost(hostHeader: string): string { | ||||||
|   // Remove port if present |   // Delegate to protocol parser | ||||||
|   const colonIndex = hostHeader.lastIndexOf(':'); |   return HttpParser.extractDomainFromHost(hostHeader); | ||||||
|   if (colonIndex !== -1) { |  | ||||||
|     // Check if it's not part of IPv6 address |  | ||||||
|     const beforeColon = hostHeader.slice(0, colonIndex); |  | ||||||
|     if (!beforeColon.includes(']')) { |  | ||||||
|       return beforeColon; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return hostHeader; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Validate domain name |  * Validate domain name | ||||||
|  */ |  */ | ||||||
| export function isValidDomain(domain: string): boolean { | export function isValidDomain(domain: string): boolean { | ||||||
|   // Basic domain validation |   // Delegate to protocol parser | ||||||
|   if (!domain || domain.length > 253) { |   return HttpParser.isValidDomain(domain); | ||||||
|     return false; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   // Check for valid characters and structure |  | ||||||
|   const domainRegex = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*$/; |  | ||||||
|   return domainRegex.test(domain); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Check if string is a valid HTTP method |  * Check if string is a valid HTTP method | ||||||
|  */ |  */ | ||||||
| export function isHttpMethod(str: string): str is THttpMethod { | export function isHttpMethod(str: string): str is THttpMethod { | ||||||
|   return HTTP_METHODS.includes(str as THttpMethod); |   // Delegate to protocol parser | ||||||
|  |   return HttpParser.isHttpMethod(str) && (str as THttpMethod) !== undefined; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,3 +36,4 @@ export type { IAcmeOptions } from './proxies/smart-proxy/models/interfaces.js'; | |||||||
| export * as tls from './tls/index.js'; | export * as tls from './tls/index.js'; | ||||||
| export * as routing from './routing/index.js'; | export * as routing from './routing/index.js'; | ||||||
| export * as detection from './detection/index.js'; | export * as detection from './detection/index.js'; | ||||||
|  | export * as protocols from './protocols/index.js'; | ||||||
							
								
								
									
										219
									
								
								ts/protocols/http/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								ts/protocols/http/constants.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | |||||||
|  | /** | ||||||
|  |  * HTTP Protocol Constants | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP methods | ||||||
|  |  */ | ||||||
|  | export const HTTP_METHODS = [ | ||||||
|  |   'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE' | ||||||
|  | ] as const; | ||||||
|  |  | ||||||
|  | export type THttpMethod = typeof HTTP_METHODS[number]; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP version strings | ||||||
|  |  */ | ||||||
|  | export const HTTP_VERSIONS = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2', 'HTTP/3'] as const; | ||||||
|  |  | ||||||
|  | export type THttpVersion = typeof HTTP_VERSIONS[number]; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP status codes | ||||||
|  |  */ | ||||||
|  | export enum HttpStatus { | ||||||
|  |   // 1xx Informational | ||||||
|  |   CONTINUE = 100, | ||||||
|  |   SWITCHING_PROTOCOLS = 101, | ||||||
|  |   PROCESSING = 102, | ||||||
|  |   EARLY_HINTS = 103, | ||||||
|  |    | ||||||
|  |   // 2xx Success | ||||||
|  |   OK = 200, | ||||||
|  |   CREATED = 201, | ||||||
|  |   ACCEPTED = 202, | ||||||
|  |   NON_AUTHORITATIVE_INFORMATION = 203, | ||||||
|  |   NO_CONTENT = 204, | ||||||
|  |   RESET_CONTENT = 205, | ||||||
|  |   PARTIAL_CONTENT = 206, | ||||||
|  |   MULTI_STATUS = 207, | ||||||
|  |   ALREADY_REPORTED = 208, | ||||||
|  |   IM_USED = 226, | ||||||
|  |    | ||||||
|  |   // 3xx Redirection | ||||||
|  |   MULTIPLE_CHOICES = 300, | ||||||
|  |   MOVED_PERMANENTLY = 301, | ||||||
|  |   FOUND = 302, | ||||||
|  |   SEE_OTHER = 303, | ||||||
|  |   NOT_MODIFIED = 304, | ||||||
|  |   USE_PROXY = 305, | ||||||
|  |   TEMPORARY_REDIRECT = 307, | ||||||
|  |   PERMANENT_REDIRECT = 308, | ||||||
|  |    | ||||||
|  |   // 4xx Client Error | ||||||
|  |   BAD_REQUEST = 400, | ||||||
|  |   UNAUTHORIZED = 401, | ||||||
|  |   PAYMENT_REQUIRED = 402, | ||||||
|  |   FORBIDDEN = 403, | ||||||
|  |   NOT_FOUND = 404, | ||||||
|  |   METHOD_NOT_ALLOWED = 405, | ||||||
|  |   NOT_ACCEPTABLE = 406, | ||||||
|  |   PROXY_AUTHENTICATION_REQUIRED = 407, | ||||||
|  |   REQUEST_TIMEOUT = 408, | ||||||
|  |   CONFLICT = 409, | ||||||
|  |   GONE = 410, | ||||||
|  |   LENGTH_REQUIRED = 411, | ||||||
|  |   PRECONDITION_FAILED = 412, | ||||||
|  |   PAYLOAD_TOO_LARGE = 413, | ||||||
|  |   URI_TOO_LONG = 414, | ||||||
|  |   UNSUPPORTED_MEDIA_TYPE = 415, | ||||||
|  |   RANGE_NOT_SATISFIABLE = 416, | ||||||
|  |   EXPECTATION_FAILED = 417, | ||||||
|  |   IM_A_TEAPOT = 418, | ||||||
|  |   MISDIRECTED_REQUEST = 421, | ||||||
|  |   UNPROCESSABLE_ENTITY = 422, | ||||||
|  |   LOCKED = 423, | ||||||
|  |   FAILED_DEPENDENCY = 424, | ||||||
|  |   TOO_EARLY = 425, | ||||||
|  |   UPGRADE_REQUIRED = 426, | ||||||
|  |   PRECONDITION_REQUIRED = 428, | ||||||
|  |   TOO_MANY_REQUESTS = 429, | ||||||
|  |   REQUEST_HEADER_FIELDS_TOO_LARGE = 431, | ||||||
|  |   UNAVAILABLE_FOR_LEGAL_REASONS = 451, | ||||||
|  |    | ||||||
|  |   // 5xx Server Error | ||||||
|  |   INTERNAL_SERVER_ERROR = 500, | ||||||
|  |   NOT_IMPLEMENTED = 501, | ||||||
|  |   BAD_GATEWAY = 502, | ||||||
|  |   SERVICE_UNAVAILABLE = 503, | ||||||
|  |   GATEWAY_TIMEOUT = 504, | ||||||
|  |   HTTP_VERSION_NOT_SUPPORTED = 505, | ||||||
|  |   VARIANT_ALSO_NEGOTIATES = 506, | ||||||
|  |   INSUFFICIENT_STORAGE = 507, | ||||||
|  |   LOOP_DETECTED = 508, | ||||||
|  |   NOT_EXTENDED = 510, | ||||||
|  |   NETWORK_AUTHENTICATION_REQUIRED = 511, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP status text mapping | ||||||
|  |  */ | ||||||
|  | export const HTTP_STATUS_TEXT: Record<HttpStatus, string> = { | ||||||
|  |   // 1xx | ||||||
|  |   [HttpStatus.CONTINUE]: 'Continue', | ||||||
|  |   [HttpStatus.SWITCHING_PROTOCOLS]: 'Switching Protocols', | ||||||
|  |   [HttpStatus.PROCESSING]: 'Processing', | ||||||
|  |   [HttpStatus.EARLY_HINTS]: 'Early Hints', | ||||||
|  |    | ||||||
|  |   // 2xx | ||||||
|  |   [HttpStatus.OK]: 'OK', | ||||||
|  |   [HttpStatus.CREATED]: 'Created', | ||||||
|  |   [HttpStatus.ACCEPTED]: 'Accepted', | ||||||
|  |   [HttpStatus.NON_AUTHORITATIVE_INFORMATION]: 'Non-Authoritative Information', | ||||||
|  |   [HttpStatus.NO_CONTENT]: 'No Content', | ||||||
|  |   [HttpStatus.RESET_CONTENT]: 'Reset Content', | ||||||
|  |   [HttpStatus.PARTIAL_CONTENT]: 'Partial Content', | ||||||
|  |   [HttpStatus.MULTI_STATUS]: 'Multi-Status', | ||||||
|  |   [HttpStatus.ALREADY_REPORTED]: 'Already Reported', | ||||||
|  |   [HttpStatus.IM_USED]: 'IM Used', | ||||||
|  |    | ||||||
|  |   // 3xx | ||||||
|  |   [HttpStatus.MULTIPLE_CHOICES]: 'Multiple Choices', | ||||||
|  |   [HttpStatus.MOVED_PERMANENTLY]: 'Moved Permanently', | ||||||
|  |   [HttpStatus.FOUND]: 'Found', | ||||||
|  |   [HttpStatus.SEE_OTHER]: 'See Other', | ||||||
|  |   [HttpStatus.NOT_MODIFIED]: 'Not Modified', | ||||||
|  |   [HttpStatus.USE_PROXY]: 'Use Proxy', | ||||||
|  |   [HttpStatus.TEMPORARY_REDIRECT]: 'Temporary Redirect', | ||||||
|  |   [HttpStatus.PERMANENT_REDIRECT]: 'Permanent Redirect', | ||||||
|  |    | ||||||
|  |   // 4xx | ||||||
|  |   [HttpStatus.BAD_REQUEST]: 'Bad Request', | ||||||
|  |   [HttpStatus.UNAUTHORIZED]: 'Unauthorized', | ||||||
|  |   [HttpStatus.PAYMENT_REQUIRED]: 'Payment Required', | ||||||
|  |   [HttpStatus.FORBIDDEN]: 'Forbidden', | ||||||
|  |   [HttpStatus.NOT_FOUND]: 'Not Found', | ||||||
|  |   [HttpStatus.METHOD_NOT_ALLOWED]: 'Method Not Allowed', | ||||||
|  |   [HttpStatus.NOT_ACCEPTABLE]: 'Not Acceptable', | ||||||
|  |   [HttpStatus.PROXY_AUTHENTICATION_REQUIRED]: 'Proxy Authentication Required', | ||||||
|  |   [HttpStatus.REQUEST_TIMEOUT]: 'Request Timeout', | ||||||
|  |   [HttpStatus.CONFLICT]: 'Conflict', | ||||||
|  |   [HttpStatus.GONE]: 'Gone', | ||||||
|  |   [HttpStatus.LENGTH_REQUIRED]: 'Length Required', | ||||||
|  |   [HttpStatus.PRECONDITION_FAILED]: 'Precondition Failed', | ||||||
|  |   [HttpStatus.PAYLOAD_TOO_LARGE]: 'Payload Too Large', | ||||||
|  |   [HttpStatus.URI_TOO_LONG]: 'URI Too Long', | ||||||
|  |   [HttpStatus.UNSUPPORTED_MEDIA_TYPE]: 'Unsupported Media Type', | ||||||
|  |   [HttpStatus.RANGE_NOT_SATISFIABLE]: 'Range Not Satisfiable', | ||||||
|  |   [HttpStatus.EXPECTATION_FAILED]: 'Expectation Failed', | ||||||
|  |   [HttpStatus.IM_A_TEAPOT]: "I'm a teapot", | ||||||
|  |   [HttpStatus.MISDIRECTED_REQUEST]: 'Misdirected Request', | ||||||
|  |   [HttpStatus.UNPROCESSABLE_ENTITY]: 'Unprocessable Entity', | ||||||
|  |   [HttpStatus.LOCKED]: 'Locked', | ||||||
|  |   [HttpStatus.FAILED_DEPENDENCY]: 'Failed Dependency', | ||||||
|  |   [HttpStatus.TOO_EARLY]: 'Too Early', | ||||||
|  |   [HttpStatus.UPGRADE_REQUIRED]: 'Upgrade Required', | ||||||
|  |   [HttpStatus.PRECONDITION_REQUIRED]: 'Precondition Required', | ||||||
|  |   [HttpStatus.TOO_MANY_REQUESTS]: 'Too Many Requests', | ||||||
|  |   [HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE]: 'Request Header Fields Too Large', | ||||||
|  |   [HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS]: 'Unavailable For Legal Reasons', | ||||||
|  |    | ||||||
|  |   // 5xx | ||||||
|  |   [HttpStatus.INTERNAL_SERVER_ERROR]: 'Internal Server Error', | ||||||
|  |   [HttpStatus.NOT_IMPLEMENTED]: 'Not Implemented', | ||||||
|  |   [HttpStatus.BAD_GATEWAY]: 'Bad Gateway', | ||||||
|  |   [HttpStatus.SERVICE_UNAVAILABLE]: 'Service Unavailable', | ||||||
|  |   [HttpStatus.GATEWAY_TIMEOUT]: 'Gateway Timeout', | ||||||
|  |   [HttpStatus.HTTP_VERSION_NOT_SUPPORTED]: 'HTTP Version Not Supported', | ||||||
|  |   [HttpStatus.VARIANT_ALSO_NEGOTIATES]: 'Variant Also Negotiates', | ||||||
|  |   [HttpStatus.INSUFFICIENT_STORAGE]: 'Insufficient Storage', | ||||||
|  |   [HttpStatus.LOOP_DETECTED]: 'Loop Detected', | ||||||
|  |   [HttpStatus.NOT_EXTENDED]: 'Not Extended', | ||||||
|  |   [HttpStatus.NETWORK_AUTHENTICATION_REQUIRED]: 'Network Authentication Required', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Common HTTP headers | ||||||
|  |  */ | ||||||
|  | export const HTTP_HEADERS = { | ||||||
|  |   // Request headers | ||||||
|  |   HOST: 'host', | ||||||
|  |   USER_AGENT: 'user-agent', | ||||||
|  |   ACCEPT: 'accept', | ||||||
|  |   ACCEPT_LANGUAGE: 'accept-language', | ||||||
|  |   ACCEPT_ENCODING: 'accept-encoding', | ||||||
|  |   AUTHORIZATION: 'authorization', | ||||||
|  |   CACHE_CONTROL: 'cache-control', | ||||||
|  |   CONNECTION: 'connection', | ||||||
|  |   CONTENT_TYPE: 'content-type', | ||||||
|  |   CONTENT_LENGTH: 'content-length', | ||||||
|  |   COOKIE: 'cookie', | ||||||
|  |    | ||||||
|  |   // Response headers | ||||||
|  |   SET_COOKIE: 'set-cookie', | ||||||
|  |   LOCATION: 'location', | ||||||
|  |   SERVER: 'server', | ||||||
|  |   DATE: 'date', | ||||||
|  |   EXPIRES: 'expires', | ||||||
|  |   LAST_MODIFIED: 'last-modified', | ||||||
|  |   ETAG: 'etag', | ||||||
|  |    | ||||||
|  |   // CORS headers | ||||||
|  |   ACCESS_CONTROL_ALLOW_ORIGIN: 'access-control-allow-origin', | ||||||
|  |   ACCESS_CONTROL_ALLOW_METHODS: 'access-control-allow-methods', | ||||||
|  |   ACCESS_CONTROL_ALLOW_HEADERS: 'access-control-allow-headers', | ||||||
|  |    | ||||||
|  |   // Security headers | ||||||
|  |   STRICT_TRANSPORT_SECURITY: 'strict-transport-security', | ||||||
|  |   X_CONTENT_TYPE_OPTIONS: 'x-content-type-options', | ||||||
|  |   X_FRAME_OPTIONS: 'x-frame-options', | ||||||
|  |   X_XSS_PROTECTION: 'x-xss-protection', | ||||||
|  |   CONTENT_SECURITY_POLICY: 'content-security-policy', | ||||||
|  | } as const; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get HTTP status text | ||||||
|  |  */ | ||||||
|  | export function getStatusText(status: HttpStatus): string { | ||||||
|  |   return HTTP_STATUS_TEXT[status] || 'Unknown'; | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								ts/protocols/http/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ts/protocols/http/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * HTTP Protocol Module | ||||||
|  |  * Generic HTTP protocol knowledge and parsing utilities | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export * from './constants.js'; | ||||||
|  | export * from './types.js'; | ||||||
|  | export * from './parser.js'; | ||||||
							
								
								
									
										219
									
								
								ts/protocols/http/parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								ts/protocols/http/parser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | |||||||
|  | /** | ||||||
|  |  * HTTP Protocol Parser | ||||||
|  |  * Generic HTTP parsing utilities | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import { HTTP_METHODS, type THttpMethod, type THttpVersion } from './constants.js'; | ||||||
|  | import type { IHttpRequestLine, IHttpHeader } from './types.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP parser utilities | ||||||
|  |  */ | ||||||
|  | export class HttpParser { | ||||||
|  |   /** | ||||||
|  |    * Check if string is a valid HTTP method | ||||||
|  |    */ | ||||||
|  |   static isHttpMethod(str: string): str is THttpMethod { | ||||||
|  |     return HTTP_METHODS.includes(str as THttpMethod); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Parse HTTP request line | ||||||
|  |    */ | ||||||
|  |   static parseRequestLine(line: string): IHttpRequestLine | null { | ||||||
|  |     const parts = line.trim().split(' '); | ||||||
|  |      | ||||||
|  |     if (parts.length !== 3) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const [method, path, version] = parts; | ||||||
|  |      | ||||||
|  |     // Validate method | ||||||
|  |     if (!this.isHttpMethod(method)) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Validate version | ||||||
|  |     if (!version.startsWith('HTTP/')) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return { | ||||||
|  |       method: method as THttpMethod, | ||||||
|  |       path, | ||||||
|  |       version: version as THttpVersion | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Parse HTTP header line | ||||||
|  |    */ | ||||||
|  |   static parseHeaderLine(line: string): IHttpHeader | null { | ||||||
|  |     const colonIndex = line.indexOf(':'); | ||||||
|  |      | ||||||
|  |     if (colonIndex === -1) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const name = line.slice(0, colonIndex).trim(); | ||||||
|  |     const value = line.slice(colonIndex + 1).trim(); | ||||||
|  |      | ||||||
|  |     if (!name) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return { name, value }; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Parse HTTP headers from lines | ||||||
|  |    */ | ||||||
|  |   static parseHeaders(lines: string[]): Record<string, string> { | ||||||
|  |     const headers: Record<string, string> = {}; | ||||||
|  |      | ||||||
|  |     for (const line of lines) { | ||||||
|  |       const header = this.parseHeaderLine(line); | ||||||
|  |       if (header) { | ||||||
|  |         // Convert header names to lowercase for consistency | ||||||
|  |         headers[header.name.toLowerCase()] = header.value; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return headers; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Extract domain from Host header value | ||||||
|  |    */ | ||||||
|  |   static extractDomainFromHost(hostHeader: string): string { | ||||||
|  |     // Remove port if present | ||||||
|  |     const colonIndex = hostHeader.lastIndexOf(':'); | ||||||
|  |     if (colonIndex !== -1) { | ||||||
|  |       // Check if it's not part of IPv6 address | ||||||
|  |       const beforeColon = hostHeader.slice(0, colonIndex); | ||||||
|  |       if (!beforeColon.includes(']')) { | ||||||
|  |         return beforeColon; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return hostHeader; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Validate domain name | ||||||
|  |    */ | ||||||
|  |   static isValidDomain(domain: string): boolean { | ||||||
|  |     // Basic domain validation | ||||||
|  |     if (!domain || domain.length > 253) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Check for valid characters and structure | ||||||
|  |     const domainRegex = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*$/; | ||||||
|  |     return domainRegex.test(domain); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Extract line from buffer | ||||||
|  |    */ | ||||||
|  |   static extractLine(buffer: Buffer, offset: number = 0): { line: string; nextOffset: number } | null { | ||||||
|  |     // Look for CRLF | ||||||
|  |     const crlfIndex = buffer.indexOf('\r\n', offset); | ||||||
|  |     if (crlfIndex === -1) { | ||||||
|  |       // Look for just LF | ||||||
|  |       const lfIndex = buffer.indexOf('\n', offset); | ||||||
|  |       if (lfIndex === -1) { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       return { | ||||||
|  |         line: buffer.slice(offset, lfIndex).toString('utf8'), | ||||||
|  |         nextOffset: lfIndex + 1 | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return { | ||||||
|  |       line: buffer.slice(offset, crlfIndex).toString('utf8'), | ||||||
|  |       nextOffset: crlfIndex + 2 | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Check if buffer contains printable ASCII | ||||||
|  |    */ | ||||||
|  |   static isPrintableAscii(buffer: Buffer, length?: number): boolean { | ||||||
|  |     const checkLength = Math.min(length || buffer.length, buffer.length); | ||||||
|  |      | ||||||
|  |     for (let i = 0; i < checkLength; i++) { | ||||||
|  |       const byte = buffer[i]; | ||||||
|  |       // Allow printable ASCII (32-126) plus tab (9), LF (10), and CR (13) | ||||||
|  |       if (byte < 32 || byte > 126) { | ||||||
|  |         if (byte !== 9 && byte !== 10 && byte !== 13) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Quick check if buffer starts with HTTP method | ||||||
|  |    */ | ||||||
|  |   static quickCheck(buffer: Buffer): boolean { | ||||||
|  |     if (buffer.length < 3) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Check common HTTP methods | ||||||
|  |     const start = buffer.slice(0, 7).toString('ascii'); | ||||||
|  |     return start.startsWith('GET ') || | ||||||
|  |            start.startsWith('POST ') || | ||||||
|  |            start.startsWith('PUT ') || | ||||||
|  |            start.startsWith('DELETE ') || | ||||||
|  |            start.startsWith('HEAD ') || | ||||||
|  |            start.startsWith('OPTIONS') || | ||||||
|  |            start.startsWith('PATCH ') || | ||||||
|  |            start.startsWith('CONNECT') || | ||||||
|  |            start.startsWith('TRACE '); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Parse query string | ||||||
|  |    */ | ||||||
|  |   static parseQueryString(queryString: string): Record<string, string> { | ||||||
|  |     const params: Record<string, string> = {}; | ||||||
|  |      | ||||||
|  |     if (!queryString) { | ||||||
|  |       return params; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Remove leading '?' if present | ||||||
|  |     if (queryString.startsWith('?')) { | ||||||
|  |       queryString = queryString.slice(1); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const pairs = queryString.split('&'); | ||||||
|  |     for (const pair of pairs) { | ||||||
|  |       const [key, value] = pair.split('='); | ||||||
|  |       if (key) { | ||||||
|  |         params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : ''; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return params; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Build query string from params | ||||||
|  |    */ | ||||||
|  |   static buildQueryString(params: Record<string, string>): string { | ||||||
|  |     const pairs: string[] = []; | ||||||
|  |      | ||||||
|  |     for (const [key, value] of Object.entries(params)) { | ||||||
|  |       pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return pairs.length > 0 ? '?' + pairs.join('&') : ''; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								ts/protocols/http/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								ts/protocols/http/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | /** | ||||||
|  |  * HTTP Protocol Type Definitions | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import type { THttpMethod, THttpVersion, HttpStatus } from './constants.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP request line structure | ||||||
|  |  */ | ||||||
|  | export interface IHttpRequestLine { | ||||||
|  |   method: THttpMethod; | ||||||
|  |   path: string; | ||||||
|  |   version: THttpVersion; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP response line structure | ||||||
|  |  */ | ||||||
|  | export interface IHttpResponseLine { | ||||||
|  |   version: THttpVersion; | ||||||
|  |   status: HttpStatus; | ||||||
|  |   statusText: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP header structure | ||||||
|  |  */ | ||||||
|  | export interface IHttpHeader { | ||||||
|  |   name: string; | ||||||
|  |   value: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP message structure (base for request and response) | ||||||
|  |  */ | ||||||
|  | export interface IHttpMessage { | ||||||
|  |   headers: Record<string, string>; | ||||||
|  |   body?: Buffer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP request structure | ||||||
|  |  */ | ||||||
|  | export interface IHttpRequest extends IHttpMessage { | ||||||
|  |   method: THttpMethod; | ||||||
|  |   path: string; | ||||||
|  |   version: THttpVersion; | ||||||
|  |   query?: Record<string, string>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP response structure   | ||||||
|  |  */ | ||||||
|  | export interface IHttpResponse extends IHttpMessage { | ||||||
|  |   status: HttpStatus; | ||||||
|  |   statusText: string; | ||||||
|  |   version: THttpVersion; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Parsed URL structure | ||||||
|  |  */ | ||||||
|  | export interface IParsedUrl { | ||||||
|  |   protocol?: string; | ||||||
|  |   hostname?: string; | ||||||
|  |   port?: number; | ||||||
|  |   path?: string; | ||||||
|  |   query?: string; | ||||||
|  |   fragment?: string; | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								ts/protocols/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ts/protocols/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | /** | ||||||
|  |  * Protocol-specific modules for smartproxy | ||||||
|  |  *  | ||||||
|  |  * This directory contains generic protocol knowledge separated from | ||||||
|  |  * smartproxy-specific implementation details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export * as tls from './tls/index.js'; | ||||||
|  | export * as http from './http/index.js'; | ||||||
|  | export * as proxy from './proxy/index.js'; | ||||||
|  | export * as websocket from './websocket/index.js'; | ||||||
							
								
								
									
										7
									
								
								ts/protocols/proxy/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ts/protocols/proxy/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | /** | ||||||
|  |  * PROXY Protocol Module | ||||||
|  |  * HAProxy PROXY protocol implementation | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export * from './types.js'; | ||||||
|  | export * from './parser.js'; | ||||||
							
								
								
									
										183
									
								
								ts/protocols/proxy/parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								ts/protocols/proxy/parser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | |||||||
|  | /** | ||||||
|  |  * PROXY Protocol Parser | ||||||
|  |  * Implementation of HAProxy PROXY protocol v1 (text format) | ||||||
|  |  * Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import type { IProxyInfo, IProxyParseResult, TProxyProtocol } from './types.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * PROXY protocol parser | ||||||
|  |  */ | ||||||
|  | export class ProxyProtocolParser { | ||||||
|  |   static readonly PROXY_V1_SIGNATURE = 'PROXY '; | ||||||
|  |   static readonly MAX_HEADER_LENGTH = 107; // Max length for v1 header | ||||||
|  |   static readonly HEADER_TERMINATOR = '\r\n'; | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Parse PROXY protocol v1 header from buffer | ||||||
|  |    * Returns proxy info and remaining data after header | ||||||
|  |    */ | ||||||
|  |   static parse(data: Buffer): IProxyParseResult { | ||||||
|  |     // Check if buffer starts with PROXY signature | ||||||
|  |     if (!data.toString('ascii', 0, 6).startsWith(this.PROXY_V1_SIGNATURE)) { | ||||||
|  |       return { | ||||||
|  |         proxyInfo: null, | ||||||
|  |         remainingData: data | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Find header terminator | ||||||
|  |     const headerEndIndex = data.indexOf(this.HEADER_TERMINATOR); | ||||||
|  |     if (headerEndIndex === -1) { | ||||||
|  |       // Header incomplete, need more data | ||||||
|  |       if (data.length > this.MAX_HEADER_LENGTH) { | ||||||
|  |         // Header too long, invalid | ||||||
|  |         throw new Error('PROXY protocol header exceeds maximum length'); | ||||||
|  |       } | ||||||
|  |       return { | ||||||
|  |         proxyInfo: null, | ||||||
|  |         remainingData: data | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Extract header line | ||||||
|  |     const headerLine = data.toString('ascii', 0, headerEndIndex); | ||||||
|  |     const remainingData = data.slice(headerEndIndex + 2); // Skip \r\n | ||||||
|  |      | ||||||
|  |     // Parse header | ||||||
|  |     const parts = headerLine.split(' '); | ||||||
|  |      | ||||||
|  |     if (parts.length < 2) { | ||||||
|  |       throw new Error(`Invalid PROXY protocol header format: ${headerLine}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const [signature, protocol] = parts; | ||||||
|  |      | ||||||
|  |     // Validate protocol | ||||||
|  |     if (!['TCP4', 'TCP6', 'UNKNOWN'].includes(protocol)) { | ||||||
|  |       throw new Error(`Invalid PROXY protocol: ${protocol}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // For UNKNOWN protocol, ignore addresses | ||||||
|  |     if (protocol === 'UNKNOWN') { | ||||||
|  |       return { | ||||||
|  |         proxyInfo: { | ||||||
|  |           protocol: 'UNKNOWN', | ||||||
|  |           sourceIP: '', | ||||||
|  |           sourcePort: 0, | ||||||
|  |           destinationIP: '', | ||||||
|  |           destinationPort: 0 | ||||||
|  |         }, | ||||||
|  |         remainingData | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // For TCP4/TCP6, we need all 6 parts | ||||||
|  |     if (parts.length !== 6) { | ||||||
|  |       throw new Error(`Invalid PROXY protocol header format: ${headerLine}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const [, , srcIP, dstIP, srcPort, dstPort] = parts; | ||||||
|  |      | ||||||
|  |     // Validate and parse ports | ||||||
|  |     const sourcePort = parseInt(srcPort, 10); | ||||||
|  |     const destinationPort = parseInt(dstPort, 10); | ||||||
|  |      | ||||||
|  |     if (isNaN(sourcePort) || sourcePort < 0 || sourcePort > 65535) { | ||||||
|  |       throw new Error(`Invalid source port: ${srcPort}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (isNaN(destinationPort) || destinationPort < 0 || destinationPort > 65535) { | ||||||
|  |       throw new Error(`Invalid destination port: ${dstPort}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Validate IP addresses | ||||||
|  |     const protocolType = protocol as TProxyProtocol; | ||||||
|  |     if (!this.isValidIP(srcIP, protocolType)) { | ||||||
|  |       throw new Error(`Invalid source IP for ${protocol}: ${srcIP}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (!this.isValidIP(dstIP, protocolType)) { | ||||||
|  |       throw new Error(`Invalid destination IP for ${protocol}: ${dstIP}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return { | ||||||
|  |       proxyInfo: { | ||||||
|  |         protocol: protocolType, | ||||||
|  |         sourceIP: srcIP, | ||||||
|  |         sourcePort, | ||||||
|  |         destinationIP: dstIP, | ||||||
|  |         destinationPort | ||||||
|  |       }, | ||||||
|  |       remainingData | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Generate PROXY protocol v1 header | ||||||
|  |    */ | ||||||
|  |   static generate(info: IProxyInfo): Buffer { | ||||||
|  |     if (info.protocol === 'UNKNOWN') { | ||||||
|  |       return Buffer.from(`PROXY UNKNOWN\r\n`, 'ascii'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const header = `PROXY ${info.protocol} ${info.sourceIP} ${info.destinationIP} ${info.sourcePort} ${info.destinationPort}\r\n`; | ||||||
|  |      | ||||||
|  |     if (header.length > this.MAX_HEADER_LENGTH) { | ||||||
|  |       throw new Error('Generated PROXY protocol header exceeds maximum length'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return Buffer.from(header, 'ascii'); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Validate IP address format | ||||||
|  |    */ | ||||||
|  |   static isValidIP(ip: string, protocol: TProxyProtocol): boolean { | ||||||
|  |     if (protocol === 'TCP4') { | ||||||
|  |       return this.isIPv4(ip); | ||||||
|  |     } else if (protocol === 'TCP6') { | ||||||
|  |       return this.isIPv6(ip); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Check if string is valid IPv4 | ||||||
|  |    */ | ||||||
|  |   static isIPv4(ip: string): boolean { | ||||||
|  |     const parts = ip.split('.'); | ||||||
|  |     if (parts.length !== 4) return false; | ||||||
|  |      | ||||||
|  |     for (const part of parts) { | ||||||
|  |       const num = parseInt(part, 10); | ||||||
|  |       if (isNaN(num) || num < 0 || num > 255 || part !== num.toString()) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Check if string is valid IPv6 | ||||||
|  |    */ | ||||||
|  |   static isIPv6(ip: string): boolean { | ||||||
|  |     // Basic IPv6 validation | ||||||
|  |     const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; | ||||||
|  |     return ipv6Regex.test(ip); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Create a connection ID string for tracking | ||||||
|  |    */ | ||||||
|  |   static createConnectionId(connectionInfo: { | ||||||
|  |     sourceIp?: string; | ||||||
|  |     sourcePort?: number; | ||||||
|  |     destIp?: string; | ||||||
|  |     destPort?: number; | ||||||
|  |   }): string { | ||||||
|  |     const { sourceIp, sourcePort, destIp, destPort } = connectionInfo; | ||||||
|  |     return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								ts/protocols/proxy/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								ts/protocols/proxy/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | /** | ||||||
|  |  * PROXY Protocol Type Definitions | ||||||
|  |  * Based on HAProxy PROXY protocol specification | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * PROXY protocol version | ||||||
|  |  */ | ||||||
|  | export type TProxyProtocolVersion = 'v1' | 'v2'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Connection protocol type | ||||||
|  |  */ | ||||||
|  | export type TProxyProtocol = 'TCP4' | 'TCP6' | 'UNKNOWN'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Interface representing parsed PROXY protocol information | ||||||
|  |  */ | ||||||
|  | export interface IProxyInfo { | ||||||
|  |   protocol: TProxyProtocol; | ||||||
|  |   sourceIP: string; | ||||||
|  |   sourcePort: number; | ||||||
|  |   destinationIP: string; | ||||||
|  |   destinationPort: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Interface for parse result including remaining data | ||||||
|  |  */ | ||||||
|  | export interface IProxyParseResult { | ||||||
|  |   proxyInfo: IProxyInfo | null; | ||||||
|  |   remainingData: Buffer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * PROXY protocol v2 header format | ||||||
|  |  */ | ||||||
|  | export interface IProxyV2Header { | ||||||
|  |   signature: Buffer; | ||||||
|  |   versionCommand: number; | ||||||
|  |   family: number; | ||||||
|  |   length: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Connection information for PROXY protocol | ||||||
|  |  */ | ||||||
|  | export interface IProxyConnectionInfo { | ||||||
|  |   sourceIp?: string; | ||||||
|  |   sourcePort?: number; | ||||||
|  |   destIp?: string; | ||||||
|  |   destPort?: number; | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import * as plugins from '../../plugins.js'; | import * as plugins from '../../../plugins.js'; | ||||||
| import { TlsAlertLevel, TlsAlertDescription, TlsVersion } from '../utils/tls-utils.js'; | import { TlsAlertLevel, TlsAlertDescription, TlsVersion } from '../utils/tls-utils.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
							
								
								
									
										37
									
								
								ts/protocols/tls/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								ts/protocols/tls/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | /** | ||||||
|  |  * TLS Protocol Module | ||||||
|  |  * Contains generic TLS protocol knowledge including parsers, constants, and utilities | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | // Export all sub-modules | ||||||
|  | export * from './alerts/index.js'; | ||||||
|  | export * from './sni/index.js'; | ||||||
|  | export * from './utils/index.js'; | ||||||
|  |  | ||||||
|  | // Re-export main utilities and types for convenience | ||||||
|  | export {  | ||||||
|  |   TlsUtils, | ||||||
|  |   TlsRecordType, | ||||||
|  |   TlsHandshakeType, | ||||||
|  |   TlsExtensionType, | ||||||
|  |   TlsAlertLevel, | ||||||
|  |   TlsAlertDescription, | ||||||
|  |   TlsVersion | ||||||
|  | } from './utils/tls-utils.js'; | ||||||
|  | export { TlsAlert } from './alerts/tls-alert.js'; | ||||||
|  | export { ClientHelloParser } from './sni/client-hello-parser.js'; | ||||||
|  | export { SniExtraction } from './sni/sni-extraction.js'; | ||||||
|  |  | ||||||
|  | // Export tlsVersionToString helper | ||||||
|  | export function tlsVersionToString(major: number, minor: number): string | null { | ||||||
|  |   if (major === 0x03) { | ||||||
|  |     switch (minor) { | ||||||
|  |       case 0x00: return 'SSLv3'; | ||||||
|  |       case 0x01: return 'TLSv1.0'; | ||||||
|  |       case 0x02: return 'TLSv1.1'; | ||||||
|  |       case 0x03: return 'TLSv1.2'; | ||||||
|  |       case 0x04: return 'TLSv1.3'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return null; | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								ts/protocols/tls/sni/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ts/protocols/tls/sni/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | /** | ||||||
|  |  * TLS SNI (Server Name Indication) protocol utilities | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export * from './client-hello-parser.js'; | ||||||
|  | export * from './sni-extraction.js'; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import * as plugins from '../../plugins.js'; | import * as plugins from '../../../plugins.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * TLS record types as defined in various RFCs |  * TLS record types as defined in various RFCs | ||||||
							
								
								
									
										60
									
								
								ts/protocols/websocket/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ts/protocols/websocket/constants.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | /** | ||||||
|  |  * WebSocket Protocol Constants | ||||||
|  |  * Based on RFC 6455 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket opcode types | ||||||
|  |  */ | ||||||
|  | export enum WebSocketOpcode { | ||||||
|  |   CONTINUATION = 0x0, | ||||||
|  |   TEXT = 0x1, | ||||||
|  |   BINARY = 0x2, | ||||||
|  |   CLOSE = 0x8, | ||||||
|  |   PING = 0x9, | ||||||
|  |   PONG = 0xa, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket close codes | ||||||
|  |  */ | ||||||
|  | export enum WebSocketCloseCode { | ||||||
|  |   NORMAL_CLOSURE = 1000, | ||||||
|  |   GOING_AWAY = 1001, | ||||||
|  |   PROTOCOL_ERROR = 1002, | ||||||
|  |   UNSUPPORTED_DATA = 1003, | ||||||
|  |   NO_STATUS_RECEIVED = 1005, | ||||||
|  |   ABNORMAL_CLOSURE = 1006, | ||||||
|  |   INVALID_FRAME_PAYLOAD_DATA = 1007, | ||||||
|  |   POLICY_VIOLATION = 1008, | ||||||
|  |   MESSAGE_TOO_BIG = 1009, | ||||||
|  |   MISSING_EXTENSION = 1010, | ||||||
|  |   INTERNAL_ERROR = 1011, | ||||||
|  |   SERVICE_RESTART = 1012, | ||||||
|  |   TRY_AGAIN_LATER = 1013, | ||||||
|  |   BAD_GATEWAY = 1014, | ||||||
|  |   TLS_HANDSHAKE = 1015, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket protocol version | ||||||
|  |  */ | ||||||
|  | export const WEBSOCKET_VERSION = 13; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket magic string for handshake | ||||||
|  |  */ | ||||||
|  | export const WEBSOCKET_MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket headers | ||||||
|  |  */ | ||||||
|  | export const WEBSOCKET_HEADERS = { | ||||||
|  |   UPGRADE: 'upgrade', | ||||||
|  |   CONNECTION: 'connection', | ||||||
|  |   SEC_WEBSOCKET_KEY: 'sec-websocket-key', | ||||||
|  |   SEC_WEBSOCKET_VERSION: 'sec-websocket-version', | ||||||
|  |   SEC_WEBSOCKET_ACCEPT: 'sec-websocket-accept', | ||||||
|  |   SEC_WEBSOCKET_PROTOCOL: 'sec-websocket-protocol', | ||||||
|  |   SEC_WEBSOCKET_EXTENSIONS: 'sec-websocket-extensions', | ||||||
|  | } as const; | ||||||
							
								
								
									
										8
									
								
								ts/protocols/websocket/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ts/protocols/websocket/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * WebSocket Protocol Module | ||||||
|  |  * WebSocket protocol utilities and constants | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export * from './constants.js'; | ||||||
|  | export * from './types.js'; | ||||||
|  | export * from './utils.js'; | ||||||
							
								
								
									
										53
									
								
								ts/protocols/websocket/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								ts/protocols/websocket/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | /** | ||||||
|  |  * WebSocket Protocol Type Definitions | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import type { WebSocketOpcode, WebSocketCloseCode } from './constants.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket frame header | ||||||
|  |  */ | ||||||
|  | export interface IWebSocketFrameHeader { | ||||||
|  |   fin: boolean; | ||||||
|  |   rsv1: boolean; | ||||||
|  |   rsv2: boolean; | ||||||
|  |   rsv3: boolean; | ||||||
|  |   opcode: WebSocketOpcode; | ||||||
|  |   masked: boolean; | ||||||
|  |   payloadLength: number; | ||||||
|  |   maskingKey?: Buffer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket frame | ||||||
|  |  */ | ||||||
|  | export interface IWebSocketFrame { | ||||||
|  |   header: IWebSocketFrameHeader; | ||||||
|  |   payload: Buffer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket close frame payload | ||||||
|  |  */ | ||||||
|  | export interface IWebSocketClosePayload { | ||||||
|  |   code: WebSocketCloseCode; | ||||||
|  |   reason?: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * WebSocket handshake request headers | ||||||
|  |  */ | ||||||
|  | export interface IWebSocketHandshakeHeaders { | ||||||
|  |   upgrade: string; | ||||||
|  |   connection: string; | ||||||
|  |   'sec-websocket-key': string; | ||||||
|  |   'sec-websocket-version': string; | ||||||
|  |   'sec-websocket-protocol'?: string; | ||||||
|  |   'sec-websocket-extensions'?: string; | ||||||
|  |   [key: string]: string | undefined; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Type for WebSocket raw data (matching ws library) | ||||||
|  |  */ | ||||||
|  | export type RawData = Buffer | ArrayBuffer | Buffer[] | any; | ||||||
							
								
								
									
										98
									
								
								ts/protocols/websocket/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								ts/protocols/websocket/utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | /** | ||||||
|  |  * WebSocket Protocol Utilities | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import * as crypto from 'crypto'; | ||||||
|  | import { WEBSOCKET_MAGIC_STRING } from './constants.js'; | ||||||
|  | import type { RawData } from './types.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get the length of a WebSocket message regardless of its type | ||||||
|  |  * (handles all possible WebSocket message data types) | ||||||
|  |  */ | ||||||
|  | export function getMessageSize(data: RawData): number { | ||||||
|  |   if (typeof data === 'string') { | ||||||
|  |     // For string data, get the byte length | ||||||
|  |     return Buffer.from(data, 'utf8').length; | ||||||
|  |   } else if (data instanceof Buffer) { | ||||||
|  |     // For Node.js Buffer | ||||||
|  |     return data.length; | ||||||
|  |   } else if (data instanceof ArrayBuffer) { | ||||||
|  |     // For ArrayBuffer | ||||||
|  |     return data.byteLength; | ||||||
|  |   } else if (Array.isArray(data)) { | ||||||
|  |     // For array of buffers, sum their lengths | ||||||
|  |     return data.reduce((sum, chunk) => { | ||||||
|  |       if (chunk instanceof Buffer) { | ||||||
|  |         return sum + chunk.length; | ||||||
|  |       } else if (chunk instanceof ArrayBuffer) { | ||||||
|  |         return sum + chunk.byteLength; | ||||||
|  |       } | ||||||
|  |       return sum; | ||||||
|  |     }, 0); | ||||||
|  |   } else { | ||||||
|  |     // For other types, try to determine the size or return 0 | ||||||
|  |     try { | ||||||
|  |       return Buffer.from(data).length; | ||||||
|  |     } catch (e) { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert any raw WebSocket data to Buffer for consistent handling | ||||||
|  |  */ | ||||||
|  | export function toBuffer(data: RawData): Buffer { | ||||||
|  |   if (typeof data === 'string') { | ||||||
|  |     return Buffer.from(data, 'utf8'); | ||||||
|  |   } else if (data instanceof Buffer) { | ||||||
|  |     return data; | ||||||
|  |   } else if (data instanceof ArrayBuffer) { | ||||||
|  |     return Buffer.from(data); | ||||||
|  |   } else if (Array.isArray(data)) { | ||||||
|  |     // For array of buffers, concatenate them | ||||||
|  |     return Buffer.concat(data.map(chunk => { | ||||||
|  |       if (chunk instanceof Buffer) { | ||||||
|  |         return chunk; | ||||||
|  |       } else if (chunk instanceof ArrayBuffer) { | ||||||
|  |         return Buffer.from(chunk); | ||||||
|  |       } | ||||||
|  |       return Buffer.from(chunk); | ||||||
|  |     })); | ||||||
|  |   } else { | ||||||
|  |     // For other types, try to convert to Buffer or return empty Buffer | ||||||
|  |     try { | ||||||
|  |       return Buffer.from(data); | ||||||
|  |     } catch (e) { | ||||||
|  |       return Buffer.alloc(0); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Generate WebSocket accept key from client key | ||||||
|  |  */ | ||||||
|  | export function generateAcceptKey(clientKey: string): string { | ||||||
|  |   const hash = crypto.createHash('sha1'); | ||||||
|  |   hash.update(clientKey + WEBSOCKET_MAGIC_STRING); | ||||||
|  |   return hash.digest('base64'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Validate WebSocket upgrade request | ||||||
|  |  */ | ||||||
|  | export function isWebSocketUpgrade(headers: Record<string, string>): boolean { | ||||||
|  |   const upgrade = headers['upgrade']; | ||||||
|  |   const connection = headers['connection']; | ||||||
|  |    | ||||||
|  |   return upgrade?.toLowerCase() === 'websocket' &&  | ||||||
|  |          connection?.toLowerCase().includes('upgrade'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Generate random WebSocket key for client handshake | ||||||
|  |  */ | ||||||
|  | export function generateWebSocketKey(): string { | ||||||
|  |   return crypto.randomBytes(16).toString('base64'); | ||||||
|  | } | ||||||
| @@ -1,4 +1,6 @@ | |||||||
| import * as plugins from '../../../plugins.js'; | import * as plugins from '../../../plugins.js'; | ||||||
|  | // Import from protocols for consistent status codes | ||||||
|  | import { HttpStatus as ProtocolHttpStatus, getStatusText as getProtocolStatusText } from '../../../protocols/http/index.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * HTTP-specific event types |  * HTTP-specific event types | ||||||
| @@ -10,34 +12,33 @@ export enum HttpEvents { | |||||||
|   REQUEST_ERROR = 'request-error', |   REQUEST_ERROR = 'request-error', | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * HTTP status codes as an enum for better type safety | // Re-export for backward compatibility with subset of commonly used codes | ||||||
|  */ | export const HttpStatus = { | ||||||
| export enum HttpStatus { |   OK: ProtocolHttpStatus.OK, | ||||||
|   OK = 200, |   MOVED_PERMANENTLY: ProtocolHttpStatus.MOVED_PERMANENTLY, | ||||||
|   MOVED_PERMANENTLY = 301, |   FOUND: ProtocolHttpStatus.FOUND, | ||||||
|   FOUND = 302, |   TEMPORARY_REDIRECT: ProtocolHttpStatus.TEMPORARY_REDIRECT, | ||||||
|   TEMPORARY_REDIRECT = 307, |   PERMANENT_REDIRECT: ProtocolHttpStatus.PERMANENT_REDIRECT, | ||||||
|   PERMANENT_REDIRECT = 308, |   BAD_REQUEST: ProtocolHttpStatus.BAD_REQUEST, | ||||||
|   BAD_REQUEST = 400, |   UNAUTHORIZED: ProtocolHttpStatus.UNAUTHORIZED, | ||||||
|   UNAUTHORIZED = 401, |   FORBIDDEN: ProtocolHttpStatus.FORBIDDEN, | ||||||
|   FORBIDDEN = 403, |   NOT_FOUND: ProtocolHttpStatus.NOT_FOUND, | ||||||
|   NOT_FOUND = 404, |   METHOD_NOT_ALLOWED: ProtocolHttpStatus.METHOD_NOT_ALLOWED, | ||||||
|   METHOD_NOT_ALLOWED = 405, |   REQUEST_TIMEOUT: ProtocolHttpStatus.REQUEST_TIMEOUT, | ||||||
|   REQUEST_TIMEOUT = 408, |   TOO_MANY_REQUESTS: ProtocolHttpStatus.TOO_MANY_REQUESTS, | ||||||
|   TOO_MANY_REQUESTS = 429, |   INTERNAL_SERVER_ERROR: ProtocolHttpStatus.INTERNAL_SERVER_ERROR, | ||||||
|   INTERNAL_SERVER_ERROR = 500, |   NOT_IMPLEMENTED: ProtocolHttpStatus.NOT_IMPLEMENTED, | ||||||
|   NOT_IMPLEMENTED = 501, |   BAD_GATEWAY: ProtocolHttpStatus.BAD_GATEWAY, | ||||||
|   BAD_GATEWAY = 502, |   SERVICE_UNAVAILABLE: ProtocolHttpStatus.SERVICE_UNAVAILABLE, | ||||||
|   SERVICE_UNAVAILABLE = 503, |   GATEWAY_TIMEOUT: ProtocolHttpStatus.GATEWAY_TIMEOUT, | ||||||
|   GATEWAY_TIMEOUT = 504, | } as const; | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Base error class for HTTP-related errors |  * Base error class for HTTP-related errors | ||||||
|  */ |  */ | ||||||
| export class HttpError extends Error { | export class HttpError extends Error { | ||||||
|   constructor(message: string, public readonly statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR) { |   constructor(message: string, public readonly statusCode: number = HttpStatus.INTERNAL_SERVER_ERROR) { | ||||||
|     super(message); |     super(message); | ||||||
|     this.name = 'HttpError'; |     this.name = 'HttpError'; | ||||||
|   } |   } | ||||||
| @@ -61,7 +62,7 @@ export class CertificateError extends HttpError { | |||||||
|  * Error related to server operations |  * Error related to server operations | ||||||
|  */ |  */ | ||||||
| export class ServerError extends HttpError { | export class ServerError extends HttpError { | ||||||
|   constructor(message: string, public readonly code?: string, statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR) { |   constructor(message: string, public readonly code?: string, statusCode: number = HttpStatus.INTERNAL_SERVER_ERROR) { | ||||||
|     super(message, statusCode); |     super(message, statusCode); | ||||||
|     this.name = 'ServerError'; |     this.name = 'ServerError'; | ||||||
|   } |   } | ||||||
| @@ -93,7 +94,7 @@ export class NotFoundError extends HttpError { | |||||||
| export interface IRedirectConfig { | export interface IRedirectConfig { | ||||||
|   source: string;           // Source path or pattern |   source: string;           // Source path or pattern | ||||||
|   destination: string;      // Destination URL   |   destination: string;      // Destination URL   | ||||||
|   type: HttpStatus;         // Redirect status code |   type: number;             // Redirect status code | ||||||
|   preserveQuery?: boolean;  // Whether to preserve query parameters |   preserveQuery?: boolean;  // Whether to preserve query parameters | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -115,30 +116,12 @@ export interface IRouterConfig { | |||||||
|  */ |  */ | ||||||
| export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE'; | export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE'; | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Helper function to get HTTP status text |  * Helper function to get HTTP status text | ||||||
|  */ |  */ | ||||||
| export function getStatusText(status: HttpStatus): string { | export function getStatusText(status: number): string { | ||||||
|   const statusTexts: Record<HttpStatus, string> = { |   return getProtocolStatusText(status as ProtocolHttpStatus); | ||||||
|     [HttpStatus.OK]: 'OK', |  | ||||||
|     [HttpStatus.MOVED_PERMANENTLY]: 'Moved Permanently', |  | ||||||
|     [HttpStatus.FOUND]: 'Found', |  | ||||||
|     [HttpStatus.TEMPORARY_REDIRECT]: 'Temporary Redirect', |  | ||||||
|     [HttpStatus.PERMANENT_REDIRECT]: 'Permanent Redirect', |  | ||||||
|     [HttpStatus.BAD_REQUEST]: 'Bad Request', |  | ||||||
|     [HttpStatus.UNAUTHORIZED]: 'Unauthorized', |  | ||||||
|     [HttpStatus.FORBIDDEN]: 'Forbidden', |  | ||||||
|     [HttpStatus.NOT_FOUND]: 'Not Found', |  | ||||||
|     [HttpStatus.METHOD_NOT_ALLOWED]: 'Method Not Allowed', |  | ||||||
|     [HttpStatus.REQUEST_TIMEOUT]: 'Request Timeout', |  | ||||||
|     [HttpStatus.TOO_MANY_REQUESTS]: 'Too Many Requests', |  | ||||||
|     [HttpStatus.INTERNAL_SERVER_ERROR]: 'Internal Server Error', |  | ||||||
|     [HttpStatus.NOT_IMPLEMENTED]: 'Not Implemented', |  | ||||||
|     [HttpStatus.BAD_GATEWAY]: 'Bad Gateway', |  | ||||||
|     [HttpStatus.SERVICE_UNAVAILABLE]: 'Service Unavailable', |  | ||||||
|     [HttpStatus.GATEWAY_TIMEOUT]: 'Gateway Timeout', |  | ||||||
|   }; |  | ||||||
|   return statusTexts[status] || 'Unknown'; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Legacy interfaces for backward compatibility | // Legacy interfaces for backward compatibility | ||||||
|   | |||||||
| @@ -1,22 +1,18 @@ | |||||||
| /** | /** | ||||||
|  * TLS module providing SNI extraction, TLS alerts, and other TLS-related utilities |  * TLS module for smartproxy | ||||||
|  |  * Re-exports protocol components and provides smartproxy-specific functionality | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| // Export TLS alert functionality | // Re-export all protocol components from protocols/tls | ||||||
| export * from './alerts/tls-alert.js'; | export * from '../protocols/tls/index.js'; | ||||||
|  |  | ||||||
| // Export SNI handling | // Export smartproxy-specific SNI handler | ||||||
| export * from './sni/sni-handler.js'; | export * from './sni/sni-handler.js'; | ||||||
| export * from './sni/sni-extraction.js'; |  | ||||||
| export * from './sni/client-hello-parser.js'; |  | ||||||
|  |  | ||||||
| // Export TLS utilities |  | ||||||
| export * from './utils/tls-utils.js'; |  | ||||||
|  |  | ||||||
| // Create a namespace for SNI utilities | // Create a namespace for SNI utilities | ||||||
| import { SniHandler } from './sni/sni-handler.js'; | import { SniHandler } from './sni/sni-handler.js'; | ||||||
| import { SniExtraction } from './sni/sni-extraction.js'; | import { SniExtraction } from '../protocols/tls/sni/sni-extraction.js'; | ||||||
| import { ClientHelloParser } from './sni/client-hello-parser.js'; | import { ClientHelloParser } from '../protocols/tls/sni/client-hello-parser.js'; | ||||||
|  |  | ||||||
| // Export utility objects for convenience | // Export utility objects for convenience | ||||||
| export const SNI = { | export const SNI = { | ||||||
|   | |||||||
| @@ -4,15 +4,15 @@ import { | |||||||
|   TlsHandshakeType, |   TlsHandshakeType, | ||||||
|   TlsExtensionType, |   TlsExtensionType, | ||||||
|   TlsUtils |   TlsUtils | ||||||
| } from '../utils/tls-utils.js'; | } from '../../protocols/tls/utils/tls-utils.js'; | ||||||
| import { | import { | ||||||
|   ClientHelloParser, |   ClientHelloParser, | ||||||
|   type LoggerFunction |   type LoggerFunction | ||||||
| } from './client-hello-parser.js'; | } from '../../protocols/tls/sni/client-hello-parser.js'; | ||||||
| import { | import { | ||||||
|   SniExtraction, |   SniExtraction, | ||||||
|   type ConnectionInfo |   type ConnectionInfo | ||||||
| } from './sni-extraction.js'; | } from '../../protocols/tls/sni/sni-extraction.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * SNI (Server Name Indication) handler for TLS connections. |  * SNI (Server Name Indication) handler for TLS connections. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user