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, PathBuf};
23use std::str;
24use std::str::FromStr;
25use std::time::Duration;
26use std::{error, fmt, iter, mem};
27use tokio::io::{AsyncRead, AsyncWrite};
28
29/// Properties required of a session.
30#[derive(Debug, Copy, Clone, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum TargetSessionAttrs {
33    /// No special properties are required.
34    Any,
35    /// The session must allow writes.
36    ReadWrite,
37    /// The session allow only reads.
38    ReadOnly,
39}
40
41/// TLS configuration.
42#[derive(Debug, Copy, Clone, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum SslMode {
45    /// Do not use TLS.
46    Disable,
47    /// Attempt to connect with TLS but allow sessions without.
48    Prefer,
49    /// Require the use of TLS.
50    Require,
51}
52
53/// TLS negotiation configuration
54///
55/// See more information at
56/// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLNEGOTIATION
57#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
58#[non_exhaustive]
59pub enum SslNegotiation {
60    /// Use PostgreSQL SslRequest for Ssl negotiation
61    #[default]
62    Postgres,
63    /// Start Ssl handshake without negotiation, only works for PostgreSQL 17+
64    Direct,
65}
66
67/// Channel binding configuration.
68#[derive(Debug, Copy, Clone, PartialEq, Eq)]
69#[non_exhaustive]
70pub enum ChannelBinding {
71    /// Do not use channel binding.
72    Disable,
73    /// Attempt to use channel binding but allow sessions without.
74    Prefer,
75    /// Require the use of channel binding.
76    Require,
77}
78
79/// Load balancing configuration.
80#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81#[non_exhaustive]
82pub enum LoadBalanceHosts {
83    /// Make connection attempts to hosts in the order provided.
84    Disable,
85    /// Make connection attempts to hosts in a random order.
86    Random,
87}
88
89/// A host specification.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum Host {
92    /// A TCP hostname.
93    Tcp(String),
94    /// A path to a directory containing the server's Unix socket.
95    ///
96    /// This variant is only available on Unix platforms.
97    #[cfg(unix)]
98    Unix(PathBuf),
99}
100
101/// Connection configuration.
102///
103/// Configuration can be parsed from libpq-style connection strings. These strings come in two formats:
104///
105/// # Key-Value
106///
107/// This format consists of space-separated key-value pairs. Values which are either the empty string or contain
108/// whitespace should be wrapped in `'`. `'` and `\` characters should be backslash-escaped.
109///
110/// ## Keys
111///
112/// * `user` - The username to authenticate with. Defaults to the user executing this process.
113/// * `password` - The password to authenticate with.
114/// * `dbname` - The name of the database to connect to. Defaults to the username.
115/// * `options` - Command line options used to configure the server.
116/// * `application_name` - Sets the `application_name` parameter on the server.
117/// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used
118///     if available, but not used otherwise. If set to `require`, TLS will be forced to be used. Defaults to `prefer`.
119/// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the
120///     path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts
121///     can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting
122///     with the `connect` method.
123/// * `sslnegotiation` - TLS negotiation method. If set to `direct`, the client
124///     will perform direct TLS handshake, this only works for PostgreSQL 17 and
125///     newer.
126///     Note that you will need to setup ALPN of TLS client configuration to
127///     `postgresql` when using direct TLS. If you are using postgres_openssl
128///     as TLS backend, a `postgres_openssl::set_postgresql_alpn` helper is
129///     provided for that.
130///     If set to `postgres`, the default value, it follows original postgres
131///     wire protocol to perform the negotiation.
132/// * `hostaddr` - Numeric IP address of host to connect to. This should be in the standard IPv4 address format,
133///     e.g., 172.28.40.9. If your machine supports IPv6, you can also use those addresses.
134///     If this parameter is not specified, the value of `host` will be looked up to find the corresponding IP address,
135///     or if host specifies an IP address, that value will be used directly.
136///     Using `hostaddr` allows the application to avoid a host name look-up, which might be important in applications
137///     with time constraints. However, a host name is required for TLS certificate verification.
138///     Specifically:
139///         * If `hostaddr` is specified without `host`, the value for `hostaddr` gives the server network address.
140///             The connection attempt will fail if the authentication method requires a host name;
141///         * If `host` is specified without `hostaddr`, a host name lookup occurs;
142///         * If both `host` and `hostaddr` are specified, the value for `hostaddr` gives the server network address.
143///             The value for `host` is ignored unless the authentication method requires it,
144///             in which case it will be used as the host name.
145/// * `port` - The port to connect to. Multiple ports can be specified, separated by commas. The number of ports must be
146///     either 1, in which case it will be used for all hosts, or the same as the number of hosts. Defaults to 5432 if
147///     omitted or the empty string.
148/// * `connect_timeout` - The time limit in seconds applied to each socket-level connection attempt. Note that hostnames
149///     can resolve to multiple IP addresses, and this limit is applied to each address. Defaults to no timeout.
150/// * `tcp_user_timeout` - The time limit that transmitted data may remain unacknowledged before a connection is forcibly closed.
151///     This is ignored for Unix domain socket connections. It is only supported on systems where TCP_USER_TIMEOUT is available
152///     and will default to the system default if omitted or set to 0; on other systems, it has no effect.
153/// * `keepalives` - Controls the use of TCP keepalive. A value of 0 disables keepalive and nonzero integers enable it.
154///     This option is ignored when connecting with Unix sockets. Defaults to on.
155/// * `keepalives_idle` - The number of seconds of inactivity after which a keepalive message is sent to the server.
156///     This option is ignored when connecting with Unix sockets. Defaults to 2 hours.
157/// * `keepalives_interval` - The time interval between TCP keepalive probes.
158///     This option is ignored when connecting with Unix sockets.
159/// * `keepalives_retries` - The maximum number of TCP keepalive probes that will be sent before dropping a connection.
160///     This option is ignored when connecting with Unix sockets.
161/// * `target_session_attrs` - Specifies requirements of the session. If set to `read-write`, the client will check that
162///     the `transaction_read_write` session parameter is set to `on`. This can be used to connect to the primary server
163///     in a database cluster as opposed to the secondary read-only mirrors. Defaults to `all`.
164/// * `channel_binding` - Controls usage of channel binding in the authentication process. If set to `disable`, channel
165///     binding will not be used. If set to `prefer`, channel binding will be used if available, but not used otherwise.
166///     If set to `require`, the authentication process will fail if channel binding is not used. Defaults to `prefer`.
167/// * `load_balance_hosts` - Controls the order in which the client tries to connect to the available hosts and
168///     addresses. Once a connection attempt is successful no other hosts and addresses will be tried. This parameter
169///     is typically used in combination with multiple host names or a DNS record that returns multiple IPs. If set to
170///     `disable`, hosts and addresses will be tried in the order provided. If set to `random`, hosts will be tried
171///     in a random order, and the IP addresses resolved from a hostname will also be tried in a random order. Defaults
172///     to `disable`.
173///
174/// ## Examples
175///
176/// ```not_rust
177/// host=localhost user=postgres connect_timeout=10 keepalives=0
178/// ```
179///
180/// ```not_rust
181/// host=/var/lib/postgresql,localhost port=1234 user=postgres password='password with spaces'
182/// ```
183///
184/// ```not_rust
185/// 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
186/// ```
187///
188/// ```not_rust
189/// host=host1,host2,host3 port=1234,,5678 user=postgres target_session_attrs=read-write
190/// ```
191///
192/// # Url
193///
194/// This format resembles a URL with a scheme of either `postgres://` or `postgresql://`. All components are optional,
195/// and the format accepts query parameters for all of the key-value pairs described in the section above. Multiple
196/// host/port pairs can be comma-separated. Unix socket paths in the host section of the URL should be percent-encoded,
197/// as the path component of the URL specifies the database name.
198///
199/// ## Examples
200///
201/// ```not_rust
202/// postgresql://user@localhost
203/// ```
204///
205/// ```not_rust
206/// postgresql://user:password@%2Fvar%2Flib%2Fpostgresql/mydb?connect_timeout=10
207/// ```
208///
209/// ```not_rust
210/// postgresql://user@host1:1234,host2,host3:5678?target_session_attrs=read-write
211/// ```
212///
213/// ```not_rust
214/// postgresql:///mydb?user=user&host=/var/lib/postgresql
215/// ```
216#[derive(Clone, PartialEq, Eq)]
217pub struct Config {
218    pub(crate) user: Option<String>,
219    pub(crate) password: Option<Vec<u8>>,
220    pub(crate) dbname: Option<String>,
221    pub(crate) options: Option<String>,
222    pub(crate) application_name: Option<String>,
223    pub(crate) ssl_mode: SslMode,
224    pub(crate) ssl_negotiation: SslNegotiation,
225    pub(crate) host: Vec<Host>,
226    pub(crate) hostaddr: Vec<IpAddr>,
227    pub(crate) port: Vec<u16>,
228    pub(crate) connect_timeout: Option<Duration>,
229    pub(crate) tcp_user_timeout: Option<Duration>,
230    pub(crate) keepalives: bool,
231    #[cfg(not(target_arch = "wasm32"))]
232    pub(crate) keepalive_config: KeepaliveConfig,
233    pub(crate) target_session_attrs: TargetSessionAttrs,
234    pub(crate) channel_binding: ChannelBinding,
235    pub(crate) load_balance_hosts: LoadBalanceHosts,
236}
237
238impl Default for Config {
239    fn default() -> Config {
240        Config::new()
241    }
242}
243
244impl Config {
245    /// Creates a new configuration.
246    pub fn new() -> Config {
247        Config {
248            user: None,
249            password: None,
250            dbname: None,
251            options: None,
252            application_name: None,
253            ssl_mode: SslMode::Prefer,
254            ssl_negotiation: SslNegotiation::Postgres,
255            host: vec![],
256            hostaddr: vec![],
257            port: vec![],
258            connect_timeout: None,
259            tcp_user_timeout: None,
260            keepalives: true,
261            #[cfg(not(target_arch = "wasm32"))]
262            keepalive_config: KeepaliveConfig {
263                idle: Duration::from_secs(2 * 60 * 60),
264                interval: None,
265                retries: None,
266            },
267            target_session_attrs: TargetSessionAttrs::Any,
268            channel_binding: ChannelBinding::Prefer,
269            load_balance_hosts: LoadBalanceHosts::Disable,
270        }
271    }
272
273    /// Sets the user to authenticate with.
274    ///
275    /// Defaults to the user executing this process.
276    pub fn user(&mut self, user: impl Into<String>) -> &mut Config {
277        self.user = Some(user.into());
278        self
279    }
280
281    /// Gets the user to authenticate with, if one has been configured with
282    /// the `user` method.
283    pub fn get_user(&self) -> Option<&str> {
284        self.user.as_deref()
285    }
286
287    /// Sets the password to authenticate with.
288    pub fn password<T>(&mut self, password: T) -> &mut Config
289    where
290        T: AsRef<[u8]>,
291    {
292        self.password = Some(password.as_ref().to_vec());
293        self
294    }
295
296    /// Gets the password to authenticate with, if one has been configured with
297    /// the `password` method.
298    pub fn get_password(&self) -> Option<&[u8]> {
299        self.password.as_deref()
300    }
301
302    /// Sets the name of the database to connect to.
303    ///
304    /// Defaults to the user.
305    pub fn dbname(&mut self, dbname: impl Into<String>) -> &mut Config {
306        self.dbname = Some(dbname.into());
307        self
308    }
309
310    /// Gets the name of the database to connect to, if one has been configured
311    /// with the `dbname` method.
312    pub fn get_dbname(&self) -> Option<&str> {
313        self.dbname.as_deref()
314    }
315
316    /// Sets command line options used to configure the server.
317    pub fn options(&mut self, options: impl Into<String>) -> &mut Config {
318        self.options = Some(options.into());
319        self
320    }
321
322    /// Gets the command line options used to configure the server, if the
323    /// options have been set with the `options` method.
324    pub fn get_options(&self) -> Option<&str> {
325        self.options.as_deref()
326    }
327
328    /// Sets the value of the `application_name` runtime parameter.
329    pub fn application_name(&mut self, application_name: impl Into<String>) -> &mut Config {
330        self.application_name = Some(application_name.into());
331        self
332    }
333
334    /// Gets the value of the `application_name` runtime parameter, if it has
335    /// been set with the `application_name` method.
336    pub fn get_application_name(&self) -> Option<&str> {
337        self.application_name.as_deref()
338    }
339
340    /// Sets the SSL configuration.
341    ///
342    /// Defaults to `prefer`.
343    pub fn ssl_mode(&mut self, ssl_mode: SslMode) -> &mut Config {
344        self.ssl_mode = ssl_mode;
345        self
346    }
347
348    /// Gets the SSL configuration.
349    pub fn get_ssl_mode(&self) -> SslMode {
350        self.ssl_mode
351    }
352
353    /// Sets the SSL negotiation method.
354    ///
355    /// Defaults to `postgres`.
356    pub fn ssl_negotiation(&mut self, ssl_negotiation: SslNegotiation) -> &mut Config {
357        self.ssl_negotiation = ssl_negotiation;
358        self
359    }
360
361    /// Gets the SSL negotiation method.
362    pub fn get_ssl_negotiation(&self) -> SslNegotiation {
363        self.ssl_negotiation
364    }
365
366    /// Adds a host to the configuration.
367    ///
368    /// Multiple hosts can be specified by calling this method multiple times, and each will be tried in order. On Unix
369    /// systems, a host starting with a `/` is interpreted as a path to a directory containing Unix domain sockets.
370    /// There must be either no hosts, or the same number of hosts as hostaddrs.
371    pub fn host(&mut self, host: impl Into<String>) -> &mut Config {
372        let host = host.into();
373
374        #[cfg(unix)]
375        {
376            if host.starts_with('/') {
377                return self.host_path(host);
378            }
379        }
380
381        self.host.push(Host::Tcp(host));
382        self
383    }
384
385    /// Gets the hosts that have been added to the configuration with `host`.
386    pub fn get_hosts(&self) -> &[Host] {
387        &self.host
388    }
389
390    /// Gets the hostaddrs that have been added to the configuration with `hostaddr`.
391    pub fn get_hostaddrs(&self) -> &[IpAddr] {
392        self.hostaddr.deref()
393    }
394
395    /// Adds a Unix socket host to the configuration.
396    ///
397    /// Unlike `host`, this method allows non-UTF8 paths.
398    #[cfg(unix)]
399    pub fn host_path<T>(&mut self, host: T) -> &mut Config
400    where
401        T: AsRef<Path>,
402    {
403        self.host.push(Host::Unix(host.as_ref().to_path_buf()));
404        self
405    }
406
407    /// Adds a hostaddr to the configuration.
408    ///
409    /// Multiple hostaddrs can be specified by calling this method multiple times, and each will be tried in order.
410    /// There must be either no hostaddrs, or the same number of hostaddrs as hosts.
411    pub fn hostaddr(&mut self, hostaddr: IpAddr) -> &mut Config {
412        self.hostaddr.push(hostaddr);
413        self
414    }
415
416    /// Adds a port to the configuration.
417    ///
418    /// Multiple ports can be specified by calling this method multiple times. There must either be no ports, in which
419    /// case the default of 5432 is used, a single port, in which it is used for all hosts, or the same number of ports
420    /// as hosts.
421    pub fn port(&mut self, port: u16) -> &mut Config {
422        self.port.push(port);
423        self
424    }
425
426    /// Gets the ports that have been added to the configuration with `port`.
427    pub fn get_ports(&self) -> &[u16] {
428        &self.port
429    }
430
431    /// Sets the timeout applied to socket-level connection attempts.
432    ///
433    /// Note that hostnames can resolve to multiple IP addresses, and this timeout will apply to each address of each
434    /// host separately. Defaults to no limit.
435    pub fn connect_timeout(&mut self, connect_timeout: Duration) -> &mut Config {
436        self.connect_timeout = Some(connect_timeout);
437        self
438    }
439
440    /// Gets the connection timeout, if one has been set with the
441    /// `connect_timeout` method.
442    pub fn get_connect_timeout(&self) -> Option<&Duration> {
443        self.connect_timeout.as_ref()
444    }
445
446    /// Sets the TCP user timeout.
447    ///
448    /// This is ignored for Unix domain socket connections. It is only supported on systems where
449    /// TCP_USER_TIMEOUT is available and will default to the system default if omitted or set to 0;
450    /// on other systems, it has no effect.
451    pub fn tcp_user_timeout(&mut self, tcp_user_timeout: Duration) -> &mut Config {
452        self.tcp_user_timeout = Some(tcp_user_timeout);
453        self
454    }
455
456    /// Gets the TCP user timeout, if one has been set with the
457    /// `user_timeout` method.
458    pub fn get_tcp_user_timeout(&self) -> Option<&Duration> {
459        self.tcp_user_timeout.as_ref()
460    }
461
462    /// Controls the use of TCP keepalive.
463    ///
464    /// This is ignored for Unix domain socket connections. Defaults to `true`.
465    pub fn keepalives(&mut self, keepalives: bool) -> &mut Config {
466        self.keepalives = keepalives;
467        self
468    }
469
470    /// Reports whether TCP keepalives will be used.
471    pub fn get_keepalives(&self) -> bool {
472        self.keepalives
473    }
474
475    /// Sets the amount of idle time before a keepalive packet is sent on the connection.
476    ///
477    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled. Defaults to 2 hours.
478    #[cfg(not(target_arch = "wasm32"))]
479    pub fn keepalives_idle(&mut self, keepalives_idle: Duration) -> &mut Config {
480        self.keepalive_config.idle = keepalives_idle;
481        self
482    }
483
484    /// Gets the configured amount of idle time before a keepalive packet will
485    /// be sent on the connection.
486    #[cfg(not(target_arch = "wasm32"))]
487    pub fn get_keepalives_idle(&self) -> Duration {
488        self.keepalive_config.idle
489    }
490
491    /// Sets the time interval between TCP keepalive probes.
492    /// On Windows, this sets the value of the tcp_keepalive struct’s keepaliveinterval field.
493    ///
494    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled.
495    #[cfg(not(target_arch = "wasm32"))]
496    pub fn keepalives_interval(&mut self, keepalives_interval: Duration) -> &mut Config {
497        self.keepalive_config.interval = Some(keepalives_interval);
498        self
499    }
500
501    /// Gets the time interval between TCP keepalive probes.
502    #[cfg(not(target_arch = "wasm32"))]
503    pub fn get_keepalives_interval(&self) -> Option<Duration> {
504        self.keepalive_config.interval
505    }
506
507    /// Sets the maximum number of TCP keepalive probes that will be sent before dropping a connection.
508    ///
509    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled.
510    #[cfg(not(target_arch = "wasm32"))]
511    pub fn keepalives_retries(&mut self, keepalives_retries: u32) -> &mut Config {
512        self.keepalive_config.retries = Some(keepalives_retries);
513        self
514    }
515
516    /// Gets the maximum number of TCP keepalive probes that will be sent before dropping a connection.
517    #[cfg(not(target_arch = "wasm32"))]
518    pub fn get_keepalives_retries(&self) -> Option<u32> {
519        self.keepalive_config.retries
520    }
521
522    /// Sets the requirements of the session.
523    ///
524    /// This can be used to connect to the primary server in a clustered database rather than one of the read-only
525    /// secondary servers. Defaults to `Any`.
526    pub fn target_session_attrs(
527        &mut self,
528        target_session_attrs: TargetSessionAttrs,
529    ) -> &mut Config {
530        self.target_session_attrs = target_session_attrs;
531        self
532    }
533
534    /// Gets the requirements of the session.
535    pub fn get_target_session_attrs(&self) -> TargetSessionAttrs {
536        self.target_session_attrs
537    }
538
539    /// Sets the channel binding behavior.
540    ///
541    /// Defaults to `prefer`.
542    pub fn channel_binding(&mut self, channel_binding: ChannelBinding) -> &mut Config {
543        self.channel_binding = channel_binding;
544        self
545    }
546
547    /// Gets the channel binding behavior.
548    pub fn get_channel_binding(&self) -> ChannelBinding {
549        self.channel_binding
550    }
551
552    /// Sets the host load balancing behavior.
553    ///
554    /// Defaults to `disable`.
555    pub fn load_balance_hosts(&mut self, load_balance_hosts: LoadBalanceHosts) -> &mut Config {
556        self.load_balance_hosts = load_balance_hosts;
557        self
558    }
559
560    /// Gets the host load balancing behavior.
561    pub fn get_load_balance_hosts(&self) -> LoadBalanceHosts {
562        self.load_balance_hosts
563    }
564
565    fn param(&mut self, key: &str, value: &str) -> Result<(), Error> {
566        match key {
567            "user" => {
568                self.user(value);
569            }
570            "password" => {
571                self.password(value);
572            }
573            "dbname" => {
574                self.dbname(value);
575            }
576            "options" => {
577                self.options(value);
578            }
579            "application_name" => {
580                self.application_name(value);
581            }
582            "sslmode" => {
583                let mode = match value {
584                    "disable" => SslMode::Disable,
585                    "prefer" => SslMode::Prefer,
586                    "require" => SslMode::Require,
587                    _ => return Err(Error::config_parse(Box::new(InvalidValue("sslmode")))),
588                };
589                self.ssl_mode(mode);
590            }
591            "sslnegotiation" => {
592                let mode = match value {
593                    "postgres" => SslNegotiation::Postgres,
594                    "direct" => SslNegotiation::Direct,
595                    _ => {
596                        return Err(Error::config_parse(Box::new(InvalidValue(
597                            "sslnegotiation",
598                        ))))
599                    }
600                };
601                self.ssl_negotiation(mode);
602            }
603            "host" => {
604                for host in value.split(',') {
605                    self.host(host);
606                }
607            }
608            "hostaddr" => {
609                for hostaddr in value.split(',') {
610                    let addr = hostaddr
611                        .parse()
612                        .map_err(|_| Error::config_parse(Box::new(InvalidValue("hostaddr"))))?;
613                    self.hostaddr(addr);
614                }
615            }
616            "port" => {
617                for port in value.split(',') {
618                    let port = if port.is_empty() {
619                        5432
620                    } else {
621                        port.parse()
622                            .map_err(|_| Error::config_parse(Box::new(InvalidValue("port"))))?
623                    };
624                    self.port(port);
625                }
626            }
627            "connect_timeout" => {
628                let timeout = value
629                    .parse::<i64>()
630                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("connect_timeout"))))?;
631                if timeout > 0 {
632                    self.connect_timeout(Duration::from_secs(timeout as u64));
633                }
634            }
635            "tcp_user_timeout" => {
636                let timeout = value
637                    .parse::<i64>()
638                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("tcp_user_timeout"))))?;
639                if timeout > 0 {
640                    self.tcp_user_timeout(Duration::from_secs(timeout as u64));
641                }
642            }
643            #[cfg(not(target_arch = "wasm32"))]
644            "keepalives" => {
645                let keepalives = value
646                    .parse::<u64>()
647                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives"))))?;
648                self.keepalives(keepalives != 0);
649            }
650            #[cfg(not(target_arch = "wasm32"))]
651            "keepalives_idle" => {
652                let keepalives_idle = value
653                    .parse::<i64>()
654                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives_idle"))))?;
655                if keepalives_idle > 0 {
656                    self.keepalives_idle(Duration::from_secs(keepalives_idle as u64));
657                }
658            }
659            #[cfg(not(target_arch = "wasm32"))]
660            "keepalives_interval" => {
661                let keepalives_interval = value.parse::<i64>().map_err(|_| {
662                    Error::config_parse(Box::new(InvalidValue("keepalives_interval")))
663                })?;
664                if keepalives_interval > 0 {
665                    self.keepalives_interval(Duration::from_secs(keepalives_interval as u64));
666                }
667            }
668            #[cfg(not(target_arch = "wasm32"))]
669            "keepalives_retries" => {
670                let keepalives_retries = value.parse::<u32>().map_err(|_| {
671                    Error::config_parse(Box::new(InvalidValue("keepalives_retries")))
672                })?;
673                self.keepalives_retries(keepalives_retries);
674            }
675            "target_session_attrs" => {
676                let target_session_attrs = match value {
677                    "any" => TargetSessionAttrs::Any,
678                    "read-write" => TargetSessionAttrs::ReadWrite,
679                    "read-only" => TargetSessionAttrs::ReadOnly,
680                    _ => {
681                        return Err(Error::config_parse(Box::new(InvalidValue(
682                            "target_session_attrs",
683                        ))));
684                    }
685                };
686                self.target_session_attrs(target_session_attrs);
687            }
688            "channel_binding" => {
689                let channel_binding = match value {
690                    "disable" => ChannelBinding::Disable,
691                    "prefer" => ChannelBinding::Prefer,
692                    "require" => ChannelBinding::Require,
693                    _ => {
694                        return Err(Error::config_parse(Box::new(InvalidValue(
695                            "channel_binding",
696                        ))))
697                    }
698                };
699                self.channel_binding(channel_binding);
700            }
701            "load_balance_hosts" => {
702                let load_balance_hosts = match value {
703                    "disable" => LoadBalanceHosts::Disable,
704                    "random" => LoadBalanceHosts::Random,
705                    _ => {
706                        return Err(Error::config_parse(Box::new(InvalidValue(
707                            "load_balance_hosts",
708                        ))))
709                    }
710                };
711                self.load_balance_hosts(load_balance_hosts);
712            }
713            key => {
714                return Err(Error::config_parse(Box::new(UnknownOption(
715                    key.to_string(),
716                ))));
717            }
718        }
719
720        Ok(())
721    }
722
723    /// Opens a connection to a PostgreSQL database.
724    ///
725    /// Requires the `runtime` Cargo feature (enabled by default).
726    #[cfg(feature = "runtime")]
727    pub async fn connect<T>(&self, tls: T) -> Result<(Client, Connection<Socket, T::Stream>), Error>
728    where
729        T: MakeTlsConnect<Socket>,
730    {
731        connect(tls, self).await
732    }
733
734    /// Connects to a PostgreSQL database over an arbitrary stream.
735    ///
736    /// All of the settings other than `user`, `password`, `dbname`, `options`, and `application_name` name are ignored.
737    pub async fn connect_raw<S, T>(
738        &self,
739        stream: S,
740        tls: T,
741    ) -> Result<(Client, Connection<S, T::Stream>), Error>
742    where
743        S: AsyncRead + AsyncWrite + Unpin,
744        T: TlsConnect<S>,
745    {
746        connect_raw(stream, tls, true, self).await
747    }
748}
749
750impl FromStr for Config {
751    type Err = Error;
752
753    fn from_str(s: &str) -> Result<Config, Error> {
754        match UrlParser::parse(s)? {
755            Some(config) => Ok(config),
756            None => Parser::parse(s),
757        }
758    }
759}
760
761// Omit password from debug output
762impl fmt::Debug for Config {
763    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764        struct Redaction {}
765        impl fmt::Debug for Redaction {
766            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767                write!(f, "_")
768            }
769        }
770
771        let mut config_dbg = &mut f.debug_struct("Config");
772        config_dbg = config_dbg
773            .field("user", &self.user)
774            .field("password", &self.password.as_ref().map(|_| Redaction {}))
775            .field("dbname", &self.dbname)
776            .field("options", &self.options)
777            .field("application_name", &self.application_name)
778            .field("ssl_mode", &self.ssl_mode)
779            .field("host", &self.host)
780            .field("hostaddr", &self.hostaddr)
781            .field("port", &self.port)
782            .field("connect_timeout", &self.connect_timeout)
783            .field("tcp_user_timeout", &self.tcp_user_timeout)
784            .field("keepalives", &self.keepalives);
785
786        #[cfg(not(target_arch = "wasm32"))]
787        {
788            config_dbg = config_dbg
789                .field("keepalives_idle", &self.keepalive_config.idle)
790                .field("keepalives_interval", &self.keepalive_config.interval)
791                .field("keepalives_retries", &self.keepalive_config.retries);
792        }
793
794        config_dbg
795            .field("target_session_attrs", &self.target_session_attrs)
796            .field("channel_binding", &self.channel_binding)
797            .field("load_balance_hosts", &self.load_balance_hosts)
798            .finish()
799    }
800}
801
802#[derive(Debug)]
803struct UnknownOption(String);
804
805impl fmt::Display for UnknownOption {
806    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
807        write!(fmt, "unknown option `{}`", self.0)
808    }
809}
810
811impl error::Error for UnknownOption {}
812
813#[derive(Debug)]
814struct InvalidValue(&'static str);
815
816impl fmt::Display for InvalidValue {
817    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
818        write!(fmt, "invalid value for option `{}`", self.0)
819    }
820}
821
822impl error::Error for InvalidValue {}
823
824struct Parser<'a> {
825    s: &'a str,
826    it: iter::Peekable<str::CharIndices<'a>>,
827}
828
829impl<'a> Parser<'a> {
830    fn parse(s: &'a str) -> Result<Config, Error> {
831        let mut parser = Parser {
832            s,
833            it: s.char_indices().peekable(),
834        };
835
836        let mut config = Config::new();
837
838        while let Some((key, value)) = parser.parameter()? {
839            config.param(key, &value)?;
840        }
841
842        Ok(config)
843    }
844
845    fn skip_ws(&mut self) {
846        self.take_while(char::is_whitespace);
847    }
848
849    fn take_while<F>(&mut self, f: F) -> &'a str
850    where
851        F: Fn(char) -> bool,
852    {
853        let start = match self.it.peek() {
854            Some(&(i, _)) => i,
855            None => return "",
856        };
857
858        loop {
859            match self.it.peek() {
860                Some(&(_, c)) if f(c) => {
861                    self.it.next();
862                }
863                Some(&(i, _)) => return &self.s[start..i],
864                None => return &self.s[start..],
865            }
866        }
867    }
868
869    fn eat(&mut self, target: char) -> Result<(), Error> {
870        match self.it.next() {
871            Some((_, c)) if c == target => Ok(()),
872            Some((i, c)) => {
873                let m = format!(
874                    "unexpected character at byte {}: expected `{}` but got `{}`",
875                    i, target, c
876                );
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}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy