undici 6.11.0 → 6.11.1

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.
@@ -20,6 +20,8 @@ diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => {
20
20
  console.log('method', request.method)
21
21
  console.log('path', request.path)
22
22
  console.log('headers') // array of strings, e.g: ['foo', 'bar']
23
+ request.addHeader('hello', 'world')
24
+ console.log('headers', request.headers) // e.g. ['foo', 'bar', 'hello', 'world']
23
25
  })
24
26
  ```
25
27
 
@@ -91,8 +91,6 @@ class Request {
91
91
 
92
92
  this.abort = null
93
93
 
94
- this.publicInterface = null
95
-
96
94
  if (body == null) {
97
95
  this.body = null
98
96
  } else if (isStream(body)) {
@@ -189,32 +187,10 @@ class Request {
189
187
  this[kHandler] = handler
190
188
 
191
189
  if (channels.create.hasSubscribers) {
192
- channels.create.publish({ request: this.getPublicInterface() })
190
+ channels.create.publish({ request: this })
193
191
  }
194
192
  }
195
193
 
196
- getPublicInterface () {
197
- const self = this
198
- this.publicInterface ??= {
199
- get origin () {
200
- return self.origin
201
- },
202
- get method () {
203
- return self.method
204
- },
205
- get path () {
206
- return self.path
207
- },
208
- get headers () {
209
- return self.headers
210
- },
211
- get completed () {
212
- return self.completed
213
- }
214
- }
215
- return this.publicInterface
216
- }
217
-
218
194
  onBodySent (chunk) {
219
195
  if (this[kHandler].onBodySent) {
220
196
  try {
@@ -227,7 +203,7 @@ class Request {
227
203
 
228
204
  onRequestSent () {
229
205
  if (channels.bodySent.hasSubscribers) {
230
- channels.bodySent.publish({ request: this.getPublicInterface() })
206
+ channels.bodySent.publish({ request: this })
231
207
  }
232
208
 
233
209
  if (this[kHandler].onRequestSent) {
@@ -260,7 +236,7 @@ class Request {
260
236
  assert(!this.completed)
261
237
 
262
238
  if (channels.headers.hasSubscribers) {
263
- channels.headers.publish({ request: this.getPublicInterface(), response: { statusCode, headers, statusText } })
239
+ channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
264
240
  }
265
241
 
266
242
  try {
@@ -296,7 +272,7 @@ class Request {
296
272
 
297
273
  this.completed = true
298
274
  if (channels.trailers.hasSubscribers) {
299
- channels.trailers.publish({ request: this.getPublicInterface(), trailers })
275
+ channels.trailers.publish({ request: this, trailers })
300
276
  }
301
277
 
302
278
  try {
@@ -311,7 +287,7 @@ class Request {
311
287
  this.onFinally()
312
288
 
313
289
  if (channels.error.hasSubscribers) {
314
- channels.error.publish({ request: this.getPublicInterface(), error })
290
+ channels.error.publish({ request: this, error })
315
291
  }
316
292
 
317
293
  if (this.aborted) {
@@ -333,6 +309,11 @@ class Request {
333
309
  this.endHandler = null
334
310
  }
335
311
  }
312
+
313
+ addHeader (key, value) {
314
+ processHeader(this, key, value)
315
+ return this
316
+ }
336
317
  }
337
318
 
338
319
  function processHeader (request, key, val) {
@@ -993,7 +993,7 @@ function writeH1 (client, request) {
993
993
  }
994
994
 
995
995
  if (channels.sendHeaders.hasSubscribers) {
996
- channels.sendHeaders.publish({ request: request.getPublicInterface(), headers: header, socket })
996
+ channels.sendHeaders.publish({ request, headers: header, socket })
997
997
  }
998
998
 
999
999
  /* istanbul ignore else: assertion */
@@ -201,9 +201,9 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) {
201
201
  if (removeContent && util.headerNameToString(header).startsWith('content-')) {
202
202
  return true
203
203
  }
204
- if (unknownOrigin && (header.length === 13 || header.length === 6)) {
204
+ if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
205
205
  const name = util.headerNameToString(header)
206
- return name === 'authorization' || name === 'cookie'
206
+ return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
207
207
  }
208
208
  return false
209
209
  }
@@ -11,11 +11,15 @@ const assert = require('node:assert')
11
11
  const { isUint8Array } = require('node:util/types')
12
12
  const { webidl } = require('./webidl')
13
13
 
14
+ let supportedHashes = []
15
+
14
16
  // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
15
17
  /** @type {import('crypto')} */
16
18
  let crypto
17
19
  try {
18
20
  crypto = require('node:crypto')
21
+ const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
22
+ supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
19
23
  /* c8 ignore next 3 */
20
24
  } catch {
21
25
 
@@ -565,66 +569,56 @@ function bytesMatch (bytes, metadataList) {
565
569
  return true
566
570
  }
567
571
 
568
- // 3. If parsedMetadata is the empty set, return true.
572
+ // 3. If response is not eligible for integrity validation, return false.
573
+ // TODO
574
+
575
+ // 4. If parsedMetadata is the empty set, return true.
569
576
  if (parsedMetadata.length === 0) {
570
577
  return true
571
578
  }
572
579
 
573
- // 4. Let metadata be the result of getting the strongest
580
+ // 5. Let metadata be the result of getting the strongest
574
581
  // metadata from parsedMetadata.
575
- const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
576
- // get the strongest algorithm
577
- const strongest = list[0].algo
578
- // get all entries that use the strongest algorithm; ignore weaker
579
- const metadata = list.filter((item) => item.algo === strongest)
582
+ const strongest = getStrongestMetadata(parsedMetadata)
583
+ const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest)
580
584
 
581
- // 5. For each item in metadata:
585
+ // 6. For each item in metadata:
582
586
  for (const item of metadata) {
583
587
  // 1. Let algorithm be the alg component of item.
584
588
  const algorithm = item.algo
585
589
 
586
590
  // 2. Let expectedValue be the val component of item.
587
- let expectedValue = item.hash
591
+ const expectedValue = item.hash
588
592
 
589
593
  // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
590
594
  // "be liberal with padding". This is annoying, and it's not even in the spec.
591
595
 
592
- if (expectedValue.endsWith('==')) {
593
- expectedValue = expectedValue.slice(0, -2)
594
- }
595
-
596
596
  // 3. Let actualValue be the result of applying algorithm to bytes.
597
597
  let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
598
598
 
599
- if (actualValue.endsWith('==')) {
600
- actualValue = actualValue.slice(0, -2)
599
+ if (actualValue[actualValue.length - 1] === '=') {
600
+ if (actualValue[actualValue.length - 2] === '=') {
601
+ actualValue = actualValue.slice(0, -2)
602
+ } else {
603
+ actualValue = actualValue.slice(0, -1)
604
+ }
601
605
  }
602
606
 
603
607
  // 4. If actualValue is a case-sensitive match for expectedValue,
604
608
  // return true.
605
- if (actualValue === expectedValue) {
606
- return true
607
- }
608
-
609
- let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url')
610
-
611
- if (actualBase64URL.endsWith('==')) {
612
- actualBase64URL = actualBase64URL.slice(0, -2)
613
- }
614
-
615
- if (actualBase64URL === expectedValue) {
609
+ if (compareBase64Mixed(actualValue, expectedValue)) {
616
610
  return true
617
611
  }
618
612
  }
619
613
 
620
- // 6. Return false.
614
+ // 7. Return false.
621
615
  return false
622
616
  }
623
617
 
624
618
  // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
625
619
  // https://www.w3.org/TR/CSP2/#source-list-syntax
626
620
  // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
627
- const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-(?<hash>[A-Za-z0-9+/]+={0,2}(?=\s|$))( +[!-~]*)?/i
621
+ const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i
628
622
 
629
623
  /**
630
624
  * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
@@ -638,8 +632,6 @@ function parseMetadata (metadata) {
638
632
  // 2. Let empty be equal to true.
639
633
  let empty = true
640
634
 
641
- const supportedHashes = crypto.getHashes()
642
-
643
635
  // 3. For each token returned by splitting metadata on spaces:
644
636
  for (const token of metadata.split(' ')) {
645
637
  // 1. Set empty to false.
@@ -649,7 +641,11 @@ function parseMetadata (metadata) {
649
641
  const parsedToken = parseHashWithOptions.exec(token)
650
642
 
651
643
  // 3. If token does not parse, continue to the next token.
652
- if (parsedToken === null || parsedToken.groups === undefined) {
644
+ if (
645
+ parsedToken === null ||
646
+ parsedToken.groups === undefined ||
647
+ parsedToken.groups.algo === undefined
648
+ ) {
653
649
  // Note: Chromium blocks the request at this point, but Firefox
654
650
  // gives a warning that an invalid integrity was given. The
655
651
  // correct behavior is to ignore these, and subsequently not
@@ -658,11 +654,11 @@ function parseMetadata (metadata) {
658
654
  }
659
655
 
660
656
  // 4. Let algorithm be the hash-algo component of token.
661
- const algorithm = parsedToken.groups.algo
657
+ const algorithm = parsedToken.groups.algo.toLowerCase()
662
658
 
663
659
  // 5. If algorithm is a hash function recognized by the user
664
660
  // agent, add the parsed token to result.
665
- if (supportedHashes.includes(algorithm.toLowerCase())) {
661
+ if (supportedHashes.includes(algorithm)) {
666
662
  result.push(parsedToken.groups)
667
663
  }
668
664
  }
@@ -675,6 +671,82 @@ function parseMetadata (metadata) {
675
671
  return result
676
672
  }
677
673
 
674
+ /**
675
+ * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
676
+ */
677
+ function getStrongestMetadata (metadataList) {
678
+ // Let algorithm be the algo component of the first item in metadataList.
679
+ // Can be sha256
680
+ let algorithm = metadataList[0].algo
681
+ // If the algorithm is sha512, then it is the strongest
682
+ // and we can return immediately
683
+ if (algorithm[3] === '5') {
684
+ return algorithm
685
+ }
686
+
687
+ for (let i = 1; i < metadataList.length; ++i) {
688
+ const metadata = metadataList[i]
689
+ // If the algorithm is sha512, then it is the strongest
690
+ // and we can break the loop immediately
691
+ if (metadata.algo[3] === '5') {
692
+ algorithm = 'sha512'
693
+ break
694
+ // If the algorithm is sha384, then a potential sha256 or sha384 is ignored
695
+ } else if (algorithm[3] === '3') {
696
+ continue
697
+ // algorithm is sha256, check if algorithm is sha384 and if so, set it as
698
+ // the strongest
699
+ } else if (metadata.algo[3] === '3') {
700
+ algorithm = 'sha384'
701
+ }
702
+ }
703
+ return algorithm
704
+ }
705
+
706
+ function filterMetadataListByAlgorithm (metadataList, algorithm) {
707
+ if (metadataList.length === 1) {
708
+ return metadataList
709
+ }
710
+
711
+ let pos = 0
712
+ for (let i = 0; i < metadataList.length; ++i) {
713
+ if (metadataList[i].algo === algorithm) {
714
+ metadataList[pos++] = metadataList[i]
715
+ }
716
+ }
717
+
718
+ metadataList.length = pos
719
+
720
+ return metadataList
721
+ }
722
+
723
+ /**
724
+ * Compares two base64 strings, allowing for base64url
725
+ * in the second string.
726
+ *
727
+ * @param {string} actualValue always base64
728
+ * @param {string} expectedValue base64 or base64url
729
+ * @returns {boolean}
730
+ */
731
+ function compareBase64Mixed (actualValue, expectedValue) {
732
+ if (actualValue.length !== expectedValue.length) {
733
+ return false
734
+ }
735
+ for (let i = 0; i < actualValue.length; ++i) {
736
+ if (actualValue[i] !== expectedValue[i]) {
737
+ if (
738
+ (actualValue[i] === '+' && expectedValue[i] === '-') ||
739
+ (actualValue[i] === '/' && expectedValue[i] === '_')
740
+ ) {
741
+ continue
742
+ }
743
+ return false
744
+ }
745
+ }
746
+
747
+ return true
748
+ }
749
+
678
750
  // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
679
751
  function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
680
752
  // TODO
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.11.0",
3
+ "version": "6.11.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {