undici 6.10.1 → 6.10.2

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
@@ -17,7 +17,7 @@ npm i undici
17
17
 
18
18
  ## Benchmarks
19
19
 
20
- The benchmark is a simple getting data [example](benchmarks/benchmark.js) using a
20
+ The benchmark is a simple getting data [example](https://github.com/nodejs/undici/blob/main/benchmarks/benchmark.js) using a
21
21
  50 TCP connections with a pipelining depth of 10 running on Node 20.10.0.
22
22
 
23
23
  | _Tests_ | _Samples_ | _Result_ | _Tolerance_ | _Difference with slowest_ |
@@ -35,7 +35,7 @@ The benchmark is a simple getting data [example](benchmarks/benchmark.js) using
35
35
  | undici - stream | 15 | 20317.29 req/sec | ± 2.13 % | + 448.46 % |
36
36
  | undici - dispatch | 10 | 24883.28 req/sec | ± 1.54 % | + 571.72 % |
37
37
 
38
- The benchmark is a simple sending data [example](benchmarks/post-benchmark.js) using a
38
+ The benchmark is a simple sending data [example](https://github.com/nodejs/undici/blob/main/benchmarks/post-benchmark.js) using a
39
39
  50 TCP connections with a pipelining depth of 10 running on Node 20.10.0.
40
40
 
41
41
  | _Tests_ | _Samples_ | _Result_ | _Tolerance_ | _Difference with slowest_ |
@@ -185,7 +185,7 @@ function setupTimeout (onConnectTimeout, timeout) {
185
185
  function onConnectTimeout (socket) {
186
186
  let message = 'Connect Timeout Error'
187
187
  if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
188
- message = +` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')})`
188
+ message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')})`
189
189
  }
190
190
  util.destroy(socket, new ConnectTimeoutError(message))
191
191
  }
@@ -394,6 +394,12 @@ function writeH2 (client, request) {
394
394
  if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
395
395
  stream.pause()
396
396
  }
397
+
398
+ stream.on('data', (chunk) => {
399
+ if (request.onData(chunk) === false) {
400
+ stream.pause()
401
+ }
402
+ })
397
403
  })
398
404
 
