@@ -9,7 +9,7 @@ public struct SQLPostgresConfiguration {
99 /// `UInt16(getservbyname("postgresql", "tcp").pointee.s_port).byteSwapped`
1010 public static var ianaPortNumber : Int { 5432 }
1111
12- /// See `` PostgresNIO/ PostgresConnection/ Configuration` `.
12+ // See `PostgresNIO. PostgresConnection. Configuration`.
1313 public var coreConfiguration : PostgresConnection . Configuration
1414
1515 /// Optional `search_path` to set on new connections.
@@ -27,40 +27,93 @@ public struct SQLPostgresConfiguration {
2727
2828 /// Create a ``SQLPostgresConfiguration`` from a properly formatted URL.
2929 ///
30- /// The allowed URL format is :
30+ /// The supported URL formats are :
3131 ///
32- /// postgres://username:password@hostname:port/database?tls=mode
32+ /// postgres://username:password@hostname:port/database?tlsmode=mode
33+ /// postgres+tcp://username:password@hostname:port/database?tlsmode=mode
34+ /// postgres+uds://username:password@localhost/path?tlsmode=mode#database
3335 ///
34- /// `hostname` and `username` are required; all other components are optional. For backwards
35- /// compatibility, `ssl` is treated as an alias of `tls` .
36+ /// The `postgres+tcp` scheme requests a connection over TCP. The `postgres` scheme is an alias
37+ /// for `postgres+tcp`. Only the `hostname` and `username` components are required .
3638 ///
37- /// The allowed `mode` values for `tls` are:
38- /// - `require` (fail to connect if the server does not support TLS)
39- /// - `true` (attempt to use TLS but continue anyway if the server doesn't support it)
40- /// - `false` (do not use TLS even if the server supports it).
41- /// If `tls` is omitted entirely, the mode defaults to `true`.
39+ /// The `postgres+uds` scheme requests a connection via a UNIX domain socket. The `username` and
40+ /// `path` components are required. The authority must always be empty or `localhost`, and may not
41+ /// specify a port.
42+ ///
43+ /// The allowed `mode` values for `tlsmode` are:
44+ ///
45+ /// Value|Behavior
46+ /// -|-
47+ /// `disable`|Don't use TLS, even if the server supports it.
48+ /// `prefer`|Use TLS if possible.
49+ /// `require`|Enforce TLS support.
50+ ///
51+ /// If no `tlsmode` is specified, the default mode is `prefer` for TCP connections, or `disable`
52+ /// for UDS connections. If more than one mode is specified, the last one wins. Whenever a TLS
53+ /// connection is made, full certificate verification (both chain of trust and hostname match)
54+ /// is always enforced, regardless of the mode used.
55+ ///
56+ /// For compatibility with `libpq` and previous versions of this package, any of "`sslmode`",
57+ /// "`tls`", or "`ssl`" may be used instead of "`tlsmode`". There are also various aliases for
58+ /// each of the TLS mode names, as follows:
59+ ///
60+ /// - "`disable`": "`false`"
61+ /// - "`prefer`": "`allow`", "`true`"
62+ /// - "`require`": "`verify-ca`", "`verify-full`"
63+ ///
64+ /// The aliases always have the same semantics as the "canonical" modes, despite any differences
65+ /// suggested by their names.
66+ ///
67+ /// > Note: It is possible to emulate `libpq`'s definitions for `prefer` (TLS if available with
68+ /// > no certificate verification), `require` (TLS enforced, but also without certificate
69+ /// > verification) and `verify-ca` (TLS enforced with no hostname verification) by manually
70+ /// > specifying the TLS configuration instead of using a URL. It is not possible, by design, to
71+ /// > emulate `libpq`'s `allow` mode (TLS only if there is no alternative). It is _strongly_
72+ /// > recommended for both security and privacy reasons to always leave full certificate
73+ /// > verification enabled whenever possible. See NIOSSL's [`TLSConfiguration`](tlsconfig) for
74+ /// > additional information and recommendations.
75+ ///
76+ /// [tlsconfig]:
77+ /// https://swiftpackageindex.com/apple/swift-nio-ssl/main/documentation/niossl/tlsconfiguration
4278 public init ( url: URL ) throws {
43- guard let comp = URLComponents ( url: url, resolvingAgainstBaseURL: true ) ,
44- comp. scheme? . hasPrefix ( " postgres " ) ?? false ,
45- let hostname = comp. host, let username = comp. user
46- else {
79+ guard let comp = URLComponents ( url: url, resolvingAgainstBaseURL: true ) , let username = comp. user else {
4780 throw URLError ( . badURL, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url. absoluteString] )
4881 }
49- let password = comp. password, port = comp. port ?? Self . ianaPortNumber
50- let tls : PostgresConnection . Configuration . TLS
51- switch ( comp. queryItems ?? [ ] ) . first ( where: { [ " ssl " , " tls " ] . contains ( 0ドル. name. lowercased ( ) ) } ) ? . value ?? " true " {
52- case " require " : tls = try . require( . init( configuration: . makeClientConfiguration( ) ) )
53- case " true " : tls = try . prefer( . init( configuration: . makeClientConfiguration( ) ) )
54- case " false " : tls = . disable
55- default : throw URLError ( . badURL, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url. absoluteString] )
82+ 83+ func decideTLSConfig( from queryItems: [ URLQueryItem ] , defaultMode: String ) throws -> PostgresConnection . Configuration . TLS {
84+ switch queryItems. last ( where: { [ " tlsmode " , " sslmode " , " ssl " , " tls " ] . contains ( 0ドル. name. lowercased ( ) ) } ) ? . value ?? defaultMode {
85+ case " verify-full " , " verify-ca " , " require " :
86+ return try . require( . init( configuration: . makeClientConfiguration( ) ) )
87+ case " prefer " , " allow " , " true " :
88+ return try . prefer( . init( configuration: . makeClientConfiguration( ) ) )
89+ case " disable " , " false " :
90+ return . disable
91+ default :
92+ throw URLError ( . badURL, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url. absoluteString] )
93+ }
5694 }
5795
58- self . init (
59- hostname: hostname, port: port,
60- username: username, password: password,
61- database: url. lastPathComponent,
62- tls: tls
63- )
96+ switch comp. scheme {
97+ case " postgres " , " postgres+tcp " :
98+ guard let hostname = comp. host, !hostname. isEmpty else {
99+ throw URLError ( . badURL, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url. absoluteString] )
100+ }
101+ self . init (
102+ hostname: hostname, port: comp. port ?? Self . ianaPortNumber,
103+ username: username, password: comp. password,
104+ database: url. lastPathComponent. isEmpty ? nil : url. lastPathComponent,
105+ tls: try decideTLSConfig ( from: comp. queryItems ?? [ ] , defaultMode: " prefer " )
106+ )
107+ case " postgres+uds " :
108+ guard ( comp. host? . isEmpty ?? true || comp. host == " localhost " ) , comp. port == nil , !comp. path. isEmpty, comp. path != " / " else {
109+ throw URLError ( . badURL, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url. absoluteString] )
110+ }
111+ var coreConfig = PostgresConnection . Configuration ( unixSocketPath: comp. path, username: username, password: comp. password, database: comp. fragment)
112+ coreConfig. tls = try decideTLSConfig ( from: comp. queryItems ?? [ ] , defaultMode: " disable " )
113+ self . init ( coreConfiguration: coreConfig)
114+ default :
115+ throw URLError ( . badURL, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSURLErrorFailingURLStringErrorKey: url. absoluteString] )
116+ }
64117 }
65118
66119 /// Create a ``SQLPostgresConfiguration`` for connecting to a server with a hostname and optional port.
0 commit comments