@@ -14,7 +14,7 @@ import { CachedDns, dnsLookup, DnsLookupFunction } from '../util/dns';
14
14
import { isMockttpBody , encodeBodyBuffer } from '../util/request-utils' ;
15
15
import { areFFDHECurvesSupported } from '../util/openssl-compat' ;
16
16
import { ErrorLike , unreachableCheck } from '@httptoolkit/util' ;
17
- import { getHeaderValue } from '../util/header-utils' ;
17
+ import { findRawHeaderIndex , getHeaderValue } from '../util/header-utils' ;
18
18
19
19
import {
20
20
CallbackRequestResult ,
@@ -23,8 +23,11 @@ import {
23
23
import { AbortError } from './requests/request-step-impls' ;
24
24
import {
25
25
CADefinition ,
26
+ PassThroughInitialTransforms ,
26
27
PassThroughLookupOptions
27
28
} from './passthrough-handling-definitions' ;
29
+ import { getDefaultPort } from '../util/url' ;
30
+ import { applyMatchReplace } from './match-replace' ;
28
31
29
32
// TLS settings for proxied connections, intended to avoid TLS fingerprint blocking
30
33
// issues so far as possible, by closely emulating a Firefox Client Hello:
@@ -232,6 +235,91 @@ function deriveUrlLinkedHeader(
232
235
return expectedValue ;
233
236
}
234
237
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
+
235
323
/**
236
324
* Autocorrect the host header only in the case that if you didn't explicitly
237
325
* override it yourself for some reason (e.g. if you're testing bad behaviour).
@@ -421,11 +509,11 @@ export const getDnsLookupFunction = _.memoize((lookupOptions: PassThroughLookupO
421
509
} ) ;
422
510
423
511
export async function getClientRelativeHostname (
424
- hostname : string | null ,
512
+ hostname : string ,
425
513
remoteIp : string | undefined ,
426
514
lookupFn : DnsLookupFunction
427
515
) {
428
- if ( ! hostname || ! remoteIp || isLocalhostAddress ( remoteIp ) ) return hostname ;
516
+ if ( ! remoteIp || isLocalhostAddress ( remoteIp ) ) return hostname ;
429
517
430
518
// Otherwise, we have a request from a different machine (or Docker container/VM/etc) and we need
431
519
// to make sure that 'localhost' means _that_ machine, not ourselves.
@@ -441,7 +529,7 @@ export async function getClientRelativeHostname(
441
529
// effectively free. We ignore errors to delegate unresolvable etc to request processing later.
442
530
isLocalhostAddress ( await dnsLookup ( lookupFn , hostname ) . catch ( ( ) => null ) )
443
531
) {
444
- return normalizeIP ( remoteIp ) as string | null ;
532
+ return normalizeIP ( remoteIp ) ;
445
533
446
534
// Note that we just redirect - we don't update the host header. From the POV of the target, it's still
447
535
// 'localhost' traffic that should appear identical to normal.
0 commit comments