tokio_postgres/
config.rs

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