undici 7.2.1 → 7.2.3

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/core/util.js CHANGED
@@ -600,20 +600,25 @@ function ReadableStreamFrom (iterable) {
600
600
  async start () {
601
601
  iterator = iterable[Symbol.asyncIterator]()
602
602
  },
603
- async pull (controller) {
604
- const { done, value } = await iterator.next()
605
- if (done) {
606
- queueMicrotask(() => {
607
- controller.close()
608
- controller.byobRequest?.respond(0)
609
- })
610
- } else {
611
- const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
612
- if (buf.byteLength) {
613
- controller.enqueue(new Uint8Array(buf))
603
+ pull (controller) {
604
+ async function pull () {
605
+ const { done, value } = await iterator.next()
606
+ if (done) {
607
+ queueMicrotask(() => {
608
+ controller.close()
609
+ controller.byobRequest?.respond(0)
610
+ })
611
+ } else {
612
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
613
+ if (buf.byteLength) {
614
+ controller.enqueue(new Uint8Array(buf))
615
+ } else {
616
+ return await pull()
617
+ }
614
618
  }
615
619
  }
616
- return controller.desiredSize > 0
620
+
621
+ return pull()
617
622
  },
618
623
  async cancel () {
619
624
  await iterator.return()
@@ -32,7 +32,7 @@ class DNSInstance {
32
32
 
33
33
  // If full, we just return the origin
34
34
  if (ips == null && this.full) {
35
- cb(null, origin.origin)
35
+ cb(null, origin)
36
36
  return
37
37
  }
38
38
 
@@ -74,9 +74,9 @@ class DNSInstance {
74
74
 
75
75
  cb(
76
76
  null,
77
- `${origin.protocol}//${
77
+ new URL(`${origin.protocol}//${
78
78
  ip.family === 6 ? `[${ip.address}]` : ip.address
79
- }${port}`
79
+ }${port}`)
80
80
  )
81
81
  })
82
82
  } else {
@@ -105,9 +105,9 @@ class DNSInstance {
105
105
 
106
106
  cb(
107
107
  null,
108
- `${origin.protocol}//${
108
+ new URL(`${origin.protocol}//${
109
109
  ip.family === 6 ? `[${ip.address}]` : ip.address
110
- }${port}`
110
+ }${port}`)
111
111
  )
112
112
  }
113
113
  }
@@ -192,6 +192,38 @@ class DNSInstance {
192
192
  return ip
193
193
  }
194
194
 
