feat(protocols): refactor protocol utilities into centralized protocols module
This commit is contained in:
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user