2
2
3
3
const { Transform } = require ( 'node:stream' )
4
4
const zlib = require ( 'node:zlib' )
5
- const { redirectStatusSet, referrerPolicySet : referrerPolicyTokens , badPortsSet } = require ( './constants' )
5
+ const { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require ( './constants' )
6
6
const { getGlobalOrigin } = require ( './global' )
7
7
const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require ( './data-url' )
8
8
const { performance } = require ( 'node:perf_hooks' )
@@ -170,29 +170,24 @@ function isValidHeaderValue (potentialValue) {
170
170
) === false
171
171
}
172
172
173
- // https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
174
- function setRequestReferrerPolicyOnRedirect ( request , actualResponse ) {
175
- // Given a request request and a response actualResponse, this algorithm
176
- // updates request’s referrer policy according to the Referrer-Policy
177
- // header (if any) in actualResponse.
178
-
179
- // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
180
- // from a Referrer-Policy header on actualResponse.
181
-
182
- // 8.1 Parse a referrer policy from a Referrer-Policy header
173
+ /**
174
+ * Parse a referrer policy from a Referrer-Policy header
175
+ * @see https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header
176
+ */
177
+ function parseReferrerPolicy ( actualResponse ) {
183
178
// 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
184
- const { headersList } = actualResponse
179
+ const policyHeader = ( actualResponse . headersList . get ( 'referrer-policy' , true ) ?? '' ) . split ( ',' )
180
+
185
181
// 2. Let policy be the empty string.
182
+ let policy = ''
183
+
186
184
// 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
187
- // 4. Return policy.
188
- const policyHeader = ( headersList . get ( 'referrer-policy' , true ) ?? '' ) . split ( ',' )
189
185
190
186
// Note: As the referrer-policy can contain multiple policies
191
187
// separated by comma, we need to loop through all of them
192
188
// and pick the first valid one.
193
189
// Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
194
- let policy = ''
195
- if ( policyHeader . length > 0 ) {
190
+ if ( policyHeader . length ) {
196
191
// The right-most policy takes precedence.
197
192
// The left-most policy is the fallback.
198
193
for ( let i = policyHeader . length ; i !== 0 ; i -- ) {
@@ -204,6 +199,23 @@ function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
204
199
}
205
200
}
206
201
202
+ // 4. Return policy.
203
+ return policy
204
+ }
205
+
206
+ /**
207
+ * Given a request request and a response actualResponse, this algorithm
208
+ * updates request’s referrer policy according to the Referrer-Policy
209
+ * header (if any) in actualResponse.
210
+ * @see https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
211
+ * @param {import('./request').Request } request
212
+ * @param {import('./response').Response } actualResponse
213
+ */
214
+ function setRequestReferrerPolicyOnRedirect ( request , actualResponse ) {
215
+ // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
216
+ // from a Referrer-Policy header on actualResponse.
217
+ const policy = parseReferrerPolicy ( actualResponse )
218
+
207
219
// 2. If policy is not the empty string, then set request’s referrer policy to policy.
208
220
if ( policy !== '' ) {
209
221
request . referrerPolicy = policy
@@ -374,8 +386,16 @@ function clonePolicyContainer (policyContainer) {
374
386
}
375
387
}
376
388
377
- // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
389
+ /**
390
+ * Determine request’s Referrer
391
+ *
392
+ * @see https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
393
+ */
378
394
function determineRequestsReferrer ( request ) {
395
+ // Given a request request, we can determine the correct referrer information
396
+ // to send by examining its referrer policy as detailed in the following
397
+ // steps, which return either no referrer or a URL:
398
+
379
399
// 1. Let policy be request's referrer policy.
380
400
const policy = request . referrerPolicy
381
401
@@ -387,6 +407,8 @@ function determineRequestsReferrer (request) {
387
407
let referrerSource = null
388
408
389
409
// 3. Switch on request’s referrer:
410
+
411
+ // "client"
390
412
if ( request . referrer === 'client' ) {
391
413
// Note: node isn't a browser and doesn't implement document/iframes,
392
414
// so we bypass this step and replace it with our own.
@@ -397,8 +419,9 @@ function determineRequestsReferrer (request) {
397
419
return 'no-referrer'
398
420
}
399
421
400
- // note : we need to clone it as it's mutated
422
+ // Note : we need to clone it as it's mutated
401
423
referrerSource = new URL ( globalOrigin )
424
+ // a URL
402
425
} else if ( webidl . is . URL ( request . referrer ) ) {
403
426
// Let referrerSource be request’s referrer.
404
427
referrerSource = request . referrer
@@ -500,18 +523,26 @@ function determineRequestsReferrer (request) {
500
523
}
501
524
502
525
/**
526
+ * Certain portions of URLs must not be included when sending a URL as the
527
+ * value of a `Referer` header: a URLs fragment, username, and password
528
+ * components must be stripped from the URL before it’s sent out. This
529
+ * algorithm accepts a origin-only flag, which defaults to false. If set to
530
+ * true, the algorithm will additionally remove the URL’s path and query
531
+ * components, leaving only the scheme, host, and port.
532
+ *
503
533
* @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
504
534
* @param {URL } url
505
- * @param {boolean } [originOnly]
535
+ * @param {boolean } [originOnly=false ]
506
536
*/
507
- function stripURLForReferrer ( url , originOnly ) {
537
+ function stripURLForReferrer ( url , originOnly = false ) {
508
538
// 1. Assert: url is a URL.
509
539
assert ( webidl . is . URL ( url ) )
510
540
541
+ // Note: Create a new URL instance to avoid mutating the original URL.
511
542
url = new URL ( url )
512
543
513
544
// 2. If url’s scheme is a local scheme, then return no referrer.
514
- if ( url . protocol === 'file:' || url . protocol === 'about:' || url . protocol === 'blank:' ) {
545
+ if ( urlIsLocal ( url ) ) {
515
546
return 'no-referrer'
516
547
}
517
548
@@ -525,7 +556,7 @@ function stripURLForReferrer (url, originOnly) {
525
556
url . hash = ''
526
557
527
558
// 6. If the origin-only flag is true, then:
528
- if ( originOnly ) {
559
+ if ( originOnly === true ) {
529
560
// 1. Set url’s path to « the empty string ».
530
561
url . pathname = ''
531
562
@@ -537,45 +568,134 @@ function stripURLForReferrer (url, originOnly) {
537
568
return url
538
569
}
539
570
540
- function isURLPotentiallyTrustworthy ( url ) {
541
- if ( ! webidl . is . URL ( url ) ) {
571
+ const potentialleTrustworthyIPv4RegExp = new RegExp ( '^(?:' +
572
+ '(?:127\\.)' +
573
+ '(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){2}' +
574
+ '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[1-9])' +
575
+ ')$' )
576
+
577
+ const potentialleTrustworthyIPv6RegExp = new RegExp ( '^(?:' +
578
+ '(?:(?:0{1,4}):){7}(?:(?:0{0,3}1))|' +
579
+ '(?:(?:0{1,4}):){1,6}(?::(?:0{0,3}1))|' +
580
+ '(?:::(?:0{0,3}1))|' +
581
+ ')$' )
582
+
583
+ /**
584
+ * Check if host matches one of the CIDR notations 127.0.0.0/8 or ::1/128.
585
+ *
586
+ * @param {string } origin
587
+ * @returns {boolean }
588
+ */
589
+ function isOriginIPPotentiallyTrustworthy ( origin ) {
590
+ // IPv6
591
+ if ( origin . includes ( ':' ) ) {
592
+ // Remove brackets from IPv6 addresses
593
+ if ( origin [ 0 ] === '[' && origin [ origin . length - 1 ] === ']' ) {
594
+ origin = origin . slice ( 1 , - 1 )
595
+ }
596
+ return potentialleTrustworthyIPv6RegExp . test ( origin )
597
+ }
598
+
599
+ // IPv4
600
+ return potentialleTrustworthyIPv4RegExp . test ( origin )
601
+ }
602
+
603
+ /**
604
+ * A potentially trustworthy origin is one which a user agent can generally
605
+ * trust as delivering data securely.
606
+ *
607
+ * Return value `true` means `Potentially Trustworthy`.
608
+ * Return value `false` means `Not Trustworthy`.
609
+ *
610
+ * @see https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
611
+ * @param {string } origin
612
+ * @returns {boolean }
613
+ */
614
+ function isOriginPotentiallyTrustworthy ( origin ) {
615
+ // 1. If origin is an opaque origin, return "Not Trustworthy".
616
+ if ( origin == null || origin === 'null' ) {
542
617
return false
543
618
}
544
619
545
- // If child of about, return true
546
- if ( url . href === 'about:blank' || url . href === 'about:srcdoc' ) {
620
+ // 2. Assert: origin is a tuple origin.
621
+ origin = new URL ( origin )
622
+
623
+ // 3. If origin’s scheme is either "https" or "wss",
624
+ // return "Potentially Trustworthy".
625
+ if ( origin . protocol === 'https:' || origin . protocol === 'wss:' ) {
547
626
return true
548
627
}
549
628
550
- // If scheme is data, return true
551
- if ( url . protocol === 'data:' ) return true
629
+ // 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or
630
+ // ::1/128 [RFC4632], return "Potentially Trustworthy".
631
+ if ( isOriginIPPotentiallyTrustworthy ( origin . hostname ) ) {
632
+ return true
633
+ }
552
634
553
- // If file, return true
554
- if ( url . protocol === 'file:' ) return true
635
+ // 5. If the user agent conforms to the name resolution rules in
636
+ // [let-localhost-be-localhost] and one of the following is true:
555
637
556
- return isOriginPotentiallyTrustworthy ( url . origin )
638
+ // origin’s host is "localhost" or "localhost."
639
+ if ( origin . hostname === 'localhost' || origin . hostname === 'localhost.' ) {
640
+ return true
641
+ }
557
642
558
- function isOriginPotentiallyTrustworthy ( origin ) {
559
- // If origin is explicitly null, return false
560
- if ( origin == null || origin === 'null' ) return false
643
+ // origin’s host ends with ".localhost" or ".localhost."
644
+ if ( origin . hostname . endsWith ( '.localhost' ) || origin . hostname . endsWith ( '.localhost.' ) ) {
645
+ return true
646
+ }
561
647
562
- const originAsURL = new URL ( origin )
648
+ // 6. If origin’s scheme is "file", return "Potentially Trustworthy".
649
+ if ( origin . protocol === 'file:' ) {
650
+ return true
651
+ }
563
652
564
- // If secure, return true
565
- if ( originAsURL . protocol === 'https:' || originAsURL . protocol === 'wss:' ) {
566
- return true
567
- }
653
+ // 7. If origin’s scheme component is one which the user agent considers to
654
+ // be authenticated, return "Potentially Trustworthy".
568
655
569
- // If localhost or variants, return true
570
- if ( / ^ 1 2 7 (?: \. [ 0 - 9 ] + ) { 0 , 2 } \. [ 0 - 9 ] + $ | ^ \[ (?: 0 * : ) * ?: ? 0 * 1 \] $ / . test ( originAsURL . hostname ) ||
571
- ( originAsURL . hostname === 'localhost' || originAsURL . hostname . includes ( 'localhost.' ) ) ||
572
- ( originAsURL . hostname . endsWith ( '.localhost' ) ) ) {
573
- return true
574
- }
656
+ // 8. If origin has been configured as a trustworthy origin, return
657
+ // "Potentially Trustworthy".
575
658
576
- // If any other, return false
659
+ // 9. Return "Not Trustworthy".
660
+ return false
661
+ }
662
+
663
+ /**
664
+ * A potentially trustworthy URL is one which either inherits context from its
665
+ * creator (about:blank, about:srcdoc, data) or one whose origin is a
666
+ * potentially trustworthy origin.
667
+ *
668
+ * Return value `true` means `Potentially Trustworthy`.
669
+ * Return value `false` means `Not Trustworthy`.
670
+ *
671
+ * @see https://www.w3.org/TR/secure-contexts/#is-url-trustworthy
672
+ * @param {URL } url
673
+ * @returns {boolean }
674
+ */
675
+ function isURLPotentiallyTrustworthy ( url ) {
676
+ // Given a URL record (url), the following algorithm returns "Potentially
677
+ // Trustworthy" or "Not Trustworthy" as appropriate:
678
+ if ( ! webidl . is . URL ( url ) ) {
577
679
return false
578
680
}
681
+
682
+ // 1. If url is "about:blank" or "about:srcdoc",
683
+ // return "Potentially Trustworthy".
684
+ if ( url . href === 'about:blank' || url . href === 'about:srcdoc' ) {
685
+ return true
686
+ }
687
+
688
+ // 2. If url’s scheme is "data", return "Potentially Trustworthy".
689
+ if ( url . protocol === 'data:' ) return true
690
+
691
+ // Note: The origin of blob: URLs is the origin of the context in which they
692
+ // were created. Therefore, blobs created in a trustworthy origin will
693
+ // themselves be potentially trustworthy.
694
+ if ( url . protocol === 'blob:' ) return true
695
+
696
+ // 3. Return the result of executing § 3.1 Is origin potentially trustworthy?
697
+ // on url’s origin.
698
+ return isOriginPotentiallyTrustworthy ( url . origin )
579
699
}
580
700
581
701
/**
@@ -1161,12 +1281,15 @@ async function readAllBytes (reader, successSteps, failureSteps) {
1161
1281
/**
1162
1282
* @see https://fetch.spec.whatwg.org/#is-local
1163
1283
* @param {URL } url
1284
+ * @returns {boolean }
1164
1285
*/
1165
1286
function urlIsLocal ( url ) {
1166
1287
assert ( 'protocol' in url ) // ensure it's a url object
1167
1288
1168
1289
const protocol = url . protocol
1169
1290
1291
+ // A URL is local if its scheme is a local scheme.
1292
+ // A local scheme is "about", "blob", or "data".
1170
1293
return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
1171
1294
}
1172
1295
@@ -1654,5 +1777,6 @@ module.exports = {
1654
1777
extractMimeType,
1655
1778
getDecodeSplit,
1656
1779
utf8DecodeBytes,
1657
- environmentSettingsObject
1780
+ environmentSettingsObject,
1781
+ isOriginIPPotentiallyTrustworthy
1658
1782
}
0 commit comments