undici 5.26.4 → 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/index-fetch.js +3 -5
- 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 +36 -34
- 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/types/index.d.ts +6 -0
package/index-fetch.js
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const fetchImpl = require('./lib/fetch').fetch
|
|
4
4
|
|
|
5
|
-
module.exports.fetch =
|
|
6
|
-
|
|
7
|
-
return await fetchImpl(resource, init)
|
|
8
|
-
} catch (err) {
|
|
5
|
+
module.exports.fetch = function fetch (resource, init = undefined) {
|
|
6
|
+
return fetchImpl(resource, init).catch((err) => {
|
|
9
7
|
Error.captureStackTrace(err, this)
|
|
10
8
|
throw err
|
|
11
|
-
}
|
|
9
|
+
})
|
|
12
10
|
}
|
|
13
11
|
module.exports.FormData = require('./lib/fetch/formdata').FormData
|
|
14
12
|
module.exports.Headers = require('./lib/fetch/headers').Headers
|
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
|
|
@@ -121,7 +122,7 @@ class Fetch extends EE {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
// https://fetch.spec.whatwg.org/#fetch-method
|
|
124
|
-
|
|
125
|
+
function fetch (input, init = {}) {
|
|
125
126
|
webidl.argumentLengthCheck(arguments, 1, { header: 'globalThis.fetch' })
|
|
126
127
|
|
|
127
128
|
// 1. Let p be a new promise.
|
|
@@ -204,7 +205,7 @@ async function fetch (input, init = {}) {
|
|
|
204
205
|
const processResponse = (response) => {
|
|
205
206
|
// 1. If locallyAborted is true, terminate these substeps.
|
|
206
207
|
if (locallyAborted) {
|
|
207
|
-
return
|
|
208
|
+
return Promise.resolve()
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
// 2. If response’s aborted flag is set, then:
|
|
@@ -217,7 +218,7 @@ async function fetch (input, init = {}) {
|
|
|
217
218
|
// deserializedError.
|
|
218
219
|
|
|
219
220
|
abortFetch(p, request, responseObject, controller.serializedAbortReason)
|
|
220
|
-
return
|
|
221
|
+
return Promise.resolve()
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
// 3. If response is a network error, then reject p with a TypeError
|
|
@@ -226,7 +227,7 @@ async function fetch (input, init = {}) {
|
|
|
226
227
|
p.reject(
|
|
227
228
|
Object.assign(new TypeError('fetch failed'), { cause: response.error })
|
|
228
229
|
)
|
|
229
|
-
return
|
|
230
|
+
return Promise.resolve()
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
// 4. Set responseObject to the result of creating a Response object,
|
|
@@ -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
|
|
|
@@ -776,13 +777,13 @@ async function mainFetch (fetchParams, recursive = false) {
|
|
|
776
777
|
|
|
777
778
|
// https://fetch.spec.whatwg.org/#concept-scheme-fetch
|
|
778
779
|
// given a fetch params fetchParams
|
|
779
|
-
|
|
780
|
+
function schemeFetch (fetchParams) {
|
|
780
781
|
// Note: since the connection is destroyed on redirect, which sets fetchParams to a
|
|
781
782
|
// cancelled state, we do not want this condition to trigger *unless* there have been
|
|
782
783
|
// no redirects. See https://github.com/nodejs/undici/issues/1776
|
|
783
784
|
// 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
|
|
784
785
|
if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) {
|
|
785
|
-
return makeAppropriateNetworkError(fetchParams)
|
|
786
|
+
return Promise.resolve(makeAppropriateNetworkError(fetchParams))
|
|
786
787
|
}
|
|
787
788
|
|
|
788
789
|
// 2. Let request be fetchParams’s request.
|
|
@@ -798,7 +799,7 @@ async function schemeFetch (fetchParams) {
|
|
|
798
799
|
// and body is the empty byte sequence as a body.
|
|
799
800
|
|
|
800
801
|
// Otherwise, return a network error.
|
|
801
|
-
return makeNetworkError('about scheme is not supported')
|
|
802
|
+
return Promise.resolve(makeNetworkError('about scheme is not supported'))
|
|
802
803
|
}
|
|
803
804
|
case 'blob:': {
|
|
804
805
|
if (!resolveObjectURL) {
|
|
@@ -811,7 +812,7 @@ async function schemeFetch (fetchParams) {
|
|
|
811
812
|
// https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/FileAPI/url/resources/fetch-tests.js#L52-L56
|
|
812
813
|
// Buffer.resolveObjectURL does not ignore URL queries.
|
|
813
814
|
if (blobURLEntry.search.length !== 0) {
|
|
814
|
-
return makeNetworkError('NetworkError when attempting to fetch resource.')
|
|
815
|
+
return Promise.resolve(makeNetworkError('NetworkError when attempting to fetch resource.'))
|
|
815
816
|
}
|
|
816
817
|
|
|
817
818
|
const blobURLEntryObject = resolveObjectURL(blobURLEntry.toString())
|
|
@@ -819,7 +820,7 @@ async function schemeFetch (fetchParams) {
|
|
|
819
820
|
// 2. If request’s method is not `GET`, blobURLEntry is null, or blobURLEntry’s
|
|
820
821
|
// object is not a Blob object, then return a network error.
|
|
821
822
|
if (request.method !== 'GET' || !isBlobLike(blobURLEntryObject)) {
|
|
822
|
-
return makeNetworkError('invalid method')
|
|
823
|
+
return Promise.resolve(makeNetworkError('invalid method'))
|
|
823
824
|
}
|
|
824
825
|
|
|
825
826
|
// 3. Let bodyWithType be the result of safely extracting blobURLEntry’s object.
|
|
@@ -846,7 +847,7 @@ async function schemeFetch (fetchParams) {
|
|
|
846
847
|
|
|
847
848
|
response.body = body
|
|
848
849
|
|
|
849
|
-
return response
|
|
850
|
+
return Promise.resolve(response)
|
|
850
851
|
}
|
|
851
852
|
case 'data:': {
|
|
852
853
|
// 1. Let dataURLStruct be the result of running the
|
|
@@ -857,7 +858,7 @@ async function schemeFetch (fetchParams) {
|
|
|
857
858
|
// 2. If dataURLStruct is failure, then return a
|
|
858
859
|
// network error.
|
|
859
860
|
if (dataURLStruct === 'failure') {
|
|
860
|
-
return makeNetworkError('failed to fetch the data URL')
|
|
861
|
+
return Promise.resolve(makeNetworkError('failed to fetch the data URL'))
|
|
861
862
|
}
|
|
862
863
|
|
|
863
864
|
// 3. Let mimeType be dataURLStruct’s MIME type, serialized.
|
|
@@ -866,28 +867,28 @@ async function schemeFetch (fetchParams) {
|
|
|
866
867
|
// 4. Return a response whose status message is `OK`,
|
|
867
868
|
// header list is « (`Content-Type`, mimeType) »,
|
|
868
869
|
// and body is dataURLStruct’s body as a body.
|
|
869
|
-
return makeResponse({
|
|
870
|
+
return Promise.resolve(makeResponse({
|
|
870
871
|
statusText: 'OK',
|
|
871
872
|
headersList: [
|
|
872
873
|
['content-type', { name: 'Content-Type', value: mimeType }]
|
|
873
874
|
],
|
|
874
875
|
body: safelyExtractBody(dataURLStruct.body)[0]
|
|
875
|
-
})
|
|
876
|
+
}))
|
|
876
877
|
}
|
|
877
878
|
case 'file:': {
|
|
878
879
|
// For now, unfortunate as it is, file URLs are left as an exercise for the reader.
|
|
879
880
|
// When in doubt, return a network error.
|
|
880
|
-
return makeNetworkError('not implemented... yet...')
|
|
881
|
+
return Promise.resolve(makeNetworkError('not implemented... yet...'))
|
|
881
882
|
}
|
|
882
883
|
case 'http:':
|
|
883
884
|
case 'https:': {
|
|
884
885
|
// Return the result of running HTTP fetch given fetchParams.
|
|
885
886
|
|
|
886
|
-
return
|
|
887
|
+
return httpFetch(fetchParams)
|
|
887
888
|
.catch((err) => makeNetworkError(err))
|
|
888
889
|
}
|
|
889
890
|
default: {
|
|
890
|
-
return makeNetworkError('unknown scheme')
|
|
891
|
+
return Promise.resolve(makeNetworkError('unknown scheme'))
|
|
891
892
|
}
|
|
892
893
|
}
|
|
893
894
|
}
|
|
@@ -906,7 +907,7 @@ function finalizeResponse (fetchParams, response) {
|
|
|
906
907
|
}
|
|
907
908
|
|
|
908
909
|
// https://fetch.spec.whatwg.org/#fetch-finale
|
|
909
|
-
|
|
910
|
+
function fetchFinale (fetchParams, response) {
|
|
910
911
|
// 1. If response is a network error, then:
|
|
911
912
|
if (response.type === 'error') {
|
|
912
913
|
// 1. Set response’s URL list to « fetchParams’s request’s URL list[0] ».
|
|
@@ -990,8 +991,9 @@ async function fetchFinale (fetchParams, response) {
|
|
|
990
991
|
} else {
|
|
991
992
|
// 4. Otherwise, fully read response’s body given processBody, processBodyError,
|
|
992
993
|
// and fetchParams’s task destination.
|
|
993
|
-
|
|
994
|
+
return fullyReadBody(response.body, processBody, processBodyError)
|
|
994
995
|
}
|
|
996
|
+
return Promise.resolve()
|
|
995
997
|
}
|
|
996
998
|
}
|
|
997
999
|
|
|
@@ -1062,7 +1064,7 @@ async function httpFetch (fetchParams) {
|
|
|
1062
1064
|
}
|
|
1063
1065
|
|
|
1064
1066
|
// 8. If actualResponse’s status is a redirect status, then:
|
|
1065
|
-
if (
|
|
1067
|
+
if (redirectStatusSet.has(actualResponse.status)) {
|
|
1066
1068
|
// 1. If actualResponse’s status is not 303, request’s body is not null,
|
|
1067
1069
|
// and the connection uses HTTP/2, then user agents may, and are even
|
|
1068
1070
|
// encouraged to, transmit an RST_STREAM frame.
|
|
@@ -1099,7 +1101,7 @@ async function httpFetch (fetchParams) {
|
|
|
1099
1101
|
}
|
|
1100
1102
|
|
|
1101
1103
|
// https://fetch.spec.whatwg.org/#http-redirect-fetch
|
|
1102
|
-
|
|
1104
|
+
function httpRedirectFetch (fetchParams, response) {
|
|
1103
1105
|
// 1. Let request be fetchParams’s request.
|
|
1104
1106
|
const request = fetchParams.request
|
|
1105
1107
|
|
|
@@ -1125,18 +1127,18 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1125
1127
|
}
|
|
1126
1128
|
} catch (err) {
|
|
1127
1129
|
// 5. If locationURL is failure, then return a network error.
|
|
1128
|
-
return makeNetworkError(err)
|
|
1130
|
+
return Promise.resolve(makeNetworkError(err))
|
|
1129
1131
|
}
|
|
1130
1132
|
|
|
1131
1133
|
// 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network
|
|
1132
1134
|
// error.
|
|
1133
1135
|
if (!urlIsHttpHttpsScheme(locationURL)) {
|
|
1134
|
-
return makeNetworkError('URL scheme must be a HTTP(S) scheme')
|
|
1136
|
+
return Promise.resolve(makeNetworkError('URL scheme must be a HTTP(S) scheme'))
|
|
1135
1137
|
}
|
|
1136
1138
|
|
|
1137
1139
|
// 7. If request’s redirect count is 20, then return a network error.
|
|
1138
1140
|
if (request.redirectCount === 20) {
|
|
1139
|
-
return makeNetworkError('redirect count exceeded')
|
|
1141
|
+
return Promise.resolve(makeNetworkError('redirect count exceeded'))
|
|
1140
1142
|
}
|
|
1141
1143
|
|
|
1142
1144
|
// 8. Increase request’s redirect count by 1.
|
|
@@ -1150,7 +1152,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1150
1152
|
(locationURL.username || locationURL.password) &&
|
|
1151
1153
|
!sameOrigin(request, locationURL)
|
|
1152
1154
|
) {
|
|
1153
|
-
return makeNetworkError('cross origin not allowed for request mode "cors"')
|
|
1155
|
+
return Promise.resolve(makeNetworkError('cross origin not allowed for request mode "cors"'))
|
|
1154
1156
|
}
|
|
1155
1157
|
|
|
1156
1158
|
// 10. If request’s response tainting is "cors" and locationURL includes
|
|
@@ -1159,9 +1161,9 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1159
1161
|
request.responseTainting === 'cors' &&
|
|
1160
1162
|
(locationURL.username || locationURL.password)
|
|
1161
1163
|
) {
|
|
1162
|
-
return makeNetworkError(
|
|
1164
|
+
return Promise.resolve(makeNetworkError(
|
|
1163
1165
|
'URL cannot contain credentials for request mode "cors"'
|
|
1164
|
-
)
|
|
1166
|
+
))
|
|
1165
1167
|
}
|
|
1166
1168
|
|
|
1167
1169
|
// 11. If actualResponse’s status is not 303, request’s body is non-null,
|
|
@@ -1171,7 +1173,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1171
1173
|
request.body != null &&
|
|
1172
1174
|
request.body.source == null
|
|
1173
1175
|
) {
|
|
1174
|
-
return makeNetworkError()
|
|
1176
|
+
return Promise.resolve(makeNetworkError())
|
|
1175
1177
|
}
|
|
1176
1178
|
|
|
1177
1179
|
// 12. If one of the following is true
|
|
@@ -1180,7 +1182,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1180
1182
|
if (
|
|
1181
1183
|
([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
|
|
1182
1184
|
(actualResponse.status === 303 &&
|
|
1183
|
-
!
|
|
1185
|
+
!GET_OR_HEAD.includes(request.method))
|
|
1184
1186
|
) {
|
|
1185
1187
|
// then:
|
|
1186
1188
|
// 1. Set request’s method to `GET` and request’s body to null.
|
|
@@ -1464,7 +1466,7 @@ async function httpNetworkOrCacheFetch (
|
|
|
1464
1466
|
// responses in httpCache, as per the "Invalidation" chapter of HTTP
|
|
1465
1467
|
// Caching, and set storedResponse to null. [HTTP-CACHING]
|
|
1466
1468
|
if (
|
|
1467
|
-
!
|
|
1469
|
+
!safeMethodsSet.has(httpRequest.method) &&
|
|
1468
1470
|
forwardResponse.status >= 200 &&
|
|
1469
1471
|
forwardResponse.status <= 399
|
|
1470
1472
|
) {
|
|
@@ -2024,7 +2026,7 @@ async function httpNetworkFetch (
|
|
|
2024
2026
|
|
|
2025
2027
|
const willFollow = request.redirect === 'follow' &&
|
|
2026
2028
|
location &&
|
|
2027
|
-
|
|
2029
|
+
redirectStatusSet.has(status)
|
|
2028
2030
|
|
|
2029
2031
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
2030
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",
|
package/types/index.d.ts
CHANGED
|
@@ -53,5 +53,11 @@ declare namespace Undici {
|
|
|
53
53
|
var MockAgent: typeof import('./mock-agent').default;
|
|
54
54
|
var mockErrors: typeof import('./mock-errors').default;
|
|
55
55
|
var fetch: typeof import('./fetch').fetch;
|
|
56
|
+
var Headers: typeof import('./fetch').Headers;
|
|
57
|
+
var Response: typeof import('./fetch').Response;
|
|
58
|
+
var Request: typeof import('./fetch').Request;
|
|
59
|
+
var FormData: typeof import('./formdata').FormData;
|
|
60
|
+
var File: typeof import('./file').File;
|
|
61
|
+
var FileReader: typeof import('./filereader').FileReader;
|
|
56
62
|
var caches: typeof import('./cache').caches;
|
|
57
63
|
}
|