399
405
  stream.once('end', () => {
@@ -418,12 +424,6 @@ function writeH2 (client, request) {
418
424
  util.destroy(stream, err)
419
425
  })
420
426
 
421
- stream.on('data', (chunk) => {
422
- if (request.onData(chunk) === false) {
423
- stream.pause()
424
- }
425
- })
426
-
427
427
  stream.once('close', () => {
428
428
  session[kOpenStreams] -= 1
429
429
  // TODO(HTTP/2): unref only if current streams count is 0
@@ -242,14 +242,12 @@ class RetryHandler {
242
242
  }
243
243
 
244
244
  const { start, size, end = size } = range
245
-
246
245
  assert(
247
- start != null && Number.isFinite(start) && this.start !== start,
246
+ start != null && Number.isFinite(start),
248
247
  'content-range mismatch'
249
248
  )
250
- assert(Number.isFinite(start))
251
249
  assert(
252
- end != null && Number.isFinite(end) && this.end !== end,
250
+ end != null && Number.isFinite(end),
253
251
  'invalid content-length'
254
252
  )
255
253
 
@@ -102,7 +102,7 @@ function setCookie (headers, cookie) {
102
102
  const str = stringify(cookie)
103
103
 
104
104
  if (str) {
105
- headers.append('Set-Cookie', stringify(cookie))
105
+ headers.append('Set-Cookie', str)
106
106
  }
107
107
  }
108
108
 
@@ -10,6 +10,7 @@ const { parseMIMEType } = require('../fetch/data-url')
10
10
  const { MessageEvent } = require('../websocket/events')
11
11
  const { isNetworkError } = require('../fetch/response')
12
12
  const { delay } = require('./util')
13
+ const { kEnumerableProperty } = require('../../core/util')
13
14
 
14
15
  let experimentalWarned = false
15
16
 
@@ -459,6 +460,16 @@ const constantsPropertyDescriptors = {
459
460
  Object.defineProperties(EventSource, constantsPropertyDescriptors)
460
461
  Object.defineProperties(EventSource.prototype, constantsPropertyDescriptors)
461
462
 
463
+ Object.defineProperties(EventSource.prototype, {
464
+ close: kEnumerableProperty,
465
+ onerror: kEnumerableProperty,
466
+ onmessage: kEnumerableProperty,
467
+ onopen: kEnumerableProperty,
468
+ readyState: kEnumerableProperty,
469
+ url: kEnumerableProperty,
470
+ withCredentials: kEnumerableProperty
471
+ })
472
+
462
473
  webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([
463
474
  { key: 'withCredentials', converter: webidl.converters.boolean, defaultValue: false }
464
475
  ])
@@ -44,6 +44,12 @@ function responseLocationURL (response, requestFragment) {
44
44
  // 3. If location is a header value, then set location to the result of
45
45
  // parsing location with response’s URL.
46
46
  if (location !== null && isValidHeaderValue(location)) {
47
+ if (!isValidEncodedURL(location)) {
48
+ // Some websites respond location header in UTF-8 form without encoding them as ASCII
49
+ // and major browsers redirect them to correctly UTF-8 encoded addresses.
50
+ // Here, we handle that behavior in the same way.
51
+ location = normalizeBinaryStringToUtf8(location)
52
+ }
47
53
  location = new URL(location, responseURL(response))
48
54
  }
49
55
 
@@ -57,6 +63,36 @@ function responseLocationURL (response, requestFragment) {
57
63
  return location
58
64
  }
59
65
 
66
+ /**
67
+ * @see https://www.rfc-editor.org/rfc/rfc1738#section-2.2
68
+ * @param {string} url
69
+ * @returns {boolean}
70
+ */
71
+ function isValidEncodedURL (url) {
72
+ for (const c of url) {
73
+ const code = c.charCodeAt(0)
74
+ // Not used in US-ASCII
75
+ if (code >= 0x80) {
76
+ return false
77
+ }
78
+ // Control characters
79
+ if ((code >= 0x00 && code <= 0x1F) || code === 0x7F) {
80
+ return false
81
+ }
82
+ }
83
+ return true
84
+ }
85
+
86
+ /**
87
+ * If string contains non-ASCII characters, assumes it's UTF-8 encoded and decodes it.
88
+ * Since UTF-8 is a superset of ASCII, this will work for ASCII strings as well.
89
+ * @param {string} value
90
+ * @returns {string}
91
+ */
92
+ function normalizeBinaryStringToUtf8 (value) {
93
+ return Buffer.from(value, 'binary').toString('utf8')
94
+ }
95
+
60
96
  /** @returns {URL} */
61
97
  function requestCurrentURL (request) {
62
98
  return request.urlList[request.urlList.length - 1]
@@ -12,8 +12,6 @@ const { WebsocketFrameSend } = require('./frame')
12
12
  // Copyright (c) 2013 Arnout Kazemier and contributors
13
13
  // Copyright (c) 2016 Luigi Pinca and contributors
14
14
 
15
- const textDecoder = new TextDecoder('utf-8', { fatal: true })
16
-
17
15
  class ByteParser extends Writable {
18
16
  #buffers = []
19
17
  #byteOffset = 0
@@ -316,7 +314,8 @@ class ByteParser extends Writable {
316
314
  }
317
315
 
318
316
  try {
319
- reason = textDecoder.decode(reason)
317
+ // TODO: optimize this
318
+ reason = new TextDecoder('utf-8', { fatal: true }).decode(reason)
320
319
  } catch {
321
320
  return null
322
321
  }
@@ -68,8 +68,6 @@ function fireEvent (e, target, eventConstructor = Event, eventInitDict = {}) {
68
68
  target.dispatchEvent(event)
69
69
  }
70
70
 
71
- const textDecoder = new TextDecoder('utf-8', { fatal: true })
72
-
73
71
  /**
74
72
  * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
75
73
  * @param {import('./websocket').WebSocket} ws
@@ -89,7 +87,7 @@ function websocketMessageReceived (ws, type, data) {
89
87
  // -> type indicates that the data is Text
90
88
  // a new DOMString containing data
91
89
  try {
92
- dataForEvent = textDecoder.decode(data)
90
+ dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data)
93
91
  } catch {
94
92
  failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.')
95
93
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.10.1",
3
+ "version": "6.10.2",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -19,8 +19,8 @@ declare class Dispatcher extends EventEmitter {
19
19
  connect(options: Dispatcher.ConnectOptions): Promise<Dispatcher.ConnectData>;
20
20
  connect(options: Dispatcher.ConnectOptions, callback: (err: Error | null, data: Dispatcher.ConnectData) => void): void;
21
21
  /** Compose a chain of dispatchers */
22
- compose(dispatchers: Dispatcher['dispatch'][]): Dispatcher.ComposedDispatcher;
23
- compose(...dispatchers: Dispatcher['dispatch'][]): Dispatcher.ComposedDispatcher;
22
+ compose(dispatchers: Dispatcher.DispatcherInterceptor[]): Dispatcher.ComposedDispatcher;
23
+ compose(...dispatchers: Dispatcher.DispatcherInterceptor[]): Dispatcher.ComposedDispatcher;
24
24
  /** Performs an HTTP request. */
25
25
  request(options: Dispatcher.RequestOptions): Promise<Dispatcher.ResponseData>;
26
26
  request(options: Dispatcher.RequestOptions, callback: (err: Error | null, data: Dispatcher.ResponseData) => void): void;