195
+ pickFamily (origin, ipFamily) {
196
+ const records = this.#records.get(origin.hostname)?.records
197
+ if (!records) {
198
+ return null
199
+ }
200
+
201
+ const family = records[ipFamily]
202
+ if (!family) {
203
+ return null
204
+ }
205
+
206
+ if (family.offset == null || family.offset === maxInt) {
207
+ family.offset = 0
208
+ } else {
209
+ family.offset++
210
+ }
211
+
212
+ const position = family.offset % family.ips.length
213
+ const ip = family.ips[position] ?? null
214
+ if (ip == null) {
215
+ return ip
216
+ }
217
+
218
+ if (Date.now() - ip.timestamp > ip.ttl) { // record TTL is already in ms
219
+ // We delete expired records
220
+ // It is possible that they have different TTL, so we manage them individually
221
+ family.ips.splice(position, 1)
222
+ }
223
+
224
+ return ip
225
+ }
226
+
195
227
  setRecords (origin, addresses) {
196
228
  const timestamp = Date.now()
197
229
  const records = { records: { 4: null, 6: null } }
@@ -228,10 +260,13 @@ class DNSDispatchHandler extends DecoratorHandler {
228
260
  #dispatch = null
229
261
  #origin = null
230
262
  #controller = null
263
+ #newOrigin = null
264
+ #firstTry = true
231
265
 
232
- constructor (state, { origin, handler, dispatch }, opts) {
266
+ constructor (state, { origin, handler, dispatch, newOrigin }, opts) {
233
267
  super(handler)
234
268
  this.#origin = origin
269
+ this.#newOrigin = newOrigin
235
270
  this.#opts = { ...opts }
236
271
  this.#state = state
237
272
  this.#dispatch = dispatch
@@ -242,21 +277,36 @@ class DNSDispatchHandler extends DecoratorHandler {
242
277
  case 'ETIMEDOUT':
243
278
  case 'ECONNREFUSED': {
244
279
  if (this.#state.dualStack) {
245
- // We delete the record and retry
246
- this.#state.runLookup(this.#origin, this.#opts, (err, newOrigin) => {
247
- if (err) {
248
- super.onResponseError(controller, err)
249
- return
250
- }
251
-
252
- const dispatchOpts = {
253
- ...this.#opts,
254
- origin: newOrigin
255
- }
280
+ if (!this.#firstTry) {
281
+ super.onResponseError(controller, err)
282
+ return
283
+ }
284
+ this.#firstTry = false
285
+
286
+ // Pick an ip address from the other family
287
+ const otherFamily = this.#newOrigin.hostname[0] === '[' ? 4 : 6
288
+ const ip = this.#state.pickFamily(this.#origin, otherFamily)
289
+ if (ip == null) {
290
+ super.onResponseError(controller, err)
291
+ return
292
+ }
256
293
 
257
- this.#dispatch(dispatchOpts, this)
258
- })
294
+ let port
295
+ if (typeof ip.port === 'number') {
296
+ port = `:${ip.port}`
297
+ } else if (this.#origin.port !== '') {
298
+ port = `:${this.#origin.port}`
299
+ } else {
300
+ port = ''
301
+ }
259
302
 
303
+ const dispatchOpts = {
304
+ ...this.#opts,
305
+ origin: `${this.#origin.protocol}//${
306
+ ip.family === 6 ? `[${ip.address}]` : ip.address
307
+ }${port}`
308
+ }
309
+ this.#dispatch(dispatchOpts, this)
260
310
  return
261
311
  }
262
312
 
@@ -266,7 +316,8 @@ class DNSDispatchHandler extends DecoratorHandler {
266
316
  }
267
317
  case 'ENOTFOUND':
268
318
  this.#state.deleteRecords(this.#origin)
269
- // eslint-disable-next-line no-fallthrough
319
+ super.onResponseError(controller, err)
320
+ break
270
321
  default:
271
322
  super.onResponseError(controller, err)
272
323
  break
@@ -356,11 +407,10 @@ module.exports = interceptorOpts => {
356
407
  return handler.onResponseError(null, err)
357
408
  }
358
409
 
359
- let dispatchOpts = null
360
- dispatchOpts = {
410
+ const dispatchOpts = {
361
411
  ...origDispatchOpts,
362
412
  servername: origin.hostname, // For SNI on TLS
363
- origin: newOrigin,
413
+ origin: newOrigin.origin,
364
414
  headers: {
365
415
  host: origin.host,
366
416
  ...origDispatchOpts.headers
@@ -369,7 +419,10 @@ module.exports = interceptorOpts => {
369
419
 
370
420
  dispatch(
371
421
  dispatchOpts,
372
- instance.getHandler({ origin, dispatch, handler }, origDispatchOpts)
422
+ instance.getHandler(
423
+ { origin, dispatch, handler, newOrigin },
424
+ origDispatchOpts
425
+ )
373
426
  )
374
427
  })
375
428
 
@@ -17,6 +17,14 @@ const { isErrored, isDisturbed } = require('node:stream')
17
17
  const { isArrayBuffer } = require('node:util/types')
18
18
  const { serializeAMimeType } = require('./data-url')
19
19
  const { multipartFormDataParser } = require('./formdata-parser')
20
+ let random
21
+
22
+ try {
23
+ const crypto = require('node:crypto')
24
+ random = (max) => crypto.randomInt(0, max)
25
+ } catch {
26
+ random = (max) => Math.floor(Math.random(max))
27
+ }
20
28
 
21
29
  const textEncoder = new TextEncoder()
22
30
  function noop () {}
@@ -110,7 +118,7 @@ function extractBody (object, keepalive = false) {
110
118
  // Set source to a copy of the bytes held by object.
111
119
  source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
112
120
  } else if (webidl.is.FormData(object)) {
113
- const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}`
121
+ const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
114
122
  const prefix = `--${boundary}\r\nContent-Disposition: form-data`
115
123
 
116
124
  /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
4
- const { failWebsocketConnection, parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
4
+ const { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
5
5
  const { channels } = require('../../core/diagnostics')
6
6
  const { makeRequest } = require('../fetch/request')
7
7
  const { fetching } = require('../fetch/index')
@@ -294,7 +294,32 @@ function closeWebSocketConnection (object, code, reason, validate = false) {
294
294
  }
295
295
  }
296
296
 
297
+ /**
298
+ * @param {import('./websocket').Handler} handler
299
+ * @param {number} code
300
+ * @param {string|undefined} reason
301
+ * @returns {void}
302
+ */
303
+ function failWebsocketConnection (handler, code, reason) {
304
+ // If _The WebSocket Connection is Established_ prior to the point where
305
+ // the endpoint is required to _Fail the WebSocket Connection_, the
306
+ // endpoint SHOULD send a Close frame with an appropriate status code
307
+ // (Section 7.4) before proceeding to _Close the WebSocket Connection_.
308
+ if (isEstablished(handler.readyState)) {
309
+ closeWebSocketConnection(handler, code, reason, false)
310
+ }
311
+
312
+ handler.controller.abort()
313
+
314
+ if (handler.socket?.destroyed === false) {
315
+ handler.socket.destroy()
316
+ }
317
+
318
+ handler.onFail(code, reason)
319
+ }
320
+
297
321
  module.exports = {
298
322
  establishWebSocketConnection,
323
+ failWebsocketConnection,
299
324
  closeWebSocketConnection
300
325
  }
@@ -7,13 +7,13 @@ const { channels } = require('../../core/diagnostics')
7
7
  const {
8
8
  isValidStatusCode,
9
9
  isValidOpcode,
10
- failWebsocketConnection,
11
10
  websocketMessageReceived,
12
11
  utf8Decode,
13
12
  isControlFrame,
14
13
  isTextBinaryFrame,
15
14
  isContinuationFrame
16
15
  } = require('./util')
16
+ const { failWebsocketConnection } = require('./connection')
17
17
  const { WebsocketFrameSend } = require('./frame')
18
18
  const { PerMessageDeflate } = require('./permessage-deflate')
19
19
 
@@ -3,8 +3,8 @@
3
3
  const { createDeferredPromise, environmentSettingsObject } = require('../../fetch/util')
4
4
  const { states, opcodes, sentCloseFrameState } = require('../constants')
5
5
  const { webidl } = require('../../fetch/webidl')
6
- const { getURLRecord, isValidSubprotocol, isEstablished, failWebsocketConnection, utf8Decode } = require('../util')
7
- const { establishWebSocketConnection, closeWebSocketConnection } = require('../connection')
6
+ const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util')
7
+ const { establishWebSocketConnection, failWebsocketConnection, closeWebSocketConnection } = require('../connection')
8
8
  const { types } = require('node:util')
9
9
  const { channels } = require('../../../core/diagnostics')
10
10
  const { WebsocketFrameSend } = require('../frame')
@@ -156,32 +156,6 @@ function isValidStatusCode (code) {
156
156
  return code >= 3000 && code <= 4999
157
157
  }
158
158
 
159
- /**
160
- * @param {import('./websocket').Handler} handler
161
- * @param {number} code
162
- * @param {string|undefined} reason
163
- * @returns {void}
164
- */
165
- function failWebsocketConnection (handler, code, reason) {
166
- // If _The WebSocket Connection is Established_ prior to the point where
167
- // the endpoint is required to _Fail the WebSocket Connection_, the
168
- // endpoint SHOULD send a Close frame with an appropriate status code
169
- // (Section 7.4) before proceeding to _Close the WebSocket Connection_.
170
- if (isEstablished(handler.readyState)) {
171
- // avoid circular require - performance is not important here
172
- const { closeWebSocketConnection } = require('./connection')
173
- closeWebSocketConnection(handler, code, reason, false)
174
- }
175
-
176
- handler.controller.abort()
177
-
178
- if (handler.socket?.destroyed === false) {
179
- handler.socket.destroy()
180
- }
181
-
182
- handler.onFail(code, reason)
183
- }
184
-
185
159
  /**
186
160
  * @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5
187
161
  * @param {number} opcode
@@ -350,7 +324,6 @@ module.exports = {
350
324
  fireEvent,
351
325
  isValidSubprotocol,
352
326
  isValidStatusCode,
353
- failWebsocketConnection,
354
327
  websocketMessageReceived,
355
328
  utf8Decode,
356
329
  isControlFrame,
@@ -10,12 +10,11 @@ const {
10
10
  isClosing,
11
11
  isValidSubprotocol,
12
12
  fireEvent,
13
- failWebsocketConnection,
14
13
  utf8Decode,
15
14
  toArrayBuffer,
16
15
  getURLRecord
17
16
  } = require('./util')
18
- const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
17
+ const { establishWebSocketConnection, closeWebSocketConnection, failWebsocketConnection } = require('./connection')
19
18
  const { ByteParser } = require('./receiver')
20
19
  const { kEnumerableProperty } = require('../../core/util')
21
20
  const { getGlobalDispatcher } = require('../../global')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.2.1",
3
+ "version": "7.2.3",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {