142 lines
3.0 KiB
TypeScript
142 lines
3.0 KiB
TypeScript
![]() |
/**
|
||
|
* 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<string, string> {
|
||
|
const headers: Record<string, string> = {};
|
||
|
|
||
|
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}(?<!-)(\.[A-Za-z0-9-]{1,63})*$/;
|
||
|
return domainRegex.test(domain);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if string is a valid HTTP method
|
||
|
*/
|
||
|
export function isHttpMethod(str: string): str is THttpMethod {
|
||
|
return HTTP_METHODS.includes(str as THttpMethod);
|
||
|
}
|
||
|
|