@@ -33,10 +33,11 @@ import {
33
33
TlsMetadata ,
34
34
TlsSetupCompleted ,
35
35
getAddressAndPort ,
36
- resetOrDestroy
36
+ resetOrDestroy ,
37
+ SocketMetadata
37
38
} from '../util/socket-util' ;
38
39
import { MockttpHttpsOptions } from '../mockttp' ;
39
- import { buildSocksServer , SocksTcpAddress } from './socks-server' ;
40
+ import { buildSocksServer , SocksServerOptions , SocksTcpAddress } from './socks-server' ;
40
41
41
42
// Hardcore monkey-patching: force TLSSocket to link servername & remoteAddress to
42
43
// sockets as soon as they're available, without waiting for the handshake to fully
@@ -145,7 +146,7 @@ export interface ComboServerOptions {
145
146
debug : boolean ;
146
147
https : MockttpHttpsOptions | undefined ;
147
148
http2 : boolean | 'fallback' ;
148
- socks : boolean ;
149
+ socks : boolean | SocksServerOptions ;
149
150
passthroughUnknownProtocols : boolean ;
150
151
151
152
requestListener : ( req : http . IncomingMessage , res : http . ServerResponse ) => void ;
@@ -225,7 +226,7 @@ export async function createComboServer(options: ComboServerOptions): Promise<De
225
226
}
226
227
227
228
if ( options . socks ) {
228
- socksServer = buildSocksServer ( ) ;
229
+ socksServer = buildSocksServer ( options . socks === true ? { } : options . socks ) ;
229
230
socksServer . on ( 'socks-tcp-connect' , ( socket : net . Socket , address : SocksTcpAddress ) => {
230
231
const addressString =
231
232
address . type === 'ipv4'
@@ -291,8 +292,7 @@ export async function createComboServer(options: ComboServerOptions): Promise<De
291
292
if ( parentSocket ) {
292
293
// Sometimes wrapper TLS sockets created by the HTTP/2 server don't include the
293
294
// underlying socket details, so it's better to make sure we copy them up.
294
- copyAddressDetails ( parentSocket , socket ) ;
295
- copyTimingDetails ( parentSocket , socket ) ;
295
+ inheritSocketDetails ( parentSocket , socket ) ;
296
296
// With TLS metadata, we only propagate directly from parent sockets, not through
297
297
// CONNECT etc - we only want it if the final hop is TLS, previous values don't matter.
298
298
socket [ TlsMetadata ] ??= parentSocket [ TlsMetadata ] ;
@@ -371,8 +371,7 @@ export async function createComboServer(options: ComboServerOptions): Promise<De
371
371
372
372
// Send a 200 OK response, and start the tunnel:
373
373
res . writeHead ( 200 , { } ) ;
374
- copyAddressDetails ( res . socket , res . stream ) ;
375
- copyTimingDetails ( res . socket , res . stream ) ;
374
+ inheritSocketDetails ( res . socket , res . stream ) ;
376
375
res . stream [ LastTunnelAddress ] = connectUrl ;
377
376
378
377
// When layering HTTP/2 on JS streams, we have to make sure the JS stream won't autoclose
@@ -390,39 +389,37 @@ export async function createComboServer(options: ComboServerOptions): Promise<De
390
389
}
391
390
392
391
393
- const SOCKET_ADDRESS_METADATA_FIELDS = [
392
+ const SOCKET_METADATA = [
394
393
'localAddress' ,
395
394
'localPort' ,
396
395
'remoteAddress' ,
397
396
'remotePort' ,
397
+ SocketTimingInfo ,
398
+ SocketMetadata ,
398
399
LastTunnelAddress
399
400
] as const ;
400
401
401
- // Update the target socket(-ish) with the address details from the source socket,
402
- // iff the target has no details of its own.
403
- function copyAddressDetails (
404
- source : SocketIsh < typeof SOCKET_ADDRESS_METADATA_FIELDS [ number ] > ,
405
- target : SocketIsh < typeof SOCKET_ADDRESS_METADATA_FIELDS [ number ] >
402
+ function inheritSocketDetails (
403
+ source : SocketIsh < typeof SOCKET_METADATA [ number ] > ,
404
+ target : SocketIsh < typeof SOCKET_METADATA [ number ] >
406
405
) {
406
+ // Update the target socket(-ish) with the assorted metadata from the source socket,
407
+ // iff the target has no details of its own.
408
+
409
+ // Make sure all properties are writable - HTTP/2 streams notably try to block this.
407
410
Object . defineProperties ( target , _ . zipObject (
408
- SOCKET_ADDRESS_METADATA_FIELDS ,
409
- _ . range ( SOCKET_ADDRESS_METADATA_FIELDS . length ) . map ( ( ) => ( { writable : true } ) )
411
+ SOCKET_METADATA ,
412
+ _ . range ( SOCKET_METADATA . length ) . map ( ( ) => ( { writable : true } ) )
410
413
) as PropertyDescriptorMap ) ;
411
414
412
- SOCKET_ADDRESS_METADATA_FIELDS . forEach ( ( fieldName ) => {
415
+ for ( let fieldName of SOCKET_METADATA ) {
413
416
if ( target [ fieldName ] === undefined ) {
414
- ( target as any ) [ fieldName ] = source [ fieldName ] ;
417
+ if ( typeof source [ fieldName ] === 'object' ) {
418
+ ( target as any ) [ fieldName ] = _ . cloneDeep ( source [ fieldName ] ) ;
419
+ } else {
420
+ ( target as any ) [ fieldName ] = source [ fieldName ] ;
421
+ }
415
422
}
416
- } ) ;
417
- }
418
-
419
- function copyTimingDetails < T extends SocketIsh < typeof SocketTimingInfo > > (
420
- source : SocketIsh < typeof SocketTimingInfo > ,
421
- target : T
422
- ) : asserts target is T & { [ SocketTimingInfo ] : Required < net . Socket > [ typeof SocketTimingInfo ] } {
423
- if ( ! target [ SocketTimingInfo ] ) {
424
- // Clone timing info, don't copy it - child sockets get their own independent timing stats
425
- target [ SocketTimingInfo ] = Object . assign ( { } , source [ SocketTimingInfo ] ) ;
426
423
}
427
424
}
428
425
0 commit comments