undici 5.3.0 → 5.5.1
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 +20 -16
- package/docs/api/MockPool.md +1 -0
- package/lib/api/api-connect.js +19 -3
- package/lib/client.js +5 -0
- package/lib/core/connect.js +3 -1
- package/lib/core/request.js +9 -6
- package/lib/fetch/body.js +2 -2
- package/lib/fetch/constants.js +0 -31
- package/lib/fetch/formdata.js +58 -21
- package/lib/fetch/headers.js +12 -68
- package/lib/fetch/index.js +1 -1
- package/lib/fetch/request.js +5 -6
- package/lib/fetch/response.js +6 -31
- package/lib/fetch/util.js +29 -1
- package/lib/mock/mock-interceptor.js +8 -3
- package/lib/mock/mock-utils.js +8 -4
- package/lib/proxy-agent.js +70 -8
- package/package.json +4 -4
- package/types/connector.d.ts +4 -5
- package/types/dispatcher.d.ts +1 -1
- package/types/fetch.d.ts +10 -10
- package/types/formdata.d.ts +15 -11
- package/types/mock-interceptor.d.ts +3 -1
- package/types/proxy-agent.d.ts +4 -0
package/README.md
CHANGED
|
@@ -185,13 +185,12 @@ Help us improve the test coverage by following instructions at [nodejs/undici/#9
|
|
|
185
185
|
Basic usage example:
|
|
186
186
|
|
|
187
187
|
```js
|
|
188
|
-
|
|
188
|
+
import { fetch } from 'undici';
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
190
|
+
|
|
191
|
+
const res = await fetch('https://example.com')
|
|
192
|
+
const json = await res.json()
|
|
193
|
+
console.log(json);
|
|
195
194
|
```
|
|
196
195
|
|
|
197
196
|
You can pass an optional dispatcher to `fetch` as:
|
|
@@ -235,9 +234,7 @@ const data = {
|
|
|
235
234
|
},
|
|
236
235
|
};
|
|
237
236
|
|
|
238
|
-
|
|
239
|
-
await fetch("https://example.com", { body: data, method: 'POST' });
|
|
240
|
-
})();
|
|
237
|
+
await fetch("https://example.com", { body: data, method: 'POST' });
|
|
241
238
|
```
|
|
242
239
|
|
|
243
240
|
#### `response.body`
|
|
@@ -245,14 +242,12 @@ const data = {
|
|
|
245
242
|
Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
|
|
246
243
|
|
|
247
244
|
```js
|
|
248
|
-
|
|
249
|
-
|
|
245
|
+
import { fetch } from 'undici';
|
|
246
|
+
import { Readable } from 'node:stream';
|
|
250
247
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const readableNodeStream = Readable.fromWeb(readableWebStream);
|
|
255
|
-
}
|
|
248
|
+
const response = await fetch('https://example.com')
|
|
249
|
+
const readableWebStream = response.body;
|
|
250
|
+
const readableNodeStream = Readable.fromWeb(readableWebStream);
|
|
256
251
|
```
|
|
257
252
|
|
|
258
253
|
#### Specification Compliance
|
|
@@ -288,6 +283,15 @@ const headers = await fetch(url)
|
|
|
288
283
|
.then(res => res.headers)
|
|
289
284
|
```
|
|
290
285
|
|
|
286
|
+
##### Forbidden and Safelisted Header Names
|
|
287
|
+
|
|
288
|
+
* https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
|
|
289
|
+
* https://fetch.spec.whatwg.org/#forbidden-header-name
|
|
290
|
+
* https://fetch.spec.whatwg.org/#forbidden-response-header-name
|
|
291
|
+
* https://github.com/wintercg/fetch/issues/6
|
|
292
|
+
|
|
293
|
+
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.
|
|
294
|
+
|
|
291
295
|
### `undici.upgrade([url, options]): Promise`
|
|
292
296
|
|
|
293
297
|
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.
|
package/docs/api/MockPool.md
CHANGED
|
@@ -57,6 +57,7 @@ Returns: `MockInterceptor` corresponding to the input options.
|
|
|
57
57
|
* **method** `string | RegExp | (method: string) => boolean` - a matcher for the HTTP request method.
|
|
58
58
|
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
|
|
59
59
|
* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
|
|
60
|
+
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.
|
|
60
61
|
|
|
61
62
|
### Return: `MockInterceptor`
|
|
62
63
|
|
package/lib/api/api-connect.js
CHANGED
|
@@ -15,7 +15,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
15
15
|
throw new InvalidArgumentError('invalid callback')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { signal, opaque, responseHeaders } = opts
|
|
18
|
+
const { signal, opaque, responseHeaders, httpTunnel } = opts
|
|
19
19
|
|
|
20
20
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
21
21
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -27,6 +27,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
27
27
|
this.responseHeaders = responseHeaders || null
|
|
28
28
|
this.callback = callback
|
|
29
29
|
this.abort = null
|
|
30
|
+
this.httpTunnel = httpTunnel
|
|
30
31
|
|
|
31
32
|
addSignal(this, signal)
|
|
32
33
|
}
|
|
@@ -40,8 +41,23 @@ class ConnectHandler extends AsyncResource {
|
|
|
40
41
|
this.context = context
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
onHeaders () {
|
|
44
|
-
|
|
44
|
+
onHeaders (statusCode) {
|
|
45
|
+
// when httpTunnel headers are allowed
|
|
46
|
+
if (this.httpTunnel) {
|
|
47
|
+
const { callback, opaque } = this
|
|
48
|
+
if (statusCode !== 200) {
|
|
49
|
+
if (callback) {
|
|
50
|
+
this.callback = null
|
|
51
|
+
const err = new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling')
|
|
52
|
+
queueMicrotask(() => {
|
|
53
|
+
this.runInAsyncScope(callback, null, err, { opaque })
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
throw new SocketError('bad connect', null)
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
onUpgrade (statusCode, rawHeaders, socket) {
|
package/lib/client.js
CHANGED
|
@@ -873,6 +873,11 @@ class Parser {
|
|
|
873
873
|
// have been queued since then.
|
|
874
874
|
util.destroy(socket, new InformationalError('reset'))
|
|
875
875
|
return constants.ERROR.PAUSED
|
|
876
|
+
} else if (client[kPipelining] === 1) {
|
|
877
|
+
// We must wait a full event loop cycle to reuse this socket to make sure
|
|
878
|
+
// that non-spec compliant servers are not closing the connection even if they
|
|
879
|
+
// said they won't.
|
|
880
|
+
setImmediate(resume, client)
|
|
876
881
|
} else {
|
|
877
882
|
resume(client)
|
|
878
883
|
}
|
package/lib/core/connect.js
CHANGED
|
@@ -21,7 +21,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
|
21
21
|
timeout = timeout == null ? 10e3 : timeout
|
|
22
22
|
maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions
|
|
23
23
|
|
|
24
|
-
return function connect ({ hostname, host, protocol, port, servername }, callback) {
|
|
24
|
+
return function connect ({ hostname, host, protocol, port, servername, httpSocket }, callback) {
|
|
25
25
|
let socket
|
|
26
26
|
if (protocol === 'https:') {
|
|
27
27
|
if (!tls) {
|
|
@@ -39,6 +39,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
|
39
39
|
...options,
|
|
40
40
|
servername,
|
|
41
41
|
session,
|
|
42
|
+
socket: httpSocket, // upgrade socket connection
|
|
42
43
|
port: port || 443,
|
|
43
44
|
host: hostname
|
|
44
45
|
})
|
|
@@ -65,6 +66,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
|
65
66
|
}
|
|
66
67
|
})
|
|
67
68
|
} else {
|
|
69
|
+
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
|
|
68
70
|
socket = net.connect({
|
|
69
71
|
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
|
|
70
72
|
...options,
|
package/lib/core/request.js
CHANGED
|
@@ -48,7 +48,11 @@ class Request {
|
|
|
48
48
|
}, handler) {
|
|
49
49
|
if (typeof path !== 'string') {
|
|
50
50
|
throw new InvalidArgumentError('path must be a string')
|
|
51
|
-
} else if (
|
|
51
|
+
} else if (
|
|
52
|
+
path[0] !== '/' &&
|
|
53
|
+
!(path.startsWith('http://') || path.startsWith('https://')) &&
|
|
54
|
+
method !== 'CONNECT'
|
|
55
|
+
) {
|
|
52
56
|
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -80,13 +84,12 @@ class Request {
|
|
|
80
84
|
this.body = null
|
|
81
85
|
} else if (util.isStream(body)) {
|
|
82
86
|
this.body = body
|
|
83
|
-
} else if (body instanceof DataView) {
|
|
84
|
-
// TODO: Why is DataView special?
|
|
85
|
-
this.body = body.buffer.byteLength ? Buffer.from(body.buffer) : null
|
|
86
|
-
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
87
|
-
this.body = body.byteLength ? Buffer.from(body) : null
|
|
88
87
|
} else if (util.isBuffer(body)) {
|
|
89
88
|
this.body = body.byteLength ? body : null
|
|
89
|
+
} else if (ArrayBuffer.isView(body)) {
|
|
90
|
+
this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
|
|
91
|
+
} else if (body instanceof ArrayBuffer) {
|
|
92
|
+
this.body = body.byteLength ? Buffer.from(body) : null
|
|
90
93
|
} else if (typeof body === 'string') {
|
|
91
94
|
this.body = body.length ? Buffer.from(body) : null
|
|
92
95
|
} else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
|
package/lib/fetch/body.js
CHANGED
|
@@ -9,7 +9,7 @@ const { kBodyUsed } = require('../core/symbols')
|
|
|
9
9
|
const assert = require('assert')
|
|
10
10
|
const { NotSupportedError } = require('../core/errors')
|
|
11
11
|
const { isErrored } = require('../core/util')
|
|
12
|
-
const { isUint8Array } = require('util/types')
|
|
12
|
+
const { isUint8Array, isArrayBuffer } = require('util/types')
|
|
13
13
|
|
|
14
14
|
let ReadableStream
|
|
15
15
|
|
|
@@ -61,7 +61,7 @@ function extractBody (object, keepalive = false) {
|
|
|
61
61
|
|
|
62
62
|
// Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`.
|
|
63
63
|
contentType = 'application/x-www-form-urlencoded;charset=UTF-8'
|
|
64
|
-
} else if (object
|
|
64
|
+
} else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) {
|
|
65
65
|
// BufferSource
|
|
66
66
|
|
|
67
67
|
if (object instanceof DataView) {
|
package/lib/fetch/constants.js
CHANGED
|
@@ -1,28 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const forbiddenHeaderNames = [
|
|
4
|
-
'accept-charset',
|
|
5
|
-
'accept-encoding',
|
|
6
|
-
'access-control-request-headers',
|
|
7
|
-
'access-control-request-method',
|
|
8
|
-
'connection',
|
|
9
|
-
'content-length',
|
|
10
|
-
'cookie',
|
|
11
|
-
'cookie2',
|
|
12
|
-
'date',
|
|
13
|
-
'dnt',
|
|
14
|
-
'expect',
|
|
15
|
-
'host',
|
|
16
|
-
'keep-alive',
|
|
17
|
-
'origin',
|
|
18
|
-
'referer',
|
|
19
|
-
'te',
|
|
20
|
-
'trailer',
|
|
21
|
-
'transfer-encoding',
|
|
22
|
-
'upgrade',
|
|
23
|
-
'via'
|
|
24
|
-
]
|
|
25
|
-
|
|
26
3
|
const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
|
|
27
4
|
|
|
28
5
|
const nullBodyStatus = [101, 204, 205, 304]
|
|
@@ -58,9 +35,6 @@ const requestCache = [
|
|
|
58
35
|
'only-if-cached'
|
|
59
36
|
]
|
|
60
37
|
|
|
61
|
-
// https://fetch.spec.whatwg.org/#forbidden-response-header-name
|
|
62
|
-
const forbiddenResponseHeaderNames = ['set-cookie', 'set-cookie2']
|
|
63
|
-
|
|
64
38
|
const requestBodyHeader = [
|
|
65
39
|
'content-encoding',
|
|
66
40
|
'content-language',
|
|
@@ -86,12 +60,8 @@ const subresource = [
|
|
|
86
60
|
''
|
|
87
61
|
]
|
|
88
62
|
|
|
89
|
-
const corsSafeListedResponseHeaderNames = [] // TODO
|
|
90
|
-
|
|
91
63
|
module.exports = {
|
|
92
64
|
subresource,
|
|
93
|
-
forbiddenResponseHeaderNames,
|
|
94
|
-
corsSafeListedResponseHeaderNames,
|
|
95
65
|
forbiddenMethods,
|
|
96
66
|
requestBodyHeader,
|
|
97
67
|
referrerPolicy,
|
|
@@ -99,7 +69,6 @@ module.exports = {
|
|
|
99
69
|
requestMode,
|
|
100
70
|
requestCredentials,
|
|
101
71
|
requestCache,
|
|
102
|
-
forbiddenHeaderNames,
|
|
103
72
|
redirectStatus,
|
|
104
73
|
corsSafeListedMethods,
|
|
105
74
|
nullBodyStatus,
|
package/lib/fetch/formdata.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { isBlobLike, isFileLike, toUSVString } = require('./util')
|
|
3
|
+
const { isBlobLike, isFileLike, toUSVString, makeIterator } = require('./util')
|
|
4
4
|
const { kState } = require('./symbols')
|
|
5
5
|
const { File, FileLike } = require('./file')
|
|
6
6
|
const { Blob } = require('buffer')
|
|
@@ -187,45 +187,68 @@ class FormData {
|
|
|
187
187
|
return this.constructor.name
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
entries () {
|
|
191
191
|
if (!(this instanceof FormData)) {
|
|
192
192
|
throw new TypeError('Illegal invocation')
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
return makeIterator(
|
|
196
|
+
makeIterable(this[kState], 'entries'),
|
|
197
|
+
'FormData'
|
|
198
|
+
)
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
keys () {
|
|
201
202
|
if (!(this instanceof FormData)) {
|
|
202
203
|
throw new TypeError('Illegal invocation')
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
return makeIterator(
|
|
207
|
+
makeIterable(this[kState], 'keys'),
|
|
208
|
+
'FormData'
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
values () {
|
|
213
|
+
if (!(this instanceof FormData)) {
|
|
214
|
+
throw new TypeError('Illegal invocation')
|
|
207
215
|
}
|
|
216
|
+
|
|
217
|
+
return makeIterator(
|
|
218
|
+
makeIterable(this[kState], 'values'),
|
|
219
|
+
'FormData'
|
|
220
|
+
)
|
|
208
221
|
}
|
|
209
222
|
|
|
210
|
-
|
|
223
|
+
/**
|
|
224
|
+
* @param {(value: string, key: string, self: FormData) => void} callbackFn
|
|
225
|
+
* @param {unknown} thisArg
|
|
226
|
+
*/
|
|
227
|
+
forEach (callbackFn, thisArg = globalThis) {
|
|
211
228
|
if (!(this instanceof FormData)) {
|
|
212
229
|
throw new TypeError('Illegal invocation')
|
|
213
230
|
}
|
|
214
231
|
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
if (arguments.length < 1) {
|
|
233
|
+
throw new TypeError(
|
|
234
|
+
`Failed to execute 'forEach' on 'FormData': 1 argument required, but only ${arguments.length} present.`
|
|
235
|
+
)
|
|
217
236
|
}
|
|
218
|
-
}
|
|
219
237
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
if (typeof callbackFn !== 'function') {
|
|
239
|
+
throw new TypeError(
|
|
240
|
+
"Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'."
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const [key, value] of this) {
|
|
245
|
+
callbackFn.apply(thisArg, [value, key, this])
|
|
225
246
|
}
|
|
226
247
|
}
|
|
227
248
|
}
|
|
228
249
|
|
|
250
|
+
FormData.prototype[Symbol.iterator] = FormData.prototype.entries
|
|
251
|
+
|
|
229
252
|
function makeEntry (name, value, filename) {
|
|
230
253
|
// To create an entry for name, value, and optionally a filename, run these
|
|
231
254
|
// steps:
|
|
@@ -243,8 +266,8 @@ function makeEntry (name, value, filename) {
|
|
|
243
266
|
// object, representing the same bytes, whose name attribute value is "blob".
|
|
244
267
|
if (isBlobLike(value) && !isFileLike(value)) {
|
|
245
268
|
value = value instanceof Blob
|
|
246
|
-
? new File([value], 'blob')
|
|
247
|
-
: new FileLike(value, 'blob')
|
|
269
|
+
? new File([value], 'blob', value)
|
|
270
|
+
: new FileLike(value, 'blob', value)
|
|
248
271
|
}
|
|
249
272
|
|
|
250
273
|
// 4. If value is (now) a File object and filename is given, then set value to a
|
|
@@ -256,8 +279,8 @@ function makeEntry (name, value, filename) {
|
|
|
256
279
|
// creating one more File instance doesn't make much sense....
|
|
257
280
|
if (isFileLike(value) && filename != null) {
|
|
258
281
|
value = value instanceof File
|
|
259
|
-
? new File([value], filename)
|
|
260
|
-
: new FileLike(value, filename)
|
|
282
|
+
? new File([value], filename, value)
|
|
283
|
+
: new FileLike(value, filename, value)
|
|
261
284
|
}
|
|
262
285
|
|
|
263
286
|
// 5. Set entry’s value to value.
|
|
@@ -267,4 +290,18 @@ function makeEntry (name, value, filename) {
|
|
|
267
290
|
return entry
|
|
268
291
|
}
|
|
269
292
|
|
|
293
|
+
function * makeIterable (entries, type) {
|
|
294
|
+
// The value pairs to iterate over are this’s entry list’s entries
|
|
295
|
+
// with the key being the name and the value being the value.
|
|
296
|
+
for (const { name, value } of entries) {
|
|
297
|
+
if (type === 'entries') {
|
|
298
|
+
yield [name, value]
|
|
299
|
+
} else if (type === 'values') {
|
|
300
|
+
yield value
|
|
301
|
+
} else {
|
|
302
|
+
yield name
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
270
307
|
module.exports = { FormData }
|
package/lib/fetch/headers.js
CHANGED
|
@@ -6,10 +6,7 @@ const { validateHeaderName, validateHeaderValue } = require('http')
|
|
|
6
6
|
const { kHeadersList } = require('../core/symbols')
|
|
7
7
|
const { kGuard } = require('./symbols')
|
|
8
8
|
const { kEnumerableProperty } = require('../core/util')
|
|
9
|
-
const {
|
|
10
|
-
forbiddenHeaderNames,
|
|
11
|
-
forbiddenResponseHeaderNames
|
|
12
|
-
} = require('./constants')
|
|
9
|
+
const { makeIterator } = require('./util')
|
|
13
10
|
|
|
14
11
|
const kHeadersMap = Symbol('headers map')
|
|
15
12
|
const kHeadersSortedMap = Symbol('headers map sorted')
|
|
@@ -77,33 +74,6 @@ function fill (headers, object) {
|
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
|
81
|
-
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
|
82
|
-
|
|
83
|
-
// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
|
|
84
|
-
function makeHeadersIterator (iterator) {
|
|
85
|
-
const i = {
|
|
86
|
-
next () {
|
|
87
|
-
if (Object.getPrototypeOf(this) !== i) {
|
|
88
|
-
throw new TypeError(
|
|
89
|
-
'\'next\' called on an object that does not implement interface Headers Iterator.'
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return iterator.next()
|
|
94
|
-
},
|
|
95
|
-
// The class string of an iterator prototype object for a given interface is the
|
|
96
|
-
// result of concatenating the identifier of the interface and the string " Iterator".
|
|
97
|
-
[Symbol.toStringTag]: 'Headers Iterator'
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
|
|
101
|
-
Object.setPrototypeOf(i, esIteratorPrototype)
|
|
102
|
-
// esIteratorPrototype needs to be the prototype of i
|
|
103
|
-
// which is the prototype of an empty object. Yes, it's confusing.
|
|
104
|
-
return Object.setPrototypeOf({}, i)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
77
|
class HeadersList {
|
|
108
78
|
constructor (init) {
|
|
109
79
|
if (init instanceof HeadersList) {
|
|
@@ -115,6 +85,11 @@ class HeadersList {
|
|
|
115
85
|
}
|
|
116
86
|
}
|
|
117
87
|
|
|
88
|
+
clear () {
|
|
89
|
+
this[kHeadersMap].clear()
|
|
90
|
+
this[kHeadersSortedMap] = null
|
|
91
|
+
}
|
|
92
|
+
|
|
118
93
|
append (name, value) {
|
|
119
94
|
this[kHeadersSortedMap] = null
|
|
120
95
|
|
|
@@ -211,22 +186,11 @@ class Headers {
|
|
|
211
186
|
)
|
|
212
187
|
}
|
|
213
188
|
|
|
214
|
-
|
|
215
|
-
|
|
189
|
+
// Note: undici does not implement forbidden header names
|
|
216
190
|
if (this[kGuard] === 'immutable') {
|
|
217
191
|
throw new TypeError('immutable')
|
|
218
|
-
} else if (
|
|
219
|
-
this[kGuard] === 'request' &&
|
|
220
|
-
forbiddenHeaderNames.includes(normalizedName)
|
|
221
|
-
) {
|
|
222
|
-
return
|
|
223
192
|
} else if (this[kGuard] === 'request-no-cors') {
|
|
224
193
|
// TODO
|
|
225
|
-
} else if (
|
|
226
|
-
this[kGuard] === 'response' &&
|
|
227
|
-
forbiddenResponseHeaderNames.includes(normalizedName)
|
|
228
|
-
) {
|
|
229
|
-
return
|
|
230
194
|
}
|
|
231
195
|
|
|
232
196
|
return this[kHeadersList].append(String(name), String(value))
|
|
@@ -244,22 +208,11 @@ class Headers {
|
|
|
244
208
|
)
|
|
245
209
|
}
|
|
246
210
|
|
|
247
|
-
|
|
248
|
-
|
|
211
|
+
// Note: undici does not implement forbidden header names
|
|
249
212
|
if (this[kGuard] === 'immutable') {
|
|
250
213
|
throw new TypeError('immutable')
|
|
251
|
-
} else if (
|
|
252
|
-
this[kGuard] === 'request' &&
|
|
253
|
-
forbiddenHeaderNames.includes(normalizedName)
|
|
254
|
-
) {
|
|
255
|
-
return
|
|
256
214
|
} else if (this[kGuard] === 'request-no-cors') {
|
|
257
215
|
// TODO
|
|
258
|
-
} else if (
|
|
259
|
-
this[kGuard] === 'response' &&
|
|
260
|
-
forbiddenResponseHeaderNames.includes(normalizedName)
|
|
261
|
-
) {
|
|
262
|
-
return
|
|
263
216
|
}
|
|
264
217
|
|
|
265
218
|
return this[kHeadersList].delete(String(name))
|
|
@@ -307,20 +260,11 @@ class Headers {
|
|
|
307
260
|
)
|
|
308
261
|
}
|
|
309
262
|
|
|
263
|
+
// Note: undici does not implement forbidden header names
|
|
310
264
|
if (this[kGuard] === 'immutable') {
|
|
311
265
|
throw new TypeError('immutable')
|
|
312
|
-
} else if (
|
|
313
|
-
this[kGuard] === 'request' &&
|
|
314
|
-
forbiddenHeaderNames.includes(String(name).toLocaleLowerCase())
|
|
315
|
-
) {
|
|
316
|
-
return
|
|
317
266
|
} else if (this[kGuard] === 'request-no-cors') {
|
|
318
267
|
// TODO
|
|
319
|
-
} else if (
|
|
320
|
-
this[kGuard] === 'response' &&
|
|
321
|
-
forbiddenResponseHeaderNames.includes(String(name).toLocaleLowerCase())
|
|
322
|
-
) {
|
|
323
|
-
return
|
|
324
268
|
}
|
|
325
269
|
|
|
326
270
|
return this[kHeadersList].set(String(name), String(value))
|
|
@@ -336,7 +280,7 @@ class Headers {
|
|
|
336
280
|
throw new TypeError('Illegal invocation')
|
|
337
281
|
}
|
|
338
282
|
|
|
339
|
-
return
|
|
283
|
+
return makeIterator(this[kHeadersSortedMap].keys(), 'Headers')
|
|
340
284
|
}
|
|
341
285
|
|
|
342
286
|
values () {
|
|
@@ -344,7 +288,7 @@ class Headers {
|
|
|
344
288
|
throw new TypeError('Illegal invocation')
|
|
345
289
|
}
|
|
346
290
|
|
|
347
|
-
return
|
|
291
|
+
return makeIterator(this[kHeadersSortedMap].values(), 'Headers')
|
|
348
292
|
}
|
|
349
293
|
|
|
350
294
|
entries () {
|
|
@@ -352,7 +296,7 @@ class Headers {
|
|
|
352
296
|
throw new TypeError('Illegal invocation')
|
|
353
297
|
}
|
|
354
298
|
|
|
355
|
-
return
|
|
299
|
+
return makeIterator(this[kHeadersSortedMap].entries(), 'Headers')
|
|
356
300
|
}
|
|
357
301
|
|
|
358
302
|
/**
|
package/lib/fetch/index.js
CHANGED
|
@@ -1164,7 +1164,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1164
1164
|
if (
|
|
1165
1165
|
([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
|
|
1166
1166
|
(actualResponse.status === 303 &&
|
|
1167
|
-
!['GET', '
|
|
1167
|
+
!['GET', 'HEAD'].includes(request.method))
|
|
1168
1168
|
) {
|
|
1169
1169
|
// then:
|
|
1170
1170
|
// 1. Set request’s method to `GET` and request’s body to null.
|
package/lib/fetch/request.js
CHANGED
|
@@ -384,8 +384,8 @@ class Request {
|
|
|
384
384
|
// Realm, whose header list is request’s header list and guard is
|
|
385
385
|
// "request".
|
|
386
386
|
this[kHeaders] = new Headers()
|
|
387
|
-
this[kHeaders][kGuard] = 'request'
|
|
388
387
|
this[kHeaders][kHeadersList] = request.headersList
|
|
388
|
+
this[kHeaders][kGuard] = 'request'
|
|
389
389
|
this[kHeaders][kRealm] = this[kRealm]
|
|
390
390
|
|
|
391
391
|
// 31. If this’s request’s mode is "no-cors", then:
|
|
@@ -406,7 +406,7 @@ class Request {
|
|
|
406
406
|
if (Object.keys(init).length !== 0) {
|
|
407
407
|
// 1. Let headers be a copy of this’s headers and its associated header
|
|
408
408
|
// list.
|
|
409
|
-
let headers = new Headers(this
|
|
409
|
+
let headers = new Headers(this[kHeaders])
|
|
410
410
|
|
|
411
411
|
// 2. If init["headers"] exists, then set headers to init["headers"].
|
|
412
412
|
if (init.headers !== undefined) {
|
|
@@ -414,18 +414,17 @@ class Request {
|
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
// 3. Empty this’s headers’s header list.
|
|
417
|
-
this[
|
|
418
|
-
this[kHeaders][kHeadersList] = this[kState].headersList
|
|
417
|
+
this[kHeaders][kHeadersList].clear()
|
|
419
418
|
|
|
420
419
|
// 4. If headers is a Headers object, then for each header in its header
|
|
421
420
|
// list, append header’s name/header’s value to this’s headers.
|
|
422
421
|
if (headers.constructor.name === 'Headers') {
|
|
423
|
-
for (const [key, val] of headers
|
|
422
|
+
for (const [key, val] of headers) {
|
|
424
423
|
this[kHeaders].append(key, val)
|
|
425
424
|
}
|
|
426
425
|
} else {
|
|
427
426
|
// 5. Otherwise, fill this’s headers with headers.
|
|
428
|
-
fillHeaders(this[
|
|
427
|
+
fillHeaders(this[kHeaders], headers)
|
|
429
428
|
}
|
|
430
429
|
}
|
|
431
430
|
|
package/lib/fetch/response.js
CHANGED
|
@@ -8,9 +8,7 @@ const { kEnumerableProperty } = util
|
|
|
8
8
|
const { responseURL, isValidReasonPhrase, toUSVString, isCancelled, isAborted, serializeJavascriptValueToJSONString } = require('./util')
|
|
9
9
|
const {
|
|
10
10
|
redirectStatus,
|
|
11
|
-
nullBodyStatus
|
|
12
|
-
forbiddenResponseHeaderNames,
|
|
13
|
-
corsSafeListedResponseHeaderNames
|
|
11
|
+
nullBodyStatus
|
|
14
12
|
} = require('./constants')
|
|
15
13
|
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
|
|
16
14
|
const { kHeadersList } = require('../core/symbols')
|
|
@@ -380,28 +378,6 @@ function makeFilteredResponse (response, state) {
|
|
|
380
378
|
})
|
|
381
379
|
}
|
|
382
380
|
|
|
383
|
-
function makeFilteredHeadersList (headersList, filter) {
|
|
384
|
-
return new Proxy(headersList, {
|
|
385
|
-
get (target, prop) {
|
|
386
|
-
// Override methods used by Headers class.
|
|
387
|
-
if (prop === 'get' || prop === 'has') {
|
|
388
|
-
const defaultReturn = prop === 'has' ? false : null
|
|
389
|
-
return (name) => filter(name) ? target[prop](name) : defaultReturn
|
|
390
|
-
} else if (prop === Symbol.iterator) {
|
|
391
|
-
return function * () {
|
|
392
|
-
for (const entry of target) {
|
|
393
|
-
if (filter(entry[0])) {
|
|
394
|
-
yield entry
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
return target[prop]
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
})
|
|
403
|
-
}
|
|
404
|
-
|
|
405
381
|
// https://fetch.spec.whatwg.org/#concept-filtered-response
|
|
406
382
|
function filterResponse (response, type) {
|
|
407
383
|
// Set response to the following filtered response with response as its
|
|
@@ -411,12 +387,10 @@ function filterResponse (response, type) {
|
|
|
411
387
|
// and header list excludes any headers in internal response’s header list
|
|
412
388
|
// whose name is a forbidden response-header name.
|
|
413
389
|
|
|
390
|
+
// Note: undici does not implement forbidden response-header names
|
|
414
391
|
return makeFilteredResponse(response, {
|
|
415
392
|
type: 'basic',
|
|
416
|
-
headersList:
|
|
417
|
-
response.headersList,
|
|
418
|
-
(name) => !forbiddenResponseHeaderNames.includes(name.toLowerCase())
|
|
419
|
-
)
|
|
393
|
+
headersList: response.headersList
|
|
420
394
|
})
|
|
421
395
|
} else if (type === 'cors') {
|
|
422
396
|
// A CORS filtered response is a filtered response whose type is "cors"
|
|
@@ -424,9 +398,10 @@ function filterResponse (response, type) {
|
|
|
424
398
|
// list whose name is not a CORS-safelisted response-header name, given
|
|
425
399
|
// internal response’s CORS-exposed header-name list.
|
|
426
400
|
|
|
401
|
+
// Note: undici does not implement CORS-safelisted response-header names
|
|
427
402
|
return makeFilteredResponse(response, {
|
|
428
403
|
type: 'cors',
|
|
429
|
-
headersList:
|
|
404
|
+
headersList: response.headersList
|
|
430
405
|
})
|
|
431
406
|
} else if (type === 'opaque') {
|
|
432
407
|
// An opaque filtered response is a filtered response whose type is
|
|
@@ -449,7 +424,7 @@ function filterResponse (response, type) {
|
|
|
449
424
|
type: 'opaqueredirect',
|
|
450
425
|
status: 0,
|
|
451
426
|
statusText: '',
|
|
452
|
-
headersList:
|
|
427
|
+
headersList: [],
|
|
453
428
|
body: null
|
|
454
429
|
})
|
|
455
430
|
} else {
|
package/lib/fetch/util.js
CHANGED
|
@@ -361,6 +361,33 @@ function serializeJavascriptValueToJSONString (value) {
|
|
|
361
361
|
return result
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
|
365
|
+
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
|
366
|
+
|
|
367
|
+
// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
|
|
368
|
+
function makeIterator (iterator, name) {
|
|
369
|
+
const i = {
|
|
370
|
+
next () {
|
|
371
|
+
if (Object.getPrototypeOf(this) !== i) {
|
|
372
|
+
throw new TypeError(
|
|
373
|
+
`'next' called on an object that does not implement interface ${name} Iterator.`
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return iterator.next()
|
|
378
|
+
},
|
|
379
|
+
// The class string of an iterator prototype object for a given interface is the
|
|
380
|
+
// result of concatenating the identifier of the interface and the string " Iterator".
|
|
381
|
+
[Symbol.toStringTag]: `${name} Iterator`
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
|
|
385
|
+
Object.setPrototypeOf(i, esIteratorPrototype)
|
|
386
|
+
// esIteratorPrototype needs to be the prototype of i
|
|
387
|
+
// which is the prototype of an empty object. Yes, it's confusing.
|
|
388
|
+
return Object.setPrototypeOf({}, i)
|
|
389
|
+
}
|
|
390
|
+
|
|
364
391
|
module.exports = {
|
|
365
392
|
isAborted,
|
|
366
393
|
isCancelled,
|
|
@@ -390,5 +417,6 @@ module.exports = {
|
|
|
390
417
|
isValidReasonPhrase,
|
|
391
418
|
sameOrigin,
|
|
392
419
|
normalizeMethod,
|
|
393
|
-
serializeJavascriptValueToJSONString
|
|
420
|
+
serializeJavascriptValueToJSONString,
|
|
421
|
+
makeIterator
|
|
394
422
|
}
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
kMockDispatch
|
|
11
11
|
} = require('./mock-symbols')
|
|
12
12
|
const { InvalidArgumentError } = require('../core/errors')
|
|
13
|
+
const { buildURL } = require('../core/util')
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Defines the scope API for an interceptor reply
|
|
@@ -70,9 +71,13 @@ class MockInterceptor {
|
|
|
70
71
|
// As per RFC 3986, clients are not supposed to send URI
|
|
71
72
|
// fragments to servers when they retrieve a document,
|
|
72
73
|
if (typeof opts.path === 'string') {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (opts.query) {
|
|
75
|
+
opts.path = buildURL(opts.path, opts.query)
|
|
76
|
+
} else {
|
|
77
|
+
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
|
|
78
|
+
const parsedURL = new URL(opts.path, 'data://')
|
|
79
|
+
opts.path = parsedURL.pathname + parsedURL.search
|
|
80
|
+
}
|
|
76
81
|
}
|
|
77
82
|
if (typeof opts.method === 'string') {
|
|
78
83
|
opts.method = opts.method.toUpperCase()
|
package/lib/mock/mock-utils.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
kOrigin,
|
|
9
9
|
kGetNetConnect
|
|
10
10
|
} = require('./mock-symbols')
|
|
11
|
+
const { buildURL } = require('../core/util')
|
|
11
12
|
|
|
12
13
|
function matchValue (match, value) {
|
|
13
14
|
if (typeof match === 'string') {
|
|
@@ -98,10 +99,12 @@ function getResponseData (data) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function getMockDispatch (mockDispatches, key) {
|
|
102
|
+
const resolvedPath = key.query ? buildURL(key.path, key.query) : key.path
|
|
103
|
+
|
|
101
104
|
// Match path
|
|
102
|
-
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path,
|
|
105
|
+
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, resolvedPath))
|
|
103
106
|
if (matchedMockDispatches.length === 0) {
|
|
104
|
-
throw new MockNotMatchedError(`Mock dispatch not matched for path '${
|
|
107
|
+
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`)
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
// Match method
|
|
@@ -146,12 +149,13 @@ function deleteMockDispatch (mockDispatches, key) {
|
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
function buildKey (opts) {
|
|
149
|
-
const { path, method, body, headers } = opts
|
|
152
|
+
const { path, method, body, headers, query } = opts
|
|
150
153
|
return {
|
|
151
154
|
path,
|
|
152
155
|
method,
|
|
153
156
|
body,
|
|
154
|
-
headers
|
|
157
|
+
headers,
|
|
158
|
+
query
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
|
package/lib/proxy-agent.js
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { kProxy, kClose, kDestroy } = require('./core/symbols')
|
|
4
|
+
const Client = require('./agent')
|
|
4
5
|
const Agent = require('./agent')
|
|
5
6
|
const DispatcherBase = require('./dispatcher-base')
|
|
6
7
|
const { InvalidArgumentError } = require('./core/errors')
|
|
8
|
+
const buildConnector = require('./core/connect')
|
|
7
9
|
|
|
8
10
|
const kAgent = Symbol('proxy agent')
|
|
11
|
+
const kClient = Symbol('proxy client')
|
|
12
|
+
const kProxyHeaders = Symbol('proxy headers')
|
|
13
|
+
const kRequestTls = Symbol('request tls settings')
|
|
14
|
+
const kProxyTls = Symbol('proxy tls settings')
|
|
15
|
+
const kConnectEndpoint = Symbol('connect endpoint function')
|
|
9
16
|
|
|
10
17
|
class ProxyAgent extends DispatcherBase {
|
|
11
18
|
constructor (opts) {
|
|
12
19
|
super(opts)
|
|
13
20
|
this[kProxy] = buildProxyOptions(opts)
|
|
14
|
-
this[
|
|
21
|
+
this[kRequestTls] = opts.requestTls
|
|
22
|
+
this[kProxyTls] = opts.proxyTls
|
|
23
|
+
this[kProxyHeaders] = {}
|
|
24
|
+
|
|
25
|
+
if (opts.auth) {
|
|
26
|
+
this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const connect = buildConnector({ ...opts.proxyTls })
|
|
30
|
+
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
|
|
31
|
+
this[kClient] = new Client({ origin: opts.origin, connect })
|
|
32
|
+
this[kAgent] = new Agent({ ...opts, connect: this.connectTunnel.bind(this) })
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
dispatch (opts, handler) {
|
|
18
36
|
const { host } = new URL(opts.origin)
|
|
37
|
+
const headers = buildHeaders(opts.headers)
|
|
38
|
+
throwIfProxyAuthIsSent(headers)
|
|
19
39
|
return this[kAgent].dispatch(
|
|
20
40
|
{
|
|
21
41
|
...opts,
|
|
22
|
-
origin: this[kProxy].uri,
|
|
23
|
-
path: opts.origin + opts.path,
|
|
24
42
|
headers: {
|
|
25
|
-
...
|
|
43
|
+
...headers,
|
|
26
44
|
host
|
|
27
45
|
}
|
|
28
46
|
},
|
|
@@ -30,12 +48,43 @@ class ProxyAgent extends DispatcherBase {
|
|
|
30
48
|
)
|
|
31
49
|
}
|
|
32
50
|
|
|
51
|
+
async connectTunnel (opts, callback) {
|
|
52
|
+
try {
|
|
53
|
+
const { socket } = await this[kClient].connect({
|
|
54
|
+
origin: this[kProxy].origin,
|
|
55
|
+
port: this[kProxy].port,
|
|
56
|
+
path: opts.host,
|
|
57
|
+
signal: opts.signal,
|
|
58
|
+
headers: {
|
|
59
|
+
...this[kProxyHeaders],
|
|
60
|
+
host: opts.host
|
|
61
|
+
},
|
|
62
|
+
httpTunnel: true
|
|
63
|
+
})
|
|
64
|
+
if (opts.protocol !== 'https:') {
|
|
65
|
+
callback(null, socket)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
let servername
|
|
69
|
+
if (this[kRequestTls]) {
|
|
70
|
+
servername = this[kRequestTls].servername
|
|
71
|
+
} else {
|
|
72
|
+
servername = opts.servername
|
|
73
|
+
}
|
|
74
|
+
this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback)
|
|
75
|
+
} catch (err) {
|
|
76
|
+
callback(err)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
33
80
|
async [kClose] () {
|
|
34
81
|
await this[kAgent].close()
|
|
82
|
+
await this[kClient].close()
|
|
35
83
|
}
|
|
36
84
|
|
|
37
85
|
async [kDestroy] () {
|
|
38
86
|
await this[kAgent].destroy()
|
|
87
|
+
await this[kClient].destroy()
|
|
39
88
|
}
|
|
40
89
|
}
|
|
41
90
|
|
|
@@ -48,10 +97,7 @@ function buildProxyOptions (opts) {
|
|
|
48
97
|
throw new InvalidArgumentError('Proxy opts.uri is mandatory')
|
|
49
98
|
}
|
|
50
99
|
|
|
51
|
-
return
|
|
52
|
-
uri: opts.uri,
|
|
53
|
-
protocol: opts.protocol || 'https'
|
|
54
|
-
}
|
|
100
|
+
return new URL(opts.uri)
|
|
55
101
|
}
|
|
56
102
|
|
|
57
103
|
/**
|
|
@@ -75,4 +121,20 @@ function buildHeaders (headers) {
|
|
|
75
121
|
return headers
|
|
76
122
|
}
|
|
77
123
|
|
|
124
|
+
/**
|
|
125
|
+
* @param {Record<string, string>} headers
|
|
126
|
+
*
|
|
127
|
+
* Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers
|
|
128
|
+
* Nevertheless, it was changed and to avoid a security vulnerability by end users
|
|
129
|
+
* this check was created.
|
|
130
|
+
* It should be removed in the next major version for performance reasons
|
|
131
|
+
*/
|
|
132
|
+
function throwIfProxyAuthIsSent (headers) {
|
|
133
|
+
const existProxyAuth = headers && Object.keys(headers)
|
|
134
|
+
.find((key) => key.toLowerCase() === 'proxy-authorization')
|
|
135
|
+
if (existProxyAuth) {
|
|
136
|
+
throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
78
140
|
module.exports = ProxyAgent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.1",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@sinonjs/fake-timers": "^9.1.2",
|
|
68
68
|
"@types/node": "^17.0.29",
|
|
69
69
|
"abort-controller": "^3.0.0",
|
|
70
|
-
"busboy": "^
|
|
70
|
+
"busboy": "^1.6.0",
|
|
71
71
|
"chai": "^4.3.4",
|
|
72
72
|
"chai-as-promised": "^7.1.1",
|
|
73
73
|
"chai-iterator": "^3.0.2",
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"delay": "^5.0.0",
|
|
78
78
|
"docsify-cli": "^4.4.3",
|
|
79
79
|
"formdata-node": "^4.3.1",
|
|
80
|
-
"https-pem": "^
|
|
81
|
-
"husky": "^
|
|
80
|
+
"https-pem": "^3.0.0",
|
|
81
|
+
"husky": "^8.0.1",
|
|
82
82
|
"import-fresh": "^3.3.0",
|
|
83
83
|
"jest": "^28.0.1",
|
|
84
84
|
"jsfuzz": "^1.0.15",
|
package/types/connector.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Socket } from 'net'
|
|
1
|
+
import {TLSSocket, ConnectionOptions} from 'tls'
|
|
2
|
+
import {IpcNetConnectOpts, Socket, TcpNetConnectOpts} from 'net'
|
|
4
3
|
|
|
5
4
|
export = buildConnector
|
|
6
5
|
declare function buildConnector (options?: buildConnector.BuildOptions): typeof buildConnector.connector
|
|
7
6
|
|
|
8
7
|
declare namespace buildConnector {
|
|
9
|
-
export
|
|
8
|
+
export type BuildOptions = (ConnectionOptions | TcpNetConnectOpts | IpcNetConnectOpts) & {
|
|
10
9
|
maxCachedSessions?: number | null;
|
|
11
10
|
socketPath?: string | null;
|
|
12
11
|
timeout?: number | null;
|
|
13
|
-
|
|
12
|
+
port?: number;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export interface Options {
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Duplex, Readable, Writable } from 'stream'
|
|
|
3
3
|
import { EventEmitter } from 'events'
|
|
4
4
|
import { IncomingHttpHeaders } from 'http'
|
|
5
5
|
import { Blob } from 'buffer'
|
|
6
|
-
import BodyReadable
|
|
6
|
+
import BodyReadable = require('./readable')
|
|
7
7
|
import { FormData } from './formdata'
|
|
8
8
|
|
|
9
9
|
type AbortSignal = unknown;
|
package/types/fetch.d.ts
CHANGED
|
@@ -38,21 +38,21 @@ export interface BodyMixin {
|
|
|
38
38
|
readonly text: () => Promise<string>
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export interface
|
|
41
|
+
export interface SpecIterator<T, TReturn = any, TNext = undefined> {
|
|
42
42
|
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export interface
|
|
46
|
-
[Symbol.iterator]():
|
|
45
|
+
export interface SpecIterableIterator<T> extends SpecIterator<T> {
|
|
46
|
+
[Symbol.iterator](): SpecIterableIterator<T>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export interface
|
|
50
|
-
[Symbol.iterator]():
|
|
49
|
+
export interface SpecIterable<T> {
|
|
50
|
+
[Symbol.iterator](): SpecIterator<T>;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers
|
|
54
54
|
|
|
55
|
-
export declare class Headers implements
|
|
55
|
+
export declare class Headers implements SpecIterable<[string, string]> {
|
|
56
56
|
constructor (init?: HeadersInit)
|
|
57
57
|
readonly append: (name: string, value: string) => void
|
|
58
58
|
readonly delete: (name: string) => void
|
|
@@ -64,10 +64,10 @@ export declare class Headers implements HeadersIterable<[string, string]> {
|
|
|
64
64
|
thisArg?: unknown
|
|
65
65
|
) => void
|
|
66
66
|
|
|
67
|
-
readonly keys: () =>
|
|
68
|
-
readonly values: () =>
|
|
69
|
-
readonly entries: () =>
|
|
70
|
-
readonly [Symbol.iterator]: () =>
|
|
67
|
+
readonly keys: () => SpecIterableIterator<string>
|
|
68
|
+
readonly values: () => SpecIterableIterator<string>
|
|
69
|
+
readonly entries: () => SpecIterableIterator<[string, string]>
|
|
70
|
+
readonly [Symbol.iterator]: () => SpecIterator<[string, string]>
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export type RequestCache =
|
package/types/formdata.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
|
|
4
4
|
import { File } from './file'
|
|
5
|
+
import { SpecIterator, SpecIterableIterator } from './fetch'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* A `string` or `File` that represents a single value from a set of `FormData` key-value pairs.
|
|
@@ -73,32 +74,35 @@ export declare class FormData {
|
|
|
73
74
|
delete(name: string): void
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
*
|
|
77
|
-
* Each key is a `string`.
|
|
77
|
+
* Executes given callback function for each field of the FormData instance
|
|
78
78
|
*/
|
|
79
|
-
|
|
79
|
+
forEach: (
|
|
80
|
+
callbackfn: (value: FormDataEntryValue, key: string, iterable: FormData) => void,
|
|
81
|
+
thisArg?: unknown
|
|
82
|
+
) => void
|
|
80
83
|
|
|
81
84
|
/**
|
|
82
|
-
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through
|
|
83
|
-
*
|
|
85
|
+
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all keys contained in this `FormData` object.
|
|
86
|
+
* Each key is a `string`.
|
|
84
87
|
*/
|
|
85
|
-
|
|
88
|
+
keys: () => SpecIterableIterator<string>
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
91
|
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all values contained in this object `FormData` object.
|
|
89
92
|
* Each value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
|
|
90
93
|
*/
|
|
91
|
-
values()
|
|
94
|
+
values: () => SpecIterableIterator<FormDataEntryValue>
|
|
92
95
|
|
|
93
96
|
/**
|
|
94
|
-
*
|
|
97
|
+
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
|
|
98
|
+
* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
|
|
95
99
|
*/
|
|
96
|
-
|
|
100
|
+
entries: () => SpecIterableIterator<[string, FormDataEntryValue]>
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
|
-
*
|
|
103
|
+
* An alias for FormData#entries()
|
|
100
104
|
*/
|
|
101
|
-
|
|
105
|
+
[Symbol.iterator]: () => SpecIterableIterator<[string, FormDataEntryValue]>
|
|
102
106
|
|
|
103
107
|
readonly [Symbol.toStringTag]: string
|
|
104
108
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IncomingHttpHeaders } from 'http'
|
|
2
|
-
import Dispatcher
|
|
2
|
+
import Dispatcher = require('./dispatcher');
|
|
3
3
|
import { BodyInit, Headers } from './fetch'
|
|
4
4
|
|
|
5
5
|
export {
|
|
@@ -50,6 +50,8 @@ declare namespace MockInterceptor {
|
|
|
50
50
|
body?: string | RegExp | ((body: string) => boolean);
|
|
51
51
|
/** Headers to intercept on. */
|
|
52
52
|
headers?: Record<string, string | RegExp | ((body: string) => boolean)> | ((headers: Record<string, string>) => boolean);
|
|
53
|
+
/** Query params to intercept on */
|
|
54
|
+
query?: Record<string, any>;
|
|
53
55
|
}
|
|
54
56
|
export interface MockDispatch<TData extends object = object, TError extends Error = Error> extends Options {
|
|
55
57
|
times: number | null;
|
package/types/proxy-agent.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TlsOptions } from 'tls'
|
|
1
2
|
import Agent = require('./agent')
|
|
2
3
|
import Dispatcher = require('./dispatcher')
|
|
3
4
|
|
|
@@ -13,5 +14,8 @@ declare class ProxyAgent extends Dispatcher {
|
|
|
13
14
|
declare namespace ProxyAgent {
|
|
14
15
|
export interface Options extends Agent.Options {
|
|
15
16
|
uri: string;
|
|
17
|
+
auth?: string;
|
|
18
|
+
requestTls?: TlsOptions & { servername?: string };
|
|
19
|
+
proxyTls?: TlsOptions & { servername?: string };
|
|
16
20
|
}
|
|
17
21
|
}
|