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 +7 -1
- package/docs/docs/api/ProxyAgent.md +3 -2
- package/index-fetch.js +3 -0
- package/lib/core/errors.js +162 -0
- package/lib/dispatcher/proxy-agent.js +83 -1
- package/lib/mock/mock-errors.js +11 -0
- package/lib/web/fetch/body.js +0 -4
- package/lib/web/fetch/index.js +16 -10
- package/lib/web/fetch/response.js +5 -0
- package/package.json +1 -1
- package/types/proxy-agent.d.ts +1 -0
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
|
-
|
|
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.
|
|
26
|
-
* **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server.
|
|
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
|
package/lib/core/errors.js
CHANGED
|
@@ -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) {
|
package/lib/mock/mock-errors.js
CHANGED
|
@@ -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 = {
|
package/lib/web/fetch/body.js
CHANGED
|
@@ -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
|
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -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 (
|
|
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