undici 5.26.5 → 5.27.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/lib/core/request.js +8 -0
- package/lib/fetch/body.js +12 -9
- package/lib/fetch/constants.js +16 -1
- package/lib/fetch/file.js +2 -1
- package/lib/fetch/index.js +9 -8
- package/lib/fetch/request.js +4 -4
- package/lib/fetch/response.js +4 -3
- package/lib/fetch/util.js +4 -4
- package/package.json +2 -2
package/lib/core/request.js
CHANGED
|
@@ -222,6 +222,14 @@ class Request {
|
|
|
222
222
|
if (channels.bodySent.hasSubscribers) {
|
|
223
223
|
channels.bodySent.publish({ request: this })
|
|
224
224
|
}
|
|
225
|
+
|
|
226
|
+
if (this[kHandler].onRequestSent) {
|
|
227
|
+
try {
|
|
228
|
+
this[kHandler].onRequestSent()
|
|
229
|
+
} catch (err) {
|
|
230
|
+
this.onError(err)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
onConnect (abort) {
|
package/lib/fetch/body.js
CHANGED
|
@@ -26,6 +26,8 @@ let ReadableStream = globalThis.ReadableStream
|
|
|
26
26
|
|
|
27
27
|
/** @type {globalThis['File']} */
|
|
28
28
|
const File = NativeFile ?? UndiciFile
|
|
29
|
+
const textEncoder = new TextEncoder()
|
|
30
|
+
const textDecoder = new TextDecoder()
|
|
29
31
|
|
|
30
32
|
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
|
31
33
|
function extractBody (object, keepalive = false) {
|
|
@@ -49,7 +51,7 @@ function extractBody (object, keepalive = false) {
|
|
|
49
51
|
stream = new ReadableStream({
|
|
50
52
|
async pull (controller) {
|
|
51
53
|
controller.enqueue(
|
|
52
|
-
typeof source === 'string' ?
|
|
54
|
+
typeof source === 'string' ? textEncoder.encode(source) : source
|
|
53
55
|
)
|
|
54
56
|
queueMicrotask(() => readableStreamClose(controller))
|
|
55
57
|
},
|
|
@@ -119,7 +121,6 @@ function extractBody (object, keepalive = false) {
|
|
|
119
121
|
// - That the content-length is calculated in advance.
|
|
120
122
|
// - And that all parts are pre-encoded and ready to be sent.
|
|
121
123
|
|
|
122
|
-
const enc = new TextEncoder()
|
|
123
124
|
const blobParts = []
|
|
124
125
|
const rn = new Uint8Array([13, 10]) // '\r\n'
|
|
125
126
|
length = 0
|
|
@@ -127,13 +128,13 @@ function extractBody (object, keepalive = false) {
|
|
|
127
128
|
|
|
128
129
|
for (const [name, value] of object) {
|
|
129
130
|
if (typeof value === 'string') {
|
|
130
|
-
const chunk =
|
|
131
|
+
const chunk = textEncoder.encode(prefix +
|
|
131
132
|
`; name="${escape(normalizeLinefeeds(name))}"` +
|
|
132
133
|
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
|
|
133
134
|
blobParts.push(chunk)
|
|
134
135
|
length += chunk.byteLength
|
|
135
136
|
} else {
|
|
136
|
-
const chunk =
|
|
137
|
+
const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
|
|
137
138
|
(value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' +
|
|
138
139
|
`Content-Type: ${
|
|
139
140
|
value.type || 'application/octet-stream'
|
|
@@ -147,7 +148,7 @@ function extractBody (object, keepalive = false) {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
const chunk =
|
|
151
|
+
const chunk = textEncoder.encode(`--${boundary}--`)
|
|
151
152
|
blobParts.push(chunk)
|
|
152
153
|
length += chunk.byteLength
|
|
153
154
|
if (hasUnknownSizeValue) {
|
|
@@ -443,14 +444,16 @@ function bodyMixinMethods (instance) {
|
|
|
443
444
|
let text = ''
|
|
444
445
|
// application/x-www-form-urlencoded parser will keep the BOM.
|
|
445
446
|
// https://url.spec.whatwg.org/#concept-urlencoded-parser
|
|
446
|
-
|
|
447
|
+
// Note that streaming decoder is stateful and cannot be reused
|
|
448
|
+
const streamingDecoder = new TextDecoder('utf-8', { ignoreBOM: true })
|
|
449
|
+
|
|
447
450
|
for await (const chunk of consumeBody(this[kState].body)) {
|
|
448
451
|
if (!isUint8Array(chunk)) {
|
|
449
452
|
throw new TypeError('Expected Uint8Array chunk')
|
|
450
453
|
}
|
|
451
|
-
text +=
|
|
454
|
+
text += streamingDecoder.decode(chunk, { stream: true })
|
|
452
455
|
}
|
|
453
|
-
text +=
|
|
456
|
+
text += streamingDecoder.decode()
|
|
454
457
|
entries = new URLSearchParams(text)
|
|
455
458
|
} catch (err) {
|
|
456
459
|
// istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
|
|
@@ -565,7 +568,7 @@ function utf8DecodeBytes (buffer) {
|
|
|
565
568
|
|
|
566
569
|
// 3. Process a queue with an instance of UTF-8’s
|
|
567
570
|
// decoder, ioQueue, output, and "replacement".
|
|
568
|
-
const output =
|
|
571
|
+
const output = textDecoder.decode(buffer)
|
|
569
572
|
|
|
570
573
|
// 4. Return output.
|
|
571
574
|
return output
|
package/lib/fetch/constants.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const { MessageChannel, receiveMessageOnPort } = require('worker_threads')
|
|
4
4
|
|
|
5
5
|
const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
|
|
6
|
+
const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
|
|
6
7
|
|
|
7
8
|
const nullBodyStatus = [101, 204, 205, 304]
|
|
8
9
|
|
|
9
10
|
const redirectStatus = [301, 302, 303, 307, 308]
|
|
11
|
+
const redirectStatusSet = new Set(redirectStatus)
|
|
10
12
|
|
|
11
13
|
// https://fetch.spec.whatwg.org/#block-bad-port
|
|
12
14
|
const badPorts = [
|
|
@@ -18,6 +20,8 @@ const badPorts = [
|
|
|
18
20
|
'10080'
|
|
19
21
|
]
|
|
20
22
|
|
|
23
|
+
const badPortsSet = new Set(badPorts)
|
|
24
|
+
|
|
21
25
|
// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
|
|
22
26
|
const referrerPolicy = [
|
|
23
27
|
'',
|
|
@@ -30,10 +34,12 @@ const referrerPolicy = [
|
|
|
30
34
|
'strict-origin-when-cross-origin',
|
|
31
35
|
'unsafe-url'
|
|
32
36
|
]
|
|
37
|
+
const referrerPolicySet = new Set(referrerPolicy)
|
|
33
38
|
|
|
34
39
|
const requestRedirect = ['follow', 'manual', 'error']
|
|
35
40
|
|
|
36
41
|
const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
|
42
|
+
const safeMethodsSet = new Set(safeMethods)
|
|
37
43
|
|
|
38
44
|
const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors']
|
|
39
45
|
|
|
@@ -68,6 +74,7 @@ const requestDuplex = [
|
|
|
68
74
|
|
|
69
75
|
// http://fetch.spec.whatwg.org/#forbidden-method
|
|
70
76
|
const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']
|
|
77
|
+
const forbiddenMethodsSet = new Set(forbiddenMethods)
|
|
71
78
|
|
|
72
79
|
const subresource = [
|
|
73
80
|
'audio',
|
|
@@ -83,6 +90,7 @@ const subresource = [
|
|
|
83
90
|
'xslt',
|
|
84
91
|
''
|
|
85
92
|
]
|
|
93
|
+
const subresourceSet = new Set(subresource)
|
|
86
94
|
|
|
87
95
|
/** @type {globalThis['DOMException']} */
|
|
88
96
|
const DOMException = globalThis.DOMException ?? (() => {
|
|
@@ -132,5 +140,12 @@ module.exports = {
|
|
|
132
140
|
nullBodyStatus,
|
|
133
141
|
safeMethods,
|
|
134
142
|
badPorts,
|
|
135
|
-
requestDuplex
|
|
143
|
+
requestDuplex,
|
|
144
|
+
subresourceSet,
|
|
145
|
+
badPortsSet,
|
|
146
|
+
redirectStatusSet,
|
|
147
|
+
corsSafeListedMethodsSet,
|
|
148
|
+
safeMethodsSet,
|
|
149
|
+
forbiddenMethodsSet,
|
|
150
|
+
referrerPolicySet
|
|
136
151
|
}
|
package/lib/fetch/file.js
CHANGED
|
@@ -7,6 +7,7 @@ const { isBlobLike } = require('./util')
|
|
|
7
7
|
const { webidl } = require('./webidl')
|
|
8
8
|
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
|
|
9
9
|
const { kEnumerableProperty } = require('../core/util')
|
|
10
|
+
const encoder = new TextEncoder()
|
|
10
11
|
|
|
11
12
|
class File extends Blob {
|
|
12
13
|
constructor (fileBits, fileName, options = {}) {
|
|
@@ -280,7 +281,7 @@ function processBlobParts (parts, options) {
|
|
|
280
281
|
}
|
|
281
282
|
|
|
282
283
|
// 3. Append the result of UTF-8 encoding s to bytes.
|
|
283
|
-
bytes.push(
|
|
284
|
+
bytes.push(encoder.encode(s))
|
|
284
285
|
} else if (
|
|
285
286
|
types.isAnyArrayBuffer(element) ||
|
|
286
287
|
types.isTypedArray(element)
|
package/lib/fetch/index.js
CHANGED
|
@@ -46,11 +46,11 @@ const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
|
|
|
46
46
|
const assert = require('assert')
|
|
47
47
|
const { safelyExtractBody } = require('./body')
|
|
48
48
|
const {
|
|
49
|
-
|
|
49
|
+
redirectStatusSet,
|
|
50
50
|
nullBodyStatus,
|
|
51
|
-
|
|
51
|
+
safeMethodsSet,
|
|
52
52
|
requestBodyHeader,
|
|
53
|
-
|
|
53
|
+
subresourceSet,
|
|
54
54
|
DOMException
|
|
55
55
|
} = require('./constants')
|
|
56
56
|
const { kHeadersList } = require('../core/symbols')
|
|
@@ -62,6 +62,7 @@ const { TransformStream } = require('stream/web')
|
|
|
62
62
|
const { getGlobalDispatcher } = require('../global')
|
|
63
63
|
const { webidl } = require('./webidl')
|
|
64
64
|
const { STATUS_CODES } = require('http')
|
|
65
|
+
const GET_OR_HEAD = ['GET', 'HEAD']
|
|
65
66
|
|
|
66
67
|
/** @type {import('buffer').resolveObjectURL} */
|
|
67
68
|
let resolveObjectURL
|
|
@@ -509,7 +510,7 @@ function fetching ({
|
|
|
509
510
|
}
|
|
510
511
|
|
|
511
512
|
// 15. If request is a subresource request, then:
|
|
512
|
-
if (
|
|
513
|
+
if (subresourceSet.has(request.destination)) {
|
|
513
514
|
// TODO
|
|
514
515
|
}
|
|
515
516
|
|
|
@@ -1063,7 +1064,7 @@ async function httpFetch (fetchParams) {
|
|
|
1063
1064
|
}
|
|
1064
1065
|
|
|
1065
1066
|
// 8. If actualResponse’s status is a redirect status, then:
|
|
1066
|
-
if (
|
|
1067
|
+
if (redirectStatusSet.has(actualResponse.status)) {
|
|
1067
1068
|
// 1. If actualResponse’s status is not 303, request’s body is not null,
|
|
1068
1069
|
// and the connection uses HTTP/2, then user agents may, and are even
|
|
1069
1070
|
// encouraged to, transmit an RST_STREAM frame.
|
|
@@ -1181,7 +1182,7 @@ function httpRedirectFetch (fetchParams, response) {
|
|
|
1181
1182
|
if (
|
|
1182
1183
|
([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
|
|
1183
1184
|
(actualResponse.status === 303 &&
|
|
1184
|
-
!
|
|
1185
|
+
!GET_OR_HEAD.includes(request.method))
|
|
1185
1186
|
) {
|
|
1186
1187
|
// then:
|
|
1187
1188
|
// 1. Set request’s method to `GET` and request’s body to null.
|
|
@@ -1465,7 +1466,7 @@ async function httpNetworkOrCacheFetch (
|
|
|
1465
1466
|
// responses in httpCache, as per the "Invalidation" chapter of HTTP
|
|
1466
1467
|
// Caching, and set storedResponse to null. [HTTP-CACHING]
|
|
1467
1468
|
if (
|
|
1468
|
-
!
|
|
1469
|
+
!safeMethodsSet.has(httpRequest.method) &&
|
|
1469
1470
|
forwardResponse.status >= 200 &&
|
|
1470
1471
|
forwardResponse.status <= 399
|
|
1471
1472
|
) {
|
|
@@ -2025,7 +2026,7 @@ async function httpNetworkFetch (
|
|
|
2025
2026
|
|
|
2026
2027
|
const willFollow = request.redirect === 'follow' &&
|
|
2027
2028
|
location &&
|
|
2028
|
-
|
|
2029
|
+
redirectStatusSet.has(status)
|
|
2029
2030
|
|
|
2030
2031
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
2031
2032
|
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
package/lib/fetch/request.js
CHANGED
|
@@ -13,8 +13,8 @@ const {
|
|
|
13
13
|
makePolicyContainer
|
|
14
14
|
} = require('./util')
|
|
15
15
|
const {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
forbiddenMethodsSet,
|
|
17
|
+
corsSafeListedMethodsSet,
|
|
18
18
|
referrerPolicy,
|
|
19
19
|
requestRedirect,
|
|
20
20
|
requestMode,
|
|
@@ -319,7 +319,7 @@ class Request {
|
|
|
319
319
|
throw TypeError(`'${init.method}' is not a valid HTTP method.`)
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
if (
|
|
322
|
+
if (forbiddenMethodsSet.has(method.toUpperCase())) {
|
|
323
323
|
throw TypeError(`'${init.method}' HTTP method is unsupported.`)
|
|
324
324
|
}
|
|
325
325
|
|
|
@@ -404,7 +404,7 @@ class Request {
|
|
|
404
404
|
if (mode === 'no-cors') {
|
|
405
405
|
// 1. If this’s request’s method is not a CORS-safelisted method,
|
|
406
406
|
// then throw a TypeError.
|
|
407
|
-
if (!
|
|
407
|
+
if (!corsSafeListedMethodsSet.has(request.method)) {
|
|
408
408
|
throw new TypeError(
|
|
409
409
|
`'${request.method} is unsupported in no-cors mode.`
|
|
410
410
|
)
|
package/lib/fetch/response.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
isomorphicEncode
|
|
15
15
|
} = require('./util')
|
|
16
16
|
const {
|
|
17
|
-
|
|
17
|
+
redirectStatusSet,
|
|
18
18
|
nullBodyStatus,
|
|
19
19
|
DOMException
|
|
20
20
|
} = require('./constants')
|
|
@@ -28,6 +28,7 @@ const assert = require('assert')
|
|
|
28
28
|
const { types } = require('util')
|
|
29
29
|
|
|
30
30
|
const ReadableStream = globalThis.ReadableStream || require('stream/web').ReadableStream
|
|
31
|
+
const textEncoder = new TextEncoder('utf-8')
|
|
31
32
|
|
|
32
33
|
// https://fetch.spec.whatwg.org/#response-class
|
|
33
34
|
class Response {
|
|
@@ -57,7 +58,7 @@ class Response {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
|
|
60
|
-
const bytes =
|
|
61
|
+
const bytes = textEncoder.encode(
|
|
61
62
|
serializeJavascriptValueToJSONString(data)
|
|
62
63
|
)
|
|
63
64
|
|
|
@@ -102,7 +103,7 @@ class Response {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
// 3. If status is not a redirect status, then throw a RangeError.
|
|
105
|
-
if (!
|
|
106
|
+
if (!redirectStatusSet.has(status)) {
|
|
106
107
|
throw new RangeError('Invalid status code ' + status)
|
|
107
108
|
}
|
|
108
109
|
|
package/lib/fetch/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require('./constants')
|
|
4
4
|
const { getGlobalOrigin } = require('./global')
|
|
5
5
|
const { performance } = require('perf_hooks')
|
|
6
6
|
const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
|
|
@@ -29,7 +29,7 @@ function responseURL (response) {
|
|
|
29
29
|
// https://fetch.spec.whatwg.org/#concept-response-location-url
|
|
30
30
|
function responseLocationURL (response, requestFragment) {
|
|
31
31
|
// 1. If response’s status is not a redirect status, then return null.
|
|
32
|
-
if (!
|
|
32
|
+
if (!redirectStatusSet.has(response.status)) {
|
|
33
33
|
return null
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -64,7 +64,7 @@ function requestBadPort (request) {
|
|
|
64
64
|
|
|
65
65
|
// 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
|
|
66
66
|
// then return blocked.
|
|
67
|
-
if (urlIsHttpHttpsScheme(url) &&
|
|
67
|
+
if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) {
|
|
68
68
|
return 'blocked'
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -206,7 +206,7 @@ function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
|
|
|
206
206
|
// The left-most policy is the fallback.
|
|
207
207
|
for (let i = policyHeader.length; i !== 0; i--) {
|
|
208
208
|
const token = policyHeader[i - 1].trim()
|
|
209
|
-
if (referrerPolicyTokens.
|
|
209
|
+
if (referrerPolicyTokens.has(token)) {
|
|
210
210
|
policy = token
|
|
211
211
|
break
|
|
212
212
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.27.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": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"test:tdd": "tap test/*.js test/diagnostics-channel/*.js -w",
|
|
85
85
|
"test:typescript": "node scripts/verifyVersion.js 14 || tsd && tsc --skipLibCheck test/imports/undici-import.ts",
|
|
86
86
|
"test:websocket": "node scripts/verifyVersion.js 18 || tap test/websocket/*.js",
|
|
87
|
-
"test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node
|
|
87
|
+
"test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs)",
|
|
88
88
|
"coverage": "nyc --reporter=text --reporter=html npm run test",
|
|
89
89
|
"coverage:ci": "nyc --reporter=lcov npm run test",
|
|
90
90
|
"bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",
|