/** * Parser utilities for protocol detection */ import type { THttpMethod, TTlsVersion } from '../models/detection-types.js'; /** * Valid HTTP methods */ 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 */ export function parseHttpRequestLine(line: string): { method: THttpMethod; path: string; version: string; } | null { const parts = line.trim().split(' '); if (parts.length !== 3) { return null; } const [method, path, version] = parts; // 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 */ export function parseHttpHeader(line: string): { name: string; value: string } | 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 */ export function parseHttpHeaders(lines: string[]): Record { const headers: Record = {}; 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 */ export function tlsVersionToString(major: number, minor: number): TTlsVersion | 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; } /** * Extract domain from Host header value */ export function 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 */ export function 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}(?