undici 5.28.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/fetch/util.js CHANGED
@@ -890,14 +890,7 @@ async function fullyReadBody (body, processBody, processBodyError) {
890
890
  }
891
891
  }
892
892
 
893
- /** @type {ReadableStream} */
894
- let ReadableStream = globalThis.ReadableStream
895
-
896
893
  function isReadableStreamLike (stream) {
897
- if (!ReadableStream) {
898
- ReadableStream = require('stream/web').ReadableStream
899
- }
900
-
901
894
  return stream instanceof ReadableStream || (
902
895
  stream[Symbol.toStringTag] === 'ReadableStream' &&
903
896
  typeof stream.tee === 'function'
@@ -928,9 +921,10 @@ function isomorphicDecode (input) {
928
921
  function readableStreamClose (controller) {
929
922
  try {
930
923
  controller.close()
924
+ controller.byobRequest?.respond(0)
931
925
  } catch (err) {
932
926
  // TODO: add comment explaining why this error occurs.
933
- if (!err.message.includes('Controller is already closed')) {
927
+ if (!err.message.includes('Controller is already closed') && !err.message.includes('ReadableStream is already closed')) {
934
928
  throw err
935
929
  }
936
930
  }
@@ -1018,10 +1012,172 @@ function urlIsHttpHttpsScheme (url) {
1018
1012
  return protocol === 'http:' || protocol === 'https:'
1019
1013
  }
1020
1014
 
1015
+ /** @type {import('./dataURL')['collectASequenceOfCodePoints']} */
1016
+ let collectASequenceOfCodePoints
1017
+
1018
+ /**
1019
+ * @see https://fetch.spec.whatwg.org/#simple-range-header-value
1020
+ * @param {string} value
1021
+ * @param {boolean} allowWhitespace
1022
+ */
1023
+ function simpleRangeHeaderValue (value, allowWhitespace) {
1024
+ // Note: avoid circular require
1025
+ collectASequenceOfCodePoints ??= require('./dataURL').collectASequenceOfCodePoints
1026
+
1027
+ // 1. Let data be the isomorphic decoding of value.
1028
+ // Note: isomorphic decoding takes a sequence of bytes (ie. a Uint8Array) and turns it into a string,
1029
+ // nothing more. We obviously don't need to do that if value is a string already.
1030
+ const data = value
1031
+
1032
+ // 2. If data does not start with "bytes", then return failure.
1033
+ if (!data.startsWith('bytes')) {
1034
+ return 'failure'
1035
+ }
1036
+
1037
+ // 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
1038
+ const position = { position: 5 }
1039
+
1040
+ // 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
1041
+ // from data given position.
1042
+ if (allowWhitespace) {
1043
+ collectASequenceOfCodePoints(
1044
+ (char) => char === '\t' || char === ' ',
1045
+ data,
1046
+ position
1047
+ )
1048
+ }
1049
+
1050
+ // 5. If the code point at position within data is not U+003D (=), then return failure.
1051
+ if (data.charCodeAt(position.position) !== 0x3D) {
1052
+ return 'failure'
1053
+ }
1054
+
1055
+ // 6. Advance position by 1.
1056
+ position.position++
1057
+
1058
+ // 7. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from
1059
+ // data given position.
1060
+ if (allowWhitespace) {
1061
+ collectASequenceOfCodePoints(
1062
+ (char) => char === '\t' || char === ' ',
1063
+ data,
1064
+ position
1065
+ )
1066
+ }
1067
+
1068
+ // 8. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits,
1069
+ // from data given position.
1070
+ const rangeStart = collectASequenceOfCodePoints(
1071
+ (char) => {
1072
+ const code = char.charCodeAt(0)
1073
+
1074
+ return code >= 0x30 && code <= 0x39
1075
+ },
1076
+ data,
1077
+ position
1078
+ )
1079
+
1080
+ // 9. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the
1081
+ // empty string; otherwise null.
1082
+ const rangeStartValue = rangeStart.length ? Number(rangeStart) : null
1083
+
1084
+ // 10. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
1085
+ // from data given position.
1086
+ if (allowWhitespace) {
1087
+ collectASequenceOfCodePoints(
1088
+ (char) => char === '\t' || char === ' ',
1089
+ data,
1090
+ position
1091
+ )
1092
+ }
1093
+
1094
+ // 11. If the code point at position within data is not U+002D (-), then return failure.
1095
+ if (data.charCodeAt(position.position) !== 0x2D) {
1096
+ return 'failure'
1097
+ }
1098
+
1099
+ // 12. Advance position by 1.
1100
+ position.position++
1101
+
1102
+ // 13. If allowWhitespace is true, collect a sequence of code points that are HTTP tab
1103
+ // or space, from data given position.
1104
+ // Note from Khafra: its the same fucking step again lol
1105
+ if (allowWhitespace) {
1106
+ collectASequenceOfCodePoints(
1107
+ (char) => char === '\t' || char === ' ',
1108
+ data,
1109
+ position
1110
+ )
1111
+ }
1112
+
1113
+ // 14. Let rangeEnd be the result of collecting a sequence of code points that are
1114
+ // ASCII digits, from data given position.
1115
+ // Note from Khafra: you wouldn't guess it, but this is also the same step as #8
1116
+ const rangeEnd = collectASequenceOfCodePoints(
1117
+ (char) => {
1118
+ const code = char.charCodeAt(0)
1119
+
1120
+ return code >= 0x30 && code <= 0x39
1121
+ },
1122
+ data,
1123
+ position
1124
+ )
1125
+
1126
+ // 15. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd
1127
+ // is not the empty string; otherwise null.
1128
+ // Note from Khafra: THE SAME STEP, AGAIN!!!
1129
+ // Note: why interpret as a decimal if we only collect ascii digits?
1130
+ const rangeEndValue = rangeEnd.length ? Number(rangeEnd) : null
1131
+
1132
+ // 16. If position is not past the end of data, then return failure.
1133
+ if (position.position < data.length) {
1134
+ return 'failure'
1135
+ }
1136
+
1137
+ // 17. If rangeEndValue and rangeStartValue are null, then return failure.
1138
+ if (rangeEndValue === null && rangeStartValue === null) {
1139
+ return 'failure'
1140
+ }
1141
+
1142
+ // 18. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is
1143
+ // greater than rangeEndValue, then return failure.
1144
+ // Note: ... when can they not be numbers?
1145
+ if (rangeStartValue > rangeEndValue) {
1146
+ return 'failure'
1147
+ }
1148
+
1149
+ // 19. Return (rangeStartValue, rangeEndValue).
1150
+ return { rangeStartValue, rangeEndValue }
1151
+ }
1152
+
1021
1153
  /**
1022
- * Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0.
1154
+ * @see https://fetch.spec.whatwg.org/#build-a-content-range
1155
+ * @param {number} rangeStart
1156
+ * @param {number} rangeEnd
1157
+ * @param {number} fullLength
1023
1158
  */
1024
- const hasOwn = Object.hasOwn || ((dict, key) => Object.prototype.hasOwnProperty.call(dict, key))
1159
+ function buildContentRange (rangeStart, rangeEnd, fullLength) {
1160
+ // 1. Let contentRange be `bytes `.
1161
+ let contentRange = 'bytes '
1162
+
1163
+ // 2. Append rangeStart, serialized and isomorphic encoded, to contentRange.
1164
+ contentRange += isomorphicEncode(`${rangeStart}`)
1165
+
1166
+ // 3. Append 0x2D (-) to contentRange.
1167
+ contentRange += '-'
1168
+
1169
+ // 4. Append rangeEnd, serialized and isomorphic encoded to contentRange.
1170
+ contentRange += isomorphicEncode(`${rangeEnd}`)
1171
+
1172
+ // 5. Append 0x2F (/) to contentRange.
1173
+ contentRange += '/'
1174
+
1175
+ // 6. Append fullLength, serialized and isomorphic encoded to contentRange.
1176
+ contentRange += isomorphicEncode(`${fullLength}`)
1177
+
1178
+ // 7. Return contentRange.
1179
+ return contentRange
1180
+ }
1025
1181
 
1026
1182
  module.exports = {
1027
1183
  isAborted,
@@ -1055,7 +1211,6 @@ module.exports = {
1055
1211
  makeIterator,
1056
1212
  isValidHeaderName,
1057
1213
  isValidHeaderValue,
1058
- hasOwn,
1059
1214
  isErrorLike,
1060
1215
  fullyReadBody,
1061
1216
  bytesMatch,
@@ -1067,5 +1222,7 @@ module.exports = {
1067
1222
  urlHasHttpsScheme,
1068
1223
  urlIsHttpHttpsScheme,
1069
1224
  readAllBytes,
1070
- normalizeMethodRecord
1225
+ normalizeMethodRecord,
1226
+ simpleRangeHeaderValue,
1227
+ buildContentRange
1071
1228
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { types } = require('util')
4
- const { hasOwn, toUSVString } = require('./util')
4
+ const { toUSVString } = require('./util')
5
5
 
6
6
  /** @type {import('../../types/webidl').Webidl} */
7
7
  const webidl = {}
@@ -346,7 +346,7 @@ webidl.dictionaryConverter = function (converters) {
346
346
  const { key, defaultValue, required, converter } = options
347
347
 
348
348
  if (required === true) {
349
- if (!hasOwn(dictionary, key)) {
349
+ if (!Object.hasOwn(dictionary, key)) {
350
350
  throw webidl.errors.exception({
351
351
  header: 'Dictionary',
352
352
  message: `Missing required key "${key}".`
@@ -355,7 +355,7 @@ webidl.dictionaryConverter = function (converters) {
355
355
  }
356
356
 
357
357
  let value = dictionary[key]
358
- const hasDefault = hasOwn(options, 'defaultValue')
358
+ const hasDefault = Object.hasOwn(options, 'defaultValue')
359
359
 
360
360
  // Only use defaultValue if value is undefined and
361
361
  // a defaultValue options was provided.
@@ -9,7 +9,6 @@ const {
9
9
  } = require('./symbols')
10
10
  const { ProgressEvent } = require('./progressevent')
11
11
  const { getEncoding } = require('./encoding')
12
- const { DOMException } = require('../fetch/constants')
13
12
  const { serializeAMimeType, parseMIMEType } = require('../fetch/dataURL')
14
13
  const { types } = require('util')
15
14
  const { StringDecoder } = require('string_decoder')
@@ -135,7 +135,7 @@ class RedirectHandler {
135
135
 
136
136
  For status 300, which is "Multiple Choices", the spec mentions both generating a Location
137
137
  response header AND a response body with the other possible location to follow.
138
- Since the spec explicitily chooses not to specify a format for such body and leave it to
138
+ Since the spec explicitly chooses not to specify a format for such body and leave it to
139
139
  servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
140
140
  */
141
141
  } else {
@@ -151,7 +151,7 @@ class RedirectHandler {
151
151
  TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections
152
152
  and neither are useful if present.
153
153
 
154
- See comment on onData method above for more detailed informations.
154
+ See comment on onData method above for more detailed information.
155
155
  */
156
156
 
157
157
  this.location = null
@@ -172,13 +172,22 @@ class RetryHandler {
172
172
  this.retryCount += 1
173
173
 
174
174
  if (statusCode >= 300) {
175
- this.abort(
176
- new RequestRetryError('Request failed', statusCode, {
177
- headers,
178
- count: this.retryCount
179
- })
180
- )
181
- return false
175
+ if (this.retryOpts.statusCodes.includes(statusCode) === false) {
176
+ return this.handler.onHeaders(
177
+ statusCode,
178
+ rawHeaders,
179
+ resume,
180
+ statusMessage
181
+ )
182
+ } else {
183
+ this.abort(
184
+ new RequestRetryError('Request failed', statusCode, {
185
+ headers,
186
+ count: this.retryCount
187
+ })
188
+ )
189
+ return false
190
+ }
182
191
  }
183
192
 
184
193
  // Checkpoint for resume from where we left it
@@ -261,6 +261,7 @@ function onSocketClose () {
261
261
  // attribute initialized to the result of applying UTF-8
262
262
  // decode without BOM to the WebSocket connection close
263
263
  // reason.
264
+ // TODO: process.nextTick
264
265
  fireEvent('close', ws, CloseEvent, {
265
266
  wasClean, code, reason
266
267
  })
@@ -182,6 +182,7 @@ function failWebsocketConnection (ws, reason) {
182
182
  }
183
183
 
184
184
  if (reason) {
185
+ // TODO: process.nextTick
185
186
  fireEvent('error', ws, ErrorEvent, {
186
187
  error: new Error(reason)
187
188
  })
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { webidl } = require('../fetch/webidl')
4
- const { DOMException } = require('../fetch/constants')
5
4
  const { URLSerializer } = require('../fetch/dataURL')
6
5
  const { getGlobalOrigin } = require('../fetch/global')
7
6
  const { staticPropertyDescriptors, states, opcodes, emptyBuffer } = require('./constants')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "5.28.2",
3
+ "version": "6.0.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": {
@@ -135,7 +135,7 @@
135
135
  "ws": "^8.11.0"
136
136
  },
137
137
  "engines": {
138
- "node": ">=14.0"
138
+ "node": ">=18.0"
139
139
  },
140
140
  "standard": {
141
141
  "env": [