feat(protocols): refactor protocol utilities into centralized protocols module
Some checks failed
Default (tags) / security (push) Successful in 55s
Default (tags) / test (push) Failing after 30m45s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped

This commit is contained in:
Juergen Kunz
2025-07-21 22:37:45 +00:00
parent d47b048517
commit 36068a6d92
32 changed files with 1155 additions and 394 deletions

View File

@@ -8,10 +8,12 @@ import type { IDetectionResult, IDetectionOptions, IConnectionInfo } from '../mo
import { readUInt16BE, readUInt24BE, BufferAccumulator } from '../utils/buffer-utils.js';
import { tlsVersionToString } from '../utils/parser-utils.js';
// Import existing TLS utilities
import { TlsUtils, TlsRecordType, TlsHandshakeType, TlsExtensionType } from '../../tls/utils/tls-utils.js';
import { SniExtraction } from '../../tls/sni/sni-extraction.js';
import { ClientHelloParser } from '../../tls/sni/client-hello-parser.js';
// Import from protocols
import { TlsRecordType, TlsHandshakeType, TlsExtensionType } from '../../protocols/tls/index.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
@@ -92,7 +94,7 @@ export class TlsDetector implements IProtocolDetector {
protocol: 'tls',
connectionInfo,
remainingBuffer: buffer.length > totalRecordLength
? buffer.slice(totalRecordLength)
? buffer.subarray(totalRecordLength)
: undefined,
isComplete: true
};
@@ -114,7 +116,7 @@ export class TlsDetector implements IProtocolDetector {
connectionInfo,
isComplete: true,
remainingBuffer: buffer.length > recordLength + 5
? buffer.slice(recordLength + 5)
? buffer.subarray(recordLength + 5)
: undefined
};
}
@@ -185,7 +187,7 @@ export class TlsDetector implements IProtocolDetector {
offset++;
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);
offset += protoLength;
} else {

View File

@@ -2,6 +2,9 @@
* Buffer manipulation utilities for protocol detection
*/
// Import from protocols
import { HttpParser } from '../../protocols/http/index.js';
/**
* 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)
*/
export function extractLine(buffer: Buffer, startOffset = 0): { line: string; nextOffset: number } | null {
let lineEnd = -1;
let skipBytes = 1;
// 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
};
// Delegate to protocol parser
return HttpParser.extractLine(buffer, startOffset);
}
/**
@@ -158,17 +136,6 @@ export function safeSlice(buffer: Buffer, start: number, end?: number): Buffer {
* Check if buffer contains printable ASCII
*/
export function isPrintableAscii(buffer: Buffer, length?: number): boolean {
const checkLength = length || 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;
// Delegate to protocol parser
return HttpParser.isPrintableAscii(buffer, length);
}

View File

@@ -1,20 +1,14 @@
/**
* Parser utilities for protocol detection
* Now delegates to protocol modules for actual parsing
*/
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';
/**
* 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'];
// Re-export constants for backward compatibility
export { HTTP_METHODS, HTTP_VERSIONS };
/**
* Parse HTTP request line
@@ -24,118 +18,60 @@ export function parseHttpRequestLine(line: string): {
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
};
// Delegate to protocol parser
const result = HttpParser.parseRequestLine(line);
return result ? {
method: result.method as THttpMethod,
path: result.path,
version: result.version
} : null;
}
/**
* 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 };
// Delegate to protocol parser
return HttpParser.parseHeaderLine(line);
}
/**
* 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;
// Delegate to protocol parser
return HttpParser.parseHeaders(lines);
}
/**
* 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;
// Delegate to protocol parser
return protocolTlsVersionToString(major, minor) as TTlsVersion;
}
/**
* 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;
// Delegate to protocol parser
return HttpParser.extractDomainFromHost(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);
// Delegate to protocol parser
return HttpParser.isValidDomain(domain);
}
/**
* Check if string is a valid HTTP method
*/
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;
}