tokio_postgres/
config.rs

1//! Connection configuration.
2
3#![allow(clippy::doc_overindented_list_items)]
4
5#[cfg(feature = "runtime")]
6use crate::connect::connect;
7use crate::connect_raw::connect_raw;
8#[cfg(not(target_arch = "wasm32"))]
9use crate::keepalive::KeepaliveConfig;
10#[cfg(feature = "runtime")]
11use crate::tls::MakeTlsConnect;
12use crate::tls::TlsConnect;
13#[cfg(feature = "runtime")]
14use crate::Socket;
15use crate::{Client, Connection, Error};
16use std::borrow::Cow;
17#[cfg(unix)]
18use std::ffi::OsStr;
19use std::net::IpAddr;
20use std::ops::Deref;
21#[cfg(unix)]
22use std::os::unix::ffi::OsStrExt;
23#[cfg(unix)]
24use std::path::{Path, PathBuf};
25use std::str;
26use std::str::FromStr;
27use std::time::Duration;
28use std::{error, fmt, iter, mem};
29use tokio::io::{AsyncRead, AsyncWrite};
30
31/// Properties required of a session.
32#[derive(Debug, Copy, Clone, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum TargetSessionAttrs {
35    /// No special properties are required.
36    Any,
37    /// The session must allow writes.
38    ReadWrite,
39    /// The session allow only reads.
40    ReadOnly,
41}
42
43/// TLS configuration.
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum SslMode {
47    /// Do not use TLS.
48    Disable,
49    /// Attempt to connect with TLS but allow sessions without.
50    Prefer,
51    /// Require the use of TLS.
52    Require,
53}
54
55/// TLS negotiation configuration
56///
57/// See more information at
58/// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLNEGOTIATION
59#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
60#[non_exhaustive]
61pub enum SslNegotiation {
62    /// Use PostgreSQL SslRequest for Ssl negotiation
63    #[default]
64    Postgres,
65    /// Start Ssl handshake without negotiation, only works for PostgreSQL 17+
66    Direct,
67}
68
69/// Channel binding configuration.
70#[derive(Debug, Copy, Clone, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum ChannelBinding {
73    /// Do not use channel binding.
74    Disable,
75    /// Attempt to use channel binding but allow sessions without.
76    Prefer,
77    /// Require the use of channel binding.
78    Require,
79}
80
81/// Load balancing configuration.
82#[derive(Debug, Copy, Clone, PartialEq, Eq)]
83#[non_exhaustive]
84pub enum LoadBalanceHosts {
85    /// Make connection attempts to hosts in the order provided.
86    Disable,
87    /// Make connection attempts to hosts in a random order.
88    Random,
89}
90
91/// A host specification.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum Host {
94    /// A TCP hostname.
95    Tcp(String),
96    /// A path to a directory containing the server's Unix socket.
97    ///
98    /// This variant is only available on Unix platforms.
99    #[cfg(unix)]
100    Unix(PathBuf),
101}
102
103/// Connection configuration.
104///
105/// Configuration can be parsed from libpq-style connection strings. These strings come in two formats:
106///
107/// # Key-Value
108///
109/// This format consists of space-separated key-value pairs. Values which are either the empty string or contain
110/// whitespace should be wrapped in `'`. `'` and `\` characters should be backslash-escaped.
111///
112/// ## Keys
113///
114/// * `user` - The username to authenticate with. Defaults to the user executing this process.
115/// * `password` - The password to authenticate with.
116/// * `dbname` - The name of the database to connect to. Defaults to the username.
117/// * `options` - Command line options used to configure the server.
118/// * `application_name` - Sets the `application_name` parameter on the server.
119/// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used
120///     if available, but not used otherwise. If set to `require`, TLS will be forced to be used. Defaults to `prefer`.
121/// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the
122///     path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts
123///     can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting
124///     with the `connect` method.
125/// * `sslnegotiation` - TLS negotiation method. If set to `direct`, the client
126///     will perform direct TLS handshake, this only works for PostgreSQL 17 and
127///     newer.
128///     Note that you will need to setup ALPN of TLS client configuration to
129///     `postgresql` when using direct TLS. If you are using postgres_openssl
130///     as TLS backend, a `postgres_openssl::set_postgresql_alpn` helper is
131///     provided for that.
132///     If set to `postgres`, the default value, it follows original postgres
133///     wire protocol to perform the negotiation.
134/// * `hostaddr` - Numeric IP address of host to connect to. This should be in the standard IPv4 address format,
135///     e.g., 172.28.40.9. If your machine supports IPv6, you can also use those addresses.
136///     If this parameter is not specified, the value of `host` will be looked up to find the corresponding IP address,
137///     or if host specifies an IP address, that value will be used directly.
138///     Using `hostaddr` allows the application to avoid a host name look-up, which might be important in applications
139///     with time constraints. However, a host name is required for TLS certificate verification.
140///     Specifically:
141///         * If `hostaddr` is specified without `host`, the value for `hostaddr` gives the server network address.
142///             The connection attempt will fail if the authentication method requires a host name;
143///         * If `host` is specified without `hostaddr`, a host name lookup occurs;
144///         * If both `host` and `hostaddr` are specified, the value for `hostaddr` gives the server network address.
145///             The value for `host` is ignored unless the authentication method requires it,
146///             in which case it will be used as the host name.
147/// * `port` - The port to connect to. Multiple ports can be specified, separated by commas. The number of ports must be
148///     either 1, in which case it will be used for all hosts, or the same as the number of hosts. Defaults to 5432 if
149///     omitted or the empty string.
150/// * `connect_timeout` - The time limit in seconds applied to each socket-level connection attempt. Note that hostnames
151///     can resolve to multiple IP addresses, and this limit is applied to each address. Defaults to no timeout.
152/// * `tcp_user_timeout` - The time limit that transmitted data may remain unacknowledged before a connection is forcibly closed.
153///     This is ignored for Unix domain socket connections. It is only supported on systems where TCP_USER_TIMEOUT is available
154///     and will default to the system default if omitted or set to 0; on other systems, it has no effect.
155/// * `keepalives` - Controls the use of TCP keepalive. A value of 0 disables keepalive and nonzero integers enable it.
156///     This option is ignored when connecting with Unix sockets. Defaults to on.
157/// * `keepalives_idle` - The number of seconds of inactivity after which a keepalive message is sent to the server.
158///     This option is ignored when connecting with Unix sockets. Defaults to 2 hours.
159/// * `keepalives_interval` - The time interval between TCP keepalive probes.
160///     This option is ignored when connecting with Unix sockets.
161/// * `keepalives_retries` - The maximum number of TCP keepalive probes that will be sent before dropping a connection.
162///     This option is ignored when connecting with Unix sockets.
163/// * `target_session_attrs` - Specifies requirements of the session. If set to `read-write`, the client will check that
164///     the `transaction_read_write` session parameter is set to `on`. This can be used to connect to the primary server
165///     in a database cluster as opposed to the secondary read-only mirrors. Defaults to `all`.
166/// * `channel_binding` - Controls usage of channel binding in the authentication process. If set to `disable`, channel
167///     binding will not be used. If set to `prefer`, channel binding will be used if available, but not used otherwise.
168///     If set to `require`, the authentication process will fail if channel binding is not used. Defaults to `prefer`.
169/// * `load_balance_hosts` - Controls the order in which the client tries to connect to the available hosts and
170///     addresses. Once a connection attempt is successful no other hosts and addresses will be tried. This parameter
171///     is typically used in combination with multiple host names or a DNS record that returns multiple IPs. If set to
172///     `disable`, hosts and addresses will be tried in the order provided. If set to `random`, hosts will be tried
173///     in a random order, and the IP addresses resolved from a hostname will also be tried in a random order. Defaults
174///     to `disable`.
175///
176/// ## Examples
177///
178/// ```not_rust
179/// host=localhost user=postgres connect_timeout=10 keepalives=0
180/// ```
181///
182/// ```not_rust
183/// host=/var/run/postgresql,localhost port=1234 user=postgres password='password with spaces'
184/// ```
185///
186/// ```not_rust
187/// host=host1,host2,host3 port=1234,,5678 hostaddr=127.0.0.1,127.0.0.2,127.0.0.3 user=postgres target_session_attrs=read-write
188/// ```
189///
190/// ```not_rust
191/// host=host1,host2,host3 port=1234,,5678 user=postgres target_session_attrs=read-write
192/// ```
193///
194/// # Url
195///
196/// This format resembles a URL with a scheme of either `postgres://` or `postgresql://`. All components are optional,
197/// and the format accepts query parameters for all of the key-value pairs described in the section above. Multiple
198/// host/port pairs can be comma-separated. Unix socket paths in the host section of the URL should be percent-encoded,
199/// as the path component of the URL specifies the database name.
200///
201/// ## Examples
202///
203/// ```not_rust
204/// postgresql://user@localhost
205/// ```
206///
207/// ```not_rust
208/// postgresql://user:password@%2Fvar%2Frun%2Fpostgresql/mydb?connect_timeout=10
209/// ```
210///
211/// ```not_rust
212/// postgresql://user@host1:1234,host2,host3:5678?target_session_attrs=read-write
213/// ```
214///
215/// ```not_rust
216/// postgresql:///mydb?user=user&host=/var/run/postgresql
217/// ```
218#[derive(Clone, PartialEq, Eq)]
219pub struct Config {
220    pub(crate) user: Option<String>,
221    pub(crate) password: Option<Vec<u8>>,
222    pub(crate) dbname: Option<String>,
223    pub(crate) options: Option<String>,
224    pub(crate) application_name: Option<String>,
225    pub(crate) ssl_mode: SslMode,
226    pub(crate) ssl_negotiation: SslNegotiation,
227    pub(crate) host: Vec<Host>,
228    pub(crate) hostaddr: Vec<IpAddr>,
229    pub(crate) port: Vec<u16>,
230    pub(crate) connect_timeout: Option<Duration>,
231    pub(crate) tcp_user_timeout: Option<Duration>,
232    pub(crate) keepalives: bool,
233    #[cfg(not(target_arch = "wasm32"))]
234    pub(crate) keepalive_config: KeepaliveConfig,
235    pub(crate) target_session_attrs: TargetSessionAttrs,
236    pub(crate) channel_binding: ChannelBinding,
237    pub(crate) load_balance_hosts: LoadBalanceHosts,
238}
239
240impl Default for Config {
241    fn default() -> Config {
242        Config::new()
243    }
244}
245
246impl Config {
247    /// Creates a new configuration.
248    pub fn new() -> Config {
249        Config {
250            user: None,
251            password: None,
252            dbname: None,
253            options: None,
254            application_name: None,
255            ssl_mode: SslMode::Prefer,
256            ssl_negotiation: SslNegotiation::Postgres,
257            host: vec![],
258            hostaddr: vec![],
259            port: vec![],
260            connect_timeout: None,
261            tcp_user_timeout: None,
262            keepalives: true,
263            #[cfg(not(target_arch = "wasm32"))]
264            keepalive_config: KeepaliveConfig {
265                idle: Duration::from_secs(2 * 60 * 60),
266                interval: None,
267                retries: None,
268            },
269            target_session_attrs: TargetSessionAttrs::Any,
270            channel_binding: ChannelBinding::Prefer,
271            load_balance_hosts: LoadBalanceHosts::Disable,
272        }
273    }
274
275    /// Sets the user to authenticate with.
276    ///
277    /// Defaults to the user executing this process.
278    pub fn user(&mut self, user: impl Into<String>) -> &mut Config {
279        self.user = Some(user.into());
280        self
281    }
282
283    /// Gets the user to authenticate with, if one has been configured with
284    /// the `user` method.
285    pub fn get_user(&self) -> Option<&str> {
286        self.user.as_deref()
287    }
288
289    /// Sets the password to authenticate with.
290    pub fn password<T>(&mut self, password: T) -> &mut Config
291    where
292        T: AsRef<[u8]>,
293    {
294        self.password = Some(password.as_ref().to_vec());
295        self
296    }
297
298    /// Gets the password to authenticate with, if one has been configured with
299    /// the `password` method.
300    pub fn get_password(&self) -> Option<&[u8]> {
301        self.password.as_deref()
302    }
303
304    /// Sets the name of the database to connect to.
305    ///
306    /// Defaults to the user.
307    pub fn dbname(&mut self, dbname: impl Into<String>) -> &mut Config {
308        self.dbname = Some(dbname.into());
309        self
310    }
311
312    /// Gets the name of the database to connect to, if one has been configured
313    /// with the `dbname` method.
314    pub fn get_dbname(&self) -> Option<&str> {
315        self.dbname.as_deref()
316    }
317
318    /// Sets command line options used to configure the server.
319    pub fn options(&mut self, options: impl Into<String>) -> &mut Config {
320        self.options = Some(options.into());
321        self
322    }
323
324    /// Gets the command line options used to configure the server, if the
325    /// options have been set with the `options` method.
326    pub fn get_options(&self) -> Option<&str> {
327        self.options.as_deref()
328    }
329
330    /// Sets the value of the `application_name` runtime parameter.
331    pub fn application_name(&mut self, application_name: impl Into<String>) -> &mut Config {
332        self.application_name = Some(application_name.into());
333        self
334    }
335
336    /// Gets the value of the `application_name` runtime parameter, if it has
337    /// been set with the `application_name` method.
338    pub fn get_application_name(&self) -> Option<&str> {
339        self.application_name.as_deref()
340    }
341
342    /// Sets the SSL configuration.
343    ///
344    /// Defaults to `prefer`.
345    pub fn ssl_mode(&mut self, ssl_mode: SslMode) -> &mut Config {
346        self.ssl_mode = ssl_mode;
347        self
348    }
349
350    /// Gets the SSL configuration.
351    pub fn get_ssl_mode(&self) -> SslMode {
352        self.ssl_mode
353    }
354
355    /// Sets the SSL negotiation method.
356    ///
357    /// Defaults to `postgres`.
358    pub fn ssl_negotiation(&mut self, ssl_negotiation: SslNegotiation) -> &mut Config {
359        self.ssl_negotiation = ssl_negotiation;
360        self
361    }
362
363    /// Gets the SSL negotiation method.
364    pub fn get_ssl_negotiation(&self) -> SslNegotiation {
365        self.ssl_negotiation
366    }
367
368    /// Adds a host to the configuration.
369    ///
370    /// Multiple hosts can be specified by calling this method multiple times, and each will be tried in order. On Unix
371    /// systems, a host starting with a `/` is interpreted as a path to a directory containing Unix domain sockets.
372    /// There must be either no hosts, or the same number of hosts as hostaddrs.
373    pub fn host(&mut self, host: impl Into<String>) -> &mut Config {
374        let host = host.into();
375
376        #[cfg(unix)]
377        {
378            if host.starts_with('/') {
379                return self.host_path(host);
380            }
381        }
382
383        self.host.push(Host::Tcp(host));
384        self
385    }
386
387    /// Gets the hosts that have been added to the configuration with `host`.
388    pub fn get_hosts(&self) -> &[Host] {
389        &self.host
390    }
391
392    /// Gets the hostaddrs that have been added to the configuration with `hostaddr`.
393    pub fn get_hostaddrs(&self) -> &[IpAddr] {
394        self.hostaddr.deref()
395    }
396
397    /// Adds a Unix socket host to the configuration.
398    ///
399    /// Unlike `host`, this method allows non-UTF8 paths.
400    #[cfg(unix)]
401    pub fn host_path<T>(&mut self, host: T) -> &mut Config
402    where
403        T: AsRef<Path>,
404    {
405        self.host.push(Host::Unix(host.as_ref().to_path_buf()));
406        self
407    }
408
409    /// Adds a hostaddr to the configuration.
410    ///
411    /// Multiple hostaddrs can be specified by calling this method multiple times, and each will be tried in order.
412    /// There must be either no hostaddrs, or the same number of hostaddrs as hosts.
413    pub fn hostaddr(&mut self, hostaddr: IpAddr) -> &mut Config {
414        self.hostaddr.push(hostaddr);
415        self
416    }
417
418    /// Adds a port to the configuration.
419    ///
420    /// Multiple ports can be specified by calling this method multiple times. There must either be no ports, in which
421    /// case the default of 5432 is used, a single port, in which it is used for all hosts, or the same number of ports
422    /// as hosts.
423    pub fn port(&mut self, port: u16) -> &mut Config {
424        self.port.push(port);
425        self
426    }
427
428    /// Gets the ports that have been added to the configuration with `port`.
429    pub fn get_ports(&self) -> &[u16] {
430        &self.port
431    }
432
433    /// Sets the timeout applied to socket-level connection attempts.
434    ///
435    /// Note that hostnames can resolve to multiple IP addresses, and this timeout will apply to each address of each
436    /// host separately. Defaults to no limit.
437    pub fn connect_timeout(&mut self, connect_timeout: Duration) -> &mut Config {
438        self.connect_timeout = Some(connect_timeout);
439        self
440    }
441
442    /// Gets the connection timeout, if one has been set with the
443    /// `connect_timeout` method.
444    pub fn get_connect_timeout(&self) -> Option<&Duration> {
445        self.connect_timeout.as_ref()
446    }
447
448    /// Sets the TCP user timeout.
449    ///
450    /// This is ignored for Unix domain socket connections. It is only supported on systems where
451    /// TCP_USER_TIMEOUT is available and will default to the system default if omitted or set to 0;
452    /// on other systems, it has no effect.
453    pub fn tcp_user_timeout(&mut self, tcp_user_timeout: Duration) -> &mut Config {
454        self.tcp_user_timeout = Some(tcp_user_timeout);
455        self
456    }
457
458    /// Gets the TCP user timeout, if one has been set with the
459    /// `user_timeout` method.
460    pub fn get_tcp_user_timeout(&self) -> Option<&Duration> {
461        self.tcp_user_timeout.as_ref()
462    }
463
464    /// Controls the use of TCP keepalive.
465    ///
466    /// This is ignored for Unix domain socket connections. Defaults to `true`.
467    pub fn keepalives(&mut self, keepalives: bool) -> &mut Config {
468        self.keepalives = keepalives;
469        self
470    }
471
472    /// Reports whether TCP keepalives will be used.
473    pub fn get_keepalives(&self) -> bool {
474        self.keepalives
475    }
476
477    /// Sets the amount of idle time before a keepalive packet is sent on the connection.
478    ///
479    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled. Defaults to 2 hours.
480    #[cfg(not(target_arch = "wasm32"))]
481    pub fn keepalives_idle(&mut self, keepalives_idle: Duration) -> &mut Config {
482        self.keepalive_config.idle = keepalives_idle;
483        self
484    }
485
486    /// Gets the configured amount of idle time before a keepalive packet will
487    /// be sent on the connection.
488    #[cfg(not(target_arch = "wasm32"))]
489    pub fn get_keepalives_idle(&self) -> Duration {
490        self.keepalive_config.idle
491    }
492
493    /// Sets the time interval between TCP keepalive probes.
494    /// On Windows, this sets the value of the tcp_keepalive struct’s keepaliveinterval field.
495    ///
496    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled.
497    #[cfg(not(target_arch = "wasm32"))]
498    pub fn keepalives_interval(&mut self, keepalives_interval: Duration) -> &mut Config {
499        self.keepalive_config.interval = Some(keepalives_interval);
500        self
501    }
502
503    /// Gets the time interval between TCP keepalive probes.
504    #[cfg(not(target_arch = "wasm32"))]
505    pub fn get_keepalives_interval(&self) -> Option<Duration> {
506        self.keepalive_config.interval
507    }
508
509    /// Sets the maximum number of TCP keepalive probes that will be sent before dropping a connection.
510    ///
511    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled.
512    #[cfg(not(target_arch = "wasm32"))]
513    pub fn keepalives_retries(&mut self, keepalives_retries: u32) -> &mut Config {
514        self.keepalive_config.retries = Some(keepalives_retries);
515        self
516    }
517
518    /// Gets the maximum number of TCP keepalive probes that will be sent before dropping a connection.
519    #[cfg(not(target_arch = "wasm32"))]
520    pub fn get_keepalives_retries(&self) -> Option<u32> {
521        self.keepalive_config.retries
522    }
523
524    /// Sets the requirements of the session.
525    ///
526    /// This can be used to connect to the primary server in a clustered database rather than one of the read-only
527    /// secondary servers. Defaults to `Any`.
528    pub fn target_session_attrs(
529        &mut self,
530        target_session_attrs: TargetSessionAttrs,
531    ) -> &mut Config {
532        self.target_session_attrs = target_session_attrs;
533        self
534    }
535
536    /// Gets the requirements of the session.
537    pub fn get_target_session_attrs(&self) -> TargetSessionAttrs {
538        self.target_session_attrs
539    }
540
541    /// Sets the channel binding behavior.
542    ///
543    /// Defaults to `prefer`.
544    pub fn channel_binding(&mut self, channel_binding: ChannelBinding) -> &mut Config {
545        self.channel_binding = channel_binding;
546        self
547    }
548
549    /// Gets the channel binding behavior.
550    pub fn get_channel_binding(&self) -> ChannelBinding {
551        self.channel_binding
552    }
553
554    /// Sets the host load balancing behavior.
555    ///
556    /// Defaults to `disable`.
557    pub fn load_balance_hosts(&mut self, load_balance_hosts: LoadBalanceHosts) -> &mut Config {
558        self.load_balance_hosts = load_balance_hosts;
559        self
560    }
561
562    /// Gets the host load balancing behavior.
563    pub fn get_load_balance_hosts(&self) -> LoadBalanceHosts {
564        self.load_balance_hosts
565    }
566
567    fn param(&mut self, key: &str, value: &str) -> Result<(), Error> {
568        match key {
569            "user" => {
570                self.user(value);
571            }
572            "password" => {
573                self.password(value);
574            }
575            "dbname" => {
576                self.dbname(value);
577            }
578            "options" => {
579                self.options(value);
580            }
581            "application_name" => {
582                self.application_name(value);
583            }
584            "sslmode" => {
585                let mode = match value {
586                    "disable" => SslMode::Disable,
587                    "prefer" => SslMode::Prefer,
588                    "require" => SslMode::Require,
589                    _ => return Err(Error::config_parse(Box::new(InvalidValue("sslmode")))),
590                };
591                self.ssl_mode(mode);
592            }
593            "sslnegotiation" => {
594                let mode = match value {
595                    "postgres" => SslNegotiation::Postgres,
596                    "direct" => SslNegotiation::Direct,
597                    _ => {
598                        return Err(Error::config_parse(Box::new(InvalidValue(
599                            "sslnegotiation",
600                        ))))
601                    }
602                };
603                self.ssl_negotiation(mode);
604            }
605            "host" => {
606                for host in value.split(',') {
607                    self.host(host);
608                }
609            }
610            "hostaddr" => {
611                for hostaddr in value.split(',') {
612                    let addr = hostaddr
613                        .parse()
614                        .map_err(|_| Error::config_parse(Box::new(InvalidValue("hostaddr"))))?;
615                    self.hostaddr(addr);
616                }
617            }
618            "port" => {
619                for port in value.split(',') {
620                    let port = if port.is_empty() {
621                        5432
622                    } else {
623                        port.parse()
624                            .map_err(|_| Error::config_parse(Box::new(InvalidValue("port"))))?
625                    };
626                    self.port(port);
627                }
628            }
629            "connect_timeout" => {
630                let timeout = value
631                    .parse::<i64>()
632                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("connect_timeout"))))?;
633                if timeout > 0 {
634                    self.connect_timeout(Duration::from_secs(timeout as u64));
635                }
636            }
637            "tcp_user_timeout" => {
638                let timeout = value
639                    .parse::<i64>()
640                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("tcp_user_timeout"))))?;
641                if timeout > 0 {
642                    self.tcp_user_timeout(Duration::from_secs(timeout as u64));
643                }
644            }
645            #[cfg(not(target_arch = "wasm32"))]
646            "keepalives" => {
647                let keepalives = value
648                    .parse::<u64>()
649                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives"))))?;
650                self.keepalives(keepalives != 0);
651            }
652            #[cfg(not(target_arch = "wasm32"))]
653            "keepalives_idle" => {
654                let keepalives_idle = value
655                    .parse::<i64>()
656                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives_idle"))))?;
657                if keepalives_idle > 0 {
658                    self.keepalives_idle(Duration::from_secs(keepalives_idle as u64));
659                }
660            }
661            #[cfg(not(target_arch = "wasm32"))]
662            "keepalives_interval" => {
663                let keepalives_interval = value.parse::<i64>().map_err(|_| {
664                    Error::config_parse(Box::new(InvalidValue("keepalives_interval")))
665                })?;
666                if keepalives_interval > 0 {
667                    self.keepalives_interval(Duration::from_secs(keepalives_interval as u64));
668                }
669            }
670            #[cfg(not(target_arch = "wasm32"))]
671            "keepalives_retries" => {
672                let keepalives_retries = value.parse::<u32>().map_err(|_| {
673                    Error::config_parse(Box::new(InvalidValue("keepalives_retries")))
674                })?;
675                self.keepalives_retries(keepalives_retries);
676            }
677            "target_session_attrs" => {
678                let target_session_attrs = match value {
679                    "any" => TargetSessionAttrs::Any,
680                    "read-write" => TargetSessionAttrs::ReadWrite,
681                    "read-only" => TargetSessionAttrs::ReadOnly,
682                    _ => {
683                        return Err(Error::config_parse(Box::new(InvalidValue(
684                            "target_session_attrs",
685                        ))));
686                    }
687                };
688                self.target_session_attrs(target_session_attrs);
689            }
690            "channel_binding" => {
691                let channel_binding = match value {
692                    "disable" => ChannelBinding::Disable,
693                    "prefer" => ChannelBinding::Prefer,
694                    "require" => ChannelBinding::Require,
695                    _ => {
696                        return Err(Error::config_parse(Box::new(InvalidValue(
697                            "channel_binding",
698                        ))))
699                    }
700                };
701                self.channel_binding(channel_binding);
702            }
703            "load_balance_hosts" => {
704                let load_balance_hosts = match value {
705                    "disable" => LoadBalanceHosts::Disable,
706                    "random" => LoadBalanceHosts::Random,
707                    _ => {
708                        return Err(Error::config_parse(Box::new(InvalidValue(
709                            "load_balance_hosts",
710                        ))))
711                    }
712                };
713                self.load_balance_hosts(load_balance_hosts);
714            }
715            key => {
716                return Err(Error::config_parse(Box::new(UnknownOption(
717                    key.to_string(),
718                ))));
719            }
720        }
721
722        Ok(())
723    }
724
725    /// Opens a connection to a PostgreSQL database.
726    ///
727    /// Requires the `runtime` Cargo feature (enabled by default).
728    #[cfg(feature = "runtime")]
729    pub async fn connect<T>(&self, tls: T) -> Result<(Client, Connection<Socket, T::Stream>), Error>
730    where
731        T: MakeTlsConnect<Socket>,
732    {
733        connect(tls, self).await
734    }
735
736    /// Connects to a PostgreSQL database over an arbitrary stream.
737    ///
738    /// All of the settings other than `user`, `password`, `dbname`, `options`, and `application_name` name are ignored.
739    pub async fn connect_raw<S, T>(
740        &self,
741        stream: S,
742        tls: T,
743    ) -> Result<(Client, Connection<S, T::Stream>), Error>
744    where
745        S: AsyncRead + AsyncWrite + Unpin,
746        T: TlsConnect<S>,
747    {
748        connect_raw(stream, tls, true, self).await
749    }
750}
751
752impl FromStr for Config {
753    type Err = Error;
754
755    fn from_str(s: &str) -> Result<Config, Error> {
756        match UrlParser::parse(s)? {
757            Some(config) => Ok(config),
758            None => Parser::parse(s),
759        }
760    }
761}
762
763// Omit password from debug output
764impl fmt::Debug for Config {
765    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
766        struct Redaction {}
767        impl fmt::Debug for Redaction {
768            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
769                write!(f, "_")
770            }
771        }
772
773        let mut config_dbg = &mut f.debug_struct("Config");
774        config_dbg = config_dbg
775            .field("user", &self.user)
776            .field("password", &self.password.as_ref().map(|_| Redaction {}))
777            .field("dbname", &self.dbname)
778            .field("options", &self.options)
779            .field("application_name", &self.application_name)
780            .field("ssl_mode", &self.ssl_mode)
781            .field("host", &self.host)
782            .field("hostaddr", &self.hostaddr)
783            .field("port", &self.port)
784            .field("connect_timeout", &self.connect_timeout)
785            .field("tcp_user_timeout", &self.tcp_user_timeout)
786            .field("keepalives", &self.keepalives);
787
788        #[cfg(not(target_arch = "wasm32"))]
789        {
790            config_dbg = config_dbg
791                .field("keepalives_idle", &self.keepalive_config.idle)
792                .field("keepalives_interval", &self.keepalive_config.interval)
793                .field("keepalives_retries", &self.keepalive_config.retries);
794        }
795
796        config_dbg
797            .field("target_session_attrs", &self.target_session_attrs)
798            .field("channel_binding", &self.channel_binding)
799            .field("load_balance_hosts", &self.load_balance_hosts)
800            .finish()
801    }
802}
803
804#[derive(Debug)]
805struct UnknownOption(String);
806
807impl fmt::Display for UnknownOption {
808    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
809        write!(fmt, "unknown option `{}`", self.0)
810    }
811}
812
813impl error::Error for UnknownOption {}
814
815#[derive(Debug)]
816struct InvalidValue(&'static str);
817
818impl fmt::Display for InvalidValue {
819    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
820        write!(fmt, "invalid value for option `{}`", self.0)
821    }
822}
823
824impl error::Error for InvalidValue {}
825
826struct Parser<'a> {
827    s: &'a str,
828    it: iter::Peekable<str::CharIndices<'a>>,
829}
830
831impl<'a> Parser<'a> {
832    fn parse(s: &'a str) -> Result<Config, Error> {
833        let mut parser = Parser {
834            s,
835            it: s.char_indices().peekable(),
836        };
837
838        let mut config = Config::new();
839
840        while let Some((key, value)) = parser.parameter()? {
841            config.param(key, &value)?;
842        }
843
844        Ok(config)
845    }
846
847    fn skip_ws(&mut self) {
848        self.take_while(char::is_whitespace);
849    }
850
851    fn take_while<F>(&mut self, f: F) -> &'a str
852    where
853        F: Fn(char) -> bool,
854    {
855        let start = match self.it.peek() {
856            Some(&(i, _)) => i,
857            None => return "",
858        };
859
860        loop {
861            match self.it.peek() {
862                Some(&(_, c)) if f(c) => {
863                    self.it.next();
864                }
865                Some(&(i, _)) => return &self.s[start..i],
866                None => return &self.s[start..],
867            }
868        }
869    }
870
871    fn eat(&mut self, target: char) -> Result<(), Error> {
872        match self.it.next() {
873            Some((_, c)) if c == target => Ok(()),
874            Some((i, c)) => {
875                let m =
876                    format!("unexpected character at byte {i}: expected `{target}` but got `{c}`");
877                Err(Error::config_parse(m.into()))
878            }
879            None => Err(Error::config_parse("unexpected EOF".into())),
880        }
881    }
882
883    fn eat_if(&mut self, target: char) -> bool {
884        match self.it.peek() {
885            Some(&(_, c)) if c == target => {
886                self.it.next();
887                true
888            }
889            _ => false,
890        }
891    }
892
893    fn keyword(&mut self) -> Option<&'a str> {
894        let s = self.take_while(|c| match c {
895            c if c.is_whitespace() => false,
896            '=' => false,
897            _ => true,
898        });
899
900        if s.is_empty() {
901            None
902        } else {
903            Some(s)
904        }
905    }
906
907    fn value(&mut self) -> Result<String, Error> {
908        let value = if self.eat_if('\'') {
909            let value = self.quoted_value()?;
910            self.eat('\'')?;
911            value
912        } else {
913            self.simple_value()?
914        };
915
916        Ok(value)
917    }
918
919    fn simple_value(&mut self) -> Result<String, Error> {
920        let mut value = String::new();
921
922        while let Some(&(_, c)) = self.it.peek() {
923            if c.is_whitespace() {
924                break;
925            }
926
927            self.it.next();
928            if c == '\\' {
929                if let Some((_, c2)) = self.it.next() {
930                    value.push(c2);
931                }
932            } else {
933                value.push(c);
934            }
935        }
936
937        if value.is_empty() {
938            return Err(Error::config_parse("unexpected EOF".into()));
939        }
940
941        Ok(value)
942    }
943
944    fn quoted_value(&mut self) -> Result<String, Error> {
945        let mut value = String::new();
946
947        while let Some(&(_, c)) = self.it.peek() {
948            if c == '\'' {
949                return Ok(value);
950            }
951
952            self.it.next();
953            if c == '\\' {
954                if let Some((_, c2)) = self.it.next() {
955                    value.push(c2);
956                }
957            } else {
958                value.push(c);
959            }
960        }
961
962        Err(Error::config_parse(
963            "unterminated quoted connection parameter value".into(),
964        ))
965    }
966
967    fn parameter(&mut self) -> Result<Option<(&'a str, String)>, Error> {
968        self.skip_ws();
969        let keyword = match self.keyword() {
970            Some(keyword) => keyword,
971            None => return Ok(None),
972        };
973        self.skip_ws();
974        self.eat('=')?;
975        self.skip_ws();
976        let value = self.value()?;
977
978        Ok(Some((keyword, value)))
979    }
980}
981
982// This is a pretty sloppy "URL" parser, but it matches the behavior of libpq, where things really aren't very strict
983struct UrlParser<'a> {
984    s: &'a str,
985    config: Config,
986}
987
988impl<'a> UrlParser<'a> {
989    fn parse(s: &'a str) -> Result<Option<Config>, Error> {
990        let s = match Self::remove_url_prefix(s) {
991            Some(s) => s,
992            None => return Ok(None),
993        };
994
995        let mut parser = UrlParser {
996            s,
997            config: Config::new(),
998        };
999
1000        parser.parse_credentials()?;
1001        parser.parse_host()?;
1002        parser.parse_path()?;
1003        parser.parse_params()?;
1004
1005        Ok(Some(parser.config))
1006    }
1007
1008    fn remove_url_prefix(s: &str) -> Option<&str> {
1009        for prefix in &["postgres://", "postgresql://"] {
1010            if let Some(stripped) = s.strip_prefix(prefix) {
1011                return Some(stripped);
1012            }
1013        }
1014
1015        None
1016    }
1017
1018    fn take_until(&mut self, end: &[char]) -> Option<&'a str> {
1019        match self.s.find(end) {
1020            Some(pos) => {
1021                let (head, tail) = self.s.split_at(pos);
1022                self.s = tail;
1023                Some(head)
1024            }
1025            None => None,
1026        }
1027    }
1028
1029    fn take_all(&mut self) -> &'a str {
1030        mem::take(&mut self.s)
1031    }
1032
1033    fn eat_byte(&mut self) {
1034        self.s = &self.s[1..];
1035    }
1036
1037    fn parse_credentials(&mut self) -> Result<(), Error> {
1038        let creds = match self.take_until(&['@']) {
1039            Some(creds) => creds,
1040            None => return Ok(()),
1041        };
1042        self.eat_byte();
1043
1044        let mut it = creds.splitn(2, ':');
1045        let user = self.decode(it.next().unwrap())?;
1046        self.config.user(user);
1047
1048        if let Some(password) = it.next() {
1049            let password = Cow::from(percent_encoding::percent_decode(password.as_bytes()));
1050            self.config.password(password);
1051        }
1052
1053        Ok(())
1054    }
1055
1056    fn parse_host(&mut self) -> Result<(), Error> {
1057        let host = match self.take_until(&['/', '?']) {
1058            Some(host) => host,
1059            None => self.take_all(),
1060        };
1061
1062        if host.is_empty() {
1063            return Ok(());
1064        }
1065
1066        for chunk in host.split(',') {
1067            let (host, port) = if chunk.starts_with('[') {
1068                let idx = match chunk.find(']') {
1069                    Some(idx) => idx,
1070                    None => return Err(Error::config_parse(InvalidValue("host").into())),
1071                };
1072
1073                let host = &chunk[1..idx];
1074                let remaining = &chunk[idx + 1..];
1075                let port = if let Some(port) = remaining.strip_prefix(':') {
1076                    Some(port)
1077                } else if remaining.is_empty() {
1078                    None
1079                } else {
1080                    return Err(Error::config_parse(InvalidValue("host").into()));
1081                };
1082
1083                (host, port)
1084            } else {
1085                let mut it = chunk.splitn(2, ':');
1086                (it.next().unwrap(), it.next())
1087            };
1088
1089            self.host_param(host)?;
1090            let port = self.decode(port.unwrap_or("5432"))?;
1091            self.config.param("port", &port)?;
1092        }
1093
1094        Ok(())
1095    }
1096
1097    fn parse_path(&mut self) -> Result<(), Error> {
1098        if !self.s.starts_with('/') {
1099            return Ok(());
1100        }
1101        self.eat_byte();
1102
1103        let dbname = match self.take_until(&['?']) {
1104            Some(dbname) => dbname,
1105            None => self.take_all(),
1106        };
1107
1108        if !dbname.is_empty() {
1109            self.config.dbname(self.decode(dbname)?);
1110        }
1111
1112        Ok(())
1113    }
1114
1115    fn parse_params(&mut self) -> Result<(), Error> {
1116        if !self.s.starts_with('?') {
1117            return Ok(());
1118        }
1119        self.eat_byte();
1120
1121        while !self.s.is_empty() {
1122            let key = match self.take_until(&['=']) {
1123                Some(key) => self.decode(key)?,
1124                None => return Err(Error::config_parse("unterminated parameter".into())),
1125            };
1126            self.eat_byte();
1127
1128            let value = match self.take_until(&['&']) {
1129                Some(value) => {
1130                    self.eat_byte();
1131                    value
1132                }
1133                None => self.take_all(),
1134            };
1135
1136            if key == "host" {
1137                self.host_param(value)?;
1138            } else {
1139                let value = self.decode(value)?;
1140                self.config.param(&key, &value)?;
1141            }
1142        }
1143
1144        Ok(())
1145    }
1146
1147    #[cfg(unix)]
1148    fn host_param(&mut self, s: &str) -> Result<(), Error> {
1149        let decoded = Cow::from(percent_encoding::percent_decode(s.as_bytes()));
1150        if decoded.first() == Some(&b'/') {
1151            self.config.host_path(OsStr::from_bytes(&decoded));
1152        } else {
1153            let decoded = str::from_utf8(&decoded).map_err(|e| Error::config_parse(Box::new(e)))?;
1154            self.config.host(decoded);
1155        }
1156
1157        Ok(())
1158    }
1159
1160    #[cfg(not(unix))]
1161    fn host_param(&mut self, s: &str) -> Result<(), Error> {
1162        let s = self.decode(s)?;
1163        self.config.param("host", &s)
1164    }
1165
1166    fn decode(&self, s: &'a str) -> Result<Cow<'a, str>, Error> {
1167        percent_encoding::percent_decode(s.as_bytes())
1168            .decode_utf8()
1169            .map_err(|e| Error::config_parse(e.into()))
1170    }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175    use std::net::IpAddr;
1176
1177    use crate::{config::Host, Config};
1178
1179    #[test]
1180    fn test_simple_parsing() {
1181        let s = "user=pass_user dbname=postgres host=host1,host2 hostaddr=127.0.0.1,127.0.0.2 port=26257";
1182        let config = s.parse::<Config>().unwrap();
1183        assert_eq!(Some("pass_user"), config.get_user());
1184        assert_eq!(Some("postgres"), config.get_dbname());
1185        assert_eq!(
1186            [
1187                Host::Tcp("host1".to_string()),
1188                Host::Tcp("host2".to_string())
1189            ],
1190            config.get_hosts(),
1191        );
1192
1193        assert_eq!(
1194            [
1195                "127.0.0.1".parse::<IpAddr>().unwrap(),
1196                "127.0.0.2".parse::<IpAddr>().unwrap()
1197            ],
1198            config.get_hostaddrs(),
1199        );
1200
1201        assert_eq!(1, 1);
1202    }
1203
1204    #[test]
1205    fn test_invalid_hostaddr_parsing() {
1206        let s = "user=pass_user dbname=postgres host=host1 hostaddr=127.0.0 port=26257";
1207        s.parse::<Config>().err().unwrap();
1208    }
1209}