@@ -14,7 +14,7 @@ import { CachedDns, dnsLookup, DnsLookupFunction } from '../util/dns';
1414import { isMockttpBody , encodeBodyBuffer } from '../util/request-utils' ;
1515import { areFFDHECurvesSupported } from '../util/openssl-compat' ;
1616import { ErrorLike , unreachableCheck } from '@httptoolkit/util' ;
17- import { getHeaderValue } from '../util/header-utils' ;
17+ import { findRawHeaderIndex , getHeaderValue } from '../util/header-utils' ;
1818
1919import {
2020 CallbackRequestResult ,
@@ -23,8 +23,11 @@ import {
2323import { AbortError } from './requests/request-step-impls' ;
2424import {
2525 CADefinition ,
26+ PassThroughInitialTransforms ,
2627 PassThroughLookupOptions
2728} from './passthrough-handling-definitions' ;
29+ import { getDefaultPort } from '../util/url' ;
30+ import { applyMatchReplace } from './match-replace' ;
2831
2932// TLS settings for proxied connections, intended to avoid TLS fingerprint blocking
3033// issues so far as possible, by closely emulating a Firefox Client Hello:
@@ -232,6 +235,91 @@ function deriveUrlLinkedHeader(
232235 return expectedValue ;
233236}
234237
238+ export function applyDestinationTransforms (
239+ transform : PassThroughInitialTransforms ,
240+ { isH2Downstream, rawHeaders, port, protocol, hostname, pathname, query } : {
241+ isH2Downstream : boolean ,
242+ rawHeaders : RawHeaders ,
243+ port : string | null
244+ protocol : string | null ,
245+ hostname : string ,
246+ pathname : string | null
247+ query : string | null
248+ } ,
249+ ) {
250+ const {
251+ setProtocol,
252+ replaceHost,
253+ matchReplaceHost,
254+ matchReplacePath,
255+ matchReplaceQuery,
256+ } = transform ;
257+
258+ if ( setProtocol ) {
259+ const wasDefaultPort = port === null || getDefaultPort ( protocol || 'http' ) === parseInt ( port , 10 ) ;
260+ protocol = setProtocol + ':' ;
261+
262+ // If we were on the default port, update that accordingly:
263+ if ( wasDefaultPort ) {
264+ port = getDefaultPort ( protocol ) . toString ( ) ;
265+ }
266+ }
267+
268+ if ( replaceHost ) {
269+ const { targetHost } = replaceHost ;
270+ [ hostname , port ] = targetHost . split ( ':' ) ;
271+ }
272+
273+ if ( matchReplaceHost ) {
274+ const result = applyMatchReplace ( port === null ? hostname ! : `${ hostname } :${ port } ` , matchReplaceHost . replacements ) ;
275+ [ hostname , port ] = result . split ( ':' ) ;
276+ }
277+
278+ if ( ( replaceHost ?. updateHostHeader ?? matchReplaceHost ?. updateHostHeader ) !== false ) {
279+ const updateHostHeader = replaceHost ?. updateHostHeader ?? matchReplaceHost ?. updateHostHeader ;
280+ const hostHeaderName = isH2Downstream ? ':authority' : 'host' ;
281+
282+ let hostHeaderIndex = findRawHeaderIndex ( rawHeaders , hostHeaderName ) ;
283+ let hostHeader : [ string , string ] ;
284+
285+ if ( hostHeaderIndex === - 1 ) {
286+ // Should never happen really, but just in case:
287+ hostHeader = [ hostHeaderName , hostname ! ] ;
288+ hostHeaderIndex = rawHeaders . length ;
289+ } else {
290+ // Clone this - we don't want to modify the original headers, as they're used for events
291+ hostHeader = _ . clone ( rawHeaders [ hostHeaderIndex ] ) ;
292+ }
293+ rawHeaders [ hostHeaderIndex ] = hostHeader ;
294+
295+ if ( updateHostHeader === undefined || updateHostHeader === true ) {
296+ // If updateHostHeader is true, or just not specified, match the new target
297+ hostHeader [ 1 ] = hostname + ( port ? `:${ port } ` : '' ) ;
298+ } else if ( updateHostHeader ) {
299+ // If it's an explicit custom value, use that directly.
300+ hostHeader [ 1 ] = updateHostHeader ;
301+ } // Otherwise: falsey means don't touch it.
302+ }
303+
304+ if ( matchReplacePath ) {
305+ pathname = applyMatchReplace ( pathname || '/' , matchReplacePath ) ;
306+ }
307+
308+ if ( matchReplaceQuery ) {
309+ query = applyMatchReplace ( query || '' , matchReplaceQuery ) ;
310+ }
311+
312+ return {
313+ reqUrl : new URL ( `${ protocol } //${ hostname } ${ ( port ? `:${ port } ` : '' ) } ${ pathname || '/' } ${ query || '' } ` ) . toString ( ) ,
314+ protocol,
315+ hostname,
316+ port,
317+ pathname,
318+ query,
319+ rawHeaders
320+ } ;
321+ }
322+
235323/**
236324 * Autocorrect the host header only in the case that if you didn't explicitly
237325 * override it yourself for some reason (e.g. if you're testing bad behaviour).
@@ -421,11 +509,11 @@ export const getDnsLookupFunction = _.memoize((lookupOptions: PassThroughLookupO
421509} ) ;
422510
423511export async function getClientRelativeHostname (
424- hostname : string | null ,
512+ hostname : string ,
425513 remoteIp : string | undefined ,
426514 lookupFn : DnsLookupFunction
427515) {
428- if ( ! hostname || ! remoteIp || isLocalhostAddress ( remoteIp ) ) return hostname ;
516+ if ( ! remoteIp || isLocalhostAddress ( remoteIp ) ) return hostname ;
429517
430518 // Otherwise, we have a request from a different machine (or Docker container/VM/etc) and we need
431519 // to make sure that 'localhost' means _that_ machine, not ourselves.
@@ -441,7 +529,7 @@ export async function getClientRelativeHostname(
441529 // effectively free. We ignore errors to delegate unresolvable etc to request processing later.
442530 isLocalhostAddress ( await dnsLookup ( lookupFn , hostname ) . catch ( ( ) => null ) )
443531 ) {
444- return normalizeIP ( remoteIp ) as string | null ;
532+ return normalizeIP ( remoteIp ) ;
445533
446534 // Note that we just redirect - we don't update the host header. From the POV of the target, it's still
447535 // 'localhost' traffic that should appear identical to normal.
0 commit comments