99 "log/slog"
1010 "net"
1111 "net/http"
12- "net/http/httptest"
1312 "net/url"
1413 "strings"
1514 "sync"
@@ -325,55 +324,55 @@ func (p *Server) handleConnect(w http.ResponseWriter, r *http.Request) {
325324 p .logger .Debug ("HTTPS request handling completed" , "hostname" , hostname )
326325}
327326
328- // handleTLSConnection processes decrypted HTTPS requests over the TLS connection
327+ // handleTLSConnection processes decrypted HTTPS requests over the TLS connection with streaming support
329328func (p * Server ) handleTLSConnection (tlsConn * tls.Conn , hostname string ) {
330- p .logger .Debug ("Creating HTTP server for TLS connection" , "hostname" , hostname )
329+ p .logger .Debug ("Creating streaming HTTP handler for TLS connection" , "hostname" , hostname )
331330
332- // Set read timeout to detect hanging connections
333- tlsConn .SetReadDeadline (time .Now ().Add (5 * time .Second ))
334-
335- // Use ReadRequest to manually read HTTP requests from the TLS connection
331+ // Use streaming HTTP parsing instead of ReadRequest
336332 bufReader := bufio .NewReader (tlsConn )
337333 for {
338- // Read HTTP request from TLS connection
339- req , err := http . ReadRequest (bufReader )
334+ // Parse HTTP request headers incrementally
335+ req , err := p . parseHTTPRequestHeaders (bufReader , hostname )
340336 if err != nil {
341337 if err == io .EOF {
342338 p .logger .Debug ("TLS connection closed by client" , "hostname" , hostname )
343- } else if netErr , ok := err .(net.Error ); ok && netErr .Timeout () {
344- p .logger .Debug ("TLS connection read timeout - client not sending HTTP requests" , "hostname" , hostname )
345339 } else {
346- p .logger .Debug ("Failed to read HTTP request" , "hostname" , hostname , "error" , err )
340+ p .logger .Debug ("Failed to parse HTTP request headers " , "hostname" , hostname , "error" , err )
347341 }
348342 break
349343 }
350344
351- p .logger .Debug ("Processing decrypted HTTPS request" , "hostname" , hostname , "method" , req .Method , "path" , req .URL .Path )
345+ p .logger .Debug ("Processing streaming HTTPS request" , "hostname" , hostname , "method" , req .Method , "path" , req .URL .Path )
352346
353- // Set the hostname and scheme if not already set
354- if req .URL .Host == "" {
355- req .URL .Host = hostname
356- }
357- if req .URL .Scheme == "" {
358- req .URL .Scheme = "https"
347+ // Handle CONNECT method for HTTPS tunneling
348+ if req .Method == "CONNECT" {
349+ p .handleConnectStreaming (tlsConn , req , hostname )
350+ return // CONNECT takes over the entire connection
359351 }
360352
361- // Create a response recorder to capture the response
362- recorder := httptest .NewRecorder ()
353+ // Check if request should be allowed (based on headers only)
354+ fullURL := p .constructFullURL (req , hostname )
355+ result := p .ruleEngine .Evaluate (req .Method , fullURL )
363356
364- // Process the HTTPS request
365- p .handleDecryptedHTTPS (recorder , req )
357+ // Audit the request
358+ p .auditor .AuditRequest (audit.Request {
359+ Method : req .Method ,
360+ URL : fullURL ,
361+ Allowed : result .Allowed ,
362+ Rule : result .Rule ,
363+ })
366364
367- // Write the response back to the TLS connection
368- resp := recorder .Result ()
369- err = resp .Write (tlsConn )
365+ if ! result .Allowed {
366+ p .writeBlockedResponseStreaming (tlsConn , req )
367+ continue
368+ }
369+
370+ // Stream the request to target server
371+ err = p .streamRequestToTarget (tlsConn , bufReader , req , hostname )
370372 if err != nil {
371- p .logger .Debug ("Failed to write response " , "hostname" , hostname , "error" , err )
373+ p .logger .Debug ("Error streaming request " , "hostname" , hostname , "error" , err )
372374 break
373375 }
374-
375- // Reset read deadline for next request
376- tlsConn .SetReadDeadline (time .Now ().Add (5 * time .Second ))
377376 }
378377
379378 p .logger .Debug ("TLS connection handling completed" , "hostname" , hostname )
@@ -532,3 +531,154 @@ func (sl *singleConnectionListener) Addr() net.Addr {
532531 }
533532 return sl .conn .LocalAddr ()
534533}
534+
535+ // parseHTTPRequestHeaders parses HTTP request headers incrementally without reading the body
536+ func (p * Server ) parseHTTPRequestHeaders (bufReader * bufio.Reader , hostname string ) (* http.Request , error ) {
537+ // Read the request line (e.g., "GET /path HTTP/1.1")
538+ requestLine , _ , err := bufReader .ReadLine ()
539+ if err != nil {
540+ return nil , err
541+ }
542+
543+ // Parse request line
544+ parts := strings .Fields (string (requestLine ))
545+ if len (parts ) != 3 {
546+ return nil , fmt .Errorf ("invalid request line: %s" , requestLine )
547+ }
548+
549+ method := parts [0 ]
550+ requestURI := parts [1 ]
551+ proto := parts [2 ]
552+
553+ // Parse URL
554+ var url * url.URL
555+ if strings .HasPrefix (requestURI , "http://" ) || strings .HasPrefix (requestURI , "https://" ) {
556+ url , err = url .Parse (requestURI )
557+ } else {
558+ // Relative URL, construct with hostname
559+ url , err = url .Parse ("https://" + hostname + requestURI )
560+ }
561+ if err != nil {
562+ return nil , fmt .Errorf ("invalid request URI: %s" , requestURI )
563+ }
564+
565+ // Read headers
566+ headers := make (http.Header )
567+ for {
568+ headerLine , _ , err := bufReader .ReadLine ()
569+ if err != nil {
570+ return nil , err
571+ }
572+
573+ // Empty line indicates end of headers
574+ if len (headerLine ) == 0 {
575+ break
576+ }
577+
578+ // Parse header
579+ headerStr := string (headerLine )
580+ colonIdx := strings .Index (headerStr , ":" )
581+ if colonIdx == - 1 {
582+ continue // Skip malformed headers
583+ }
584+
585+ headerName := strings .TrimSpace (headerStr [:colonIdx ])
586+ headerValue := strings .TrimSpace (headerStr [colonIdx + 1 :])
587+ headers .Add (headerName , headerValue )
588+ }
589+
590+ // Create request object (without body)
591+ req := & http.Request {
592+ Method : method ,
593+ URL : url ,
594+ Proto : proto ,
595+ Header : headers ,
596+ Host : url .Host ,
597+ // Note: Body is intentionally nil - we'll stream it separately
598+ }
599+
600+ return req , nil
601+ }
602+
603+ // constructFullURL builds the full URL from request and hostname
604+ func (p * Server ) constructFullURL (req * http.Request , hostname string ) string {
605+ if req .URL .Host == "" {
606+ req .URL .Host = hostname
607+ }
608+ if req .URL .Scheme == "" {
609+ req .URL .Scheme = "https"
610+ }
611+ return req .URL .String ()
612+ }
613+
614+ // writeBlockedResponseStreaming writes a blocked response directly to the TLS connection
615+ func (p * Server ) writeBlockedResponseStreaming (tlsConn * tls.Conn , req * http.Request ) {
616+ response := fmt .Sprintf ("HTTP/1.1 403 Forbidden\r \n Content-Type: text/plain\r \n Connection: close\r \n \r \n 🚫 Request Blocked by Boundary\n \n Request: %s %s\n Host: %s\n \n To allow this request, restart boundary with:\n --allow \" %s\" \n " ,
617+ req .Method , req .URL .Path , req .Host , req .Host )
618+ tlsConn .Write ([]byte (response ))
619+ }
620+
621+ // streamRequestToTarget streams the HTTP request (including body) to the target server
622+ func (p * Server ) streamRequestToTarget (clientConn * tls.Conn , bufReader * bufio.Reader , req * http.Request , hostname string ) error {
623+ // Connect to target server
624+ targetConn , err := tls .Dial ("tcp" , hostname + ":443" , & tls.Config {ServerName : hostname })
625+ if err != nil {
626+ return fmt .Errorf ("failed to connect to target %s: %v" , hostname , err )
627+ }
628+ defer targetConn .Close ()
629+
630+ // Send HTTP request headers to target
631+ reqLine := fmt .Sprintf ("%s %s %s\r \n " , req .Method , req .URL .RequestURI (), req .Proto )
632+ targetConn .Write ([]byte (reqLine ))
633+
634+ // Send headers
635+ for name , values := range req .Header {
636+ for _ , value := range values {
637+ headerLine := fmt .Sprintf ("%s: %s\r \n " , name , value )
638+ targetConn .Write ([]byte (headerLine ))
639+ }
640+ }
641+ targetConn .Write ([]byte ("\r \n " )) // End of headers
642+
643+ // Stream request body and response bidirectionally
644+ go func () {
645+ // Stream request body: client -> target
646+ io .Copy (targetConn , bufReader )
647+ }()
648+
649+ // Stream response: target -> client
650+ io .Copy (clientConn , targetConn )
651+ return nil
652+ }
653+
654+ // handleConnectStreaming handles CONNECT requests with streaming TLS termination
655+ func (p * Server ) handleConnectStreaming (tlsConn * tls.Conn , req * http.Request , hostname string ) {
656+ p .logger .Debug ("Handling CONNECT request with streaming" , "hostname" , hostname )
657+
658+ // For CONNECT, we need to establish a tunnel but still maintain TLS termination
659+ // This is the tricky part - we're already inside a TLS connection from the client
660+ // The client is asking us to CONNECT to another server, but we want to intercept that too
661+
662+ // Send CONNECT response
663+ response := "HTTP/1.1 200 Connection established\r \n \r \n "
664+ tlsConn .Write ([]byte (response ))
665+
666+ // Now the client will try to do TLS handshake for the target server
667+ // But we want to intercept and terminate it
668+ // This means we need to do another level of TLS termination
669+
670+ // For now, let's create a simple tunnel and log that we're not inspecting
671+ p .logger .Warn ("CONNECT tunnel established - content not inspected" , "hostname" , hostname )
672+
673+ // Create connection to real target
674+ targetConn , err := net .Dial ("tcp" , req .Host )
675+ if err != nil {
676+ p .logger .Error ("Failed to connect to CONNECT target" , "target" , req .Host , "error" , err )
677+ return
678+ }
679+ defer targetConn .Close ()
680+
681+ // Bidirectional copy
682+ go io .Copy (targetConn , tlsConn )
683+ io .Copy (tlsConn , targetConn )
684+ }
0 commit comments