undici 6.12.0 → 6.13.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 CHANGED
@@ -248,6 +248,18 @@ const data = {
248
248
  await fetch('https://example.com', { body: data, method: 'POST', duplex: 'half' })
249
249
  ```
250
250
 
251
+ [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) besides text data and buffers can also utilize streams via [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects:
252
+
253
+ ```js
254
+ import { openAsBlob } from 'node:fs'
255
+
256
+ const file = await openAsBlob('./big.csv')
257
+ const body = new FormData()
258
+ body.set('file', file, 'big.csv')
259
+
260
+ await fetch('http://example.com', { method: 'POST', body })
261
+ ```
262
+
251
263
  #### `request.duplex`
252
264
 
253
265
  - half
@@ -63,9 +63,7 @@ class BodyReadable extends Readable {
63
63
  // tick as it is created, then a user who is waiting for a
64
64
  // promise (i.e micro tick) for installing a 'error' listener will
65
65
  // never get a chance and will always encounter an unhandled exception.
66
- // - tick => process.nextTick(fn)
67
- // - micro tick => queueMicrotask(fn)
68
- queueMicrotask(() => {
66
+ setImmediate(() => {
69
67
  callback(err)
70
68
  })
71
69
  }
@@ -98,28 +98,22 @@ async function connectH2 (client, socket) {
98
98
  util.addListener(session, 'goaway', onHTTP2GoAway)
99
99
  util.addListener(session, 'close', function () {
100
100
  const { [kClient]: client } = this
101
+ const { [kSocket]: socket } = client
101
102
 
102
- const err = this[kSocket][kError] || new SocketError('closed', util.getSocketInfo(this))
103
+ const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
103
104
 
104
- client[kSocket] = null
105
105
  client[kHTTP2Session] = null
106
106
 
107
- assert(client[kPending] === 0)
107
+ if (client.destroyed) {
108
+ assert(client[kPending] === 0)
108
109
 
109
- // Fail entire queue.
110
- const requests = client[kQueue].splice(client[kRunningIdx])
111
- for (let i = 0; i < requests.length; i++) {
112
- const request = requests[i]
113
- util.errorRequest(client, request, err)
110
+ // Fail entire queue.
111
+ const requests = client[kQueue].splice(client[kRunningIdx])
112
+ for (let i = 0; i < requests.length; i++) {
113
+ const request = requests[i]
114
+ util.errorRequest(client, request, err)
115
+ }
114
116
  }
115
-
116
- client[kPendingIdx] = client[kRunningIdx]
117
-
118
- assert(client[kRunning] === 0)
119
-
120
- client.emit('disconnect', client[kUrl], [client], err)
121
-
122
- client[kResume]()
123
117
  })
124
118
 
125
119
  session.unref()
@@ -139,6 +133,24 @@ async function connectH2 (client, socket) {
139
133
  util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
140
134
  })
141
135
 
136
+ util.addListener(socket, 'close', function () {
137
+ const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
138
+
139
+ client[kSocket] = null
140
+
141
+ if (this[kHTTP2Session] != null) {
142
+ this[kHTTP2Session].destroy(err)
143
+ }
144
+
145
+ client[kPendingIdx] = client[kRunningIdx]
146
+
147
+ assert(client[kRunning] === 0)
148
+
149
+ client.emit('disconnect', client[kUrl], [client], err)
150
+
151
+ client[kResume]()
152
+ })
153
+
142
154
  let closed = false
143
155
  socket.on('close', () => {
144
156
  closed = true
@@ -155,10 +167,10 @@ async function connectH2 (client, socket) {
155
167
 
156
168
  },
157
169
  destroy (err, callback) {
158
- session.destroy(err)
159
170
  if (closed) {
160
171
  queueMicrotask(callback)
161
172
  } else {
173
+ // Destroying the socket will trigger the session close
162
174
  socket.destroy(err).on('close', callback)
163
175
  }
164
176
  },
@@ -257,27 +269,28 @@ function writeH2 (client, request) {
257
269
  headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
258
270
  headers[HTTP2_HEADER_METHOD] = method
259
271
 
260
- try {
261
- // We are already connected, streams are pending.
262
- // We can call on connect, and wait for abort
263
- request.onConnect((err) => {
264
- if (request.aborted || request.completed) {
265
- return
266
- }
272
+ const abort = (err) => {
273
+ if (request.aborted || request.completed) {
274
+ return
275
+ }
267
276
 
268
- err = err || new RequestAbortedError()
277
+ err = err || new RequestAbortedError()
269
278
 
270
- if (stream != null) {
271
- util.destroy(stream, err)
279
+ util.errorRequest(client, request, err)
272
280
 
273
- session[kOpenStreams] -= 1
274
- if (session[kOpenStreams] === 0) {
275
- session.unref()
276
- }
277
- }
281
+ if (stream != null) {
282
+ util.destroy(stream, err)
283
+ }
278
284
 
279
- util.errorRequest(client, request, err)
280
- })
285
+ // We do not destroy the socket as we can continue using the session
286
+ // the stream get's destroyed and the session remains to create new streams
287
+ util.destroy(body, err)
288
+ }
289
+
290
+ try {
291
+ // We are already connected, streams are pending.
292
+ // We can call on connect, and wait for abort
293
+ request.onConnect(abort)
281
294
  } catch (err) {
282
295
  util.errorRequest(client, request, err)
283
296
  }
@@ -302,7 +315,6 @@ function writeH2 (client, request) {
302
315
 
303
316
  stream.once('close', () => {
304
317
  session[kOpenStreams] -= 1
305
- // TODO(HTTP/2): unref only if current streams count is 0
306
318
  if (session[kOpenStreams] === 0) session.unref()
307
319
  })
308
320
 
@@ -382,7 +394,7 @@ function writeH2 (client, request) {
382
394
  writeBodyH2()
383
395
  }
384
396
 
385
- // Increment counter as we have new several streams open
397
+ // Increment counter as we have new streams open
386
398
  ++session[kOpenStreams]
387
399
 
388
400
  stream.once('response', headers => {
@@ -394,7 +406,7 @@ function writeH2 (client, request) {
394
406
  // the request remains in-flight and headers hasn't been received yet
395
407
  // for those scenarios, best effort is to destroy the stream immediately
396
408
  // as there's no value to keep it open.
397
- if (request.aborted || request.completed) {
409
+ if (request.aborted) {
398
410
  const err = new RequestAbortedError()
399
411
  util.errorRequest(client, request, err)
400
412
  util.destroy(stream, err)
@@ -424,14 +436,11 @@ function writeH2 (client, request) {
424
436
  // Stream is closed or half-closed-remote (6), decrement counter and cleanup
425
437
  // It does not have sense to continue working with the stream as we do not
426
438
  // have yet RST_STREAM support on client-side
427
- session[kOpenStreams] -= 1
428
439
  if (session[kOpenStreams] === 0) {
429
440
  session.unref()
430
441
  }
431
442
 
432
- const err = new InformationalError('HTTP/2: stream half-closed (remote)')
433
- util.errorRequest(client, request, err)
434
- util.destroy(stream, err)
443
+ abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
435
444
  })
436
445
 
437
446
  stream.once('close', () => {
@@ -442,21 +451,11 @@ function writeH2 (client, request) {
442
451
  })
443
452
 
444
453
  stream.once('error', function (err) {
445
- if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) {
446
- session[kOpenStreams] -= 1
447
- util.errorRequest(client, request, err)
448
- util.destroy(stream, err)
449
- }
454
+ abort(err)
450
455
  })
451
456
 
452
457
  stream.once('frameError', (type, code) => {
453
- const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
454
- util.errorRequest(client, request, err)
455
-
456
- if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) {
457
- session[kOpenStreams] -= 1
458
- util.destroy(stream, err)
459
- }
458
+ abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
460
459
  })
461
460
 
462
461
  // stream.on('aborted', () => {
@@ -479,37 +478,49 @@ function writeH2 (client, request) {
479
478
 
480
479
  function writeBodyH2 () {
481
480
  /* istanbul ignore else: assertion */
482
- if (!body) {
483
- request.onRequestSent()
481
+ if (!body || contentLength === 0) {
482
+ writeBuffer({
483
+ abort,
484
+ client,
485
+ request,
486
+ contentLength,
487
+ expectsPayload,
488
+ h2stream: stream,
489
+ body: null,
490
+ socket: client[kSocket]
491
+ })
484
492
  } else if (util.isBuffer(body)) {
485
- assert(contentLength === body.byteLength, 'buffer body must have content length')
486
- stream.cork()
487
- stream.write(body)
488
- stream.uncork()
489
- stream.end()
490
- request.onBodySent(body)
491
- request.onRequestSent()
493
+ writeBuffer({
494
+ abort,
495
+ client,
496
+ request,
497
+ contentLength,
498
+ body,
499
+ expectsPayload,
500
+ h2stream: stream,
501
+ socket: client[kSocket]
502
+ })
492
503
  } else if (util.isBlobLike(body)) {
493
504
  if (typeof body.stream === 'function') {
494
505
  writeIterable({
506
+ abort,
495
507
  client,
496
508
  request,
497
509
  contentLength,
498
- h2stream: stream,
499
510
  expectsPayload,
511
+ h2stream: stream,
500
512
  body: body.stream(),
501
- socket: client[kSocket],
502
- header: ''
513
+ socket: client[kSocket]
503
514
  })
504
515
  } else {
505
516
  writeBlob({
517
+ abort,
506
518
  body,
507
519
  client,
508
520
  request,
509
521
  contentLength,
510
522
  expectsPayload,
511
523
  h2stream: stream,
512
- header: '',
513
524
  socket: client[kSocket]
514
525
  })
515
526
  }
@@ -541,7 +552,30 @@ function writeH2 (client, request) {
541
552
  }
542
553
  }
543
554
 
544
- function writeStream ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
555
+ function writeBuffer ({ abort, h2stream, body, client, request, socket, contentLength, expectsPayload }) {
556
+ try {
557
+ if (body != null && util.isBuffer(body)) {
558
+ assert(contentLength === body.byteLength, 'buffer body must have content length')
559
+ h2stream.cork()
560
+ h2stream.write(body)
561
+ h2stream.uncork()
562
+ h2stream.end()
563
+
564
+ request.onBodySent(body)
565
+ }
566
+
567
+ if (!expectsPayload) {
568
+ socket[kReset] = true
569
+ }
570
+
571
+ request.onRequestSent()
572
+ client[kResume]()
573
+ } catch (error) {
574
+ abort(error)
575
+ }
576
+ }
577
+
578
+ function writeStream ({ abort, socket, expectsPayload, h2stream, body, client, request, contentLength }) {
545
579
  assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
546
580
 
547
581
  // For HTTP/2, is enough to pipe the stream
@@ -550,26 +584,29 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
550
584
  h2stream,
551
585
  (err) => {
552
586
  if (err) {
553
- util.destroy(body, err)
554
- util.destroy(h2stream, err)
587
+ util.destroy(pipe, err)
588
+ abort(err)
555
589
  } else {
590
+ util.removeAllListeners(pipe)
556
591
  request.onRequestSent()
592
+
593
+ if (!expectsPayload) {
594
+ socket[kReset] = true
595
+ }
596
+
597
+ client[kResume]()
557
598
  }
558
599
  }
559
600
  )
560
601
 
561
- pipe.on('data', onPipeData)
562
- pipe.once('end', () => {
563
- pipe.removeListener('data', onPipeData)
564
- util.destroy(pipe)
565
- })
602
+ util.addListener(pipe, 'data', onPipeData)
566
603
 
567
604
  function onPipeData (chunk) {
568
605
  request.onBodySent(chunk)
569
606
  }
570
607
  }
571
608
 
572
- async function writeBlob ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
609
+ async function writeBlob ({ abort, h2stream, body, client, request, socket, contentLength, expectsPayload }) {
573
610
  assert(contentLength === body.size, 'blob body must have content length')
574
611
 
575
612
  try {
@@ -582,6 +619,7 @@ async function writeBlob ({ h2stream, body, client, request, socket, contentLeng
582
619
  h2stream.cork()
583
620
  h2stream.write(buffer)
584
621
  h2stream.uncork()
622
+ h2stream.end()
585
623
 
586
624
  request.onBodySent(buffer)
587
625
  request.onRequestSent()
@@ -592,11 +630,11 @@ async function writeBlob ({ h2stream, body, client, request, socket, contentLeng
592
630
 
593
631
  client[kResume]()
594
632
  } catch (err) {
595
- util.destroy(h2stream)
633
+ abort(err)
596
634
  }
597
635
  }
598
636
 
599
- async function writeIterable ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
637
+ async function writeIterable ({ abort, h2stream, body, client, request, socket, contentLength, expectsPayload }) {
600
638
  assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
601
639
 
602
640
  let callback = null
@@ -635,11 +673,19 @@ async function writeIterable ({ h2stream, body, client, request, socket, content
635
673
  await waitForDrain()
636
674
  }
637
675
  }
676
+
677
+ h2stream.end()
678
+
679
+ request.onRequestSent()
680
+
681
+ if (!expectsPayload) {
682
+ socket[kReset] = true
683
+ }
684
+
685
+ client[kResume]()
638
686
  } catch (err) {
639
- h2stream.destroy(err)
687
+ abort(err)
640
688
  } finally {
641
- request.onRequestSent()
642
- h2stream.end()
643
689
  h2stream
644
690
  .off('close', onDrain)
645
691
  .off('drain', onDrain)
@@ -1,35 +1,44 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = class DecoratorHandler {
4
+ #handler
5
+
4
6
  constructor (handler) {
5
- this.handler = handler
7
+ if (typeof handler !== 'object' || handler === null) {
8
+ throw new TypeError('handler must be an object')
9
+ }
10
+ this.#handler = handler
6
11
  }
7
12
 
8
13
  onConnect (...args) {
9
- return this.handler.onConnect(...args)
14
+ return this.#handler.onConnect?.(...args)
10
15
  }
11
16
 
12
17
  onError (...args) {
13
- return this.handler.onError(...args)
18
+ return this.#handler.onError?.(...args)
14
19
  }
15
20
 
16
21
  onUpgrade (...args) {
17
- return this.handler.onUpgrade(...args)
22
+ return this.#handler.onUpgrade?.(...args)
23
+ }
24
+
25
+ onResponseStarted (...args) {
26
+ return this.#handler.onResponseStarted?.(...args)
18
27
  }
19
28
 
20
29
  onHeaders (...args) {
21
- return this.handler.onHeaders(...args)
30
+ return this.#handler.onHeaders?.(...args)
22
31
  }
23
32
 
24
33
  onData (...args) {
25
- return this.handler.onData(...args)
34
+ return this.#handler.onData?.(...args)
26
35
  }
27
36
 
28
37
  onComplete (...args) {
29
- return this.handler.onComplete(...args)
38
+ return this.#handler.onComplete?.(...args)
30
39
  }
31
40
 
32
41
  onBodySent (...args) {
33
- return this.handler.onBodySent(...args)
42
+ return this.#handler.onBodySent?.(...args)
34
43
  }
35
44
  }
@@ -403,14 +403,14 @@ function mixinBody (prototype) {
403
403
  async function consumeBody (object, convertBytesToJSValue, instance) {
404
404
  webidl.brandCheck(object, instance)
405
405
 
406
- throwIfAborted(object[kState])
407
-
408
406
  // 1. If object is unusable, then return a promise rejected
409
407
  // with a TypeError.
410
408
  if (bodyUnusable(object[kState].body)) {
411
409
  throw new TypeError('Body is unusable')
412
410
  }
413
411
 
412
+ throwIfAborted(object[kState])
413
+
414
414
  // 2. Let promise be a new promise.
415
415
  const promise = createDeferredPromise()
416
416
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { toUSVString, isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
3
+ const { isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
4
4
  const { utf8DecodeBytes } = require('./util')
5
5
  const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
6
6
  const { isFileLike, File: UndiciFile } = require('./file')
@@ -60,43 +60,6 @@ function validateBoundary (boundary) {
60
60
  return true
61
61
  }
62
62
 
63
- /**
64
- * @see https://andreubotella.github.io/multipart-form-data/#escape-a-multipart-form-data-name
65
- * @param {string} name
66
- * @param {string} [encoding='utf-8']
67
- * @param {boolean} [isFilename=false]
68
- */
69
- function escapeFormDataName (name, encoding = 'utf-8', isFilename = false) {
70
- // 1. If isFilename is true:
71
- if (isFilename) {
72
- // 1.1. Set name to the result of converting name into a scalar value string.
73
- name = toUSVString(name)
74
- } else {
75
- // 2. Otherwise:
76
-
77
- // 2.1. Assert: name is a scalar value string.
78
- assert(isUSVString(name))
79
-
80
- // 2.2. Replace every occurrence of U+000D (CR) not followed by U+000A (LF),
81
- // and every occurrence of U+000A (LF) not preceded by U+000D (CR), in
82
- // name, by a string consisting of U+000D (CR) and U+000A (LF).
83
- name = name.replace(/\r\n?|\r?\n/g, '\r\n')
84
- }
85
-
86
- // 3. Let encoded be the result of encoding name with encoding.
87
- assert(Buffer.isEncoding(encoding))
88
-
89
- // 4. Replace every 0x0A (LF) bytes in encoded with the byte sequence `%0A`,
90
- // 0x0D (CR) with `%0D` and 0x22 (") with `%22`.
91
- name = name
92
- .replace(/\n/g, '%0A')
93
- .replace(/\r/g, '%0D')
94
- .replace(/"/g, '%22')
95
-
96
- // 5. Return encoded.
97
- return Buffer.from(name, encoding) // encoded
98
- }
99
-
100
63
  /**
101
64
  * @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-parser
102
65
  * @param {Buffer} input
@@ -497,6 +460,5 @@ function bufferStartsWith (buffer, start, position) {
497
460
 
498
461
  module.exports = {
499
462
  multipartFormDataParser,
500
- validateBoundary,
501
- escapeFormDataName
463
+ validateBoundary
502
464
  }
@@ -58,7 +58,7 @@ const {
58
58
  subresourceSet
59
59
  } = require('./constants')
60
60
  const EE = require('node:events')
61
- const { Readable, pipeline } = require('node:stream')
61
+ const { Readable, pipeline, finished } = require('node:stream')
62
62
  const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor, bufferToLowerCasedHeaderName } = require('../../core/util')
63
63
  const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./data-url')
64
64
  const { getGlobalDispatcher } = require('../../global')
@@ -1080,42 +1080,19 @@ function fetchFinale (fetchParams, response) {
1080
1080
  if (internalResponse.body == null) {
1081
1081
  processResponseEndOfBody()
1082
1082
  } else {
1083
+ // mcollina: all the following steps of the specs are skipped.
1084
+ // The internal transform stream is not needed.
1085
+ // See https://github.com/nodejs/undici/pull/3093#issuecomment-2050198541
1086
+
1083
1087
  // 1. Let transformStream be a new TransformStream.
1084
1088
  // 2. Let identityTransformAlgorithm be an algorithm which, given chunk, enqueues chunk in transformStream.
1085
1089
  // 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm and flushAlgorithm
1086
1090
  // set to processResponseEndOfBody.
1087
- const transformStream = new TransformStream({
1088
- start () { },
1089
- transform (chunk, controller) {
1090
- controller.enqueue(chunk)
1091
- },
1092
- flush: processResponseEndOfBody
1093
- })
1094
-
1095
1091
  // 4. Set internalResponse’s body’s stream to the result of internalResponse’s body’s stream piped through transformStream.
1096
- internalResponse.body.stream.pipeThrough(transformStream)
1097
-
1098
- const byteStream = new ReadableStream({
1099
- readableStream: transformStream.readable,
1100
- async start () {
1101
- this._bodyReader = this.readableStream.getReader()
1102
- },
1103
- async pull (controller) {
1104
- while (controller.desiredSize >= 0) {
1105
- const { done, value } = await this._bodyReader.read()
1106
1092
 
1107
- if (done) {
1108
- queueMicrotask(() => readableStreamClose(controller))
1109
- break
1110
- }
1111
-
1112
- controller.enqueue(value)
1113
- }
1114
- },
1115
- type: 'bytes'
1093
+ finished(internalResponse.body.stream, () => {
1094
+ processResponseEndOfBody()
1116
1095
  })
1117
-
1118
- internalResponse.body.stream = byteStream
1119
1096
  }
1120
1097
  }
1121
1098
 
@@ -73,14 +73,13 @@ function responseLocationURL (response, requestFragment) {
73
73
  * @returns {boolean}
74
74
  */
75
75
  function isValidEncodedURL (url) {
76
- for (const c of url) {
77
- const code = c.charCodeAt(0)
78
- // Not used in US-ASCII
79
- if (code >= 0x80) {
80
- return false
81
- }
82
- // Control characters
83
- if ((code >= 0x00 && code <= 0x1F) || code === 0x7F) {
76
+ for (let i = 0; i < url.length; ++i) {
77
+ const code = url.charCodeAt(i)
78
+
79
+ if (
80
+ code > 0x7E || // Non-US-ASCII + DEL
81
+ code < 0x20 // Control characters NUL - US
82
+ ) {
84
83
  return false
85
84
  }
86
85
  }
@@ -160,24 +159,15 @@ const isValidHeaderName = isValidHTTPToken
160
159
  function isValidHeaderValue (potentialValue) {
161
160
  // - Has no leading or trailing HTTP tab or space bytes.
162
161
  // - Contains no 0x00 (NUL) or HTTP newline bytes.
163
- if (
164
- potentialValue.startsWith('\t') ||
165
- potentialValue.startsWith(' ') ||
166
- potentialValue.endsWith('\t') ||
167
- potentialValue.endsWith(' ')
168
- ) {
169
- return false
170
- }
171
-
172
- if (
173
- potentialValue.includes('\0') ||
162
+ return (
163
+ potentialValue[0] === '\t' ||
164
+ potentialValue[0] === ' ' ||
165
+ potentialValue[potentialValue.length - 1] === '\t' ||
166
+ potentialValue[potentialValue.length - 1] === ' ' ||
167
+ potentialValue.includes('\n') ||
174
168
  potentialValue.includes('\r') ||
175
- potentialValue.includes('\n')
176
- ) {
177
- return false
178
- }
179
-
180
- return true
169
+ potentialValue.includes('\0')
170
+ ) === false
181
171
  }
182
172
 
183
173
  // https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
@@ -1169,13 +1159,21 @@ function urlIsLocal (url) {
1169
1159
 
1170
1160
  /**
1171
1161
  * @param {string|URL} url
1162
+ * @returns {boolean}
1172
1163
  */
1173
1164
  function urlHasHttpsScheme (url) {
1174
- if (typeof url === 'string') {
1175
- return url.startsWith('https:')
1176
- }
1177
-
1178
- return url.protocol === 'https:'
1165
+ return (
1166
+ (
1167
+ typeof url === 'string' &&
1168
+ url[5] === ':' &&
1169
+ url[0] === 'h' &&
1170
+ url[1] === 't' &&
1171
+ url[2] === 't' &&
1172
+ url[3] === 'p' &&
1173
+ url[4] === 's'
1174
+ ) ||
1175
+ url.protocol === 'https:'
1176
+ )
1179
1177
  }
1180
1178
 
1181
1179
  /**
@@ -1567,6 +1565,7 @@ function utf8DecodeBytes (buffer) {
1567
1565
  module.exports = {
1568
1566
  isAborted,
1569
1567
  isCancelled,
1568
+ isValidEncodedURL,
1570
1569
  createDeferredPromise,
1571
1570
  ReadableStreamFrom,
1572
1571
  tryUpgradeRequestToAPotentiallyTrustworthyURL,
@@ -209,24 +209,21 @@ const fatalDecoder = hasIntl ? new TextDecoder('utf-8', { fatal: true }) : undef
209
209
  * Converts a Buffer to utf-8, even on platforms without icu.
210
210
  * @param {Buffer} buffer
211
211
  */
212
- function utf8Decode (buffer) {
213
- if (hasIntl) {
214
- return fatalDecoder.decode(buffer)
215
- } else {
216
- if (!isUtf8?.(buffer)) {
217
- // TODO: remove once node 18 or < node v18.14.0 is dropped
218
- if (!isUtf8) {
212
+ const utf8Decode = hasIntl
213
+ ? fatalDecoder.decode.bind(fatalDecoder)
214
+ : !isUtf8
215
+ ? function () { // TODO: remove once node 18 or < node v18.14.0 is dropped
219
216
  process.emitWarning('ICU is not supported and no fallback exists. Please upgrade to at least Node v18.14.0.', {
220
217
  code: 'UNDICI-WS-NO-ICU'
221
218
  })
219
+ throw new TypeError('Invalid utf-8 received.')
220
+ }
221
+ : function (buffer) {
222
+ if (isUtf8(buffer)) {
223
+ return buffer.toString('utf-8')
224
+ }
225
+ throw new TypeError('Invalid utf-8 received.')
222
226
  }
223
-
224
- throw new TypeError('Invalid utf-8 received.')
225
- }
226
-
227
- return buffer.toString('utf-8')
228
- }
229
- }
230
227
 
231
228
  module.exports = {
232
229
  isConnecting,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.12.0",
3
+ "version": "6.13.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": {