undici 6.21.3 → 6.23.0

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.
package/README.md CHANGED
@@ -329,7 +329,13 @@ const headers = await fetch(url, { method: 'HEAD' })
329
329
 
330
330
  The [Fetch Standard](https://fetch.spec.whatwg.org) requires implementations to exclude certain headers from requests and responses. In browser environments, some headers are forbidden so the user agent remains in full control over them. In Undici, these constraints are removed to give more control to the user.
331
331
 
332
- ### `undici.upgrade([url, options]): Promise`
332
+ #### Content-Encoding
333
+
334
+ * https://www.rfc-editor.org/rfc/rfc9110#field.content-encoding
335
+
336
+ Undici limits the number of `Content-Encoding` layers in a response to **5** to prevent resource exhaustion attacks. If a server responds with more than 5 content-encodings (e.g., `Content-Encoding: gzip, gzip, gzip, gzip, gzip, gzip`), the fetch will be rejected with an error. This limit matches the approach taken by [curl](https://curl.se/docs/CVE-2022-32206.html) and [urllib3](https://github.com/advisories/GHSA-gm62-xv2j-4rw9).
337
+
338
+ #### `undici.upgrade([url, options]): Promise`
333
339
 
334
340
  Upgrade to a different protocol. See [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details.
335
341
 
@@ -22,8 +22,9 @@ For detailed information on the parsing process and potential validation errors,
22
22
  * **token** `string` (optional) - It can be passed by a string of token for authentication.
23
23
  * **auth** `string` (**deprecated**) - Use token.
24
24
  * **clientFactory** `(origin: URL, opts: Object) => Dispatcher` (optional) - Default: `(origin, opts) => new Pool(origin, opts)`
25
- * **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. See [TLS](https://nodejs.org/api/tls.html#tlsconnectoptions-callback).
26
- * **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. See [TLS](https://nodejs.org/api/tls.html#tlsconnectoptions-callback).
25
+ * **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
26
+ * **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
27
+ * **proxyTunnel** `boolean` (optional) - For connections involving secure protocols, Undici will always establish a tunnel via the HTTP2 CONNECT extension. If proxyTunnel is set to true, this will occur for unsecured proxy/endpoint connections as well. Currently, there is no way to facilitate HTTP1 IP tunneling as described in https://www.rfc-editor.org/rfc/rfc9484.html#name-http-11-request. If proxyTunnel is set to false (the default), ProxyAgent connections where both the Proxy and Endpoint are unsecured will issue all requests to the Proxy, and prefix the endpoint request path with the endpoint origin address.
27
28
 
28
29
  Examples:
29
30
 
package/index-fetch.js CHANGED
@@ -26,6 +26,9 @@ module.exports.createFastMessageEvent = createFastMessageEvent
26
26
 
27
27
  module.exports.EventSource = require('./lib/web/eventsource/eventsource').EventSource
28
28
 
29
+ const api = require('./lib/api')
30
+ const Dispatcher = require('./lib/dispatcher/dispatcher')
31
+ Object.assign(Dispatcher.prototype, api)
29
32
  // Expose the fetch implementation to be enabled in Node.js core via a flag
30
33
  module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent
31
34
  module.exports.getGlobalDispatcher = getGlobalDispatcher
@@ -1,13 +1,21 @@
1
1
  'use strict'
2
2
 
3
+ const kUndiciError = Symbol.for('undici.error.UND_ERR')
3
4
  class UndiciError extends Error {
4
5
  constructor (message) {
5
6
  super(message)
6
7
  this.name = 'UndiciError'
7
8
  this.code = 'UND_ERR'
8
9
  }
10
+
11
+ static [Symbol.hasInstance] (instance) {
12
+ return instance && instance[kUndiciError] === true
13
+ }
14
+
15
+ [kUndiciError] = true
9
16
  }
10
17
 
18
+ const kConnectTimeoutError = Symbol.for('undici.error.UND_ERR_CONNECT_TIMEOUT')
11
19
  class ConnectTimeoutError extends UndiciError {
12
20
  constructor (message) {
13
21
  super(message)
@@ -15,8 +23,15 @@ class ConnectTimeoutError extends UndiciError {
15
23
  this.message = message || 'Connect Timeout Error'
16
24
  this.code = 'UND_ERR_CONNECT_TIMEOUT'
17
25
  }
26
+
27
+ static [Symbol.hasInstance] (instance) {
28
+ return instance && instance[kConnectTimeoutError] === true
29
+ }
30
+
31
+ [kConnectTimeoutError] = true
18
32
  }
19
33
 
34
+ const kHeadersTimeoutError = Symbol.for('undici.error.UND_ERR_HEADERS_TIMEOUT')
20
35
  class HeadersTimeoutError extends UndiciError {
21
36
  constructor (message) {
22
37
  super(message)
@@ -24,8 +39,15 @@ class HeadersTimeoutError extends UndiciError {
24
39
  this.message = message || 'Headers Timeout Error'
25
40
  this.code = 'UND_ERR_HEADERS_TIMEOUT'
26
41
  }
42
+
43
+ static [Symbol.hasInstance] (instance) {
44
+ return instance && instance[kHeadersTimeoutError] === true
45
+ }
46
+
47
+ [kHeadersTimeoutError] = true
27
48
  }
28
49
 
50
+ const kHeadersOverflowError = Symbol.for('undici.error.UND_ERR_HEADERS_OVERFLOW')
29
51
  class HeadersOverflowError extends UndiciError {
30
52
  constructor (message) {
31
53
  super(message)
@@ -33,8 +55,15 @@ class HeadersOverflowError extends UndiciError {
33
55
  this.message = message || 'Headers Overflow Error'
34
56
  this.code = 'UND_ERR_HEADERS_OVERFLOW'
35
57
  }
58
+
59
+ static [Symbol.hasInstance] (instance) {
60
+ return instance && instance[kHeadersOverflowError] === true
61
+ }
62
+
63
+ [kHeadersOverflowError] = true
36
64
  }
37
65
 
66
+ const kBodyTimeoutError = Symbol.for('undici.error.UND_ERR_BODY_TIMEOUT')
38
67
  class BodyTimeoutError extends UndiciError {
39
68
  constructor (message) {
40
69
  super(message)
@@ -42,8 +71,15 @@ class BodyTimeoutError extends UndiciError {
42
71
  this.message = message || 'Body Timeout Error'
43
72
  this.code = 'UND_ERR_BODY_TIMEOUT'
44
73
  }
74
+
75
+ static [Symbol.hasInstance] (instance) {
76
+ return instance && instance[kBodyTimeoutError] === true
77
+ }
78
+
79
+ [kBodyTimeoutError] = true
45
80
  }
46
81
 
82
+ const kResponseStatusCodeError = Symbol.for('undici.error.UND_ERR_RESPONSE_STATUS_CODE')
47
83
  class ResponseStatusCodeError extends UndiciError {
48
84
  constructor (message, statusCode, headers, body) {
49
85
  super(message)
@@ -55,8 +91,15 @@ class ResponseStatusCodeError extends UndiciError {
55
91
  this.statusCode = statusCode
56
92
  this.headers = headers
57
93
  }
94
+
95
+ static [Symbol.hasInstance] (instance) {
96
+ return instance && instance[kResponseStatusCodeError] === true
97
+ }
98
+
99
+ [kResponseStatusCodeError] = true
58
100
  }
59
101
 
102
+ const kInvalidArgumentError = Symbol.for('undici.error.UND_ERR_INVALID_ARG')
60
103
  class InvalidArgumentError extends UndiciError {
61
104
  constructor (message) {
62
105
  super(message)
@@ -64,8 +107,15 @@ class InvalidArgumentError extends UndiciError {
64
107
  this.message = message || 'Invalid Argument Error'
65
108
  this.code = 'UND_ERR_INVALID_ARG'
66
109
  }
110
+
111
+ static [Symbol.hasInstance] (instance) {
112
+ return instance && instance[kInvalidArgumentError] === true
113
+ }
114
+
115
+ [kInvalidArgumentError] = true
67
116
  }
68
117
 
118
+ const kInvalidReturnValueError = Symbol.for('undici.error.UND_ERR_INVALID_RETURN_VALUE')
69
119
  class InvalidReturnValueError extends UndiciError {
70
120
  constructor (message) {
71
121
  super(message)
@@ -73,16 +123,31 @@ class InvalidReturnValueError extends UndiciError {
73
123
  this.message = message || 'Invalid Return Value Error'
74
124
  this.code = 'UND_ERR_INVALID_RETURN_VALUE'
75
125
  }
126
+
127
+ static [Symbol.hasInstance] (instance) {
128
+ return instance && instance[kInvalidReturnValueError] === true
129
+ }
130
+
131
+ [kInvalidReturnValueError] = true
76
132
  }
77
133
 
134
+ const kAbortError = Symbol.for('undici.error.UND_ERR_ABORT')
78
135
  class AbortError extends UndiciError {
79
136
  constructor (message) {
80
137
  super(message)
81
138
  this.name = 'AbortError'
82
139
  this.message = message || 'The operation was aborted'
140
+ this.code = 'UND_ERR_ABORT'
141
+ }
142
+
143
+ static [Symbol.hasInstance] (instance) {
144
+ return instance && instance[kAbortError] === true
83
145
  }
146
+
147
+ [kAbortError] = true
84
148
  }
85
149
 
150
+ const kRequestAbortedError = Symbol.for('undici.error.UND_ERR_ABORTED')
86
151
  class RequestAbortedError extends AbortError {
87
152
  constructor (message) {
88
153
  super(message)
@@ -90,8 +155,15 @@ class RequestAbortedError extends AbortError {
90
155
  this.message = message || 'Request aborted'
91
156
  this.code = 'UND_ERR_ABORTED'
92
157
  }
158
+
159
+ static [Symbol.hasInstance] (instance) {
160
+ return instance && instance[kRequestAbortedError] === true
161
+ }
162
+
163
+ [kRequestAbortedError] = true
93
164
  }
94
165
 
166
+ const kInformationalError = Symbol.for('undici.error.UND_ERR_INFO')
95
167
  class InformationalError extends UndiciError {
96
168
  constructor (message) {
97
169
  super(message)
@@ -99,8 +171,15 @@ class InformationalError extends UndiciError {
99
171
  this.message = message || 'Request information'
100
172
  this.code = 'UND_ERR_INFO'
101
173
  }
174
+
175
+ static [Symbol.hasInstance] (instance) {
176
+ return instance && instance[kInformationalError] === true
177
+ }
178
+
179
+ [kInformationalError] = true
102
180
  }
103
181
 
182
+ const kRequestContentLengthMismatchError = Symbol.for('undici.error.UND_ERR_REQ_CONTENT_LENGTH_MISMATCH')
104
183
  class RequestContentLengthMismatchError extends UndiciError {
105
184
  constructor (message) {
106
185
  super(message)
@@ -108,8 +187,15 @@ class RequestContentLengthMismatchError extends UndiciError {
108
187
  this.message = message || 'Request body length does not match content-length header'
109
188
  this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
110
189
  }
190
+
191
+ static [Symbol.hasInstance] (instance) {
192
+ return instance && instance[kRequestContentLengthMismatchError] === true
193
+ }
194
+
195
+ [kRequestContentLengthMismatchError] = true
111
196
  }
112
197
 
198
+ const kResponseContentLengthMismatchError = Symbol.for('undici.error.UND_ERR_RES_CONTENT_LENGTH_MISMATCH')
113
199
  class ResponseContentLengthMismatchError extends UndiciError {
114
200
  constructor (message) {
115
201
  super(message)
@@ -117,8 +203,15 @@ class ResponseContentLengthMismatchError extends UndiciError {
117
203
  this.message = message || 'Response body length does not match content-length header'
118
204
  this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
119
205
  }
206
+
207
+ static [Symbol.hasInstance] (instance) {
208
+ return instance && instance[kResponseContentLengthMismatchError] === true
209
+ }
210
+
211
+ [kResponseContentLengthMismatchError] = true
120
212
  }
121
213
 
214
+ const kClientDestroyedError = Symbol.for('undici.error.UND_ERR_DESTROYED')
122
215
  class ClientDestroyedError extends UndiciError {
123
216
  constructor (message) {
124
217
  super(message)
@@ -126,8 +219,15 @@ class ClientDestroyedError extends UndiciError {
126
219
  this.message = message || 'The client is destroyed'
127
220
  this.code = 'UND_ERR_DESTROYED'
128
221
  }
222
+
223
+ static [Symbol.hasInstance] (instance) {
224
+ return instance && instance[kClientDestroyedError] === true
225
+ }
226
+
227
+ [kClientDestroyedError] = true
129
228
  }
130
229
 
230
+ const kClientClosedError = Symbol.for('undici.error.UND_ERR_CLOSED')
131
231
  class ClientClosedError extends UndiciError {
132
232
  constructor (message) {
133
233
  super(message)
@@ -135,8 +235,15 @@ class ClientClosedError extends UndiciError {
135
235
  this.message = message || 'The client is closed'
136
236
  this.code = 'UND_ERR_CLOSED'
137
237
  }
238
+
239
+ static [Symbol.hasInstance] (instance) {
240
+ return instance && instance[kClientClosedError] === true
241
+ }
242
+
243
+ [kClientClosedError] = true
138
244
  }
139
245
 
246
+ const kSocketError = Symbol.for('undici.error.UND_ERR_SOCKET')
140
247
  class SocketError extends UndiciError {
141
248
  constructor (message, socket) {
142
249
  super(message)
@@ -145,8 +252,15 @@ class SocketError extends UndiciError {
145
252
  this.code = 'UND_ERR_SOCKET'
146
253
  this.socket = socket
147
254
  }
255
+
256
+ static [Symbol.hasInstance] (instance) {
257
+ return instance && instance[kSocketError] === true
258
+ }
259
+
260
+ [kSocketError] = true
148
261
  }
149
262
 
263
+ const kNotSupportedError = Symbol.for('undici.error.UND_ERR_NOT_SUPPORTED')
150
264
  class NotSupportedError extends UndiciError {
151
265
  constructor (message) {
152
266
  super(message)
@@ -154,8 +268,15 @@ class NotSupportedError extends UndiciError {
154
268
  this.message = message || 'Not supported error'
155
269
  this.code = 'UND_ERR_NOT_SUPPORTED'
156
270
  }
271
+
272
+ static [Symbol.hasInstance] (instance) {
273
+ return instance && instance[kNotSupportedError] === true
274
+ }
275
+
276
+ [kNotSupportedError] = true
157
277
  }
158
278
 
279
+ const kBalancedPoolMissingUpstreamError = Symbol.for('undici.error.UND_ERR_BPL_MISSING_UPSTREAM')
159
280
  class BalancedPoolMissingUpstreamError extends UndiciError {
160
281
  constructor (message) {
161
282
  super(message)
@@ -163,8 +284,15 @@ class BalancedPoolMissingUpstreamError extends UndiciError {
163
284
  this.message = message || 'No upstream has been added to the BalancedPool'
164
285
  this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
165
286
  }
287
+
288
+ static [Symbol.hasInstance] (instance) {
289
+ return instance && instance[kBalancedPoolMissingUpstreamError] === true
290
+ }
291
+
292
+ [kBalancedPoolMissingUpstreamError] = true
166
293
  }
167
294
 
295
+ const kHTTPParserError = Symbol.for('undici.error.UND_ERR_HTTP_PARSER')
168
296
  class HTTPParserError extends Error {
169
297
  constructor (message, code, data) {
170
298
  super(message)
@@ -172,8 +300,15 @@ class HTTPParserError extends Error {
172
300
  this.code = code ? `HPE_${code}` : undefined
173
301
  this.data = data ? data.toString() : undefined
174
302
  }
303
+
304
+ static [Symbol.hasInstance] (instance) {
305
+ return instance && instance[kHTTPParserError] === true
306
+ }
307
+
308
+ [kHTTPParserError] = true
175
309
  }
176
310
 
311
+ const kResponseExceededMaxSizeError = Symbol.for('undici.error.UND_ERR_RES_EXCEEDED_MAX_SIZE')
177
312
  class ResponseExceededMaxSizeError extends UndiciError {
178
313
  constructor (message) {
179
314
  super(message)
@@ -181,8 +316,15 @@ class ResponseExceededMaxSizeError extends UndiciError {
181
316
  this.message = message || 'Response content exceeded max size'
182
317
  this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
183
318
  }
319
+
320
+ static [Symbol.hasInstance] (instance) {
321
+ return instance && instance[kResponseExceededMaxSizeError] === true
322
+ }
323
+
324
+ [kResponseExceededMaxSizeError] = true
184
325
  }
185
326
 
327
+ const kRequestRetryError = Symbol.for('undici.error.UND_ERR_REQ_RETRY')
186
328
  class RequestRetryError extends UndiciError {
187
329
  constructor (message, code, { headers, data }) {
188
330
  super(message)
@@ -193,8 +335,15 @@ class RequestRetryError extends UndiciError {
193
335
  this.data = data
194
336
  this.headers = headers
195
337
  }
338
+
339
+ static [Symbol.hasInstance] (instance) {
340
+ return instance && instance[kRequestRetryError] === true
341
+ }
342
+
343
+ [kRequestRetryError] = true
196
344
  }
197
345
 
346
+ const kResponseError = Symbol.for('undici.error.UND_ERR_RESPONSE')
198
347
  class ResponseError extends UndiciError {
199
348
  constructor (message, code, { headers, data }) {
200
349
  super(message)
@@ -205,8 +354,15 @@ class ResponseError extends UndiciError {
205
354
  this.data = data
206
355
  this.headers = headers
207
356
  }
357
+
358
+ static [Symbol.hasInstance] (instance) {
359
+ return instance && instance[kResponseError] === true
360
+ }
361
+
362
+ [kResponseError] = true
208
363
  }
209
364
 
365
+ const kSecureProxyConnectionError = Symbol.for('undici.error.UND_ERR_PRX_TLS')
210
366
  class SecureProxyConnectionError extends UndiciError {
211
367
  constructor (cause, message, options) {
212
368
  super(message, { cause, ...(options ?? {}) })
@@ -215,6 +371,12 @@ class SecureProxyConnectionError extends UndiciError {
215
371
  this.code = 'UND_ERR_PRX_TLS'
216
372
  this.cause = cause
217
373
  }
374
+
375
+ static [Symbol.hasInstance] (instance) {
376
+ return instance && instance[kSecureProxyConnectionError] === true
377
+ }
378
+
379
+ [kSecureProxyConnectionError] = true
218
380
  }
219
381
 
220
382
  module.exports = {
@@ -1,12 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { kProxy, kClose, kDestroy, kInterceptors } = require('../core/symbols')
3
+ const { kProxy, kClose, kDestroy, kDispatch, kInterceptors } = require('../core/symbols')
4
4
  const { URL } = require('node:url')
5
5
  const Agent = require('./agent')
6
6
  const Pool = require('./pool')
7
7
  const DispatcherBase = require('./dispatcher-base')
8
8
  const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors')
9
9
  const buildConnector = require('../core/connect')
10
+ const Client = require('./client')
10
11
 
11
12
  const kAgent = Symbol('proxy agent')
12
13
  const kClient = Symbol('proxy client')
@@ -14,6 +15,7 @@ const kProxyHeaders = Symbol('proxy headers')
14
15
  const kRequestTls = Symbol('request tls settings')
15
16
  const kProxyTls = Symbol('proxy tls settings')
16
17
  const kConnectEndpoint = Symbol('connect endpoint function')
18
+ const kTunnelProxy = Symbol('tunnel proxy')
17
19
 
18
20
  function defaultProtocolPort (protocol) {
19
21
  return protocol === 'https:' ? 443 : 80
@@ -25,6 +27,69 @@ function defaultFactory (origin, opts) {
25
27
 
26
28
  const noop = () => {}
27
29
 
30
+ function defaultAgentFactory (origin, opts) {
31
+ if (opts.connections === 1) {
32
+ return new Client(origin, opts)
33
+ }
34
+ return new Pool(origin, opts)
35
+ }
36
+
37
+ class Http1ProxyWrapper extends DispatcherBase {
38
+ #client
39
+
40
+ constructor (proxyUrl, { headers = {}, connect, factory }) {
41
+ super()
42
+ if (!proxyUrl) {
43
+ throw new InvalidArgumentError('Proxy URL is mandatory')
44
+ }
45
+
46
+ this[kProxyHeaders] = headers
47
+ if (factory) {
48
+ this.#client = factory(proxyUrl, { connect })
49
+ } else {
50
+ this.#client = new Client(proxyUrl, { connect })
51
+ }
52
+ }
53
+
54
+ [kDispatch] (opts, handler) {
55
+ const onHeaders = handler.onHeaders
56
+ handler.onHeaders = function (statusCode, data, resume) {
57
+ if (statusCode === 407) {
58
+ if (typeof handler.onError === 'function') {
59
+ handler.onError(new InvalidArgumentError('Proxy Authentication Required (407)'))
60
+ }
61
+ return
62
+ }
63
+ if (onHeaders) onHeaders.call(this, statusCode, data, resume)
64
+ }
65
+
66
+ // Rewrite request as an HTTP1 Proxy request, without tunneling.
67
+ const {
68
+ origin,
69
+ path = '/',
70
+ headers = {}
71
+ } = opts
72
+
73
+ opts.path = origin + path
74
+
75
+ if (!('host' in headers) && !('Host' in headers)) {
76
+ const { host } = new URL(origin)
77
+ headers.host = host
78
+ }
79
+ opts.headers = { ...this[kProxyHeaders], ...headers }
80
+
81
+ return this.#client[kDispatch](opts, handler)
82
+ }
83
+
84
+ async [kClose] () {
85
+ return this.#client.close()
86
+ }
87
+
88
+ async [kDestroy] (err) {
89
+ return this.#client.destroy(err)
90
+ }
91
+ }
92
+
28
93
  class ProxyAgent extends DispatcherBase {
29
94
  constructor (opts) {
30
95
  super()
@@ -38,6 +103,8 @@ class ProxyAgent extends DispatcherBase {
38
103
  throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
39
104
  }
40
105
 
106
+ const { proxyTunnel = true } = opts
107
+
41
108
  const url = this.#getUrl(opts)
42
109
  const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
43
110
 
@@ -48,6 +115,7 @@ class ProxyAgent extends DispatcherBase {
48
115
  this[kRequestTls] = opts.requestTls
49
116
  this[kProxyTls] = opts.proxyTls
50
117
  this[kProxyHeaders] = opts.headers || {}
118
+ this[kTunnelProxy] = proxyTunnel
51
119
 
52
120
  if (opts.auth && opts.token) {
53
121
  throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token')
@@ -62,9 +130,23 @@ class ProxyAgent extends DispatcherBase {
62
130
 
63
131
  const connect = buildConnector({ ...opts.proxyTls })
64
132
  this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
133
+
134
+ const agentFactory = opts.factory || defaultAgentFactory
135
+ const factory = (origin, options) => {
136
+ const { protocol } = new URL(origin)
137
+ if (!this[kTunnelProxy] && protocol === 'http:' && this[kProxy].protocol === 'http:') {
138
+ return new Http1ProxyWrapper(this[kProxy].uri, {
139
+ headers: this[kProxyHeaders],
140
+ connect,
141
+ factory: agentFactory
142
+ })
143
+ }
144
+ return agentFactory(origin, options)
145
+ }
65
146
  this[kClient] = clientFactory(url, { connect })
66
147
  this[kAgent] = new Agent({
67
148
  ...opts,
149
+ factory,
68
150
  connect: async (opts, callback) => {
69
151
  let requestedPath = opts.host
70
152
  if (!opts.port) {
@@ -2,6 +2,11 @@
2
2
 
3
3
  const { UndiciError } = require('../core/errors')
4
4
 
5
+ const kMockNotMatchedError = Symbol.for('undici.error.UND_MOCK_ERR_MOCK_NOT_MATCHED')
6
+
7
+ /**
8
+ * The request does not match any registered mock dispatches.
9
+ */
5
10
  class MockNotMatchedError extends UndiciError {
6
11
  constructor (message) {
7
12
  super(message)
@@ -10,6 +15,12 @@ class MockNotMatchedError extends UndiciError {
10
15
  this.message = message || 'The request does not match any registered mock dispatches'
11
16
  this.code = 'UND_MOCK_ERR_MOCK_NOT_MATCHED'
12
17
  }
18
+
19
+ static [Symbol.hasInstance] (instance) {
20
+ return instance && instance[kMockNotMatchedError] === true
21
+ }
22
+
23
+ [kMockNotMatchedError] = true
13
24
  }
14
25
 
15
26
  module.exports = {
@@ -296,10 +296,6 @@ function cloneBody (instance, body) {
296
296
  // 1. Let « out1, out2 » be the result of teeing body’s stream.
297
297
  const [out1, out2] = body.stream.tee()
298
298
 
299
- if (hasFinalizationRegistry) {
300
- streamRegistry.register(instance, new WeakRef(out1))
301
- }
302
-
303
299
  // 2. Set body’s stream to out1.
304
300
  body.stream = out1
305
301
 
@@ -2111,8 +2111,6 @@ async function httpNetworkFetch (
2111
2111
  return
2112
2112
  }
2113
2113
 
2114
- /** @type {string[]} */
2115
- let codings = []
2116
2114
  let location = ''
2117
2115
 
2118
2116
  const headersList = new HeadersList()
@@ -2120,12 +2118,6 @@ async function httpNetworkFetch (
2120
2118
  for (let i = 0; i < rawHeaders.length; i += 2) {
2121
2119
  headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true)
2122
2120
  }
2123
- const contentEncoding = headersList.get('content-encoding', true)
2124
- if (contentEncoding) {
2125
- // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
2126
- // "All content-coding values are case-insensitive..."
2127
- codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
2128
- }
2129
2121
  location = headersList.get('location', true)
2130
2122
 
2131
2123
  this.body = new Readable({ read: resume })
@@ -2136,9 +2128,23 @@ async function httpNetworkFetch (
2136
2128
  redirectStatusSet.has(status)
2137
2129
 
2138
2130
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
2139
- if (codings.length !== 0 && request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
2131
+ if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
2132
+ // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
2133
+ const contentEncoding = headersList.get('content-encoding', true)
2134
+ // "All content-coding values are case-insensitive..."
2135
+ /** @type {string[]} */
2136
+ const codings = contentEncoding ? contentEncoding.toLowerCase().split(',') : []
2137
+
2138
+ // Limit the number of content-encodings to prevent resource exhaustion.
2139
+ // CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206).
2140
+ const maxContentEncodings = 5
2141
+ if (codings.length > maxContentEncodings) {
2142
+ reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
2143
+ return true
2144
+ }
2145
+
2140
2146
  for (let i = codings.length - 1; i >= 0; --i) {
2141
- const coding = codings[i]
2147
+ const coding = codings[i].trim()
2142
2148
  // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
2143
2149
  if (coding === 'x-gzip' || coding === 'gzip') {
2144
2150
  decoders.push(zlib.createGunzip({
@@ -240,6 +240,11 @@ class Response {
240
240
  // 2. Let clonedResponse be the result of cloning this’s response.
241
241
  const clonedResponse = cloneResponse(this[kState])
242
242
 
243
+ // Note: To re-register because of a new stream.
244
+ if (hasFinalizationRegistry && this[kState].body?.stream) {
245
+ streamRegistry.register(this, new WeakRef(this[kState].body.stream))
246
+ }
247
+
243
248
  // 3. Return the result of creating a Response object, given
244
249
  // clonedResponse, this’s headers’s guard, and this’s relevant Realm.
245
250
  return fromInnerResponse(clonedResponse, getHeadersGuard(this[kHeaders]))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.21.3",
3
+ "version": "6.23.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -24,5 +24,6 @@ declare namespace ProxyAgent {
24
24
  requestTls?: buildConnector.BuildOptions;
25
25
  proxyTls?: buildConnector.BuildOptions;
26
26
  clientFactory?(origin: URL, opts: object): Dispatcher;
27
+ proxyTunnel?: boolean;
27
28
  }
28
29
  }