1
+ const { URL } = require ( 'node:url' ) ;
2
+ const net = require ( 'node:net' ) ;
3
+
4
+ const isLoopbackV4 = ( address ) => {
5
+ // 127.0.0.0/8: first octet = 127
6
+ const octets = address . split ( '.' ) ;
7
+ return (
8
+ octets . length === 4
9
+ ) && parseInt ( octets [ 0 ] , 10 ) === 127 ;
10
+ }
11
+
12
+ const isLoopbackV6 = ( address ) => {
13
+ // new URL(...) follows the WHATWG URL Standard
14
+ // which compresses IPv6 addresses, therefore the IPv6
15
+ // loopback address will always be compressed to '[::1]':
16
+ // https://url.spec.whatwg.org/#concept-ipv6-serializer
17
+ return ( address === '::1' ) ;
18
+ }
19
+
20
+ const isIpLoopback = ( address ) => {
21
+ if ( net . isIPv4 ( address ) ) {
22
+ return isLoopbackV4 ( address ) ;
23
+ }
24
+
25
+ if ( net . isIPv6 ( address ) ) {
26
+ return isLoopbackV6 ( address ) ;
27
+ }
28
+
29
+ return false ;
30
+ }
31
+
32
+ const isNormalizedLocalhostTLD = ( host ) => {
33
+ return host . toLowerCase ( ) . endsWith ( '.localhost' ) ;
34
+ }
35
+
36
+ const isLocalHostname = ( host ) => {
37
+ return host . toLowerCase ( ) === 'localhost' ||
38
+ isNormalizedLocalhostTLD ( host ) ;
39
+ }
40
+
41
+ /**
42
+ * Removes leading and trailing square brackets if present.
43
+ * Adapted from https://github.com/chromium/chromium/blob/main/url/gurl.cc#L440-L448
44
+ *
45
+ * @param {string } host
46
+ * @returns {string }
47
+ */
48
+ const hostNoBrackets = ( host ) => {
49
+ if ( host . length >= 2 && host . startsWith ( '[' ) && host . endsWith ( ']' ) ) {
50
+ return host . substring ( 1 , host . length - 1 ) ;
51
+ }
52
+ return host ;
53
+ }
54
+
55
+ /**
56
+ * Determines if a URL string represents a potentially trustworthy origin.
57
+ *
58
+ * A URL is considered potentially trustworthy if it:
59
+ * - Uses HTTPS, WSS or file schemes
60
+ * - Points to a loopback address (IPv4 127.0.0.0/8 or IPv6 ::1)
61
+ * - Uses localhost or *.localhost hostnames
62
+ *
63
+ * @param {string } urlString - The URL to check
64
+ * @returns {boolean }
65
+ * @see {@link https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin W3C Spec }
66
+ */
67
+ const isPotentiallyTrustworthyOrigin = ( urlString ) => {
68
+ let url ;
69
+
70
+ // try ... catch doubles as an opaque origin check
71
+ try {
72
+ url = new URL ( urlString ) ;
73
+ } catch ( e ) {
74
+ if ( e instanceof TypeError && e . code === 'ERR_INVALID_URL' ) {
75
+ return false ;
76
+ } else throw e ;
77
+ }
78
+
79
+ const scheme = url . protocol . replace ( ':' , '' ) . toLowerCase ( ) ;
80
+ const hostname = hostNoBrackets (
81
+ url . hostname
82
+ ) . replace ( / \. + $ / , '' ) ;
83
+
84
+ if (
85
+ scheme === 'https' ||
86
+ scheme === 'wss' ||
87
+ scheme === 'file' // https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin
88
+ ) {
89
+ return true ;
90
+ }
91
+
92
+ // If it's already an IP literal, check if it's a loopback address
93
+ if ( net . isIP ( hostname ) ) {
94
+ return isIpLoopback ( hostname ) ;
95
+ }
96
+
97
+ // RFC 6761 states that localhost names will always resolve
98
+ // to the respective IP loopback address:
99
+ // https://datatracker.ietf.org/doc/html/rfc6761#section-6.3
100
+ return isLocalHostname ( hostname ) ;
101
+ }
102
+
103
+ module . exports = {
104
+ isPotentiallyTrustworthyOrigin
105
+ } ;
0 commit comments