import * as plugins from '../../plugins.js'; import '../../core/models/socket-augmentation.js'; import type { IRouteContext, IHttpRouteContext, IHttp2RouteContext } from '../../core/models/route-context.js'; /** * Context creator for NetworkProxy * Creates route contexts for matching and function evaluation */ export class ContextCreator { /** * Create a route context from HTTP request information */ public createHttpRouteContext(req: any, options: { tlsVersion?: string; connectionId: string; clientIp: string; serverIp: string; }): IHttpRouteContext { // Parse headers const headers: Record = {}; for (const [key, value] of Object.entries(req.headers)) { if (typeof value === 'string') { headers[key.toLowerCase()] = value; } else if (Array.isArray(value) && value.length > 0) { headers[key.toLowerCase()] = value[0]; } } // Parse domain from Host header const domain = headers['host']?.split(':')[0] || ''; // Parse URL const url = new URL(`http://${domain}${req.url || '/'}`); return { // Connection basics port: req.socket.localPort || 0, domain, clientIp: options.clientIp, serverIp: options.serverIp, // HTTP specifics path: url.pathname, query: url.search ? url.search.substring(1) : '', headers, // TLS information isTls: !!req.socket.encrypted, tlsVersion: options.tlsVersion, // Request objects req, // Metadata timestamp: Date.now(), connectionId: options.connectionId }; } /** * Create a route context from HTTP/2 stream and headers */ public createHttp2RouteContext( stream: plugins.http2.ServerHttp2Stream, headers: plugins.http2.IncomingHttpHeaders, options: { connectionId: string; clientIp: string; serverIp: string; } ): IHttp2RouteContext { // Parse headers, excluding HTTP/2 pseudo-headers const processedHeaders: Record = {}; for (const [key, value] of Object.entries(headers)) { if (!key.startsWith(':') && typeof value === 'string') { processedHeaders[key.toLowerCase()] = value; } } // Get domain from :authority pseudo-header const authority = headers[':authority'] as string || ''; const domain = authority.split(':')[0]; // Get path from :path pseudo-header const path = headers[':path'] as string || '/'; // Parse the path to extract query string const pathParts = path.split('?'); const pathname = pathParts[0]; const query = pathParts.length > 1 ? pathParts[1] : ''; // Get the socket from the session const socket = (stream.session as any)?.socket; return { // Connection basics port: socket?.localPort || 0, domain, clientIp: options.clientIp, serverIp: options.serverIp, // HTTP specifics path: pathname, query, headers: processedHeaders, // HTTP/2 specific properties method: headers[':method'] as string, stream, // TLS information - HTTP/2 is always on TLS in browsers isTls: true, tlsVersion: socket?.getTLSVersion?.() || 'TLSv1.3', // Metadata timestamp: Date.now(), connectionId: options.connectionId }; } /** * Create a basic route context from socket information */ public createSocketRouteContext(socket: plugins.net.Socket, options: { domain?: string; tlsVersion?: string; connectionId: string; }): IRouteContext { return { // Connection basics port: socket.localPort || 0, domain: options.domain, clientIp: socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0', serverIp: socket.localAddress?.replace('::ffff:', '') || '0.0.0.0', // TLS information isTls: options.tlsVersion !== undefined, tlsVersion: options.tlsVersion, // Metadata timestamp: Date.now(), connectionId: options.connectionId }; } }