@zuplo/cli 6.71.0 → 6.71.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/node_modules/@fastify/reply-from/node_modules/undici/docs/docs/api/Client.md +3 -0
  2. package/node_modules/@fastify/reply-from/node_modules/undici/docs/docs/api/Cookies.md +27 -0
  3. package/node_modules/@fastify/reply-from/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +1 -0
  4. package/node_modules/@fastify/reply-from/node_modules/undici/lib/core/socks5-client.js +14 -4
  5. package/node_modules/@fastify/reply-from/node_modules/undici/lib/core/socks5-utils.js +18 -4
  6. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/agent.js +1 -1
  7. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/balanced-pool.js +1 -1
  8. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/client-h1.js +69 -1
  9. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/client.js +3 -2
  10. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/dispatcher-base.js +19 -0
  11. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/pool.js +1 -1
  12. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/proxy-agent.js +2 -1
  13. package/node_modules/@fastify/reply-from/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +45 -34
  14. package/node_modules/@fastify/reply-from/node_modules/undici/lib/util/cache.js +8 -2
  15. package/node_modules/@fastify/reply-from/node_modules/undici/lib/web/cookies/parse.js +17 -25
  16. package/node_modules/@fastify/reply-from/node_modules/undici/lib/web/websocket/permessage-deflate.js +13 -31
  17. package/node_modules/@fastify/reply-from/node_modules/undici/lib/web/websocket/receiver.js +80 -23
  18. package/node_modules/@fastify/reply-from/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -1
  19. package/node_modules/@fastify/reply-from/node_modules/undici/lib/web/websocket/websocket.js +8 -1
  20. package/node_modules/@fastify/reply-from/node_modules/undici/package.json +1 -1
  21. package/node_modules/@fastify/reply-from/node_modules/undici/types/client.d.ts +16 -0
  22. package/node_modules/@zuplo/core/package.json +1 -1
  23. package/node_modules/@zuplo/graphql/package.json +1 -1
  24. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  25. package/node_modules/@zuplo/otel/package.json +1 -1
  26. package/node_modules/@zuplo/runtime/package.json +1 -1
  27. package/node_modules/body-parser/README.md +16 -1
  28. package/node_modules/body-parser/index.js +4 -20
  29. package/node_modules/body-parser/lib/read.js +17 -17
  30. package/node_modules/body-parser/lib/types/json.js +60 -32
  31. package/node_modules/body-parser/lib/types/raw.js +3 -3
  32. package/node_modules/body-parser/lib/types/text.js +3 -3
  33. package/node_modules/body-parser/lib/types/urlencoded.js +16 -20
  34. package/node_modules/body-parser/lib/utils.js +18 -16
  35. package/node_modules/body-parser/node_modules/content-type/LICENSE +22 -0
  36. package/node_modules/body-parser/node_modules/content-type/README.md +69 -0
  37. package/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +26 -0
  38. package/node_modules/body-parser/node_modules/content-type/dist/index.js +170 -0
  39. package/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -0
  40. package/node_modules/body-parser/node_modules/content-type/package.json +52 -0
  41. package/node_modules/body-parser/node_modules/http-errors/HISTORY.md +186 -0
  42. package/node_modules/body-parser/node_modules/http-errors/LICENSE +23 -0
  43. package/node_modules/body-parser/node_modules/http-errors/README.md +169 -0
  44. package/node_modules/body-parser/node_modules/http-errors/index.js +290 -0
  45. package/node_modules/body-parser/node_modules/http-errors/package.json +54 -0
  46. package/node_modules/body-parser/package.json +23 -10
  47. package/package.json +6 -6
@@ -24,6 +24,9 @@ Returns: `Client`
24
24
  * **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `2e3` - A number of milliseconds subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 2 seconds.
25
25
  * **maxHeaderSize** `number | null` (optional) - Default: `--max-http-header-size` or `16384` - The maximum length of request headers in bytes. Defaults to Node.js' --max-http-header-size or 16KiB.
26
26
  * **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable.
27
+ * **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options.
28
+ * **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit.
29
+ * **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit.
27
30
  * **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections.
28
31
  * **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
29
32
  * **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. **Security Warning:** Disabling this option can expose your application to HTTP Request Smuggling attacks, where mismatched content-length headers cause servers and proxies to interpret request boundaries differently. This can lead to cache poisoning, credential hijacking, and bypassing security controls. Only disable this in controlled environments where you fully trust the request source.
@@ -80,6 +80,33 @@ Arguments:
80
80
 
81
81
  Returns: `Cookie[]`
82
82
 
83
+ ## `parseCookie(cookie)`
84
+
85
+ Parses a single `Set-Cookie` header value into a `Cookie` object.
86
+
87
+ ```js
88
+ import { parseCookie } from 'undici'
89
+
90
+ console.log(parseCookie('undici=getSetCookies; Secure; SameSite=Lax'))
91
+ // {
92
+ // name: 'undici',
93
+ // value: 'getSetCookies',
94
+ // secure: true,
95
+ // sameSite: 'Lax'
96
+ // }
97
+ ```
98
+
99
+ Notes:
100
+
101
+ * The cookie value is returned as it appears in the header. Percent-encoded sequences such as `%20` or `%0D%0A` are **not** decoded.
102
+ * `sameSite` is only set for exact case-insensitive matches of `Strict`, `Lax`, or `None`.
103
+
104
+ Arguments:
105
+
106
+ * **cookie** `string`
107
+
108
+ Returns: `Cookie | null`
109
+
83
110
  ## `setCookie(headers, cookie)`
84
111
 
85
112
  Appends a cookie to the `Set-Cookie` header.
@@ -22,6 +22,7 @@ Extends: [`PoolOptions`](/docs/docs/api/Pool.md#parameter-pooloptions)
22
22
  * **password** `string` (optional) - SOCKS5 proxy password for authentication. Can also be provided in the proxy URL.
23
23
  * **connect** `Function` (optional) - Custom connector function for the proxy connection.
24
24
  * **proxyTls** `BuildOptions` (optional) - TLS options for the proxy connection (when using SOCKS5 over TLS).
25
+ * **requestTls** `BuildOptions` (optional) - TLS options applied to the HTTPS connection to the target server through the SOCKS5 tunnel. Use this to configure `ca`, `cert`, `key`, `rejectUnauthorized`, `servername`, etc. for the target HTTPS endpoint.
25
26
 
26
27
  Examples:
27
28
 
@@ -52,6 +52,7 @@ const STATES = {
52
52
  INITIAL: 'initial',
53
53
  HANDSHAKING: 'handshaking',
54
54
  AUTHENTICATING: 'authenticating',
55
+ AUTHENTICATED: 'authenticated',
55
56
  CONNECTING: 'connecting',
56
57
  CONNECTED: 'connected',
57
58
  ERROR: 'error',
@@ -143,6 +144,11 @@ class Socks5Client extends EventEmitter {
143
144
  }
144
145
  }
145
146
 
147
+ markAuthenticated () {
148
+ this.state = STATES.AUTHENTICATED
149
+ this.emit('authenticated')
150
+ }
151
+
146
152
  /**
147
153
  * Start the SOCKS5 handshake
148
154
  */
@@ -193,7 +199,7 @@ class Socks5Client extends EventEmitter {
193
199
  debug('server selected auth method', method)
194
200
 
195
201
  if (method === AUTH_METHODS.NO_AUTH) {
196
- this.emit('authenticated')
202
+ this.markAuthenticated()
197
203
  } else if (method === AUTH_METHODS.USERNAME_PASSWORD) {
198
204
  this.state = STATES.AUTHENTICATING
199
205
  this.sendAuthRequest()
@@ -258,7 +264,7 @@ class Socks5Client extends EventEmitter {
258
264
 
259
265
  this.buffer = this.buffer.subarray(2)
260
266
  debug('authentication successful')
261
- this.emit('authenticated')
267
+ this.markAuthenticated()
262
268
  }
263
269
 
264
270
  /**
@@ -267,8 +273,12 @@ class Socks5Client extends EventEmitter {
267
273
  * @param {number} port - Target port
268
274
  */
269
275
  connect (address, port) {
270
- if (this.state === STATES.CONNECTED) {
271
- throw new InvalidArgumentError('Already connected')
276
+ if (this.state === STATES.CONNECTING || this.state === STATES.CONNECTED) {
277
+ throw new InvalidArgumentError('Connection already in progress')
278
+ }
279
+
280
+ if (this.state !== STATES.AUTHENTICATED) {
281
+ throw new InvalidArgumentError('Client must be authenticated before CONNECT')
272
282
  }
273
283
 
274
284
  debug('connecting to', address, port)
@@ -46,12 +46,26 @@ function parseAddress (address) {
46
46
  */
47
47
  function parseIPv6 (address) {
48
48
  const buffer = Buffer.alloc(16)
49
+ let normalizedAddress = address
50
+
51
+ // Expand an embedded IPv4 tail into the last two IPv6 groups.
52
+ if (address.includes('.')) {
53
+ const lastColonIndex = address.lastIndexOf(':')
54
+ const ipv4Part = address.slice(lastColonIndex + 1)
55
+
56
+ if (net.isIPv4(ipv4Part)) {
57
+ const octets = ipv4Part.split('.').map(Number)
58
+ const high = ((octets[0] << 8) | octets[1]).toString(16)
59
+ const low = ((octets[2] << 8) | octets[3]).toString(16)
60
+ normalizedAddress = `${address.slice(0, lastColonIndex)}:${high}:${low}`
61
+ }
62
+ }
49
63
 
50
64
  // Handle compressed notation (::)
51
- const doubleColonIndex = address.indexOf('::')
65
+ const doubleColonIndex = normalizedAddress.indexOf('::')
52
66
  if (doubleColonIndex !== -1) {
53
- const before = address.slice(0, doubleColonIndex)
54
- const after = address.slice(doubleColonIndex + 2)
67
+ const before = normalizedAddress.slice(0, doubleColonIndex)
68
+ const after = normalizedAddress.slice(doubleColonIndex + 2)
55
69
  const beforeParts = before === '' ? [] : before.split(':')
56
70
  const afterParts = after === '' ? [] : after.split(':')
57
71
 
@@ -66,7 +80,7 @@ function parseIPv6 (address) {
66
80
  bufferIndex += 2
67
81
  }
68
82
  } else {
69
- const parts = address.split(':')
83
+ const parts = normalizedAddress.split(':')
70
84
  for (let i = 0; i < parts.length; i++) {
71
85
  buffer.writeUInt16BE(parseInt(parts[i], 16), i * 2)
72
86
  }
@@ -35,7 +35,7 @@ class Agent extends DispatcherBase {
35
35
  throw new InvalidArgumentError('maxOrigins must be a number greater than 0')
36
36
  }
37
37
 
38
- super()
38
+ super(options)
39
39
 
40
40
  if (connect && typeof connect !== 'function') {
41
41
  connect = { ...connect }
@@ -54,7 +54,7 @@ class BalancedPool extends PoolBase {
54
54
  throw new InvalidArgumentError('factory must be a function.')
55
55
  }
56
56
 
57
- super()
57
+ super(opts)
58
58
 
59
59
  this[kOptions] = { ...util.deepClone(opts) }
60
60
  this[kOptions].interceptors = opts.interceptors
@@ -57,6 +57,9 @@ const constants = require('../llhttp/constants.js')
57
57
  const EMPTY_BUF = Buffer.alloc(0)
58
58
  const FastBuffer = Buffer[Symbol.species]
59
59
  const removeAllListeners = util.removeAllListeners
60
+ const kIdleSocketValidation = Symbol('kIdleSocketValidation')
61
+ const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
62
+ const kSocketUsed = Symbol('kSocketUsed')
60
63
 
61
64
  let extractBody
62
65
 
@@ -440,6 +443,11 @@ class Parser {
440
443
  return -1
441
444
  }
442
445
 
446
+ if (client[kRunning] === 0) {
447
+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
448
+ return -1
449
+ }
450
+
443
451
  const request = client[kQueue][client[kRunningIdx]]
444
452
  if (!request) {
445
453
  return -1
@@ -568,6 +576,11 @@ class Parser {
568
576
  return -1
569
577
  }
570
578
 
579
+ if (client[kRunning] === 0) {
580
+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
581
+ return -1
582
+ }
583
+
571
584
  const request = client[kQueue][client[kRunningIdx]]
572
585
 
573
586
  if (!request) {
@@ -746,6 +759,7 @@ class Parser {
746
759
  request.onComplete(headers)
747
760
 
748
761
  client[kQueue][client[kRunningIdx]++] = null
762
+ socket[kSocketUsed] = client[kPending] === 0
749
763
 
750
764
  if (socket[kWriting]) {
751
765
  assert(client[kRunning] === 0)
@@ -822,6 +836,9 @@ function connectH1 (client, socket) {
822
836
  socket[kWriting] = false
823
837
  socket[kReset] = false
824
838
  socket[kBlocking] = false
839
+ socket[kIdleSocketValidation] = 0
840
+ socket[kIdleSocketValidationTimeout] = null
841
+ socket[kSocketUsed] = false
825
842
  socket[kParser] = new Parser(client, socket, llhttpInstance)
826
843
 
827
844
  util.addListener(socket, 'error', onHttpSocketError)
@@ -864,7 +881,7 @@ function connectH1 (client, socket) {
864
881
  * @returns {boolean}
865
882
  */
866
883
  busy (request) {
867
- if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
884
+ if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
868
885
  return true
869
886
  }
870
887
 
@@ -944,6 +961,8 @@ function onHttpSocketEnd () {
944
961
  function onHttpSocketClose () {
945
962
  const parser = this[kParser]
946
963
 
964
+ clearIdleSocketValidation(this)
965
+
947
966
  if (parser) {
948
967
  if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
949
968
  this[kError] = parser.finish() || this[kError]
@@ -990,6 +1009,28 @@ function onSocketClose () {
990
1009
  this[kClosed] = true
991
1010
  }
992
1011
 
1012
+ function clearIdleSocketValidation (socket) {
1013
+ if (socket[kIdleSocketValidationTimeout]) {
1014
+ clearTimeout(socket[kIdleSocketValidationTimeout])
1015
+ socket[kIdleSocketValidationTimeout] = null
1016
+ }
1017
+
1018
+ socket[kIdleSocketValidation] = 0
1019
+ }
1020
+
1021
+ function scheduleIdleSocketValidation (client, socket) {
1022
+ socket[kIdleSocketValidation] = 1
1023
+ socket[kIdleSocketValidationTimeout] = setTimeout(() => {
1024
+ socket[kIdleSocketValidationTimeout] = null
1025
+ socket[kIdleSocketValidation] = 2
1026
+
1027
+ if (client[kSocket] === socket && !socket.destroyed) {
1028
+ client[kResume]()
1029
+ }
1030
+ }, 0)
1031
+ socket[kIdleSocketValidationTimeout].unref?.()
1032
+ }
1033
+
993
1034
  /**
994
1035
  * @param {import('./client.js')} client
995
1036
  */
@@ -1007,6 +1048,32 @@ function resumeH1 (client) {
1007
1048
  socket[kNoRef] = false
1008
1049
  }
1009
1050
 
1051
+ if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
1052
+ if (socket[kIdleSocketValidation] === 0) {
1053
+ scheduleIdleSocketValidation(client, socket)
1054
+ socket[kParser].readMore()
1055
+ if (socket.destroyed) {
1056
+ return
1057
+ }
1058
+ return
1059
+ }
1060
+
1061
+ if (socket[kIdleSocketValidation] === 1) {
1062
+ socket[kParser].readMore()
1063
+ if (socket.destroyed) {
1064
+ return
1065
+ }
1066
+ return
1067
+ }
1068
+ }
1069
+
1070
+ if (client[kRunning] === 0) {
1071
+ socket[kParser].readMore()
1072
+ if (socket.destroyed) {
1073
+ return
1074
+ }
1075
+ }
1076
+
1010
1077
  if (client[kSize] === 0) {
1011
1078
  if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
1012
1079
  socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
@@ -1105,6 +1172,7 @@ function writeH1 (client, request) {
1105
1172
  }
1106
1173
 
1107
1174
  const socket = client[kSocket]
1175
+ clearIdleSocketValidation(socket)
1108
1176
 
1109
1177
  /**
1110
1178
  * @param {Error} [err]
@@ -114,7 +114,8 @@ class Client extends DispatcherBase {
114
114
  useH2c,
115
115
  initialWindowSize,
116
116
  connectionWindowSize,
117
- pingInterval
117
+ pingInterval,
118
+ webSocket
118
119
  } = {}) {
119
120
  if (keepAlive !== undefined) {
120
121
  throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
@@ -222,7 +223,7 @@ class Client extends DispatcherBase {
222
223
  throw new InvalidArgumentError('pingInterval must be a positive integer, greater or equal to 0')
223
224
  }
224
225
 
225
- super()
226
+ super({ webSocket })
226
227
 
227
228
  if (typeof connect !== 'function') {
228
229
  connect = buildConnector({
@@ -11,6 +11,7 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch } = require('../core/sy
11
11
 
12
12
  const kOnDestroyed = Symbol('onDestroyed')
13
13
  const kOnClosed = Symbol('onClosed')
14
+ const kWebSocketOptions = Symbol('webSocketOptions')
14
15
 
15
16
  class DispatcherBase extends Dispatcher {
16
17
  /** @type {boolean} */
@@ -25,6 +26,24 @@ class DispatcherBase extends Dispatcher {
25
26
  /** @type {Array<Function>|null} */
26
27
  [kOnClosed] = null
27
28
 
29
+ /**
30
+ * @param {import('../../types/dispatcher').DispatcherOptions} [opts]
31
+ */
32
+ constructor (opts) {
33
+ super()
34
+ this[kWebSocketOptions] = opts?.webSocket ?? {}
35
+ }
36
+
37
+ /**
38
+ * @returns {import('../../types/dispatcher').WebSocketOptions}
39
+ */
40
+ get webSocketOptions () {
41
+ return {
42
+ maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
43
+ maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 // 128 MB default
44
+ }
45
+ }
46
+
28
47
  /** @returns {boolean} */
29
48
  get destroyed () {
30
49
  return this[kDestroyed]
@@ -63,7 +63,7 @@ class Pool extends PoolBase {
63
63
  })
64
64
  }
65
65
 
66
- super()
66
+ super(options)
67
67
 
68
68
  this[kConnections] = connections || null
69
69
  this[kUrl] = util.parseOrigin(origin)
@@ -142,7 +142,8 @@ class ProxyAgent extends DispatcherBase {
142
142
  factory: agentFactory,
143
143
  username: opts.username || username,
144
144
  password: opts.password || password,
145
- proxyTls: opts.proxyTls
145
+ proxyTls: opts.proxyTls,
146
+ requestTls: opts.requestTls
146
147
  })
147
148
  }
148
149
 
@@ -1,12 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const net = require('node:net')
4
3
  const { URL } = require('node:url')
5
4
 
6
5
  let tls // include tls conditionally since it is not always available
7
6
  const DispatcherBase = require('./dispatcher-base')
8
7
  const { InvalidArgumentError } = require('../core/errors')
9
- const { Socks5Client } = require('../core/socks5-client')
8
+ const { Socks5Client, STATES } = require('../core/socks5-client')
10
9
  const { kDispatch, kClose, kDestroy } = require('../core/symbols')
11
10
  const Pool = require('./pool')
12
11
  const buildConnector = require('../core/connect')
@@ -17,8 +16,10 @@ const debug = debuglog('undici:socks5-proxy')
17
16
  const kProxyUrl = Symbol('proxy url')
18
17
  const kProxyHeaders = Symbol('proxy headers')
19
18
  const kProxyAuth = Symbol('proxy auth')
20
- const kPool = Symbol('pool')
19
+ const kProxyProtocol = Symbol('proxy protocol')
20
+ const kPools = Symbol('pools')
21
21
  const kConnector = Symbol('connector')
22
+ const kRequestTls = Symbol('request tls settings')
22
23
 
23
24
  // Static flag to ensure warning is only emitted once per process
24
25
  let experimentalWarningEmitted = false
@@ -52,6 +53,8 @@ class Socks5ProxyAgent extends DispatcherBase {
52
53
 
53
54
  this[kProxyUrl] = url
54
55
  this[kProxyHeaders] = options.headers || {}
56
+ this[kProxyProtocol] = options.proxyTls ? 'https:' : 'http:'
57
+ this[kRequestTls] = options.requestTls
55
58
 
56
59
  // Extract auth from URL or options
57
60
  this[kProxyAuth] = {
@@ -65,8 +68,8 @@ class Socks5ProxyAgent extends DispatcherBase {
65
68
  servername: options.proxyTls?.servername || url.hostname
66
69
  })
67
70
 
68
- // Pool for the actual HTTP connections (with SOCKS5 tunnel connect function)
69
- this[kPool] = null
71
+ // Pools for the actual HTTP connections (with SOCKS5 tunnel connect function), keyed by origin
72
+ this[kPools] = new Map()
70
73
  }
71
74
 
72
75
  /**
@@ -80,23 +83,18 @@ class Socks5ProxyAgent extends DispatcherBase {
80
83
 
81
84
  // Connect to the SOCKS5 proxy
82
85
  const socket = await new Promise((resolve, reject) => {
83
- const onConnect = () => {
84
- socket.removeListener('error', onError)
85
- resolve(socket)
86
- }
87
-
88
- const onError = (err) => {
89
- socket.removeListener('connect', onConnect)
90
- reject(err)
91
- }
92
-
93
- const socket = net.connect({
86
+ this[kConnector]({
87
+ hostname: proxyHost,
94
88
  host: proxyHost,
95
- port: proxyPort
89
+ port: proxyPort,
90
+ protocol: this[kProxyProtocol]
91
+ }, (err, socket) => {
92
+ if (err) {
93
+ reject(err)
94
+ } else {
95
+ resolve(socket)
96
+ }
96
97
  })
97
-
98
- socket.once('connect', onConnect)
99
- socket.once('error', onError)
100
98
  })
101
99
 
102
100
  // Create SOCKS5 client
@@ -130,7 +128,7 @@ class Socks5ProxyAgent extends DispatcherBase {
130
128
  }
131
129
 
132
130
  // Check if already authenticated (for NO_AUTH method)
133
- if (socks5Client.state === 'authenticated') {
131
+ if (socks5Client.state === STATES.AUTHENTICATED) {
134
132
  clearTimeout(timeout)
135
133
  resolve()
136
134
  } else {
@@ -171,15 +169,17 @@ class Socks5ProxyAgent extends DispatcherBase {
171
169
  /**
172
170
  * Dispatch a request through the SOCKS5 proxy
173
171
  */
174
- async [kDispatch] (opts, handler) {
172
+ [kDispatch] (opts, handler) {
175
173
  const { origin } = opts
176
174
 
177
175
  debug('dispatching request to', origin, 'via SOCKS5')
178
176
 
179
177
  try {
180
- // Create Pool with custom connect function if we don't have one yet
181
- if (!this[kPool] || this[kPool].destroyed || this[kPool].closed) {
182
- this[kPool] = new Pool(origin, {
178
+ const originKey = String(origin)
179
+ let pool = this[kPools].get(originKey)
180
+ // Create a Pool per origin so requests are not routed to the wrong host
181
+ if (!pool || pool.destroyed || pool.closed) {
182
+ pool = new Pool(origin, {
183
183
  pipelining: opts.pipelining,
184
184
  connections: opts.connections,
185
185
  connect: async (connectOpts, callback) => {
@@ -201,9 +201,9 @@ class Socks5ProxyAgent extends DispatcherBase {
201
201
  }
202
202
  debug('upgrading to TLS')
203
203
  finalSocket = tls.connect({
204
+ ...this[kRequestTls],
204
205
  socket,
205
- servername: targetHost,
206
- ...connectOpts.tls || {}
206
+ servername: this[kRequestTls]?.servername || targetHost
207
207
  })
208
208
 
209
209
  await new Promise((resolve, reject) => {
@@ -219,14 +219,19 @@ class Socks5ProxyAgent extends DispatcherBase {
219
219
  }
220
220
  }
221
221
  })
222
+ this[kPools].set(originKey, pool)
222
223
  }
223
224
 
224
- // Dispatch the request through the pool
225
- return this[kPool][kDispatch](opts, handler)
225
+ // Dispatch the request through the per-origin pool
226
+ return pool[kDispatch](opts, handler)
226
227
  } catch (err) {
227
228
  debug('dispatch error:', err)
228
- if (typeof handler.onError === 'function') {
229
+ if (typeof handler.onResponseError === 'function') {
230
+ handler.onResponseError(null, err)
231
+ return false
232
+ } else if (typeof handler.onError === 'function') {
229
233
  handler.onError(err)
234
+ return false
230
235
  } else {
231
236
  throw err
232
237
  }
@@ -234,15 +239,21 @@ class Socks5ProxyAgent extends DispatcherBase {
234
239
  }
235
240
 
236
241
  async [kClose] () {
237
- if (this[kPool]) {
238
- await this[kPool].close()
242
+ const closePromises = []
243
+ for (const pool of this[kPools].values()) {
244
+ closePromises.push(pool.close())
239
245
  }
246
+ this[kPools].clear()
247
+ await Promise.all(closePromises)
240
248
  }
241
249
 
242
250
  async [kDestroy] (err) {
243
- if (this[kPool]) {
244
- await this[kPool].destroy(err)
251
+ const destroyPromises = []
252
+ for (const pool of this[kPools].values()) {
253
+ destroyPromises.push(pool.destroy(err))
245
254
  }
255
+ this[kPools].clear()
256
+ await Promise.all(destroyPromises)
246
257
  }
247
258
  }
248
259
 
@@ -228,6 +228,10 @@ function parseCacheControlHeader (header) {
228
228
  headers[headers.length - 1] = lastHeader
229
229
  }
230
230
 
231
+ for (let j = 0; j < headers.length; j++) {
232
+ headers[j] = headers[j].trim()
233
+ }
234
+
231
235
  if (key in output) {
232
236
  output[key] = output[key].concat(headers)
233
237
  } else {
@@ -236,10 +240,12 @@ function parseCacheControlHeader (header) {
236
240
  }
237
241
  } else {
238
242
  // Something like `no-cache="some-header"`
243
+ const fieldName = value.trim()
244
+
239
245
  if (key in output) {
240
- output[key] = output[key].concat(value)
246
+ output[key] = output[key].concat(fieldName)
241
247
  } else {
242
- output[key] = [value]
248
+ output[key] = [fieldName]
243
249
  }
244
250
  }
245
251
 
@@ -4,7 +4,6 @@ const { collectASequenceOfCodePointsFast } = require('../infra')
4
4
  const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
5
5
  const { isCTLExcludingHtab } = require('./util')
6
6
  const assert = require('node:assert')
7
- const { unescape: qsUnescape } = require('node:querystring')
8
7
 
9
8
  /**
10
9
  * @description Parses the field-value attributes of a set-cookie header string.
@@ -82,7 +81,7 @@ function parseSetCookie (header) {
82
81
  // store arbitrary data in a cookie-value SHOULD encode that data, for
83
82
  // example, using Base64 [RFC4648].
84
83
  return {
85
- name, value: qsUnescape(value), ...parseUnparsedAttributes(unparsedAttributes)
84
+ name, value, ...parseUnparsedAttributes(unparsedAttributes)
86
85
  }
87
86
  }
88
87
 
@@ -280,32 +279,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
280
279
  // If the attribute-name case-insensitively matches the string
281
280
  // "SameSite", the user agent MUST process the cookie-av as follows:
282
281
 
283
- // 1. Let enforcement be "Default".
284
- let enforcement = 'Default'
285
-
286
282
  const attributeValueLowercase = attributeValue.toLowerCase()
287
- // 2. If cookie-av's attribute-value is a case-insensitive match for
288
- // "None", set enforcement to "None".
289
- if (attributeValueLowercase.includes('none')) {
290
- enforcement = 'None'
291
- }
292
283
 
293
- // 3. If cookie-av's attribute-value is a case-insensitive match for
294
- // "Strict", set enforcement to "Strict".
295
- if (attributeValueLowercase.includes('strict')) {
296
- enforcement = 'Strict'
284
+ // 1. If cookie-av's attribute-value is a case-insensitive match for
285
+ // "None", append an attribute to the cookie-attribute-list with an
286
+ // attribute-name of "SameSite" and an attribute-value of "None".
287
+ if (attributeValueLowercase === 'none') {
288
+ cookieAttributeList.sameSite = 'None'
289
+ } else if (attributeValueLowercase === 'strict') {
290
+ // 2. If cookie-av's attribute-value is a case-insensitive match for
291
+ // "Strict", append an attribute to the cookie-attribute-list with
292
+ // an attribute-name of "SameSite" and an attribute-value of
293
+ // "Strict".
294
+ cookieAttributeList.sameSite = 'Strict'
295
+ } else if (attributeValueLowercase === 'lax') {
296
+ // 3. If cookie-av's attribute-value is a case-insensitive match for
297
+ // "Lax", append an attribute to the cookie-attribute-list with an
298
+ // attribute-name of "SameSite" and an attribute-value of "Lax".
299
+ cookieAttributeList.sameSite = 'Lax'
297
300
  }
298
-
299
- // 4. If cookie-av's attribute-value is a case-insensitive match for
300
- // "Lax", set enforcement to "Lax".
301
- if (attributeValueLowercase.includes('lax')) {
302
- enforcement = 'Lax'
303
- }
304
-
305
- // 5. Append an attribute to the cookie-attribute-list with an
306
- // attribute-name of "SameSite" and an attribute-value of
307
- // enforcement.
308
- cookieAttributeList.sameSite = enforcement
309
301
  } else {
310
302
  cookieAttributeList.unparsed ??= []
311
303