undici 7.15.0 → 7.17.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 +48 -2
- package/docs/docs/api/Agent.md +1 -0
- 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/Errors.md +0 -1
- 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-fetch.js +2 -2
- package/index.js +8 -9
- package/lib/api/api-request.js +22 -8
- package/lib/api/api-upgrade.js +2 -1
- package/lib/api/readable.js +7 -5
- package/lib/core/connect.js +4 -1
- package/lib/core/diagnostics.js +28 -1
- package/lib/core/errors.js +217 -13
- package/lib/core/request.js +5 -1
- package/lib/core/symbols.js +3 -0
- package/lib/core/util.js +61 -41
- package/lib/dispatcher/agent.js +19 -7
- package/lib/dispatcher/balanced-pool.js +10 -0
- package/lib/dispatcher/client-h1.js +18 -23
- package/lib/dispatcher/client-h2.js +166 -26
- package/lib/dispatcher/client.js +64 -59
- package/lib/dispatcher/dispatcher-base.js +20 -16
- package/lib/dispatcher/env-http-proxy-agent.js +12 -16
- package/lib/dispatcher/fixed-queue.js +15 -39
- package/lib/dispatcher/h2c-client.js +7 -78
- package/lib/dispatcher/pool-base.js +60 -43
- package/lib/dispatcher/pool.js +2 -2
- package/lib/dispatcher/proxy-agent.js +27 -11
- package/lib/dispatcher/round-robin-pool.js +137 -0
- package/lib/encoding/index.js +33 -0
- package/lib/global.js +19 -1
- 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 +94 -15
- 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-agent.js +4 -4
- package/lib/mock/mock-errors.js +10 -0
- package/lib/mock/mock-utils.js +13 -12
- 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/date.js +534 -140
- package/lib/util/runtime-features.js +124 -0
- package/lib/web/cookies/index.js +1 -1
- package/lib/web/cookies/parse.js +1 -1
- package/lib/web/eventsource/eventsource-stream.js +2 -2
- package/lib/web/eventsource/eventsource.js +34 -29
- package/lib/web/eventsource/util.js +1 -9
- package/lib/web/fetch/body.js +45 -61
- package/lib/web/fetch/data-url.js +12 -160
- package/lib/web/fetch/formdata-parser.js +204 -127
- package/lib/web/fetch/index.js +21 -19
- package/lib/web/fetch/request.js +6 -0
- package/lib/web/fetch/response.js +4 -7
- package/lib/web/fetch/util.js +10 -79
- package/lib/web/infra/index.js +229 -0
- package/lib/web/subresource-integrity/subresource-integrity.js +6 -5
- package/lib/web/webidl/index.js +207 -44
- package/lib/web/websocket/connection.js +33 -22
- package/lib/web/websocket/events.js +1 -1
- package/lib/web/websocket/frame.js +9 -15
- package/lib/web/websocket/stream/websocketerror.js +22 -1
- package/lib/web/websocket/stream/websocketstream.js +17 -8
- package/lib/web/websocket/util.js +2 -1
- package/lib/web/websocket/websocket.js +32 -42
- package/package.json +9 -7
- package/types/agent.d.ts +2 -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/errors.d.ts +5 -15
- 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/webidl.d.ts +82 -21
- package/types/websocket.d.ts +9 -9
package/lib/web/fetch/index.js
CHANGED
|
@@ -35,7 +35,6 @@ const {
|
|
|
35
35
|
isErrorLike,
|
|
36
36
|
fullyReadBody,
|
|
37
37
|
readableStreamClose,
|
|
38
|
-
isomorphicEncode,
|
|
39
38
|
urlIsLocal,
|
|
40
39
|
urlIsHttpHttpsScheme,
|
|
41
40
|
urlHasHttpsScheme,
|
|
@@ -63,6 +62,12 @@ const { webidl } = require('../webidl')
|
|
|
63
62
|
const { STATUS_CODES } = require('node:http')
|
|
64
63
|
const { bytesMatch } = require('../subresource-integrity/subresource-integrity')
|
|
65
64
|
const { createDeferredPromise } = require('../../util/promise')
|
|
65
|
+
const { isomorphicEncode } = require('../infra')
|
|
66
|
+
const { runtimeFeatures } = require('../../util/runtime-features')
|
|
67
|
+
|
|
68
|
+
// Node.js v23.8.0+ and v22.15.0+ supports Zstandard
|
|
69
|
+
const hasZstd = runtimeFeatures.has('zstd')
|
|
70
|
+
|
|
66
71
|
const GET_OR_HEAD = ['GET', 'HEAD']
|
|
67
72
|
|
|
68
73
|
const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esbuildDetection !== 'undefined'
|
|
@@ -884,7 +889,7 @@ function schemeFetch (fetchParams) {
|
|
|
884
889
|
|
|
885
890
|
// 8. Let slicedBlob be the result of invoking slice blob given blob, rangeStart,
|
|
886
891
|
// rangeEnd + 1, and type.
|
|
887
|
-
const slicedBlob = blob.slice(rangeStart, rangeEnd, type)
|
|
892
|
+
const slicedBlob = blob.slice(rangeStart, rangeEnd + 1, type)
|
|
888
893
|
|
|
889
894
|
// 9. Let slicedBodyWithType be the result of safely extracting slicedBlob.
|
|
890
895
|
// Note: same reason as mentioned above as to why we use extractBody
|
|
@@ -2104,33 +2109,29 @@ async function httpNetworkFetch (
|
|
|
2104
2109
|
return false
|
|
2105
2110
|
}
|
|
2106
2111
|
|
|
2107
|
-
/** @type {string[]} */
|
|
2108
|
-
let codings = []
|
|
2109
|
-
|
|
2110
2112
|
const headersList = new HeadersList()
|
|
2111
2113
|
|
|
2112
2114
|
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
2113
2115
|
headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true)
|
|
2114
2116
|
}
|
|
2115
|
-
const contentEncoding = headersList.get('content-encoding', true)
|
|
2116
|
-
if (contentEncoding) {
|
|
2117
|
-
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
2118
|
-
// "All content-coding values are case-insensitive..."
|
|
2119
|
-
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
|
|
2120
|
-
}
|
|
2121
2117
|
const location = headersList.get('location', true)
|
|
2122
2118
|
|
|
2123
2119
|
this.body = new Readable({ read: resume })
|
|
2124
2120
|
|
|
2125
|
-
const decoders = []
|
|
2126
|
-
|
|
2127
2121
|
const willFollow = location && request.redirect === 'follow' &&
|
|
2128
2122
|
redirectStatusSet.has(status)
|
|
2129
2123
|
|
|
2124
|
+
const decoders = []
|
|
2125
|
+
|
|
2130
2126
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
2131
|
-
if (
|
|
2127
|
+
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
|
2128
|
+
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
2129
|
+
const contentEncoding = headersList.get('content-encoding', true)
|
|
2130
|
+
// "All content-coding values are case-insensitive..."
|
|
2131
|
+
/** @type {string[]} */
|
|
2132
|
+
const codings = contentEncoding ? contentEncoding.toLowerCase().split(',') : []
|
|
2132
2133
|
for (let i = codings.length - 1; i >= 0; --i) {
|
|
2133
|
-
const coding = codings[i]
|
|
2134
|
+
const coding = codings[i].trim()
|
|
2134
2135
|
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
|
|
2135
2136
|
if (coding === 'x-gzip' || coding === 'gzip') {
|
|
2136
2137
|
decoders.push(zlib.createGunzip({
|
|
@@ -2151,8 +2152,7 @@ async function httpNetworkFetch (
|
|
|
2151
2152
|
flush: zlib.constants.BROTLI_OPERATION_FLUSH,
|
|
2152
2153
|
finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
|
|
2153
2154
|
}))
|
|
2154
|
-
} else if (coding === 'zstd' &&
|
|
2155
|
-
// Node.js v23.8.0+ and v22.15.0+ supports Zstandard
|
|
2155
|
+
} else if (coding === 'zstd' && hasZstd) {
|
|
2156
2156
|
decoders.push(zlib.createZstdDecompress({
|
|
2157
2157
|
flush: zlib.constants.ZSTD_e_continue,
|
|
2158
2158
|
finishFlush: zlib.constants.ZSTD_e_end
|
|
@@ -2228,8 +2228,10 @@ async function httpNetworkFetch (
|
|
|
2228
2228
|
},
|
|
2229
2229
|
|
|
2230
2230
|
onUpgrade (status, rawHeaders, socket) {
|
|
2231
|
-
|
|
2232
|
-
|
|
2231
|
+
// We need to support 200 for websocket over h2 as per RFC-8441
|
|
2232
|
+
// Absence of session means H1
|
|
2233
|
+
if ((socket.session != null && status !== 200) || (socket.session == null && status !== 101)) {
|
|
2234
|
+
return false
|
|
2233
2235
|
}
|
|
2234
2236
|
|
|
2235
2237
|
const headersList = new HeadersList()
|
package/lib/web/fetch/request.js
CHANGED
|
@@ -1094,6 +1094,12 @@ webidl.converters.RequestInit = webidl.dictionaryConverter([
|
|
|
1094
1094
|
{
|
|
1095
1095
|
key: 'dispatcher', // undici specific option
|
|
1096
1096
|
converter: webidl.converters.any
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
key: 'priority',
|
|
1100
|
+
converter: webidl.converters.DOMString,
|
|
1101
|
+
allowedValues: ['high', 'low', 'auto'],
|
|
1102
|
+
defaultValue: () => 'auto'
|
|
1097
1103
|
}
|
|
1098
1104
|
])
|
|
1099
1105
|
|
|
@@ -9,9 +9,7 @@ const {
|
|
|
9
9
|
isValidReasonPhrase,
|
|
10
10
|
isCancelled,
|
|
11
11
|
isAborted,
|
|
12
|
-
serializeJavascriptValueToJSONString,
|
|
13
12
|
isErrorLike,
|
|
14
|
-
isomorphicEncode,
|
|
15
13
|
environmentSettingsObject: relevantRealm
|
|
16
14
|
} = require('./util')
|
|
17
15
|
const {
|
|
@@ -22,8 +20,7 @@ const { webidl } = require('../webidl')
|
|
|
22
20
|
const { URLSerializer } = require('./data-url')
|
|
23
21
|
const { kConstruct } = require('../../core/symbols')
|
|
24
22
|
const assert = require('node:assert')
|
|
25
|
-
|
|
26
|
-
const { isArrayBuffer } = nodeUtil.types
|
|
23
|
+
const { isomorphicEncode, serializeJavascriptValueToJSONString } = require('../infra')
|
|
27
24
|
|
|
28
25
|
const textEncoder = new TextEncoder('utf-8')
|
|
29
26
|
|
|
@@ -120,7 +117,7 @@ class Response {
|
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
if (body !== null) {
|
|
123
|
-
body = webidl.converters.BodyInit(body)
|
|
120
|
+
body = webidl.converters.BodyInit(body, 'Response', 'body')
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
init = webidl.converters.ResponseInit(init)
|
|
@@ -456,7 +453,7 @@ function filterResponse (response, type) {
|
|
|
456
453
|
|
|
457
454
|
return makeFilteredResponse(response, {
|
|
458
455
|
type: 'opaque',
|
|
459
|
-
urlList:
|
|
456
|
+
urlList: [],
|
|
460
457
|
status: 0,
|
|
461
458
|
statusText: '',
|
|
462
459
|
body: null
|
|
@@ -580,7 +577,7 @@ webidl.converters.XMLHttpRequestBodyInit = function (V, prefix, name) {
|
|
|
580
577
|
return V
|
|
581
578
|
}
|
|
582
579
|
|
|
583
|
-
if (
|
|
580
|
+
if (webidl.is.BufferSource(V)) {
|
|
584
581
|
return V
|
|
585
582
|
}
|
|
586
583
|
|
package/lib/web/fetch/util.js
CHANGED
|
@@ -4,12 +4,13 @@ const { Transform } = require('node:stream')
|
|
|
4
4
|
const zlib = require('node:zlib')
|
|
5
5
|
const { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require('./constants')
|
|
6
6
|
const { getGlobalOrigin } = require('./global')
|
|
7
|
-
const {
|
|
7
|
+
const { collectAnHTTPQuotedString, parseMIMEType } = require('./data-url')
|
|
8
8
|
const { performance } = require('node:perf_hooks')
|
|
9
9
|
const { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
|
|
10
10
|
const assert = require('node:assert')
|
|
11
11
|
const { isUint8Array } = require('node:util/types')
|
|
12
12
|
const { webidl } = require('../webidl')
|
|
13
|
+
const { isomorphicEncode, collectASequenceOfCodePoints, removeChars } = require('../infra')
|
|
13
14
|
|
|
14
15
|
function responseURL (response) {
|
|
15
16
|
// https://fetch.spec.whatwg.org/#responses
|
|
@@ -502,8 +503,8 @@ function determineRequestsReferrer (request) {
|
|
|
502
503
|
if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
|
|
503
504
|
return 'no-referrer'
|
|
504
505
|
}
|
|
505
|
-
// 2. Return
|
|
506
|
-
return
|
|
506
|
+
// 2. Return referrerURL.
|
|
507
|
+
return referrerURL
|
|
507
508
|
}
|
|
508
509
|
}
|
|
509
510
|
}
|
|
@@ -554,17 +555,11 @@ function stripURLForReferrer (url, originOnly = false) {
|
|
|
554
555
|
return url
|
|
555
556
|
}
|
|
556
557
|
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
'(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){2}' +
|
|
560
|
-
'(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[1-9])' +
|
|
561
|
-
')$')
|
|
558
|
+
const isPotentialleTrustworthyIPv4 = RegExp.prototype.test
|
|
559
|
+
.bind(/^127\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){2}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)$/)
|
|
562
560
|
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
'(?:(?:0{1,4}):){1,6}(?::(?:0{0,3}1))|' +
|
|
566
|
-
'(?:::(?:0{0,3}1))|' +
|
|
567
|
-
')$')
|
|
561
|
+
const isPotentiallyTrustworthyIPv6 = RegExp.prototype.test
|
|
562
|
+
.bind(/^(?:(?:0{1,4}:){7}|(?:0{1,4}:){1,6}:|::)0{0,3}1$/)
|
|
568
563
|
|
|
569
564
|
/**
|
|
570
565
|
* Check if host matches one of the CIDR notations 127.0.0.0/8 or ::1/128.
|
|
@@ -579,11 +574,11 @@ function isOriginIPPotentiallyTrustworthy (origin) {
|
|
|
579
574
|
if (origin[0] === '[' && origin[origin.length - 1] === ']') {
|
|
580
575
|
origin = origin.slice(1, -1)
|
|
581
576
|
}
|
|
582
|
-
return
|
|
577
|
+
return isPotentiallyTrustworthyIPv6(origin)
|
|
583
578
|
}
|
|
584
579
|
|
|
585
580
|
// IPv4
|
|
586
|
-
return
|
|
581
|
+
return isPotentialleTrustworthyIPv4(origin)
|
|
587
582
|
}
|
|
588
583
|
|
|
589
584
|
/**
|
|
@@ -727,23 +722,6 @@ function normalizeMethod (method) {
|
|
|
727
722
|
return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
|
|
728
723
|
}
|
|
729
724
|
|
|
730
|
-
// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
|
|
731
|
-
function serializeJavascriptValueToJSONString (value) {
|
|
732
|
-
// 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
|
|
733
|
-
const result = JSON.stringify(value)
|
|
734
|
-
|
|
735
|
-
// 2. If result is undefined, then throw a TypeError.
|
|
736
|
-
if (result === undefined) {
|
|
737
|
-
throw new TypeError('Value is not JSON serializable')
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// 3. Assert: result is a string.
|
|
741
|
-
assert(typeof result === 'string')
|
|
742
|
-
|
|
743
|
-
// 4. Return result.
|
|
744
|
-
return result
|
|
745
|
-
}
|
|
746
|
-
|
|
747
725
|
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
|
748
726
|
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
|
749
727
|
|
|
@@ -999,22 +977,6 @@ function readableStreamClose (controller) {
|
|
|
999
977
|
}
|
|
1000
978
|
}
|
|
1001
979
|
|
|
1002
|
-
const invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/ // eslint-disable-line
|
|
1003
|
-
|
|
1004
|
-
/**
|
|
1005
|
-
* @see https://infra.spec.whatwg.org/#isomorphic-encode
|
|
1006
|
-
* @param {string} input
|
|
1007
|
-
*/
|
|
1008
|
-
function isomorphicEncode (input) {
|
|
1009
|
-
// 1. Assert: input contains no code points greater than U+00FF.
|
|
1010
|
-
assert(!invalidIsomorphicEncodeValueRegex.test(input))
|
|
1011
|
-
|
|
1012
|
-
// 2. Return a byte sequence whose length is equal to input’s code
|
|
1013
|
-
// point length and whose bytes have the same values as the
|
|
1014
|
-
// values of input’s code points, in the same order
|
|
1015
|
-
return input
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
980
|
/**
|
|
1019
981
|
* @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
|
|
1020
982
|
* @see https://streams.spec.whatwg.org/#read-loop
|
|
@@ -1467,34 +1429,6 @@ function getDecodeSplit (name, list) {
|
|
|
1467
1429
|
return gettingDecodingSplitting(value)
|
|
1468
1430
|
}
|
|
1469
1431
|
|
|
1470
|
-
const textDecoder = new TextDecoder()
|
|
1471
|
-
|
|
1472
|
-
/**
|
|
1473
|
-
* @see https://encoding.spec.whatwg.org/#utf-8-decode
|
|
1474
|
-
* @param {Buffer} buffer
|
|
1475
|
-
*/
|
|
1476
|
-
function utf8DecodeBytes (buffer) {
|
|
1477
|
-
if (buffer.length === 0) {
|
|
1478
|
-
return ''
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// 1. Let buffer be the result of peeking three bytes from
|
|
1482
|
-
// ioQueue, converted to a byte sequence.
|
|
1483
|
-
|
|
1484
|
-
// 2. If buffer is 0xEF 0xBB 0xBF, then read three
|
|
1485
|
-
// bytes from ioQueue. (Do nothing with those bytes.)
|
|
1486
|
-
if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
|
|
1487
|
-
buffer = buffer.subarray(3)
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
// 3. Process a queue with an instance of UTF-8’s
|
|
1491
|
-
// decoder, ioQueue, output, and "replacement".
|
|
1492
|
-
const output = textDecoder.decode(buffer)
|
|
1493
|
-
|
|
1494
|
-
// 4. Return output.
|
|
1495
|
-
return output
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
1432
|
class EnvironmentSettingsObjectBase {
|
|
1499
1433
|
get baseUrl () {
|
|
1500
1434
|
return getGlobalOrigin()
|
|
@@ -1540,7 +1474,6 @@ module.exports = {
|
|
|
1540
1474
|
isValidReasonPhrase,
|
|
1541
1475
|
sameOrigin,
|
|
1542
1476
|
normalizeMethod,
|
|
1543
|
-
serializeJavascriptValueToJSONString,
|
|
1544
1477
|
iteratorMixin,
|
|
1545
1478
|
createIterator,
|
|
1546
1479
|
isValidHeaderName,
|
|
@@ -1548,7 +1481,6 @@ module.exports = {
|
|
|
1548
1481
|
isErrorLike,
|
|
1549
1482
|
fullyReadBody,
|
|
1550
1483
|
readableStreamClose,
|
|
1551
|
-
isomorphicEncode,
|
|
1552
1484
|
urlIsLocal,
|
|
1553
1485
|
urlHasHttpsScheme,
|
|
1554
1486
|
urlIsHttpHttpsScheme,
|
|
@@ -1558,7 +1490,6 @@ module.exports = {
|
|
|
1558
1490
|
createInflate,
|
|
1559
1491
|
extractMimeType,
|
|
1560
1492
|
getDecodeSplit,
|
|
1561
|
-
utf8DecodeBytes,
|
|
1562
1493
|
environmentSettingsObject,
|
|
1563
1494
|
isOriginIPPotentiallyTrustworthy
|
|
1564
1495
|
}
|
|
@@ -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).
|