undici 6.6.1 → 6.7.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 (114) hide show
  1. package/{README.md → docs/README.md} +19 -15
  2. package/docs/{api → docs/api}/Dispatcher.md +39 -3
  3. package/docs/docs/api/Fetch.md +57 -0
  4. package/docs/{api → docs/api}/ProxyAgent.md +3 -1
  5. package/docs/docs/api/RetryAgent.md +45 -0
  6. package/docs/{api → docs/api}/RetryHandler.md +1 -1
  7. package/docs/{api → docs/api}/api-lifecycle.md +33 -4
  8. package/docs/{best-practices → docs/best-practices}/proxy.md +6 -6
  9. package/index-fetch.js +11 -7
  10. package/index.js +31 -25
  11. package/lib/core/request.js +72 -135
  12. package/lib/core/symbols.js +6 -5
  13. package/lib/core/tree.js +46 -26
  14. package/lib/core/util.js +41 -20
  15. package/lib/{agent.js → dispatcher/agent.js} +4 -4
  16. package/lib/{balanced-pool.js → dispatcher/balanced-pool.js} +3 -3
  17. package/lib/dispatcher/client-h1.js +1339 -0
  18. package/lib/dispatcher/client-h2.js +639 -0
  19. package/lib/dispatcher/client.js +611 -0
  20. package/lib/{dispatcher-base.js → dispatcher/dispatcher-base.js} +2 -2
  21. package/lib/{pool-base.js → dispatcher/pool-base.js} +3 -3
  22. package/lib/{pool-stats.js → dispatcher/pool-stats.js} +1 -1
  23. package/lib/{pool.js → dispatcher/pool.js} +4 -4
  24. package/lib/{proxy-agent.js → dispatcher/proxy-agent.js} +29 -35
  25. package/lib/dispatcher/retry-agent.js +35 -0
  26. package/lib/global.js +1 -1
  27. package/lib/handler/{RetryHandler.js → retry-handler.js} +2 -2
  28. package/lib/interceptor/{redirectInterceptor.js → redirect-interceptor.js} +1 -1
  29. package/lib/mock/mock-agent.js +2 -2
  30. package/lib/mock/mock-client.js +1 -1
  31. package/lib/mock/mock-interceptor.js +2 -2
  32. package/lib/mock/mock-pool.js +1 -1
  33. package/lib/mock/mock-utils.js +6 -4
  34. package/lib/{cache → web/cache}/cache.js +2 -4
  35. package/lib/{cache → web/cache}/cachestorage.js +1 -1
  36. package/lib/web/cache/symbols.js +5 -0
  37. package/lib/{cache → web/cache}/util.js +5 -9
  38. package/lib/{cookies → web/cookies}/parse.js +1 -1
  39. package/lib/{cookies → web/cookies}/util.js +76 -60
  40. package/lib/{eventsource → web/eventsource}/eventsource.js +2 -6
  41. package/lib/{fetch → web/fetch}/body.js +23 -52
  42. package/lib/{fetch/dataURL.js → web/fetch/data-url.js} +2 -0
  43. package/lib/{compat → web/fetch}/dispatcher-weakref.js +1 -1
  44. package/lib/{fetch → web/fetch}/file.js +2 -2
  45. package/lib/{fetch → web/fetch}/formdata.js +6 -67
  46. package/lib/{fetch → web/fetch}/headers.js +99 -71
  47. package/lib/{fetch → web/fetch}/index.js +40 -31
  48. package/lib/{fetch → web/fetch}/request.js +14 -6
  49. package/lib/{fetch → web/fetch}/response.js +3 -3
  50. package/lib/{fetch → web/fetch}/symbols.js +2 -1
  51. package/lib/{fetch → web/fetch}/util.js +142 -48
  52. package/lib/{fetch → web/fetch}/webidl.js +53 -19
  53. package/lib/{fileapi → web/fileapi}/filereader.js +1 -1
  54. package/lib/{fileapi → web/fileapi}/util.js +1 -1
  55. package/lib/{websocket → web/websocket}/connection.js +20 -10
  56. package/lib/{websocket → web/websocket}/constants.js +7 -0
  57. package/lib/{websocket → web/websocket}/events.js +1 -1
  58. package/lib/{websocket → web/websocket}/frame.js +1 -0
  59. package/lib/{websocket → web/websocket}/receiver.js +9 -16
  60. package/lib/{websocket → web/websocket}/util.js +37 -23
  61. package/lib/{websocket → web/websocket}/websocket.js +21 -9
  62. package/package.json +27 -52
  63. package/types/dispatcher.d.ts +1 -1
  64. package/types/fetch.d.ts +20 -21
  65. package/types/index.d.ts +2 -1
  66. package/types/retry-agent.d.ts +11 -0
  67. package/types/webidl.d.ts +6 -1
  68. package/docs/api/Fetch.md +0 -27
  69. package/docs/assets/lifecycle-diagram.png +0 -0
  70. package/lib/cache/symbols.js +0 -5
  71. package/lib/client.js +0 -2295
  72. package/lib/llhttp/llhttp-wasm.js +0 -3
  73. package/lib/llhttp/llhttp.wasm +0 -0
  74. package/lib/llhttp/llhttp_simd.wasm +0 -0
  75. /package/docs/{api → docs/api}/Agent.md +0 -0
  76. /package/docs/{api → docs/api}/BalancedPool.md +0 -0
  77. /package/docs/{api → docs/api}/CacheStorage.md +0 -0
  78. /package/docs/{api → docs/api}/Client.md +0 -0
  79. /package/docs/{api → docs/api}/Connector.md +0 -0
  80. /package/docs/{api → docs/api}/ContentType.md +0 -0
  81. /package/docs/{api → docs/api}/Cookies.md +0 -0
  82. /package/docs/{api → docs/api}/Debug.md +0 -0
  83. /package/docs/{api → docs/api}/DiagnosticsChannel.md +0 -0
  84. /package/docs/{api → docs/api}/DispatchInterceptor.md +0 -0
  85. /package/docs/{api → docs/api}/Errors.md +0 -0
  86. /package/docs/{api → docs/api}/EventSource.md +0 -0
  87. /package/docs/{api → docs/api}/MockAgent.md +0 -0
  88. /package/docs/{api → docs/api}/MockClient.md +0 -0
  89. /package/docs/{api → docs/api}/MockErrors.md +0 -0
  90. /package/docs/{api → docs/api}/MockPool.md +0 -0
  91. /package/docs/{api → docs/api}/Pool.md +0 -0
  92. /package/docs/{api → docs/api}/PoolStats.md +0 -0
  93. /package/docs/{api → docs/api}/RedirectHandler.md +0 -0
  94. /package/docs/{api → docs/api}/Util.md +0 -0
  95. /package/docs/{api → docs/api}/WebSocket.md +0 -0
  96. /package/docs/{best-practices → docs/best-practices}/client-certificate.md +0 -0
  97. /package/docs/{best-practices → docs/best-practices}/mocking-request.md +0 -0
  98. /package/docs/{best-practices → docs/best-practices}/writing-tests.md +0 -0
  99. /package/lib/{dispatcher.js → dispatcher/dispatcher.js} +0 -0
  100. /package/lib/{node → dispatcher}/fixed-queue.js +0 -0
  101. /package/lib/handler/{DecoratorHandler.js → decorator-handler.js} +0 -0
  102. /package/lib/handler/{RedirectHandler.js → redirect-handler.js} +0 -0
  103. /package/lib/{timers.js → util/timers.js} +0 -0
  104. /package/lib/{cookies → web/cookies}/constants.js +0 -0
  105. /package/lib/{cookies → web/cookies}/index.js +0 -0
  106. /package/lib/{eventsource → web/eventsource}/eventsource-stream.js +0 -0
  107. /package/lib/{eventsource → web/eventsource}/util.js +0 -0
  108. /package/lib/{fetch → web/fetch}/LICENSE +0 -0
  109. /package/lib/{fetch → web/fetch}/constants.js +0 -0
  110. /package/lib/{fetch → web/fetch}/global.js +0 -0
  111. /package/lib/{fileapi → web/fileapi}/encoding.js +0 -0
  112. /package/lib/{fileapi → web/fileapi}/progressevent.js +0 -0
  113. /package/lib/{fileapi → web/fileapi}/symbols.js +0 -0
  114. /package/lib/{websocket → web/websocket}/symbols.js +0 -0
@@ -5,29 +5,27 @@ const {
5
5
  NotSupportedError
6
6
  } = require('./errors')
7
7
  const assert = require('node:assert')
8
- const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = require('./symbols')
9
- const util = require('./util')
8
+ const {
9
+ isValidHTTPToken,
10
+ isValidHeaderChar,
11
+ isStream,
12
+ destroy,
13
+ isBuffer,
14
+ isFormDataLike,
15
+ isIterable,
16
+ isBlobLike,
17
+ buildURL,
18
+ validateHandler,
19
+ getServerName
20
+ } = require('./util')
10
21
  const { channels } = require('./diagnostics.js')
11
22
  const { headerNameLowerCasedRecord } = require('./constants')
12
23
 
13
- // headerCharRegex have been lifted from
14
- // https://github.com/nodejs/node/blob/main/lib/_http_common.js
15
-
16
- /**
17
- * Matches if val contains an invalid field-vchar
18
- * field-value = *( field-content / obs-fold )
19
- * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
20
- * field-vchar = VCHAR / obs-text
21
- */
22
- const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
23
-
24
24
  // Verifies that a given path is valid does not contain control chars \x00 to \x20
25
25
  const invalidPathRegex = /[^\u0021-\u00ff]/
26
26
 
27
27
  const kHandler = Symbol('handler')
