undici 6.20.0 → 6.21.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 +1 -0
- package/docs/docs/api/Dispatcher.md +7 -5
- package/docs/docs/api/Fetch.md +1 -0
- package/lib/api/readable.js +33 -9
- package/lib/core/connect.js +5 -0
- package/lib/dispatcher/client-h1.js +7 -4
- package/lib/dispatcher/client-h2.js +56 -19
- package/lib/dispatcher/client.js +4 -2
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/dispatcher/proxy-agent.js +3 -1
- package/lib/handler/retry-handler.js +3 -3
- package/lib/web/cache/cache.js +1 -0
- package/lib/web/cache/cachestorage.js +2 -0
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/constants.js +35 -26
- package/lib/web/fetch/formdata.js +2 -0
- package/lib/web/fetch/headers.js +2 -0
- package/lib/web/fetch/index.js +1 -1
- package/lib/web/fetch/request.js +1 -0
- package/lib/web/fetch/response.js +1 -0
- package/lib/web/fetch/webidl.js +2 -0
- package/lib/web/websocket/events.js +4 -0
- package/lib/web/websocket/websocket.js +2 -0
- package/package.json +4 -1
- package/types/dispatcher.d.ts +1 -0
- package/types/readable.d.ts +5 -0
- package/types/webidl.d.ts +6 -0
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ The `body` mixins are the most common way to format the request/response body. M
|
|
|
84
84
|
|
|
85
85
|
- [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
|
|
86
86
|
- [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
|
|
87
|
+
- [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
|
|
87
88
|
- [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
|
|
88
89
|
- [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
|
|
89
90
|
|
|
@@ -488,11 +488,13 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
488
488
|
|
|
489
489
|
`body` contains the following additional [body mixin](https://fetch.spec.whatwg.org/#body-mixin) methods and properties:
|
|
490
490
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
491
|
+
* [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
|
|
492
|
+
* [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
|
|
493
|
+
* [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
|
|
494
|
+
* [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
|
|
495
|
+
* [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
|
|
496
|
+
* `body`
|
|
497
|
+
* `bodyUsed`
|
|
496
498
|
|
|
497
499
|
`body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`.
|
|
498
500
|
|
package/docs/docs/api/Fetch.md
CHANGED
|
@@ -28,6 +28,7 @@ This API is implemented as per the standard, you can find documentation on [MDN]
|
|
|
28
28
|
|
|
29
29
|
- [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
|
|
30
30
|
- [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
|
|
31
|
+
- [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
|
|
31
32
|
- [`.formData()`](https://fetch.spec.whatwg.org/#dom-body-formdata)
|
|
32
33
|
- [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
|
|
33
34
|
- [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
|
package/lib/api/readable.js
CHANGED
|
@@ -121,6 +121,11 @@ class BodyReadable extends Readable {
|
|
|
121
121
|
return consume(this, 'blob')
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// https://fetch.spec.whatwg.org/#dom-body-bytes
|
|
125
|
+
async bytes () {
|
|
126
|
+
return consume(this, 'bytes')
|
|
127
|
+
}
|
|
128
|
+
|
|
124
129
|
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
125
130
|
async arrayBuffer () {
|
|
126
131
|
return consume(this, 'arrayBuffer')
|
|
@@ -306,6 +311,31 @@ function chunksDecode (chunks, length) {
|
|
|
306
311
|
return buffer.utf8Slice(start, bufferLength)
|
|
307
312
|
}
|
|
308
313
|
|
|
314
|
+
/**
|
|
315
|
+
* @param {Buffer[]} chunks
|
|
316
|
+
* @param {number} length
|
|
317
|
+
* @returns {Uint8Array}
|
|
318
|
+
*/
|
|
319
|
+
function chunksConcat (chunks, length) {
|
|
320
|
+
if (chunks.length === 0 || length === 0) {
|
|
321
|
+
return new Uint8Array(0)
|
|
322
|
+
}
|
|
323
|
+
if (chunks.length === 1) {
|
|
324
|
+
// fast-path
|
|
325
|
+
return new Uint8Array(chunks[0])
|
|
326
|
+
}
|
|
327
|
+
const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer)
|
|
328
|
+
|
|
329
|
+
let offset = 0
|
|
330
|
+
for (let i = 0; i < chunks.length; ++i) {
|
|
331
|
+
const chunk = chunks[i]
|
|
332
|
+
buffer.set(chunk, offset)
|
|
333
|
+
offset += chunk.length
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return buffer
|
|
337
|
+
}
|
|
338
|
+
|
|
309
339
|
function consumeEnd (consume) {
|
|
310
340
|
const { type, body, resolve, stream, length } = consume
|
|
311
341
|
|
|
@@ -315,17 +345,11 @@ function consumeEnd (consume) {
|
|
|
315
345
|
} else if (type === 'json') {
|
|
316
346
|
resolve(JSON.parse(chunksDecode(body, length)))
|
|
317
347
|
} else if (type === 'arrayBuffer') {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
let pos = 0
|
|
321
|
-
for (const buf of body) {
|
|
322
|
-
dst.set(buf, pos)
|
|
323
|
-
pos += buf.byteLength
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
resolve(dst.buffer)
|
|
348
|
+
resolve(chunksConcat(body, length).buffer)
|
|
327
349
|
} else if (type === 'blob') {
|
|
328
350
|
resolve(new Blob(body, { type: stream[kContentType] }))
|
|
351
|
+
} else if (type === 'bytes') {
|
|
352
|
+
resolve(chunksConcat(body, length))
|
|
329
353
|
}
|
|
330
354
|
|
|
331
355
|
consumeFinish(consume)
|
package/lib/core/connect.js
CHANGED
|
@@ -220,6 +220,11 @@ const setupConnectTimeout = process.platform === 'win32'
|
|
|
220
220
|
* @param {number} opts.port
|
|
221
221
|
*/
|
|
222
222
|
function onConnectTimeout (socket, opts) {
|
|
223
|
+
// The socket could be already garbage collected
|
|
224
|
+
if (socket == null) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
223
228
|
let message = 'Connect Timeout Error'
|
|
224
229
|
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
|
|
225
230
|
message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},`
|
|
@@ -860,7 +860,10 @@ function writeH1 (client, request) {
|
|
|
860
860
|
const expectsPayload = (
|
|
861
861
|
method === 'PUT' ||
|
|
862
862
|
method === 'POST' ||
|
|
863
|
-
method === 'PATCH'
|
|
863
|
+
method === 'PATCH' ||
|
|
864
|
+
method === 'QUERY' ||
|
|
865
|
+
method === 'PROPFIND' ||
|
|
866
|
+
method === 'PROPPATCH'
|
|
864
867
|
)
|
|
865
868
|
|
|
866
869
|
if (util.isFormDataLike(body)) {
|
|
@@ -1139,7 +1142,7 @@ function writeBuffer (abort, body, client, request, socket, contentLength, heade
|
|
|
1139
1142
|
socket.uncork()
|
|
1140
1143
|
request.onBodySent(body)
|
|
1141
1144
|
|
|
1142
|
-
if (!expectsPayload) {
|
|
1145
|
+
if (!expectsPayload && request.reset !== false) {
|
|
1143
1146
|
socket[kReset] = true
|
|
1144
1147
|
}
|
|
1145
1148
|
}
|
|
@@ -1169,7 +1172,7 @@ async function writeBlob (abort, body, client, request, socket, contentLength, h
|
|
|
1169
1172
|
request.onBodySent(buffer)
|
|
1170
1173
|
request.onRequestSent()
|
|
1171
1174
|
|
|
1172
|
-
if (!expectsPayload) {
|
|
1175
|
+
if (!expectsPayload && request.reset !== false) {
|
|
1173
1176
|
socket[kReset] = true
|
|
1174
1177
|
}
|
|
1175
1178
|
|
|
@@ -1270,7 +1273,7 @@ class AsyncWriter {
|
|
|
1270
1273
|
socket.cork()
|
|
1271
1274
|
|
|
1272
1275
|
if (bytesWritten === 0) {
|
|
1273
|
-
if (!expectsPayload) {
|
|
1276
|
+
if (!expectsPayload && request.reset !== false) {
|
|
1274
1277
|
socket[kReset] = true
|
|
1275
1278
|
}
|
|
1276
1279
|
|
|
@@ -24,7 +24,9 @@ const {
|
|
|
24
24
|
kOnError,
|
|
25
25
|
kMaxConcurrentStreams,
|
|
26
26
|
kHTTP2Session,
|
|
27
|
-
kResume
|
|
27
|
+
kResume,
|
|
28
|
+
kSize,
|
|
29
|
+
kHTTPContext
|
|
28
30
|
} = require('../core/symbols.js')
|
|
29
31
|
|
|
30
32
|
const kOpenStreams = Symbol('open streams')
|
|
@@ -160,11 +162,10 @@ async function connectH2 (client, socket) {
|
|
|
160
162
|
version: 'h2',
|
|
161
163
|
defaultPipelining: Infinity,
|
|
162
164
|
write (...args) {
|
|
163
|
-
|
|
164
|
-
writeH2(client, ...args)
|
|
165
|
+
return writeH2(client, ...args)
|
|
165
166
|
},
|
|
166
167
|
resume () {
|
|
167
|
-
|
|
168
|
+
resumeH2(client)
|
|
168
169
|
},
|
|
169
170
|
destroy (err, callback) {
|
|
170
171
|
if (closed) {
|
|
@@ -183,6 +184,20 @@ async function connectH2 (client, socket) {
|
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
187
|
+
function resumeH2 (client) {
|
|
188
|
+
const socket = client[kSocket]
|
|
189
|
+
|
|
190
|
+
if (socket?.destroyed === false) {
|
|
191
|
+
if (client[kSize] === 0 && client[kMaxConcurrentStreams] === 0) {
|
|
192
|
+
socket.unref()
|
|
193
|
+
client[kHTTP2Session].unref()
|
|
194
|
+
} else {
|
|
195
|
+
socket.ref()
|
|
196
|
+
client[kHTTP2Session].ref()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
186
201
|
function onHttp2SessionError (err) {
|
|
187
202
|
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
188
203
|
|
|
@@ -210,17 +225,32 @@ function onHttp2SessionEnd () {
|
|
|
210
225
|
* along with the socket right away
|
|
211
226
|
*/
|
|
212
227
|
function onHTTP2GoAway (code) {
|
|
213
|
-
|
|
228
|
+
// We cannot recover, so best to close the session and the socket
|
|
229
|
+
const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${code}`, util.getSocketInfo(this))
|
|
230
|
+
const client = this[kClient]
|
|
214
231
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Requests should be failed with the error after the current one is handled
|
|
218
|
-
this[kSocket][kError] = err
|
|
219
|
-
this[kClient][kOnError](err)
|
|
232
|
+
client[kSocket] = null
|
|
233
|
+
client[kHTTPContext] = null
|
|
220
234
|
|
|
221
|
-
this
|
|
235
|
+
if (this[kHTTP2Session] != null) {
|
|
236
|
+
this[kHTTP2Session].destroy(err)
|
|
237
|
+
this[kHTTP2Session] = null
|
|
238
|
+
}
|
|
222
239
|
|
|
223
240
|
util.destroy(this[kSocket], err)
|
|
241
|
+
|
|
242
|
+
// Fail head of pipeline.
|
|
243
|
+
const request = client[kQueue][client[kRunningIdx]]
|
|
244
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
245
|
+
util.errorRequest(client, request, err)
|
|
246
|
+
|
|
247
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
248
|
+
|
|
249
|
+
assert(client[kRunning] === 0)
|
|
250
|
+
|
|
251
|
+
client.emit('disconnect', client[kUrl], [client], err)
|
|
252
|
+
|
|
253
|
+
client[kResume]()
|
|
224
254
|
}
|
|
225
255
|
|
|
226
256
|
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
@@ -237,10 +267,6 @@ function writeH2 (client, request) {
|
|
|
237
267
|
return false
|
|
238
268
|
}
|
|
239
269
|
|
|
240
|
-
if (request.aborted) {
|
|
241
|
-
return false
|
|
242
|
-
}
|
|
243
|
-
|
|
244
270
|
const headers = {}
|
|
245
271
|
for (let n = 0; n < reqHeaders.length; n += 2) {
|
|
246
272
|
const key = reqHeaders[n + 0]
|
|
@@ -283,6 +309,8 @@ function writeH2 (client, request) {
|
|
|
283
309
|
// We do not destroy the socket as we can continue using the session
|
|
284
310
|
// the stream get's destroyed and the session remains to create new streams
|
|
285
311
|
util.destroy(body, err)
|
|
312
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
313
|
+
client[kResume]()
|
|
286
314
|
}
|
|
287
315
|
|
|
288
316
|
try {
|
|
@@ -293,6 +321,10 @@ function writeH2 (client, request) {
|
|
|
293
321
|
util.errorRequest(client, request, err)
|
|
294
322
|
}
|
|
295
323
|
|
|
324
|
+
if (request.aborted) {
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
|
|
296
328
|
if (method === 'CONNECT') {
|
|
297
329
|
session.ref()
|
|
298
330
|
// We are already connected, streams are pending, first request
|
|
@@ -304,10 +336,12 @@ function writeH2 (client, request) {
|
|
|
304
336
|
if (stream.id && !stream.pending) {
|
|
305
337
|
request.onUpgrade(null, null, stream)
|
|
306
338
|
++session[kOpenStreams]
|
|
339
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
307
340
|
} else {
|
|
308
341
|
stream.once('ready', () => {
|
|
309
342
|
request.onUpgrade(null, null, stream)
|
|
310
343
|
++session[kOpenStreams]
|
|
344
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
311
345
|
})
|
|
312
346
|
}
|
|
313
347
|
|
|
@@ -428,17 +462,20 @@ function writeH2 (client, request) {
|
|
|
428
462
|
// Present specially when using pipeline or stream
|
|
429
463
|
if (stream.state?.state == null || stream.state.state < 6) {
|
|
430
464
|
request.onComplete([])
|
|
431
|
-
return
|
|
432
465
|
}
|
|
433
466
|
|
|
434
|
-
// Stream is closed or half-closed-remote (6), decrement counter and cleanup
|
|
435
|
-
// It does not have sense to continue working with the stream as we do not
|
|
436
|
-
// have yet RST_STREAM support on client-side
|
|
437
467
|
if (session[kOpenStreams] === 0) {
|
|
468
|
+
// Stream is closed or half-closed-remote (6), decrement counter and cleanup
|
|
469
|
+
// It does not have sense to continue working with the stream as we do not
|
|
470
|
+
// have yet RST_STREAM support on client-side
|
|
471
|
+
|
|
438
472
|
session.unref()
|
|
439
473
|
}
|
|
440
474
|
|
|
441
475
|
abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
476
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
477
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
478
|
+
client[kResume]()
|
|
442
479
|
})
|
|
443
480
|
|
|
444
481
|
stream.once('close', () => {
|
package/lib/dispatcher/client.js
CHANGED
|
@@ -63,6 +63,8 @@ let deprecatedInterceptorWarned = false
|
|
|
63
63
|
|
|
64
64
|
const kClosedResolve = Symbol('kClosedResolve')
|
|
65
65
|
|
|
66
|
+
const noop = () => {}
|
|
67
|
+
|
|
66
68
|
function getPipelining (client) {
|
|
67
69
|
return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1
|
|
68
70
|
}
|
|
@@ -442,7 +444,7 @@ async function connect (client) {
|
|
|
442
444
|
})
|
|
443
445
|
|
|
444
446
|
if (client.destroyed) {
|
|
445
|
-
util.destroy(socket.on('error',
|
|
447
|
+
util.destroy(socket.on('error', noop), new ClientDestroyedError())
|
|
446
448
|
return
|
|
447
449
|
}
|
|
448
450
|
|
|
@@ -453,7 +455,7 @@ async function connect (client) {
|
|
|
453
455
|
? await connectH2(client, socket)
|
|
454
456
|
: await connectH1(client, socket)
|
|
455
457
|
} catch (err) {
|
|
456
|
-
socket.destroy().on('error',
|
|
458
|
+
socket.destroy().on('error', noop)
|
|
457
459
|
throw err
|
|
458
460
|
}
|
|
459
461
|
|
|
@@ -113,9 +113,9 @@ class PoolBase extends DispatcherBase {
|
|
|
113
113
|
|
|
114
114
|
async [kClose] () {
|
|
115
115
|
if (this[kQueue].isEmpty()) {
|
|
116
|
-
|
|
116
|
+
await Promise.all(this[kClients].map(c => c.close()))
|
|
117
117
|
} else {
|
|
118
|
-
|
|
118
|
+
await new Promise((resolve) => {
|
|
119
119
|
this[kClosedResolve] = resolve
|
|
120
120
|
})
|
|
121
121
|
}
|
|
@@ -130,7 +130,7 @@ class PoolBase extends DispatcherBase {
|
|
|
130
130
|
item.handler.onError(err)
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
await Promise.all(this[kClients].map(c => c.destroy(err)))
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
[kDispatch] (opts, handler) {
|
|
@@ -23,6 +23,8 @@ function defaultFactory (origin, opts) {
|
|
|
23
23
|
return new Pool(origin, opts)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const noop = () => {}
|
|
27
|
+
|
|
26
28
|
class ProxyAgent extends DispatcherBase {
|
|
27
29
|
constructor (opts) {
|
|
28
30
|
super()
|
|
@@ -81,7 +83,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
81
83
|
servername: this[kProxyTls]?.servername || proxyHostname
|
|
82
84
|
})
|
|
83
85
|
if (statusCode !== 200) {
|
|
84
|
-
socket.on('error',
|
|
86
|
+
socket.on('error', noop).destroy()
|
|
85
87
|
callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
|
|
86
88
|
}
|
|
87
89
|
if (opts.protocol !== 'https:') {
|
|
@@ -229,7 +229,7 @@ class RetryHandler {
|
|
|
229
229
|
return false
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
const { start, size, end = size } = contentRange
|
|
232
|
+
const { start, size, end = size - 1 } = contentRange
|
|
233
233
|
|
|
234
234
|
assert(this.start === start, 'content-range mismatch')
|
|
235
235
|
assert(this.end == null || this.end === end, 'content-range mismatch')
|
|
@@ -252,7 +252,7 @@ class RetryHandler {
|
|
|
252
252
|
)
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
const { start, size, end = size } = range
|
|
255
|
+
const { start, size, end = size - 1 } = range
|
|
256
256
|
assert(
|
|
257
257
|
start != null && Number.isFinite(start),
|
|
258
258
|
'content-range mismatch'
|
|
@@ -266,7 +266,7 @@ class RetryHandler {
|
|
|
266
266
|
// We make our best to checkpoint the body for further range headers
|
|
267
267
|
if (this.end == null) {
|
|
268
268
|
const contentLength = headers['content-length']
|
|
269
|
-
this.end = contentLength != null ? Number(contentLength) : null
|
|
269
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
assert(Number.isFinite(this.start))
|
package/lib/web/cache/cache.js
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
|
|
3
|
+
const corsSafeListedMethods = /** @type {const} */ (['GET', 'HEAD', 'POST'])
|
|
4
4
|
const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
|
|
5
5
|
|
|
6
|
-
const nullBodyStatus = [101, 204, 205, 304]
|
|
6
|
+
const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304])
|
|
7
7
|
|
|
8
|
-
const redirectStatus = [301, 302, 303, 307, 308]
|
|
8
|
+
const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308])
|
|
9
9
|
const redirectStatusSet = new Set(redirectStatus)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @see https://fetch.spec.whatwg.org/#block-bad-port
|
|
13
|
+
*/
|
|
14
|
+
const badPorts = /** @type {const} */ ([
|
|
13
15
|
'1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
|
|
14
16
|
'87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
|
|
15
17
|
'139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
|
|
16
18
|
'540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
|
|
17
19
|
'2049', '3659', '4045', '4190', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6679',
|
|
18
20
|
'6697', '10080'
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
+
])
|
|
21
22
|
const badPortsSet = new Set(badPorts)
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
|
|
26
|
+
*/
|
|
27
|
+
const referrerPolicy = /** @type {const} */ ([
|
|
25
28
|
'',
|
|
26
29
|
'no-referrer',
|
|
27
30
|
'no-referrer-when-downgrade',
|
|
@@ -31,29 +34,31 @@ const referrerPolicy = [
|
|
|
31
34
|
'origin-when-cross-origin',
|
|
32
35
|
'strict-origin-when-cross-origin',
|
|
33
36
|
'unsafe-url'
|
|
34
|
-
]
|
|
37
|
+
])
|
|
35
38
|
const referrerPolicySet = new Set(referrerPolicy)
|
|
36
39
|
|
|
37
|
-
const requestRedirect = ['follow', 'manual', 'error']
|
|
40
|
+
const requestRedirect = /** @type {const} */ (['follow', 'manual', 'error'])
|
|
38
41
|
|
|
39
|
-
const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
|
42
|
+
const safeMethods = /** @type {const} */ (['GET', 'HEAD', 'OPTIONS', 'TRACE'])
|
|
40
43
|
const safeMethodsSet = new Set(safeMethods)
|
|
41
44
|
|
|
42
|
-
const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors']
|
|
45
|
+
const requestMode = /** @type {const} */ (['navigate', 'same-origin', 'no-cors', 'cors'])
|
|
43
46
|
|
|
44
|
-
const requestCredentials = ['omit', 'same-origin', 'include']
|
|
47
|
+
const requestCredentials = /** @type {const} */ (['omit', 'same-origin', 'include'])
|
|
45
48
|
|
|
46
|
-
const requestCache = [
|
|
49
|
+
const requestCache = /** @type {const} */ ([
|
|
47
50
|
'default',
|
|
48
51
|
'no-store',
|
|
49
52
|
'reload',
|
|
50
53
|
'no-cache',
|
|
51
54
|
'force-cache',
|
|
52
55
|
'only-if-cached'
|
|
53
|
-
]
|
|
56
|
+
])
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* @see https://fetch.spec.whatwg.org/#request-body-header-name
|
|
60
|
+
*/
|
|
61
|
+
const requestBodyHeader = /** @type {const} */ ([
|
|
57
62
|
'content-encoding',
|
|
58
63
|
'content-language',
|
|
59
64
|
'content-location',
|
|
@@ -63,18 +68,22 @@ const requestBodyHeader = [
|
|
|
63
68
|
// removed in the Headers implementation. However, undici doesn't
|
|
64
69
|
// filter out headers, so we add it here.
|
|
65
70
|
'content-length'
|
|
66
|
-
]
|
|
71
|
+
])
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
/**
|
|
74
|
+
* @see https://fetch.spec.whatwg.org/#enumdef-requestduplex
|
|
75
|
+
*/
|
|
76
|
+
const requestDuplex = /** @type {const} */ ([
|
|
70
77
|
'half'
|
|
71
|
-
]
|
|
78
|
+
])
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
/**
|
|
81
|
+
* @see http://fetch.spec.whatwg.org/#forbidden-method
|
|
82
|
+
*/
|
|
83
|
+
const forbiddenMethods = /** @type {const} */ (['CONNECT', 'TRACE', 'TRACK'])
|
|
75
84
|
const forbiddenMethodsSet = new Set(forbiddenMethods)
|
|
76
85
|
|
|
77
|
-
const subresource = [
|
|
86
|
+
const subresource = /** @type {const} */ ([
|
|
78
87
|
'audio',
|
|
79
88
|
'audioworklet',
|
|
80
89
|
'font',
|
|
@@ -87,7 +96,7 @@ const subresource = [
|
|
|
87
96
|
'video',
|
|
88
97
|
'xslt',
|
|
89
98
|
''
|
|
90
|
-
]
|
|
99
|
+
])
|
|
91
100
|
const subresourceSet = new Set(subresource)
|
|
92
101
|
|
|
93
102
|
module.exports = {
|
|
@@ -14,6 +14,8 @@ const File = globalThis.File ?? NativeFile
|
|
|
14
14
|
// https://xhr.spec.whatwg.org/#formdata
|
|
15
15
|
class FormData {
|
|
16
16
|
constructor (form) {
|
|
17
|
+
webidl.util.markAsUncloneable(this)
|
|
18
|
+
|
|
17
19
|
if (form !== undefined) {
|
|
18
20
|
throw webidl.errors.conversionFailed({
|
|
19
21
|
prefix: 'FormData constructor',
|
package/lib/web/fetch/headers.js
CHANGED
package/lib/web/fetch/index.js
CHANGED
|
@@ -2137,7 +2137,7 @@ async function httpNetworkFetch (
|
|
|
2137
2137
|
|
|
2138
2138
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
2139
2139
|
if (codings.length !== 0 && request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
|
2140
|
-
for (let i =
|
|
2140
|
+
for (let i = codings.length - 1; i >= 0; --i) {
|
|
2141
2141
|
const coding = codings[i]
|
|
2142
2142
|
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
|
|
2143
2143
|
if (coding === 'x-gzip' || coding === 'gzip') {
|
package/lib/web/fetch/request.js
CHANGED
package/lib/web/fetch/webidl.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { types, inspect } = require('node:util')
|
|
4
|
+
const { markAsUncloneable } = require('node:worker_threads')
|
|
4
5
|
const { toUSVString } = require('../../core/util')
|
|
5
6
|
|
|
6
7
|
/** @type {import('../../../types/webidl').Webidl} */
|
|
@@ -86,6 +87,7 @@ webidl.util.Type = function (V) {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
webidl.util.markAsUncloneable = markAsUncloneable || (() => {})
|
|
89
91
|
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
|
90
92
|
webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
|
91
93
|
let upperBound
|
|
@@ -14,6 +14,7 @@ class MessageEvent extends Event {
|
|
|
14
14
|
constructor (type, eventInitDict = {}) {
|
|
15
15
|
if (type === kConstruct) {
|
|
16
16
|
super(arguments[1], arguments[2])
|
|
17
|
+
webidl.util.markAsUncloneable(this)
|
|
17
18
|
return
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -26,6 +27,7 @@ class MessageEvent extends Event {
|
|
|
26
27
|
super(type, eventInitDict)
|
|
27
28
|
|
|
28
29
|
this.#eventInit = eventInitDict
|
|
30
|
+
webidl.util.markAsUncloneable(this)
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
get data () {
|
|
@@ -112,6 +114,7 @@ class CloseEvent extends Event {
|
|
|
112
114
|
super(type, eventInitDict)
|
|
113
115
|
|
|
114
116
|
this.#eventInit = eventInitDict
|
|
117
|
+
webidl.util.markAsUncloneable(this)
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
get wasClean () {
|
|
@@ -142,6 +145,7 @@ class ErrorEvent extends Event {
|
|
|
142
145
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
143
146
|
|
|
144
147
|
super(type, eventInitDict)
|
|
148
|
+
webidl.util.markAsUncloneable(this)
|
|
145
149
|
|
|
146
150
|
type = webidl.converters.DOMString(type, prefix, 'type')
|
|
147
151
|
eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.21.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": {
|
|
@@ -78,6 +78,9 @@
|
|
|
78
78
|
"test:fuzzing": "node test/fuzzing/fuzzing.test.js",
|
|
79
79
|
"test:fetch": "npm run build:node && npm run test:fetch:nobuild",
|
|
80
80
|
"test:fetch:nobuild": "borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
|
|
81
|
+
"test:h2": "npm run test:h2:core && npm run test:h2:fetch",
|
|
82
|
+
"test:h2:core": "borp -p \"test/http2*.js\"",
|
|
83
|
+
"test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"",
|
|
81
84
|
"test:interceptors": "borp -p \"test/interceptors/*.js\"",
|
|
82
85
|
"test:jest": "cross-env NODE_V8_COVERAGE= jest",
|
|
83
86
|
"test:unit": "borp --expose-gc -p \"test/*.js\"",
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -244,6 +244,7 @@ declare namespace Dispatcher {
|
|
|
244
244
|
readonly bodyUsed: boolean;
|
|
245
245
|
arrayBuffer(): Promise<ArrayBuffer>;
|
|
246
246
|
blob(): Promise<Blob>;
|
|
247
|
+
bytes(): Promise<Uint8Array>;
|
|
247
248
|
formData(): Promise<never>;
|
|
248
249
|
json(): Promise<unknown>;
|
|
249
250
|
text(): Promise<string>;
|
package/types/readable.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ declare class BodyReadable extends Readable {
|
|
|
25
25
|
*/
|
|
26
26
|
blob(): Promise<Blob>
|
|
27
27
|
|
|
28
|
+
/** Consumes and returns the body as an Uint8Array
|
|
29
|
+
* https://fetch.spec.whatwg.org/#dom-body-bytes
|
|
30
|
+
*/
|
|
31
|
+
bytes(): Promise<Uint8Array>
|
|
32
|
+
|
|
28
33
|
/** Consumes and returns the body as an ArrayBuffer
|
|
29
34
|
* https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
30
35
|
*/
|
package/types/webidl.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ interface WebidlUtil {
|
|
|
67
67
|
* Stringifies {@param V}
|
|
68
68
|
*/
|
|
69
69
|
Stringify (V: any): string
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Mark a value as uncloneable for Node.js.
|
|
73
|
+
* This is only effective in some newer Node.js versions.
|
|
74
|
+
*/
|
|
75
|
+
markAsUncloneable (V: any): void
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
interface WebidlConverters {
|