undici 7.16.0 → 7.18.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 +53 -1
- package/docs/docs/api/Client.md +1 -0
- package/docs/docs/api/DiagnosticsChannel.md +57 -0
- package/docs/docs/api/Dispatcher.md +86 -0
- package/docs/docs/api/RoundRobinPool.md +145 -0
- package/docs/docs/api/WebSocket.md +21 -0
- package/docs/docs/best-practices/crawling.md +58 -0
- package/index.js +4 -1
- package/lib/api/api-upgrade.js +2 -1
- package/lib/core/connect.js +4 -1
- package/lib/core/diagnostics.js +28 -1
- package/lib/core/symbols.js +3 -0
- package/lib/core/util.js +29 -31
- package/lib/dispatcher/balanced-pool.js +10 -0
- package/lib/dispatcher/client-h1.js +0 -16
- package/lib/dispatcher/client-h2.js +153 -23
- package/lib/dispatcher/client.js +7 -2
- package/lib/dispatcher/dispatcher-base.js +11 -12
- package/lib/dispatcher/h2c-client.js +7 -78
- package/lib/dispatcher/pool-base.js +1 -1
- package/lib/dispatcher/proxy-agent.js +13 -2
- package/lib/dispatcher/round-robin-pool.js +137 -0
- package/lib/encoding/index.js +33 -0
- package/lib/handler/cache-handler.js +84 -27
- package/lib/handler/deduplication-handler.js +216 -0
- package/lib/handler/retry-handler.js +0 -2
- package/lib/interceptor/cache.js +35 -17
- package/lib/interceptor/decompress.js +2 -1
- package/lib/interceptor/deduplicate.js +109 -0
- package/lib/interceptor/dns.js +55 -13
- package/lib/mock/mock-utils.js +1 -2
- package/lib/mock/snapshot-agent.js +11 -5
- package/lib/mock/snapshot-recorder.js +12 -4
- package/lib/mock/snapshot-utils.js +4 -4
- package/lib/util/cache.js +29 -1
- package/lib/util/runtime-features.js +124 -0
- package/lib/web/cookies/parse.js +1 -1
- package/lib/web/fetch/body.js +29 -39
- package/lib/web/fetch/data-url.js +12 -160
- package/lib/web/fetch/formdata-parser.js +204 -127
- package/lib/web/fetch/index.js +18 -6
- package/lib/web/fetch/request.js +6 -0
- package/lib/web/fetch/response.js +2 -3
- package/lib/web/fetch/util.js +2 -65
- package/lib/web/infra/index.js +229 -0
- package/lib/web/subresource-integrity/subresource-integrity.js +6 -5
- package/lib/web/webidl/index.js +4 -2
- package/lib/web/websocket/connection.js +31 -21
- package/lib/web/websocket/frame.js +9 -15
- package/lib/web/websocket/stream/websocketstream.js +1 -1
- package/lib/web/websocket/util.js +2 -1
- package/package.json +5 -4
- package/types/agent.d.ts +1 -1
- package/types/api.d.ts +2 -2
- package/types/balanced-pool.d.ts +2 -1
- package/types/cache-interceptor.d.ts +1 -0
- package/types/client.d.ts +1 -1
- package/types/connector.d.ts +2 -2
- package/types/diagnostics-channel.d.ts +2 -2
- package/types/dispatcher.d.ts +12 -12
- package/types/fetch.d.ts +4 -4
- package/types/formdata.d.ts +1 -1
- package/types/h2c-client.d.ts +1 -1
- package/types/index.d.ts +9 -1
- package/types/interceptors.d.ts +36 -2
- package/types/pool.d.ts +1 -1
- package/types/readable.d.ts +2 -2
- package/types/round-robin-pool.d.ts +41 -0
- package/types/websocket.d.ts +9 -9
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { utf8DecodeBytes } = require('../../encoding')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {(char: string) => boolean} condition
|
|
8
|
+
* @param {string} input
|
|
9
|
+
* @param {{ position: number }} position
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*
|
|
12
|
+
* @see https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
|
13
|
+
*/
|
|
14
|
+
function collectASequenceOfCodePoints (condition, input, position) {
|
|
15
|
+
// 1. Let result be the empty string.
|
|
16
|
+
let result = ''
|
|
17
|
+
|
|
18
|
+
// 2. While position doesn’t point past the end of input and the
|
|
19
|
+
// code point at position within input meets the condition condition:
|
|
20
|
+
while (position.position < input.length && condition(input[position.position])) {
|
|
21
|
+
// 1. Append that code point to the end of result.
|
|
22
|
+
result += input[position.position]
|
|
23
|
+
|
|
24
|
+
// 2. Advance position by 1.
|
|
25
|
+
position.position++
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 3. Return result.
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A faster collectASequenceOfCodePoints that only works when comparing a single character.
|
|
34
|
+
* @param {string} char
|
|
35
|
+
* @param {string} input
|
|
36
|
+
* @param {{ position: number }} position
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*
|
|
39
|
+
* @see https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
|
40
|
+
*/
|
|
41
|
+
function collectASequenceOfCodePointsFast (char, input, position) {
|
|
42
|
+
const idx = input.indexOf(char, position.position)
|
|
43
|
+
const start = position.position
|
|
44
|
+
|
|
45
|
+
if (idx === -1) {
|
|
46
|
+
position.position = input.length
|
|
47
|
+
return input.slice(start)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
position.position = idx
|
|
51
|
+
return input.slice(start, position.position)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line no-control-regex
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} data
|
|
58
|
+
* @returns {Uint8Array | 'failure'}
|
|
59
|
+
*
|
|
60
|
+
* @see https://infra.spec.whatwg.org/#forgiving-base64-decode
|
|
61
|
+
*/
|
|
62
|
+
function forgivingBase64 (data) {
|
|
63
|
+
// 1. Remove all ASCII whitespace from data.
|
|
64
|
+
data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '')
|
|
65
|
+
|
|
66
|
+
let dataLength = data.length
|
|
67
|
+
// 2. If data’s code point length divides by 4 leaving
|
|
68
|
+
// no remainder, then:
|
|
69
|
+
if (dataLength % 4 === 0) {
|
|
70
|
+
// 1. If data ends with one or two U+003D (=) code points,
|
|
71
|
+
// then remove them from data.
|
|
72
|
+
if (data.charCodeAt(dataLength - 1) === 0x003D) {
|
|
73
|
+
--dataLength
|
|
74
|
+
if (data.charCodeAt(dataLength - 1) === 0x003D) {
|
|
75
|
+
--dataLength
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. If data’s code point length divides by 4 leaving
|
|
81
|
+
// a remainder of 1, then return failure.
|
|
82
|
+
if (dataLength % 4 === 1) {
|
|
83
|
+
return 'failure'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 4. If data contains a code point that is not one of
|
|
87
|
+
// U+002B (+)
|
|
88
|
+
// U+002F (/)
|
|
89
|
+
// ASCII alphanumeric
|
|
90
|
+
// then return failure.
|
|
91
|
+
if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) {
|
|
92
|
+
return 'failure'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const buffer = Buffer.from(data, 'base64')
|
|
96
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {number} char
|
|
101
|
+
* @returns {boolean}
|
|
102
|
+
*
|
|
103
|
+
* @see https://infra.spec.whatwg.org/#ascii-whitespace
|
|
104
|
+
*/
|
|
105
|
+
function isASCIIWhitespace (char) {
|
|
106
|
+
return (
|
|
107
|
+
char === 0x09 || // \t
|
|
108
|
+
char === 0x0a || // \n
|
|
109
|
+
char === 0x0c || // \f
|
|
110
|
+
char === 0x0d || // \r
|
|
111
|
+
char === 0x20 // space
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Uint8Array} input
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*
|
|
119
|
+
* @see https://infra.spec.whatwg.org/#isomorphic-decode
|
|
120
|
+
*/
|
|
121
|
+
function isomorphicDecode (input) {
|
|
122
|
+
// 1. To isomorphic decode a byte sequence input, return a string whose code point
|
|
123
|
+
// length is equal to input’s length and whose code points have the same values
|
|
124
|
+
// as the values of input’s bytes, in the same order.
|
|
125
|
+
const length = input.length
|
|
126
|
+
if ((2 << 15) - 1 > length) {
|
|
127
|
+
return String.fromCharCode.apply(null, input)
|
|
128
|
+
}
|
|
129
|
+
let result = ''
|
|
130
|
+
let i = 0
|
|
131
|
+
let addition = (2 << 15) - 1
|
|
132
|
+
while (i < length) {
|
|
133
|
+
if (i + addition > length) {
|
|
134
|
+
addition = length - i
|
|
135
|
+
}
|
|
136
|
+
result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
|
|
137
|
+
}
|
|
138
|
+
return result
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/ // eslint-disable-line no-control-regex
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {string} input
|
|
145
|
+
* @returns {string}
|
|
146
|
+
*
|
|
147
|
+
* @see https://infra.spec.whatwg.org/#isomorphic-encode
|
|
148
|
+
*/
|
|
149
|
+
function isomorphicEncode (input) {
|
|
150
|
+
// 1. Assert: input contains no code points greater than U+00FF.
|
|
151
|
+
assert(!invalidIsomorphicEncodeValueRegex.test(input))
|
|
152
|
+
|
|
153
|
+
// 2. Return a byte sequence whose length is equal to input’s code
|
|
154
|
+
// point length and whose bytes have the same values as the
|
|
155
|
+
// values of input’s code points, in the same order
|
|
156
|
+
return input
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
|
|
161
|
+
* @param {Uint8Array} bytes
|
|
162
|
+
*/
|
|
163
|
+
function parseJSONFromBytes (bytes) {
|
|
164
|
+
return JSON.parse(utf8DecodeBytes(bytes))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} str
|
|
169
|
+
* @param {boolean} [leading=true]
|
|
170
|
+
* @param {boolean} [trailing=true]
|
|
171
|
+
* @returns {string}
|
|
172
|
+
*
|
|
173
|
+
* @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
|
|
174
|
+
*/
|
|
175
|
+
function removeASCIIWhitespace (str, leading = true, trailing = true) {
|
|
176
|
+
return removeChars(str, leading, trailing, isASCIIWhitespace)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {string} str
|
|
181
|
+
* @param {boolean} leading
|
|
182
|
+
* @param {boolean} trailing
|
|
183
|
+
* @param {(charCode: number) => boolean} predicate
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
function removeChars (str, leading, trailing, predicate) {
|
|
187
|
+
let lead = 0
|
|
188
|
+
let trail = str.length - 1
|
|
189
|
+
|
|
190
|
+
if (leading) {
|
|
191
|
+
while (lead < str.length && predicate(str.charCodeAt(lead))) lead++
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (trailing) {
|
|
195
|
+
while (trail > 0 && predicate(str.charCodeAt(trail))) trail--
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
|
|
202
|
+
function serializeJavascriptValueToJSONString (value) {
|
|
203
|
+
// 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
|
|
204
|
+
const result = JSON.stringify(value)
|
|
205
|
+
|
|
206
|
+
// 2. If result is undefined, then throw a TypeError.
|
|
207
|
+
if (result === undefined) {
|
|
208
|
+
throw new TypeError('Value is not JSON serializable')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 3. Assert: result is a string.
|
|
212
|
+
assert(typeof result === 'string')
|
|
213
|
+
|
|
214
|
+
// 4. Return result.
|
|
215
|
+
return result
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
collectASequenceOfCodePoints,
|
|
220
|
+
collectASequenceOfCodePointsFast,
|
|
221
|
+
forgivingBase64,
|
|
222
|
+
isASCIIWhitespace,
|
|
223
|
+
isomorphicDecode,
|
|
224
|
+
isomorphicEncode,
|
|
225
|
+
parseJSONFromBytes,
|
|
226
|
+
removeASCIIWhitespace,
|
|
227
|
+
removeChars,
|
|
228
|
+
serializeJavascriptValueToJSONString
|
|
229
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
+
const { runtimeFeatures } = require('../../util/runtime-features.js')
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @typedef {object} Metadata
|
|
@@ -29,9 +30,10 @@ const assert = require('node:assert')
|
|
|
29
30
|
const validSRIHashAlgorithmTokenSet = new Map([['sha256', 0], ['sha384', 1], ['sha512', 2]])
|
|
30
31
|
|
|
31
32
|
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
|
|
32
|
-
/** @type {import('crypto')} */
|
|
33
|
+
/** @type {import('node:crypto')} */
|
|
33
34
|
let crypto
|
|
34
|
-
|
|
35
|
+
|
|
36
|
+
if (runtimeFeatures.has('crypto')) {
|
|
35
37
|
crypto = require('node:crypto')
|
|
36
38
|
const cryptoHashes = crypto.getHashes()
|
|
37
39
|
|
|
@@ -46,8 +48,7 @@ try {
|
|
|
46
48
|
validSRIHashAlgorithmTokenSet.delete(algorithm)
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
|
-
|
|
50
|
-
} catch {
|
|
51
|
+
} else {
|
|
51
52
|
// If crypto is not available, we cannot support SRI.
|
|
52
53
|
validSRIHashAlgorithmTokenSet.clear()
|
|
53
54
|
}
|
|
@@ -81,7 +82,7 @@ const isValidSRIHashAlgorithm = /** @type {IsValidSRIHashAlgorithm} */ (
|
|
|
81
82
|
*
|
|
82
83
|
* @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
|
|
83
84
|
*/
|
|
84
|
-
const bytesMatch = crypto ===
|
|
85
|
+
const bytesMatch = runtimeFeatures.has('crypto') === false || validSRIHashAlgorithmTokenSet.size === 0
|
|
85
86
|
// If node is not built with OpenSSL support, we cannot check
|
|
86
87
|
// a request's integrity, so allow it by default (the spec will
|
|
87
88
|
// allow requests if an invalid hash is given, as precedence).
|
package/lib/web/webidl/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { types, inspect } = require('node:util')
|
|
4
|
-
const {
|
|
4
|
+
const { runtimeFeatures } = require('../../util/runtime-features')
|
|
5
5
|
|
|
6
6
|
const UNDEFINED = 1
|
|
7
7
|
const BOOLEAN = 2
|
|
@@ -157,7 +157,9 @@ webidl.util.TypeValueToString = function (o) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
webidl.util.markAsUncloneable = markAsUncloneable
|
|
160
|
+
webidl.util.markAsUncloneable = runtimeFeatures.has('markAsUncloneable')
|
|
161
|
+
? require('node:worker_threads').markAsUncloneable
|
|
162
|
+
: () => {}
|
|
161
163
|
|
|
162
164
|
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
|
163
165
|
webidl.util.ConvertToInt = function (V, bitLength, signedness, flags) {
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
|
|
4
|
-
const { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
|
|
4
|
+
const { parseExtensions, isClosed, isClosing, isEstablished, isConnecting, validateCloseCodeAndReason } = require('./util')
|
|
5
5
|
const { makeRequest } = require('../fetch/request')
|
|
6
6
|
const { fetching } = require('../fetch/index')
|
|
7
7
|
const { Headers, getHeadersList } = require('../fetch/headers')
|
|
8
8
|
const { getDecodeSplit } = require('../fetch/util')
|
|
9
9
|
const { WebsocketFrameSend } = require('./frame')
|
|
10
10
|
const assert = require('node:assert')
|
|
11
|
+
const { runtimeFeatures } = require('../../util/runtime-features')
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
crypto = require('node:crypto')
|
|
16
|
-
/* c8 ignore next 3 */
|
|
17
|
-
} catch {
|
|
13
|
+
const crypto = runtimeFeatures.has('crypto')
|
|
14
|
+
? require('node:crypto')
|
|
15
|
+
: null
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
let warningEmitted = false
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* @see https://websockets.spec.whatwg.org/#concept-websocket-establish
|
|
@@ -95,17 +93,27 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
95
93
|
useParallelQueue: true,
|
|
96
94
|
dispatcher: options.dispatcher,
|
|
97
95
|
processResponse (response) {
|
|
98
|
-
if (response.type === 'error') {
|
|
99
|
-
// If the WebSocket connection could not be established, it is also said
|
|
100
|
-
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
|
101
|
-
handler.readyState = states.CLOSED
|
|
102
|
-
}
|
|
103
|
-
|
|
104
96
|
// 1. If response is a network error or its status is not 101,
|
|
105
97
|
// fail the WebSocket connection.
|
|
98
|
+
// if (response.type === 'error' || ((response.socket?.session != null && response.status !== 200) && response.status !== 101)) {
|
|
106
99
|
if (response.type === 'error' || response.status !== 101) {
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
// The presence of a session property on the socket indicates HTTP2
|
|
101
|
+
// HTTP1
|
|
102
|
+
if (response.socket?.session == null) {
|
|
103
|
+
failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.', response.error)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// HTTP2
|
|
108
|
+
if (response.status !== 200) {
|
|
109
|
+
failWebsocketConnection(handler, 1002, 'Received network error or non-200 status code.', response.error)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (warningEmitted === false && response.socket?.session != null) {
|
|
115
|
+
process.emitWarning('WebSocket over HTTP2 is experimental, and subject to change.', 'ExperimentalWarning')
|
|
116
|
+
warningEmitted = true
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
// 2. If protocols is not the empty list and extracting header
|
|
@@ -127,7 +135,8 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
127
135
|
// header field contains a value that is not an ASCII case-
|
|
128
136
|
// insensitive match for the value "websocket", the client MUST
|
|
129
137
|
// _Fail the WebSocket Connection_.
|
|
130
|
-
|
|
138
|
+
// For H2, no upgrade header is expected.
|
|
139
|
+
if (response.socket.session == null && response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
|
|
131
140
|
failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
|
|
132
141
|
return
|
|
133
142
|
}
|
|
@@ -136,7 +145,8 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
136
145
|
// |Connection| header field doesn't contain a token that is an
|
|
137
146
|
// ASCII case-insensitive match for the value "Upgrade", the client
|
|
138
147
|
// MUST _Fail the WebSocket Connection_.
|
|
139
|
-
|
|
148
|
+
// For H2, no connection header is expected.
|
|
149
|
+
if (response.socket.session == null && response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
|
|
140
150
|
failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
|
|
141
151
|
return
|
|
142
152
|
}
|
|
@@ -149,7 +159,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
149
159
|
// trailing whitespace, the client MUST _Fail the WebSocket
|
|
150
160
|
// Connection_.
|
|
151
161
|
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
|
|
152
|
-
const digest = crypto.
|
|
162
|
+
const digest = crypto.hash('sha1', keyValue + uid, 'base64')
|
|
153
163
|
if (secWSAccept !== digest) {
|
|
154
164
|
failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
|
155
165
|
return
|
|
@@ -303,10 +313,10 @@ function failWebsocketConnection (handler, code, reason, cause) {
|
|
|
303
313
|
|
|
304
314
|
handler.controller.abort()
|
|
305
315
|
|
|
306
|
-
if (
|
|
316
|
+
if (isConnecting(handler.readyState)) {
|
|
307
317
|
// If the connection was not established, we must still emit an 'error' and 'close' events
|
|
308
318
|
handler.onSocketClose()
|
|
309
|
-
} else if (handler.socket
|
|
319
|
+
} else if (handler.socket?.destroyed === false) {
|
|
310
320
|
handler.socket.destroy()
|
|
311
321
|
}
|
|
312
322
|
}
|
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { runtimeFeatures } = require('../../util/runtime-features')
|
|
3
4
|
const { maxUnsigned16Bit, opcodes } = require('./constants')
|
|
4
5
|
|
|
5
6
|
const BUFFER_SIZE = 8 * 1024
|
|
6
7
|
|
|
7
|
-
/** @type {import('crypto')} */
|
|
8
|
-
let crypto
|
|
9
8
|
let buffer = null
|
|
10
9
|
let bufIdx = BUFFER_SIZE
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
randomFillSync: function randomFillSync (buffer, _offset, _size) {
|
|
19
|
-
for (let i = 0; i < buffer.length; ++i) {
|
|
20
|
-
buffer[i] = Math.random() * 255 | 0
|
|
21
|
-
}
|
|
22
|
-
return buffer
|
|
11
|
+
const randomFillSync = runtimeFeatures.has('crypto')
|
|
12
|
+
? require('node:crypto').randomFillSync
|
|
13
|
+
// not full compatibility, but minimum.
|
|
14
|
+
: function randomFillSync (buffer, _offset, _size) {
|
|
15
|
+
for (let i = 0; i < buffer.length; ++i) {
|
|
16
|
+
buffer[i] = Math.random() * 255 | 0
|
|
23
17
|
}
|
|
18
|
+
return buffer
|
|
24
19
|
}
|
|
25
|
-
}
|
|
26
20
|
|
|
27
21
|
function generateMask () {
|
|
28
22
|
if (bufIdx === BUFFER_SIZE) {
|
|
29
23
|
bufIdx = 0
|
|
30
|
-
|
|
24
|
+
randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
|
|
31
25
|
}
|
|
32
26
|
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
|
|
33
27
|
}
|
|
@@ -10,8 +10,8 @@ const { channels } = require('../../../core/diagnostics')
|
|
|
10
10
|
const { WebsocketFrameSend } = require('../frame')
|
|
11
11
|
const { ByteParser } = require('../receiver')
|
|
12
12
|
const { WebSocketError, createUnvalidatedWebSocketError } = require('./websocketerror')
|
|
13
|
-
const { utf8DecodeBytes } = require('../../fetch/util')
|
|
14
13
|
const { kEnumerableProperty } = require('../../../core/util')
|
|
14
|
+
const { utf8DecodeBytes } = require('../../../encoding')
|
|
15
15
|
|
|
16
16
|
let emittedExperimentalWarning = false
|
|
17
17
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { states, opcodes } = require('./constants')
|
|
4
4
|
const { isUtf8 } = require('node:buffer')
|
|
5
|
-
const {
|
|
5
|
+
const { removeHTTPWhitespace } = require('../fetch/data-url')
|
|
6
|
+
const { collectASequenceOfCodePointsFast } = require('../infra')
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {number} readyState
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.18.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": {
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
"lint:fix": "eslint --fix --cache",
|
|
70
70
|
"test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
|
|
71
71
|
"test:javascript": "npm run test:javascript:no-jest && npm run test:jest",
|
|
72
|
-
"test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:fetch && npm run test:node-fetch && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:cookies && npm run test:eventsource && npm run test:subresource-integrity && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests",
|
|
72
|
+
"test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:fetch && npm run test:node-fetch && npm run test:infra && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:cookies && npm run test:eventsource && npm run test:subresource-integrity && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests",
|
|
73
73
|
"test:javascript:without-intl": "npm run test:javascript:no-jest",
|
|
74
74
|
"test:busboy": "borp -p \"test/busboy/*.js\"",
|
|
75
75
|
"test:cache": "borp -p \"test/cache/*.js\"",
|
|
76
|
-
"test:sqlite": "cross-env NODE_OPTIONS=--experimental-sqlite borp -p \"test/cache-interceptor/*.js\"",
|
|
77
76
|
"test:cache-interceptor": "borp -p \"test/cache-interceptor/*.js\"",
|
|
77
|
+
"test:cache-interceptor:sqlite": "cross-env NODE_OPTIONS=--experimental-sqlite npm run test:cache-interceptor",
|
|
78
78
|
"test:cookies": "borp -p \"test/cookie/*.js\"",
|
|
79
79
|
"test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"",
|
|
80
80
|
"test:fuzzing": "node test/fuzzing/fuzzing.test.js",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"test:h2": "npm run test:h2:core && npm run test:h2:fetch",
|
|
84
84
|
"test:h2:core": "borp -p \"test/+(http2|h2)*.js\"",
|
|
85
85
|
"test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"",
|
|
86
|
+
"test:infra": "borp -p \"test/infra/*.js\"",
|
|
86
87
|
"test:interceptors": "borp -p \"test/interceptors/*.js\"",
|
|
87
88
|
"test:jest": "cross-env NODE_V8_COVERAGE= jest",
|
|
88
89
|
"test:unit": "borp --expose-gc -p \"test/*.js\"",
|
|
@@ -112,7 +113,7 @@
|
|
|
112
113
|
"@matteo.collina/tspl": "^0.2.0",
|
|
113
114
|
"@metcoder95/https-pem": "^1.0.0",
|
|
114
115
|
"@sinonjs/fake-timers": "^12.0.0",
|
|
115
|
-
"@types/node": "^
|
|
116
|
+
"@types/node": "^20.19.22",
|
|
116
117
|
"abort-controller": "^3.0.0",
|
|
117
118
|
"borp": "^0.20.0",
|
|
118
119
|
"c8": "^10.0.0",
|
package/types/agent.d.ts
CHANGED
package/types/api.d.ts
CHANGED
package/types/balanced-pool.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Pool from './pool'
|
|
2
2
|
import Dispatcher from './dispatcher'
|
|
3
|
-
import { URL } from 'url'
|
|
3
|
+
import { URL } from 'node:url'
|
|
4
4
|
|
|
5
5
|
export default BalancedPool
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ declare class BalancedPool extends Dispatcher {
|
|
|
11
11
|
|
|
12
12
|
addUpstream (upstream: string | URL): BalancedPool
|
|
13
13
|
removeUpstream (upstream: string | URL): BalancedPool
|
|
14
|
+
getUpstream (upstream: string | URL): Pool | undefined
|
|
14
15
|
upstreams: Array<string>
|
|
15
16
|
|
|
16
17
|
/** `true` after `pool.close()` has been called. */
|
package/types/client.d.ts
CHANGED
package/types/connector.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TLSSocket, ConnectionOptions } from 'tls'
|
|
2
|
-
import { IpcNetConnectOpts, Socket, TcpNetConnectOpts } from 'net'
|
|
1
|
+
import { TLSSocket, ConnectionOptions } from 'node:tls'
|
|
2
|
+
import { IpcNetConnectOpts, Socket, TcpNetConnectOpts } from 'node:net'
|
|
3
3
|
|
|
4
4
|
export default buildConnector
|
|
5
5
|
declare function buildConnector (options?: buildConnector.BuildOptions): buildConnector.connector
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { URL } from 'url'
|
|
2
|
-
import { Duplex, Readable, Writable } from 'stream'
|
|
3
|
-
import { EventEmitter } from 'events'
|
|
4
|
-
import { Blob } from 'buffer'
|
|
1
|
+
import { URL } from 'node:url'
|
|
2
|
+
import { Duplex, Readable, Writable } from 'node:stream'
|
|
3
|
+
import { EventEmitter } from 'node:events'
|
|
4
|
+
import { Blob } from 'node:buffer'
|
|
5
5
|
import { IncomingHttpHeaders } from './header'
|
|
6
6
|
import BodyReadable from './readable'
|
|
7
7
|
import { FormData } from './formdata'
|
|
@@ -19,30 +19,30 @@ declare class Dispatcher extends EventEmitter {
|
|
|
19
19
|
/** Dispatches a request. This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this. */
|
|
20
20
|
dispatch (options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
|
|
21
21
|
/** Starts two-way communications with the requested resource. */
|
|
22
|
-
connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>): Promise<Dispatcher.ConnectData<TOpaque>>
|
|
23
22
|
connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>, callback: (err: Error | null, data: Dispatcher.ConnectData<TOpaque>) => void): void
|
|
23
|
+
connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>): Promise<Dispatcher.ConnectData<TOpaque>>
|
|
24
24
|
/** Compose a chain of dispatchers */
|
|
25
25
|
compose (dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher
|
|
26
26
|
compose (...dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher
|
|
27
27
|
/** Performs an HTTP request. */
|
|
28
|
-
request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>): Promise<Dispatcher.ResponseData<TOpaque>>
|
|
29
28
|
request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, callback: (err: Error | null, data: Dispatcher.ResponseData<TOpaque>) => void): void
|
|
29
|
+
request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>): Promise<Dispatcher.ResponseData<TOpaque>>
|
|
30
30
|
/** For easy use with `stream.pipeline`. */
|
|
31
31
|
pipeline<TOpaque = null>(options: Dispatcher.PipelineOptions<TOpaque>, handler: Dispatcher.PipelineHandler<TOpaque>): Duplex
|
|
32
32
|
/** A faster version of `Dispatcher.request`. */
|
|
33
|
-
stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>): Promise<Dispatcher.StreamData<TOpaque>>
|
|
34
33
|
stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>, callback: (err: Error | null, data: Dispatcher.StreamData<TOpaque>) => void): void
|
|
34
|
+
stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>): Promise<Dispatcher.StreamData<TOpaque>>
|
|
35
35
|
/** Upgrade to a different protocol. */
|
|
36
|
-
upgrade (options: Dispatcher.UpgradeOptions): Promise<Dispatcher.UpgradeData>
|
|
37
36
|
upgrade (options: Dispatcher.UpgradeOptions, callback: (err: Error | null, data: Dispatcher.UpgradeData) => void): void
|
|
37
|
+
upgrade (options: Dispatcher.UpgradeOptions): Promise<Dispatcher.UpgradeData>
|
|
38
38
|
/** Closes the client and gracefully waits for enqueued requests to complete before invoking the callback (or returning a promise if no callback is provided). */
|
|
39
|
-
close (): Promise<void>
|
|
40
39
|
close (callback: () => void): void
|
|
40
|
+
close (): Promise<void>
|
|
41
41
|
/** Destroy the client abruptly with the given err. All the pending and running requests will be asynchronously aborted and error. Waits until socket is closed before invoking the callback (or returning a promise if no callback is provided). Since this operation is asynchronously dispatched there might still be some progress on dispatched requests. */
|
|
42
|
-
destroy (): Promise<void>
|
|
43
|
-
destroy (err: Error | null): Promise<void>
|
|
44
|
-
destroy (callback: () => void): void
|
|
45
42
|
destroy (err: Error | null, callback: () => void): void
|
|
43
|
+
destroy (callback: () => void): void
|
|
44
|
+
destroy (err: Error | null): Promise<void>
|
|
45
|
+
destroy (): Promise<void>
|
|
46
46
|
|
|
47
47
|
on (eventName: 'connect', callback: (origin: URL, targets: readonly Dispatcher[]) => void): this
|
|
48
48
|
on (eventName: 'disconnect', callback: (origin: URL, targets: readonly Dispatcher[], error: Errors.UndiciError) => void): this
|
package/types/fetch.d.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// and https://github.com/node-fetch/node-fetch/blob/914ce6be5ec67a8bab63d68510aabf07cb818b6d/index.d.ts (MIT license)
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
|
|
5
|
-
import { Blob } from 'buffer'
|
|
6
|
-
import { URL, URLSearchParams } from 'url'
|
|
7
|
-
import { ReadableStream } from 'stream/web'
|
|
5
|
+
import { Blob } from 'node:buffer'
|
|
6
|
+
import { URL, URLSearchParams } from 'node:url'
|
|
7
|
+
import { ReadableStream } from 'node:stream/web'
|
|
8
8
|
import { FormData } from './formdata'
|
|
9
9
|
import { HeaderRecord } from './header'
|
|
10
10
|
import Dispatcher from './dispatcher'
|
|
@@ -207,5 +207,5 @@ export declare class Response extends BodyMixin {
|
|
|
207
207
|
|
|
208
208
|
static error (): Response
|
|
209
209
|
static json (data: any, init?: ResponseInit): Response
|
|
210
|
-
static redirect (url: string | URL, status
|
|
210
|
+
static redirect (url: string | URL, status?: ResponseRedirectStatus): Response
|
|
211
211
|
}
|
package/types/formdata.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Based on https://github.com/octet-stream/form-data/blob/2d0f0dc371517444ce1f22cdde13f51995d0953a/lib/FormData.ts (MIT)
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
|
|
4
|
-
import { File } from 'buffer'
|
|
4
|
+
import { File } from 'node:buffer'
|
|
5
5
|
import { SpecIterableIterator } from './fetch'
|
|
6
6
|
|
|
7
7
|
/**
|
package/types/h2c-client.d.ts
CHANGED