28
28
 
29
- let extractBody
30
-
31
29
  class Request {
32
30
  constructor (origin, {
33
31
  path,
@@ -58,7 +56,7 @@ class Request {
58
56
 
59
57
  if (typeof method !== 'string') {
60
58
  throw new InvalidArgumentError('method must be a string')
61
- } else if (!util.isValidHTTPToken(method)) {
59
+ } else if (!isValidHTTPToken(method)) {
62
60
  throw new InvalidArgumentError('invalid request method')
63
61
  }
64
62
 
@@ -94,13 +92,13 @@ class Request {
94
92
 
95
93
  if (body == null) {
96
94
  this.body = null
97
- } else if (util.isStream(body)) {
95
+ } else if (isStream(body)) {
98
96
  this.body = body
99
97
 
100
98
  const rState = this.body._readableState
101
99
  if (!rState || !rState.autoDestroy) {
102
100
  this.endHandler = function autoDestroy () {
103
- util.destroy(this)
101
+ destroy(this)
104
102
  }
105
103
  this.body.on('end', this.endHandler)
106
104
  }
@@ -113,7 +111,7 @@ class Request {
113
111
  }
114
112
  }
115
113
  this.body.on('error', this.errorHandler)
116
- } else if (util.isBuffer(body)) {
114
+ } else if (isBuffer(body)) {
117
115
  this.body = body.byteLength ? body : null
118
116
  } else if (ArrayBuffer.isView(body)) {
119
117
  this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
@@ -121,7 +119,7 @@ class Request {
121
119
  this.body = body.byteLength ? Buffer.from(body) : null
122
120
  } else if (typeof body === 'string') {
123
121
  this.body = body.length ? Buffer.from(body) : null
124
- } else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
122
+ } else if (isFormDataLike(body) || isIterable(body) || isBlobLike(body)) {
125
123
  this.body = body
126
124
  } else {
127
125
  throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable')
@@ -133,7 +131,7 @@ class Request {
133
131
 
134
132
  this.upgrade = upgrade || null
135
133
 
136
- this.path = query ? util.buildURL(path, query) : path
134
+ this.path = query ? buildURL(path, query) : path
137
135
 
138
136
  this.origin = origin
139
137
 
@@ -151,7 +149,7 @@ class Request {
151
149
 
152
150
  this.contentType = null
153
151
 
154
- this.headers = ''
152
+ this.headers = []
155
153
 
156
154
  // Only for H2
157
155
  this.expectContinue = expectContinue != null ? expectContinue : false
@@ -164,35 +162,26 @@ class Request {
164
162
  processHeader(this, headers[i], headers[i + 1])
165
163
  }
166
164
  } else if (headers && typeof headers === 'object') {
167
- const keys = Object.keys(headers)
168
- for (let i = 0; i < keys.length; i++) {
169
- const key = keys[i]
170
- processHeader(this, key, headers[key])
165
+ if (headers[Symbol.iterator]) {
166
+ for (const header of headers) {
167
+ if (!Array.isArray(header) || header.length !== 2) {
168
+ throw new InvalidArgumentError('headers must be in key-value pair format')
169
+ }
170
+ processHeader(this, header[0], header[1])
171
+ }
172
+ } else {
173
+ const keys = Object.keys(headers)
174
+ for (let i = 0; i < keys.length; ++i) {
175
+ processHeader(this, keys[i], headers[keys[i]])
176
+ }
171
177
  }
172
178
  } else if (headers != null) {
173
179
  throw new InvalidArgumentError('headers must be an object or an array')
174
180
  }
175
181
 
176
- if (util.isFormDataLike(this.body)) {
177
- if (!extractBody) {
178
- extractBody = require('../fetch/body.js').extractBody
179
- }
180
-
181
- const [bodyStream, contentType] = extractBody(body)
182
- if (this.contentType == null) {
183
- this.contentType = contentType
184
- this.headers += `content-type: ${contentType}\r\n`
185
- }
186
- this.body = bodyStream.stream
187
- this.contentLength = bodyStream.length
188
- } else if (util.isBlobLike(body) && this.contentType == null && body.type) {
189
- this.contentType = body.type
190
- this.headers += `content-type: ${body.type}\r\n`
191
- }
192
-
193
- util.validateHandler(handler, method, upgrade)
182
+ validateHandler(handler, method, upgrade)
194
183
 
195
- this.servername = util.getServerName(this.host)
184
+ this.servername = getServerName(this.host)
196
185
 
197
186
  this[kHandler] = handler
198
187
 
@@ -320,81 +309,13 @@ class Request {
320
309
  }
321
310
  }
322
311
 
323
- // TODO: adjust to support H2
324
312
  addHeader (key, value) {
325
313
  processHeader(this, key, value)
326
314
  return this
327
315
  }
328
-
329
- static [kHTTP1BuildRequest] (origin, opts, handler) {
330
- // TODO: Migrate header parsing here, to make Requests
331
- // HTTP agnostic
332
- return new Request(origin, opts, handler)
333
- }
334
-
335
- static [kHTTP2BuildRequest] (origin, opts, handler) {
336
- const headers = opts.headers
337
- opts = { ...opts, headers: null }
338
-
339
- const request = new Request(origin, opts, handler)
340
-
341
- request.headers = {}
342
-
343
- if (Array.isArray(headers)) {
344
- if (headers.length % 2 !== 0) {
345
- throw new InvalidArgumentError('headers array must be even')
346
- }
347
- for (let i = 0; i < headers.length; i += 2) {
348
- processHeader(request, headers[i], headers[i + 1], true)
349
- }
350
- } else if (headers && typeof headers === 'object') {
351
- const keys = Object.keys(headers)
352
- for (let i = 0; i < keys.length; i++) {
353
- const key = keys[i]
354
- processHeader(request, key, headers[key], true)
355
- }
356
- } else if (headers != null) {
357
- throw new InvalidArgumentError('headers must be an object or an array')
358
- }
359
-
360
- return request
361
- }
362
-
363
- static [kHTTP2CopyHeaders] (raw) {
364
- const rawHeaders = raw.split('\r\n')
365
- const headers = {}
366
-
367
- for (const header of rawHeaders) {
368
- const [key, value] = header.split(': ')
369
-
370
- if (value == null || value.length === 0) continue
371
-
372
- if (headers[key]) {
373
- headers[key] += `,${value}`
374
- } else {
375
- headers[key] = value
376
- }
377
- }
378
-
379
- return headers
380
- }
381
316
  }
382
317
 
383
- function processHeaderValue (key, val, skipAppend) {
384
- if (val && typeof val === 'object') {
385
- throw new InvalidArgumentError(`invalid ${key} header`)
386
- }
387
-
388
- val = val != null ? `${val}` : ''
389
-
390
- if (headerCharRegex.exec(val) !== null) {
391
- throw new InvalidArgumentError(`invalid ${key} header`)
392
- }
393
-
394
- return skipAppend ? val : `${key}: ${val}\r\n`
395
- }
396
-
397
- function processHeader (request, key, val, skipAppend = false) {
318
+ function processHeader (request, key, val) {
398
319
  if (val && (typeof val === 'object' && !Array.isArray(val))) {
399
320
  throw new InvalidArgumentError(`invalid ${key} header`)
400
321
  } else if (val === undefined) {
@@ -405,15 +326,44 @@ function processHeader (request, key, val, skipAppend = false) {
405
326
 
406
327
  if (headerName === undefined) {
407
328
  headerName = key.toLowerCase()
408
- if (headerNameLowerCasedRecord[headerName] === undefined && !util.isValidHTTPToken(headerName)) {
329
+ if (headerNameLowerCasedRecord[headerName] === undefined && !isValidHTTPToken(headerName)) {
409
330
  throw new InvalidArgumentError('invalid header key')
410
331
  }
411
332
  }
412
333
 
413
- if (request.host === null && headerName === 'host') {
414
- if (headerCharRegex.exec(val) !== null) {
334
+ if (Array.isArray(val)) {
335
+ const arr = []
336
+ for (let i = 0; i < val.length; i++) {
337
+ if (typeof val[i] === 'string') {
338
+ if (!isValidHeaderChar(val[i])) {
339
+ throw new InvalidArgumentError(`invalid ${key} header`)
340
+ }
341
+ arr.push(val[i])
342
+ } else if (val[i] === null) {
343
+ arr.push('')
344
+ } else if (typeof val[i] === 'object') {
345
+ throw new InvalidArgumentError(`invalid ${key} header`)
346
+ } else {
347
+ arr.push(`${val[i]}`)
348
+ }
349
+ }
350
+ val = arr
351
+ } else if (typeof val === 'string') {
352
+ if (!isValidHeaderChar(val)) {
415
353
  throw new InvalidArgumentError(`invalid ${key} header`)
416
354
  }
355
+ } else if (val === null) {
356
+ val = ''
357
+ } else if (typeof val === 'object') {
358
+ throw new InvalidArgumentError(`invalid ${key} header`)
359
+ } else {
360
+ val = `${val}`
361
+ }
362
+
363
+ if (request.host === null && headerName === 'host') {
364
+ if (typeof val !== 'string') {
365
+ throw new InvalidArgumentError('invalid host header')
366
+ }
417
367
  // Consumed by Client
418
368
  request.host = val
419
369
  } else if (request.contentLength === null && headerName === 'content-length') {
@@ -423,35 +373,22 @@ function processHeader (request, key, val, skipAppend = false) {
423
373
  }
424
374
  } else if (request.contentType === null && headerName === 'content-type') {
425
375
  request.contentType = val
426
- if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
427
- else request.headers += processHeaderValue(key, val)
376
+ request.headers.push(key, val)
428
377
  } else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
429
378
  throw new InvalidArgumentError(`invalid ${headerName} header`)
430
379
  } else if (headerName === 'connection') {
431
380
  const value = typeof val === 'string' ? val.toLowerCase() : null
432
381
  if (value !== 'close' && value !== 'keep-alive') {
433
382
  throw new InvalidArgumentError('invalid connection header')
434
- } else if (value === 'close') {
383
+ }
384
+
385
+ if (value === 'close') {
435
386
  request.reset = true
436
387
  }
437
388
  } else if (headerName === 'expect') {
438
389
  throw new NotSupportedError('expect header not supported')
439
- } else if (Array.isArray(val)) {
440
- for (let i = 0; i < val.length; i++) {
441
- if (skipAppend) {
442
- if (request.headers[key]) {
443
- request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}`
444
- } else {
445
- request.headers[key] = processHeaderValue(key, val[i], skipAppend)
446
- }
447
- } else {
448
- request.headers += processHeaderValue(key, val[i])
449
- }
450
- }
451
- } else if (skipAppend) {
452
- request.headers[key] = processHeaderValue(key, val, skipAppend)
453
390
  } else {
454
- request.headers += processHeaderValue(key, val)
391
+ request.headers.push(key, val)
455
392
  }
456
393
  }
457
394
 
@@ -33,6 +33,8 @@ module.exports = {
33
33
  kNeedDrain: Symbol('need drain'),
34
34
  kReset: Symbol('reset'),
35
35
  kDestroyed: Symbol.for('nodejs.stream.destroyed'),
36
+ kResume: Symbol('resume'),
37
+ kOnError: Symbol('on error'),
36
38
  kMaxHeadersSize: Symbol('max headers size'),
37
39
  kRunningIdx: Symbol('running index'),
38
40
  kPendingIdx: Symbol('pending index'),
@@ -54,10 +56,9 @@ module.exports = {
54
56
  kMaxResponseSize: Symbol('max response size'),
55
57
  kHTTP2Session: Symbol('http2Session'),
56
58
  kHTTP2SessionState: Symbol('http2Session state'),
57
- kHTTP2BuildRequest: Symbol('http2 build request'),
58
- kHTTP1BuildRequest: Symbol('http1 build request'),
59
- kHTTP2CopyHeaders: Symbol('http2 copy headers'),
60
- kHTTPConnVersion: Symbol('http connection version'),
61
59
  kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
62
- kConstruct: Symbol('constructable')
60
+ kConstruct: Symbol('constructable'),
61
+ kListeners: Symbol('listeners'),
62
+ kHTTPContext: Symbol('http context'),
63
+ kMaxConcurrentStreams: Symbol('max concurrent streams')
63
64
  }
package/lib/core/tree.js CHANGED
@@ -17,7 +17,7 @@ class TstNode {
17
17
  /** @type {number} */
18
18
  code
19
19
  /**
20
- * @param {Uint8Array} key
20
+ * @param {string} key
21
21
  * @param {any} value
22
22
  * @param {number} index
23
23
  */
@@ -25,7 +25,11 @@ class TstNode {
25
25
  if (index === undefined || index >= key.length) {
26
26
  throw new TypeError('Unreachable')
27
27
  }
28
- this.code = key[index]
28
+ const code = this.code = key.charCodeAt(index)
29
+ // check code is ascii string
30
+ if (code > 0x7F) {
31
+ throw new TypeError('key must be ascii string')
32
+ }
29
33
  if (key.length !== ++index) {
30
34
  this.middle = new TstNode(key, value, index)
31
35
  } else {
@@ -34,33 +38,45 @@ class TstNode {
34
38
  }
35
39
 
36
40
  /**
37
- * @param {Uint8Array} key
41
+ * @param {string} key
38
42
  * @param {any} value
39
- * @param {number} index
40
43
  */
41
- add (key, value, index) {
42
- if (index === undefined || index >= key.length) {
44
+ add (key, value) {
45
+ const length = key.length
46
+ if (length === 0) {
43
47
  throw new TypeError('Unreachable')
44
48
  }
45
- const code = key[index]
46
- if (this.code === code) {
47
- if (key.length === ++index) {
48
- this.value = value
49
- } else if (this.middle !== null) {
50
- this.middle.add(key, value, index)
51
- } else {
52
- this.middle = new TstNode(key, value, index)
49
+ let index = 0
50
+ let node = this
51
+ while (true) {
52
+ const code = key.charCodeAt(index)
53
+ // check code is ascii string
54
+ if (code > 0x7F) {
55
+ throw new TypeError('key must be ascii string')
53
56
  }
54
- } else if (this.code < code) {
55
- if (this.left !== null) {
56
- this.left.add(key, value, index)
57
+ if (node.code === code) {
58
+ if (length === ++index) {
59
+ node.value = value
60
+ break
61
+ } else if (node.middle !== null) {
62
+ node = node.middle
63
+ } else {
64
+ node.middle = new TstNode(key, value, index)
65
+ break
66
+ }
67
+ } else if (node.code < code) {
68
+ if (node.left !== null) {
69
+ node = node.left
70
+ } else {
71
+ node.left = new TstNode(key, value, index)
72
+ break
73
+ }
74
+ } else if (node.right !== null) {
75
+ node = node.right
57
76
  } else {
58
- this.left = new TstNode(key, value, index)
77
+ node.right = new TstNode(key, value, index)
78
+ break
59
79
  }
60
- } else if (this.right !== null) {
61
- this.right.add(key, value, index)
62
- } else {
63
- this.right = new TstNode(key, value, index)
64
80
  }
65
81
  }
66
82
 
@@ -75,7 +91,10 @@ class TstNode {
75
91
  while (node !== null && index < keylength) {
76
92
  let code = key[index]
77
93
  // A-Z
78
- if (code >= 0x41 && code <= 0x5a) {
94
+ // First check if it is bigger than 0x5a.
95
+ // Lowercase letters have higher char codes than uppercase ones.
96
+ // Also we assume that headers will mostly contain lowercase characters.
97
+ if (code <= 0x5a && code >= 0x41) {
79
98
  // Lowercase for uppercase.
80
99
  code |= 32
81
100
  }
@@ -100,19 +119,20 @@ class TernarySearchTree {
100
119
  node = null
101
120
 
102
121
  /**
103
- * @param {Uint8Array} key
122
+ * @param {string} key
104
123
  * @param {any} value
105
124
  * */
106
125
  insert (key, value) {
107
126
  if (this.node === null) {
108
127
  this.node = new TstNode(key, value, 0)
109
128
  } else {
110
- this.node.add(key, value, 0)
129
+ this.node.add(key, value)
111
130
  }
112
131
  }
113
132
 
114
133
  /**
115
134
  * @param {Uint8Array} key
135
+ * @return {any}
116
136
  */
117
137
  lookup (key) {
118
138
  return this.node?.search(key)?.value ?? null
@@ -123,7 +143,7 @@ const tree = new TernarySearchTree()
123
143
 
124
144
  for (let i = 0; i < wellknownHeaderNames.length; ++i) {
125
145
  const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]]
126
- tree.insert(Buffer.from(key), key)
146
+ tree.insert(key, key)
127
147
  }
128
148
 
129
149
  module.exports = {
package/lib/core/util.js CHANGED
@@ -182,8 +182,8 @@ function bodyLength (body) {
182
182
  return null
183
183
  }
184
184
 
185
- function isDestroyed (stream) {
186
- return !stream || !!(stream.destroyed || stream[kDestroyed])
185
+ function isDestroyed (body) {
186
+ return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
187
187
  }
188
188
 
189
189
  function isReadableAborted (stream) {
@@ -204,9 +204,9 @@ function destroy (stream, err) {
204
204
 
205
205
  stream.destroy(err)
206
206
  } else if (err) {
207
- process.nextTick((stream, err) => {
207
+ queueMicrotask(() => {
208
208
  stream.emit('error', err)
209
- }, stream, err)
209
+ })
210
210
  }
211
211
 
212
212
  if (stream.destroyed !== true) {
@@ -279,22 +279,30 @@ function parseHeaders (headers, obj) {
279
279
  }
280
280
 
281
281
  function parseRawHeaders (headers) {
282
- const ret = []
282
+ const len = headers.length
283
+ const ret = new Array(len)
284
+
283
285
  let hasContentLength = false
284
286
  let contentDispositionIdx = -1
287
+ let key
288
+ let val
289
+ let kLen = 0
285
290
 
286
291
  for (let n = 0; n < headers.length; n += 2) {
287
- const key = headers[n + 0].toString()
288
- const val = headers[n + 1].toString('utf8')
292
+ key = headers[n]
293
+ val = headers[n + 1]
294
+
295
+ typeof key !== 'string' && (key = key.toString())
296
+ typeof val !== 'string' && (val = val.toString('utf8'))
289
297
 
290
- if (key.length === 14 && (key === 'content-length' || key.toLowerCase() === 'content-length')) {
291
- ret.push(key, val)
298
+ kLen = key.length
299
+ if (kLen === 14 && key[7] === '-' && (key === 'content-length' || key.toLowerCase() === 'content-length')) {
292
300
  hasContentLength = true
293
- } else if (key.length === 19 && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) {
294
- contentDispositionIdx = ret.push(key, val) - 1
295
- } else {
296
- ret.push(key, val)
301
+ } else if (kLen === 19 && key[7] === '-' && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) {
302
+ contentDispositionIdx = n + 1
297
303
  }
304
+ ret[n] = key
305
+ ret[n + 1] = val
298
306
  }
299
307
 
300
308
  // See https://github.com/nodejs/node/pull/46528
@@ -438,13 +446,7 @@ const hasToWellFormed = !!String.prototype.toWellFormed
438
446
  * @param {string} val
439
447
  */
440
448
  function toUSVString (val) {
441
- if (hasToWellFormed) {
442
- return `${val}`.toWellFormed()
443
- } else if (nodeUtil.toUSVString) {
444
- return nodeUtil.toUSVString(val)
445
- }
446
-
447
- return `${val}`
449
+ return hasToWellFormed ? `${val}`.toWellFormed() : nodeUtil.toUSVString(val)
448
450
  }
449
451
 
450
452
  /**
@@ -493,6 +495,24 @@ function isValidHTTPToken (characters) {
493
495
  return true
494
496
  }
495
497
 
498
+ // headerCharRegex have been lifted from
499
+ // https://github.com/nodejs/node/blob/main/lib/_http_common.js
500
+
501
+ /**
502
+ * Matches if val contains an invalid field-vchar
503
+ * field-value = *( field-content / obs-fold )
504
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
505
+ * field-vchar = VCHAR / obs-text
506
+ */
507
+ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
508
+
509
+ /**
510
+ * @param {string} characters
511
+ */
512
+ function isValidHeaderChar (characters) {
513
+ return !headerCharRegex.test(characters)
514
+ }
515
+
496
516
  // Parsed accordingly to RFC 9110
497
517
  // https://www.rfc-editor.org/rfc/rfc9110#field.content-range
498
518
  function parseRangeHeader (range) {
@@ -543,6 +563,7 @@ module.exports = {
543
563
  buildURL,
544
564
  addAbortListener,
545
565
  isValidHTTPToken,
566
+ isValidHeaderChar,
546
567
  isTokenCharCode,
547
568
  parseRangeHeader,
548
569
  nodeMajor,
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const { InvalidArgumentError } = require('./core/errors')
4
- const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('./core/symbols')
3
+ const { InvalidArgumentError } = require('../core/errors')
4
+ const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('../core/symbols')
5
5
  const DispatcherBase = require('./dispatcher-base')
6
6
  const Pool = require('./pool')
7
7
  const Client = require('./client')
8
- const util = require('./core/util')
9
- const createRedirectInterceptor = require('./interceptor/redirectInterceptor')
8
+ const util = require('../core/util')
9
+ const createRedirectInterceptor = require('../interceptor/redirect-interceptor')
10
10
 
11
11
  const kOnConnect = Symbol('onConnect')
12
12
  const kOnDisconnect = Symbol('onDisconnect')
@@ -3,7 +3,7 @@
3
3
  const {
4
4
  BalancedPoolMissingUpstreamError,
5
5
  InvalidArgumentError
6
- } = require('./core/errors')
6
+ } = require('../core/errors')
7
7
  const {
8
8
  PoolBase,
9
9
  kClients,
@@ -13,8 +13,8 @@ const {
13
13
  kGetDispatcher
14
14
  } = require('./pool-base')
15
15
  const Pool = require('./pool')
16
- const { kUrl, kInterceptors } = require('./core/symbols')
17
- const { parseOrigin } = require('./core/util')
16
+ const { kUrl, kInterceptors } = require('../core/symbols')
17
+ const { parseOrigin } = require('../core/util')
18
18
  const kFactory = Symbol('factory')
19
19
 
20
20
  const kOptions = Symbol('options')