11import { isIP } from 'node:net'
2- import { logger } from '@internal/monitoring '
2+ import fastDecodeURIComponent from 'fast-decode-uri-component '
33import { ConnectionOptions } from 'tls'
44
55export function getSslSettings ( {
@@ -11,24 +11,86 @@ export function getSslSettings({
1111} ) : ConnectionOptions | undefined {
1212 if ( ! databaseSSLRootCert ) return undefined
1313
14- try {
15- // When connecting through PGBouncer, we connect through an IPv6 address rather than a hostname
16- // When passing in the root CA for SSL, this will always fail, so we need to skip passing in the SSL root cert
17- // in case the hostname is an IP address
18- const url = new URL ( connectionString )
19- if ( url . hostname && isIpAddress ( url . hostname ) ) {
20- return { ca : databaseSSLRootCert , rejectUnauthorized : false }
21- }
22- } catch ( err ) {
23- // ignore to ensure this never breaks the connection in case of an invalid URL
24- logger . warn ( err , 'Failed to parse connection string' )
14+ // When connecting through PGBouncer, we connect through an IPv6 address rather than a hostname.
15+ // When passing in the root CA for SSL, this will always fail,
16+ // so we need to skip passing the SSL root cert if host name is an IP address.
17+ const hostname = getConnectionStringHostname ( connectionString )
18+ if ( hostname && isIpAddress ( hostname ) ) {
19+ return { ca : databaseSSLRootCert , rejectUnauthorized : false }
2520 }
2621
2722 return { ca : databaseSSLRootCert }
2823}
2924
3025export function isIpAddress ( ip : string ) {
31- // IP might be URL-encoded
32- const decodedIp = decodeURIComponent ( ip )
33- return isIP ( decodedIp ) !== 0
26+ const decoded = fastDecodeURIComponent ( ip ) ?? ip
27+
28+ if ( decoded . startsWith ( '[' ) && decoded . endsWith ( ']' ) ) {
29+ return isIP ( decoded . slice ( 1 , - 1 ) ) !== 0
30+ }
31+
32+ return isIP ( decoded ) !== 0
33+ }
34+
35+ function getConnectionStringHostname ( connectionString : string ) : string | undefined {
36+ const protocolSeparatorIndex = connectionString . indexOf ( '://' )
37+ if ( protocolSeparatorIndex === - 1 ) {
38+ return undefined
39+ }
40+
41+ const authorityStartIndex = protocolSeparatorIndex + 3
42+ let authorityEndIndex = connectionString . length
43+
44+ for ( let index = authorityStartIndex ; index < connectionString . length ; index ++ ) {
45+ const charCode = connectionString . charCodeAt ( index )
46+ if ( charCode === 47 /* / */ || charCode === 63 /* ? */ || charCode === 35 /* # */ ) {
47+ authorityEndIndex = index
48+ break
49+ }
50+ }
51+
52+ if ( authorityStartIndex >= authorityEndIndex ) {
53+ return undefined
54+ }
55+
56+ let hostStartIndex = authorityStartIndex
57+ for ( let index = authorityEndIndex - 1 ; index >= authorityStartIndex ; index -- ) {
58+ if ( connectionString . charCodeAt ( index ) === 64 /* @ */ ) {
59+ hostStartIndex = index + 1
60+ break
61+ }
62+ }
63+
64+ if ( hostStartIndex >= authorityEndIndex ) {
65+ return undefined
66+ }
67+
68+ if ( connectionString . charCodeAt ( hostStartIndex ) === 91 /* [ */ ) {
69+ const bracketEndIndex = connectionString . indexOf ( ']' , hostStartIndex + 1 )
70+
71+ if (
72+ bracketEndIndex === - 1 ||
73+ bracketEndIndex >= authorityEndIndex ||
74+ ( bracketEndIndex + 1 < authorityEndIndex &&
75+ connectionString . charCodeAt ( bracketEndIndex + 1 ) !== 58 ) /* : */
76+ ) {
77+ return undefined
78+ }
79+
80+ return connectionString . slice ( hostStartIndex + 1 , bracketEndIndex )
81+ }
82+
83+ let hostEndIndex = authorityEndIndex
84+ for ( let index = hostStartIndex ; index < authorityEndIndex ; index ++ ) {
85+ if ( connectionString . charCodeAt ( index ) === 58 /* : */ ) {
86+ hostEndIndex = index
87+ break
88+ }
89+ }
90+
91+ if ( hostStartIndex >= hostEndIndex ) {
92+ return undefined
93+ }
94+
95+ return connectionString . slice ( hostStartIndex , hostEndIndex )
3496}
0 commit comments