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
|
|
package/lib/core/request.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/lib/web/fetch/util.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
580
|
+
// 5. Let metadata be the result of getting the strongest
|
|
574
581
|
// metadata from parsedMetadata.
|
|
575
|
-
const
|
|
576
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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.
|
|
600
|
-
|
|
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
|
|
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
|
-
//
|
|
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+/]
|
|
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 (
|
|
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
|
|
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
|