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.
Files changed (93) hide show
  1. package/README.md +48 -2
  2. package/docs/docs/api/Agent.md +1 -0
  3. package/docs/docs/api/Client.md +1 -0
  4. package/docs/docs/api/DiagnosticsChannel.md +57 -0
  5. package/docs/docs/api/Dispatcher.md +86 -0
  6. package/docs/docs/api/Errors.md +0 -1
  7. package/docs/docs/api/RoundRobinPool.md +145 -0
  8. package/docs/docs/api/WebSocket.md +21 -0
  9. package/docs/docs/best-practices/crawling.md +58 -0
  10. package/index-fetch.js +2 -2
  11. package/index.js +8 -9
  12. package/lib/api/api-request.js +22 -8
  13. package/lib/api/api-upgrade.js +2 -1
  14. package/lib/api/readable.js +7 -5
  15. package/lib/core/connect.js +4 -1
  16. package/lib/core/diagnostics.js +28 -1
  17. package/lib/core/errors.js +217 -13
  18. package/lib/core/request.js +5 -1
  19. package/lib/core/symbols.js +3 -0
  20. package/lib/core/util.js +61 -41
  21. package/lib/dispatcher/agent.js +19 -7
  22. package/lib/dispatcher/balanced-pool.js +10 -0
  23. package/lib/dispatcher/client-h1.js +18 -23
  24. package/lib/dispatcher/client-h2.js +166 -26
  25. package/lib/dispatcher/client.js +64 -59
  26. package/lib/dispatcher/dispatcher-base.js +20 -16
  27. package/lib/dispatcher/env-http-proxy-agent.js +12 -16
  28. package/lib/dispatcher/fixed-queue.js +15 -39
  29. package/lib/dispatcher/h2c-client.js +7 -78
  30. package/lib/dispatcher/pool-base.js +60 -43
  31. package/lib/dispatcher/pool.js +2 -2
  32. package/lib/dispatcher/proxy-agent.js +27 -11
  33. package/lib/dispatcher/round-robin-pool.js +137 -0
  34. package/lib/encoding/index.js +33 -0
  35. package/lib/global.js +19 -1
  36. package/lib/handler/cache-handler.js +84 -27
  37. package/lib/handler/deduplication-handler.js +216 -0
  38. package/lib/handler/retry-handler.js +0 -2
  39. package/lib/interceptor/cache.js +94 -15
  40. package/lib/interceptor/decompress.js +2 -1
  41. package/lib/interceptor/deduplicate.js +109 -0
  42. package/lib/interceptor/dns.js +55 -13
  43. package/lib/mock/mock-agent.js +4 -4
  44. package/lib/mock/mock-errors.js +10 -0
  45. package/lib/mock/mock-utils.js +13 -12
  46. package/lib/mock/snapshot-agent.js +11 -5
  47. package/lib/mock/snapshot-recorder.js +12 -4
  48. package/lib/mock/snapshot-utils.js +4 -4
  49. package/lib/util/cache.js +29 -1
  50. package/lib/util/date.js +534 -140
  51. package/lib/util/runtime-features.js +124 -0
  52. package/lib/web/cookies/index.js +1 -1
  53. package/lib/web/cookies/parse.js +1 -1
  54. package/lib/web/eventsource/eventsource-stream.js +2 -2
  55. package/lib/web/eventsource/eventsource.js +34 -29
  56. package/lib/web/eventsource/util.js +1 -9
  57. package/lib/web/fetch/body.js +45 -61
  58. package/lib/web/fetch/data-url.js +12 -160
  59. package/lib/web/fetch/formdata-parser.js +204 -127
  60. package/lib/web/fetch/index.js +21 -19
  61. package/lib/web/fetch/request.js +6 -0
  62. package/lib/web/fetch/response.js +4 -7
  63. package/lib/web/fetch/util.js +10 -79
  64. package/lib/web/infra/index.js +229 -0
  65. package/lib/web/subresource-integrity/subresource-integrity.js +6 -5
  66. package/lib/web/webidl/index.js +207 -44
  67. package/lib/web/websocket/connection.js +33 -22
  68. package/lib/web/websocket/events.js +1 -1
  69. package/lib/web/websocket/frame.js +9 -15
  70. package/lib/web/websocket/stream/websocketerror.js +22 -1
  71. package/lib/web/websocket/stream/websocketstream.js +17 -8
  72. package/lib/web/websocket/util.js +2 -1
  73. package/lib/web/websocket/websocket.js +32 -42
  74. package/package.json +9 -7
  75. package/types/agent.d.ts +2 -1
  76. package/types/api.d.ts +2 -2
  77. package/types/balanced-pool.d.ts +2 -1
  78. package/types/cache-interceptor.d.ts +1 -0
  79. package/types/client.d.ts +1 -1
  80. package/types/connector.d.ts +2 -2
  81. package/types/diagnostics-channel.d.ts +2 -2
  82. package/types/dispatcher.d.ts +12 -12
  83. package/types/errors.d.ts +5 -15
  84. package/types/fetch.d.ts +4 -4
  85. package/types/formdata.d.ts +1 -1
  86. package/types/h2c-client.d.ts +1 -1
  87. package/types/index.d.ts +9 -1
  88. package/types/interceptors.d.ts +36 -2
  89. package/types/pool.d.ts +1 -1
  90. package/types/readable.d.ts +2 -2
  91. package/types/round-robin-pool.d.ts +41 -0
  92. package/types/webidl.d.ts +82 -21
  93. package/types/websocket.d.ts +9 -9
@@ -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 (codings.length !== 0 && request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
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' && typeof zlib.createZstdDecompress === 'function') {
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
- if (status !== 101) {
2232
- return
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()
@@ -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: Object.freeze([]),
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 (ArrayBuffer.isView(V) || isArrayBuffer(V)) {
580
+ if (webidl.is.BufferSource(V)) {
584
581
  return V
585
582
  }
586
583
 
@@ -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 { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./data-url')
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 referrerOrigin
506
- return referrerOrigin
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 potentialleTrustworthyIPv4RegExp = new RegExp('^(?:' +
558
- '(?:127\\.)' +
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 potentialleTrustworthyIPv6RegExp = new RegExp('^(?:' +
564
- '(?:(?:0{1,4}):){7}(?:(?:0{0,3}1))|' +
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 potentialleTrustworthyIPv6RegExp.test(origin)
577
+ return isPotentiallyTrustworthyIPv6(origin)
583
578
  }
584
579
 
585
580
  // IPv4
586
- return potentialleTrustworthyIPv4RegExp.test(origin)
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
- try {
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
- /* c8 ignore next 4 */
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 === undefined || validSRIHashAlgorithmTokenSet.size === 0
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).