@@ -54,8 +54,8 @@ import {
5454 getHostFromAbsoluteUrl ,
5555 getDestination ,
5656 normalizeHost ,
57- getDefaultPort
5857} from "../util/url" ;
58+ import { isIP } from "../util/ip-utils" ;
5959import {
6060 buildRawSocketEventData ,
6161 buildTlsSocketEventData ,
@@ -614,35 +614,38 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
614614 ( req . socket [ LastHopEncrypted ] ? 'https' : 'http' ) ;
615615 req . path = req . url ;
616616
617- const tunnelUrlHost = (
618- req . socket [ LastTunnelAddress ] &&
619- ! net . isIP ( getDestination ( req . protocol , req . socket [ LastTunnelAddress ] ) . hostname )
620- )
621- ? normalizeHost ( req . protocol , req . socket [ LastTunnelAddress ] )
617+ const tunnelDestination = req . socket [ LastTunnelAddress ]
618+ ? getDestination ( req . protocol , req . socket [ LastTunnelAddress ] )
622619 : undefined ;
623620
624- // If you explicitly tunnel to a hostname, that's the URL's hostname:
625- const hostname = tunnelUrlHost
626- // Otherwise, we infer based on headers: HTTP/2 or HTTP/1
627- ?? getHeaderValue ( rawHeaders , ':authority' )
628- ?? getHeaderValue ( rawHeaders , 'host' )
629- ?? req . socket [ LastTunnelAddress ] // Iff we have no hostname available at all
630- ?? `localhost:${ this . port } ` ; // If you specify literally nothing, it's a direct request
621+ const isTunnelToIp = tunnelDestination && isIP ( tunnelDestination . hostname ) ;
631622
632- // Destination may be either a hostname or an IP (unlike tunnel host)
633- req . destination = getDestination (
634- req . protocol ,
635- req . socket [ LastTunnelAddress ] ?? hostname
623+ const urlDestination = getDestination ( req . protocol ,
624+ ( ! isTunnelToIp
625+ ? (
626+ req . socket [ LastTunnelAddress ] ?? // Tunnel domain name is preferred if available
627+ getHeaderValue ( rawHeaders , ':authority' ) ??
628+ getHeaderValue ( rawHeaders , 'host' )
629+ )
630+ : (
631+ getHeaderValue ( rawHeaders , ':authority' ) ??
632+ getHeaderValue ( rawHeaders , 'host' ) ??
633+ req . socket [ LastTunnelAddress ] // We use the IP iff we have no hostname available at all
634+ ) )
635+ ?? `localhost:${ this . port } ` // If you specify literally nothing, it's a direct request
636636 ) ;
637637
638- // If we don't have a port in the hostname, but we know the final destination port needs
639- // specifying, then we do include it in the URL. Happens if you have an IP tunnel address
640- // with a port, and then a port-less 'Host' header - not common.
641- const host = ! hostname . includes ( ':' ) && req . destination . port !== getDefaultPort ( req . protocol )
642- ? `${ hostname } :${ req . destination . port } `
643- : hostname ;
644638
645- const absoluteUrl = `${ req . protocol } ://${ host } ${ req . path } ` ;
639+ // Actual destination always follows the tunnel - even if it's an IP
640+ req . destination = tunnelDestination
641+ ?? urlDestination ;
642+
643+ // URL port should always match the real port - even if (e.g) the Host header is lying.
644+ urlDestination . port = req . destination . port ;
645+
646+ const absoluteUrl = `${ req . protocol } ://${
647+ normalizeHost ( req . protocol , `${ urlDestination . hostname } :${ urlDestination . port } ` )
648+ } ${ req . path } `;
646649
647650 if ( ! getHeaderValue ( rawHeaders , ':path' ) ) {
648651 ( req as Mutable < ExtendedRawRequest > ) . url = new url . URL ( absoluteUrl ) . toString ( ) ;
0 commit comments