1#![allow(clippy::doc_overindented_list_items)]
4
5#[cfg(feature = "runtime")]
6use crate::Socket;
7#[cfg(feature = "runtime")]
8use crate::connect::connect;
9use crate::connect_raw::connect_raw;
10#[cfg(not(target_arch = "wasm32"))]
11use crate::keepalive::KeepaliveConfig;
12#[cfg(feature = "runtime")]
13use crate::tls::MakeTlsConnect;
14use crate::tls::TlsConnect;
15use crate::{Client, Connection, Error};
16use std::borrow::Cow;
17#[cfg(unix)]
18use std::ffi::OsStr;
19use std::net::IpAddr;
20use std::ops::Deref;
21#[cfg(unix)]
22use std::os::unix::ffi::OsStrExt;
23#[cfg(unix)]
24use std::path::{Path, PathBuf};
25use std::str;
26use std::str::FromStr;
27use std::time::Duration;
28use std::{error, fmt, iter, mem};
29use tokio::io::{AsyncRead, AsyncWrite};
30
31#[derive(Debug, Copy, Clone, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum TargetSessionAttrs {
35 Any,
37 ReadWrite,
39 ReadOnly,
41}
42
43#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum SslMode {
47 Disable,
49 Prefer,
51 Require,
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
60#[non_exhaustive]
61pub enum SslNegotiation {
62 #[default]
64 Postgres,
65 Direct,
67}
68
69#[derive(Debug, Copy, Clone, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum ChannelBinding {
73 Disable,
75 Prefer,
77 Require,
79}
80
81#[derive(Debug, Copy, Clone, PartialEq, Eq)]
83#[non_exhaustive]
84pub enum LoadBalanceHosts {
85 Disable,
87 Random,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum Host {
94 Tcp(String),
96 #[cfg(unix)]
100 Unix(PathBuf),
101}
102
103#[derive(Clone, PartialEq, Eq)]
219pub struct Config {
220 pub(crate) user: Option<String>,
221 pub(crate) password: Option<Vec<u8>>,
222 pub(crate) dbname: Option<String>,
223 pub(crate) options: Option<String>,
224 pub(crate) application_name: Option<String>,
225 pub(crate) ssl_mode: SslMode,
226 pub(crate) ssl_negotiation: SslNegotiation,
227 pub(crate) host: Vec<Host>,
228 pub(crate) hostaddr: Vec<IpAddr>,
229 pub(crate) port: Vec<u16>,
230 pub(crate) connect_timeout: Option<Duration>,
231 pub(crate) tcp_user_timeout: Option<Duration>,
232 pub(crate) keepalives: bool,
233 #[cfg(not(target_arch = "wasm32"))]
234 pub(crate) keepalive_config: KeepaliveConfig,
235 pub(crate) target_session_attrs: TargetSessionAttrs,
236 pub(crate) channel_binding: ChannelBinding,
237 pub(crate) load_balance_hosts: LoadBalanceHosts,
238}
239
240impl Default for Config {
241 fn default() -> Config {
242 Config::new()
243 }
244}
245
246impl Config {
247 pub fn new() -> Config {
249 Config {
250 user: None,
251 password: None,
252 dbname: None,
253 options: None,
254 application_name: None,
255 ssl_mode: SslMode::Prefer,
256 ssl_negotiation: SslNegotiation::Postgres,
257 host: vec![],
258 hostaddr: vec![],
259 port: vec![],
260 connect_timeout: None,
261 tcp_user_timeout: None,
262 keepalives: true,
263 #[cfg(not(target_arch = "wasm32"))]
264 keepalive_config: KeepaliveConfig {
265 idle: Duration::from_secs(2 * 60 * 60),
266 interval: None,
267 retries: None,
268 },
269 target_session_attrs: TargetSessionAttrs::Any,
270 channel_binding: ChannelBinding::Prefer,
271 load_balance_hosts: LoadBalanceHosts::Disable,
272 }
273 }
274
275 pub fn user(&mut self, user: impl Into<String>) -> &mut Config {
279 self.user = Some(user.into());
280 self
281 }
282
283 pub fn get_user(&self) -> Option<&str> {
286 self.user.as_deref()
287 }
288
289 pub fn password<T>(&mut self, password: T) -> &mut Config
291 where
292 T: AsRef<[u8]>,
293 {
294 self.password = Some(password.as_ref().to_vec());
295 self
296 }
297
298 pub fn get_password(&self) -> Option<&[u8]> {
301 self.password.as_deref()
302 }
303
304 pub fn dbname(&mut self, dbname: impl Into<String>) -> &mut Config {
308 self.dbname = Some(dbname.into());
309 self
310 }
311
312 pub fn get_dbname(&self) -> Option<&str> {
315 self.dbname.as_deref()
316 }
317
318 pub fn options(&mut self, options: impl Into<String>) -> &mut Config {
320 self.options = Some(options.into());
321 self
322 }
323
324 pub fn get_options(&self) -> Option<&str> {
327 self.options.as_deref()
328 }
329
330 pub fn application_name(&mut self, application_name: impl Into<String>) -> &mut Config {
332 self.application_name = Some(application_name.into());
333 self
334 }
335
336 pub fn get_application_name(&self) -> Option<&str> {
339 self.application_name.as_deref()
340 }
341
342 pub fn ssl_mode(&mut self, ssl_mode: SslMode) -> &mut Config {
346 self.ssl_mode = ssl_mode;
347 self
348 }
349
350 pub fn get_ssl_mode(&self) -> SslMode {
352 self.ssl_mode
353 }
354
355 pub fn ssl_negotiation(&mut self, ssl_negotiation: SslNegotiation) -> &mut Config {
359 self.ssl_negotiation = ssl_negotiation;
360 self
361 }
362
363 pub fn get_ssl_negotiation(&self) -> SslNegotiation {
365 self.ssl_negotiation
366 }
367
368 pub fn host(&mut self, host: impl Into<String>) -> &mut Config {
374 let host = host.into();
375
376 #[cfg(unix)]
377 {
378 if host.starts_with('/') {
379 return self.host_path(host);
380 }
381 }
382
383 self.host.push(Host::Tcp(host));
384 self
385 }
386
387 pub fn get_hosts(&self) -> &[Host] {
389 &self.host
390 }
391
392 pub fn get_hostaddrs(&self) -> &[IpAddr] {
394 self.hostaddr.deref()
395 }
396
397 #[cfg(unix)]
401 pub fn host_path<T>(&mut self, host: T) -> &mut Config
402 where
403 T: AsRef<Path>,
404 {
405 self.host.push(Host::Unix(host.as_ref().to_path_buf()));
406 self
407 }
408
409 pub fn hostaddr(&mut self, hostaddr: IpAddr) -> &mut Config {
414 self.hostaddr.push(hostaddr);
415 self
416 }
417
418 pub fn port(&mut self, port: u16) -> &mut Config {
424 self.port.push(port);
425 self
426 }
427
428 pub fn get_ports(&self) -> &[u16] {
430 &self.port
431 }
432
433 pub fn connect_timeout(&mut self, connect_timeout: Duration) -> &mut Config {
438 self.connect_timeout = Some(connect_timeout);
439 self
440 }
441
442 pub fn get_connect_timeout(&self) -> Option<&Duration> {
445 self.connect_timeout.as_ref()
446 }
447
448 pub fn tcp_user_timeout(&mut self, tcp_user_timeout: Duration) -> &mut Config {
454 self.tcp_user_timeout = Some(tcp_user_timeout);
455 self
456 }
457
458 pub fn get_tcp_user_timeout(&self) -> Option<&Duration> {
461 self.tcp_user_timeout.as_ref()
462 }
463
464 pub fn keepalives(&mut self, keepalives: bool) -> &mut Config {
468 self.keepalives = keepalives;
469 self
470 }
471
472 pub fn get_keepalives(&self) -> bool {
474 self.keepalives
475 }
476
477 #[cfg(not(target_arch = "wasm32"))]
481 pub fn keepalives_idle(&mut self, keepalives_idle: Duration) -> &mut Config {
482 self.keepalive_config.idle = keepalives_idle;
483 self
484 }
485
486 #[cfg(not(target_arch = "wasm32"))]
489 pub fn get_keepalives_idle(&self) -> Duration {
490 self.keepalive_config.idle
491 }
492
493 #[cfg(not(target_arch = "wasm32"))]
498 pub fn keepalives_interval(&mut self, keepalives_interval: Duration) -> &mut Config {
499 self.keepalive_config.interval = Some(keepalives_interval);
500 self
501 }
502
503 #[cfg(not(target_arch = "wasm32"))]
505 pub fn get_keepalives_interval(&self) -> Option<Duration> {
506 self.keepalive_config.interval
507 }
508
509 #[cfg(not(target_arch = "wasm32"))]
513 pub fn keepalives_retries(&mut self, keepalives_retries: u32) -> &mut Config {
514 self.keepalive_config.retries = Some(keepalives_retries);
515 self
516 }
517
518 #[cfg(not(target_arch = "wasm32"))]
520 pub fn get_keepalives_retries(&self) -> Option<u32> {
521 self.keepalive_config.retries
522 }
523
524 pub fn target_session_attrs(
529 &mut self,
530 target_session_attrs: TargetSessionAttrs,
531 ) -> &mut Config {
532 self.target_session_attrs = target_session_attrs;
533 self
534 }
535
536 pub fn get_target_session_attrs(&self) -> TargetSessionAttrs {
538 self.target_session_attrs
539 }
540
541 pub fn channel_binding(&mut self, channel_binding: ChannelBinding) -> &mut Config {
545 self.channel_binding = channel_binding;
546 self
547 }
548
549 pub fn get_channel_binding(&self) -> ChannelBinding {
551 self.channel_binding
552 }
553
554 pub fn load_balance_hosts(&mut self, load_balance_hosts: LoadBalanceHosts) -> &mut Config {
558 self.load_balance_hosts = load_balance_hosts;
559 self
560 }
561
562 pub fn get_load_balance_hosts(&self) -> LoadBalanceHosts {
564 self.load_balance_hosts
565 }
566
567 fn param(&mut self, key: &str, value: &str) -> Result<(), Error> {
568 match key {
569 "user" => {
570 self.user(value);
571 }
572 "password" => {
573 self.password(value);
574 }
575 "dbname" => {
576 self.dbname(value);
577 }
578 "options" => {
579 self.options(value);
580 }
581 "application_name" => {
582 self.application_name(value);
583 }
584 "sslmode" => {
585 let mode = match value {
586 "disable" => SslMode::Disable,
587 "prefer" => SslMode::Prefer,
588 "require" => SslMode::Require,
589 _ => return Err(Error::config_parse(Box::new(InvalidValue("sslmode")))),
590 };
591 self.ssl_mode(mode);
592 }
593 "sslnegotiation" => {
594 let mode = match value {
595 "postgres" => SslNegotiation::Postgres,
596 "direct" => SslNegotiation::Direct,
597 _ => {
598 return Err(Error::config_parse(Box::new(InvalidValue(
599 "sslnegotiation",
600 ))));
601 }
602 };
603 self.ssl_negotiation(mode);
604 }
605 "host" => {
606 for host in value.split(',') {
607 self.host(host);
608 }
609 }
610 "hostaddr" => {
611 for hostaddr in value.split(',') {
612 let addr = hostaddr
613 .parse()
614 .map_err(|_| Error::config_parse(Box::new(InvalidValue("hostaddr"))))?;
615 self.hostaddr(addr);
616 }
617 }
618 "port" => {
619 for port in value.split(',') {
620 let port = if port.is_empty() {
621 5432
622 } else {
623 port.parse()
624 .map_err(|_| Error::config_parse(Box::new(InvalidValue("port"))))?
625 };
626 self.port(port);
627 }
628 }
629 "connect_timeout" => {
630 let timeout = value
631 .parse::<i64>()
632 .map_err(|_| Error::config_parse(Box::new(InvalidValue("connect_timeout"))))?;
633 if timeout > 0 {
634 self.connect_timeout(Duration::from_secs(timeout as u64));
635 }
636 }
637 "tcp_user_timeout" => {
638 let timeout = value
639 .parse::<i64>()
640 .map_err(|_| Error::config_parse(Box::new(InvalidValue("tcp_user_timeout"))))?;
641 if timeout > 0 {
642 self.tcp_user_timeout(Duration::from_secs(timeout as u64));
643 }
644 }
645 #[cfg(not(target_arch = "wasm32"))]
646 "keepalives" => {
647 let keepalives = value
648 .parse::<u64>()
649 .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives"))))?;
650 self.keepalives(keepalives != 0);
651 }
652 #[cfg(not(target_arch = "wasm32"))]
653 "keepalives_idle" => {
654 let keepalives_idle = value
655 .parse::<i64>()
656 .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives_idle"))))?;
657 if keepalives_idle > 0 {
658 self.keepalives_idle(Duration::from_secs(keepalives_idle as u64));
659 }
660 }
661 #[cfg(not(target_arch = "wasm32"))]
662 "keepalives_interval" => {
663 let keepalives_interval = value.parse::<i64>().map_err(|_| {
664 Error::config_parse(Box::new(InvalidValue("keepalives_interval")))
665 })?;
666 if keepalives_interval > 0 {
667 self.keepalives_interval(Duration::from_secs(keepalives_interval as u64));
668 }
669 }
670 #[cfg(not(target_arch = "wasm32"))]
671 "keepalives_retries" => {
672 let keepalives_retries = value.parse::<u32>().map_err(|_| {
673 Error::config_parse(Box::new(InvalidValue("keepalives_retries")))
674 })?;
675 self.keepalives_retries(keepalives_retries);
676 }
677 "target_session_attrs" => {
678 let target_session_attrs = match value {
679 "any" => TargetSessionAttrs::Any,
680 "read-write" => TargetSessionAttrs::ReadWrite,
681 "read-only" => TargetSessionAttrs::ReadOnly,
682 _ => {
683 return Err(Error::config_parse(Box::new(InvalidValue(
684 "target_session_attrs",
685 ))));
686 }
687 };
688 self.target_session_attrs(target_session_attrs);
689 }
690 "channel_binding" => {
691 let channel_binding = match value {
692 "disable" => ChannelBinding::Disable,
693 "prefer" => ChannelBinding::Prefer,
694 "require" => ChannelBinding::Require,
695 _ => {
696 return Err(Error::config_parse(Box::new(InvalidValue(
697 "channel_binding",
698 ))));
699 }
700 };
701 self.channel_binding(channel_binding);
702 }
703 "load_balance_hosts" => {
704 let load_balance_hosts = match value {
705 "disable" => LoadBalanceHosts::Disable,
706 "random" => LoadBalanceHosts::Random,
707 _ => {
708 return Err(Error::config_parse(Box::new(InvalidValue(
709 "load_balance_hosts",
710 ))));
711 }
712 };
713 self.load_balance_hosts(load_balance_hosts);
714 }
715 key => {
716 return Err(Error::config_parse(Box::new(UnknownOption(
717 key.to_string(),
718 ))));
719 }
720 }
721
722 Ok(())
723 }
724
725 #[cfg(feature = "runtime")]
729 pub async fn connect<T>(&self, tls: T) -> Result<(Client, Connection<Socket, T::Stream>), Error>
730 where
731 T: MakeTlsConnect<Socket>,
732 {
733 connect(tls, self).await
734 }
735
736 pub async fn connect_raw<S, T>(
740 &self,
741 stream: S,
742 tls: T,
743 ) -> Result<(Client, Connection<S, T::Stream>), Error>
744 where
745 S: AsyncRead + AsyncWrite + Unpin,
746 T: TlsConnect<S>,
747 {
748 connect_raw(stream, tls, true, self).await
749 }
750}
751
752impl FromStr for Config {
753 type Err = Error;
754
755 fn from_str(s: &str) -> Result<Config, Error> {
756 match UrlParser::parse(s)? {
757 Some(config) => Ok(config),
758 None => Parser::parse(s),
759 }
760 }
761}
762
763impl fmt::Debug for Config {
765 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
766 struct Redaction {}
767 impl fmt::Debug for Redaction {
768 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
769 write!(f, "_")
770 }
771 }
772
773 let mut config_dbg = &mut f.debug_struct("Config");
774 config_dbg = config_dbg
775 .field("user", &self.user)
776 .field("password", &self.password.as_ref().map(|_| Redaction {}))
777 .field("dbname", &self.dbname)
778 .field("options", &self.options)
779 .field("application_name", &self.application_name)
780 .field("ssl_mode", &self.ssl_mode)
781 .field("host", &self.host)
782 .field("hostaddr", &self.hostaddr)
783 .field("port", &self.port)
784 .field("connect_timeout", &self.connect_timeout)
785 .field("tcp_user_timeout", &self.tcp_user_timeout)
786 .field("keepalives", &self.keepalives);
787
788 #[cfg(not(target_arch = "wasm32"))]
789 {
790 config_dbg = config_dbg
791 .field("keepalives_idle", &self.keepalive_config.idle)
792 .field("keepalives_interval", &self.keepalive_config.interval)
793 .field("keepalives_retries", &self.keepalive_config.retries);
794 }
795
796 config_dbg
797 .field("target_session_attrs", &self.target_session_attrs)
798 .field("channel_binding", &self.channel_binding)
799 .field("load_balance_hosts", &self.load_balance_hosts)
800 .finish()
801 }
802}
803
804#[derive(Debug)]
805struct UnknownOption(String);
806
807impl fmt::Display for UnknownOption {
808 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
809 write!(fmt, "unknown option `{}`", self.0)
810 }
811}
812
813impl error::Error for UnknownOption {}
814
815#[derive(Debug)]
816struct InvalidValue(&'static str);
817
818impl fmt::Display for InvalidValue {
819 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
820 write!(fmt, "invalid value for option `{}`", self.0)
821 }
822}
823
824impl error::Error for InvalidValue {}
825
826struct Parser<'a> {
827 s: &'a str,
828 it: iter::Peekable<str::CharIndices<'a>>,
829}
830
831impl<'a> Parser<'a> {
832 fn parse(s: &'a str) -> Result<Config, Error> {
833 let mut parser = Parser {
834 s,
835 it: s.char_indices().peekable(),
836 };
837
838 let mut config = Config::new();
839
840 while let Some((key, value)) = parser.parameter()? {
841 config.param(key, &value)?;
842 }
843
844 Ok(config)
845 }
846
847 fn skip_ws(&mut self) {
848 self.take_while(char::is_whitespace);
849 }
850
851 fn take_while<F>(&mut self, f: F) -> &'a str
852 where
853 F: Fn(char) -> bool,
854 {
855 let start = match self.it.peek() {
856 Some(&(i, _)) => i,
857 None => return "",
858 };
859
860 loop {
861 match self.it.peek() {
862 Some(&(_, c)) if f(c) => {
863 self.it.next();
864 }
865 Some(&(i, _)) => return &self.s[start..i],
866 None => return &self.s[start..],
867 }
868 }
869 }
870
871 fn eat(&mut self, target: char) -> Result<(), Error> {
872 match self.it.next() {
873 Some((_, c)) if c == target => Ok(()),
874 Some((i, c)) => {
875 let m =
876 format!("unexpected character at byte {i}: expected `{target}` but got `{c}`");
877 Err(Error::config_parse(m.into()))
878 }
879 None => Err(Error::config_parse("unexpected EOF".into())),
880 }
881 }
882
883 fn eat_if(&mut self, target: char) -> bool {
884 match self.it.peek() {
885 Some(&(_, c)) if c == target => {
886 self.it.next();
887 true
888 }
889 _ => false,
890 }
891 }
892
893 fn keyword(&mut self) -> Option<&'a str> {
894 let s = self.take_while(|c| match c {
895 c if c.is_whitespace() => false,
896 '=' => false,
897 _ => true,
898 });
899
900 if s.is_empty() { None } else { Some(s) }
901 }
902
903 fn value(&mut self) -> Result<String, Error> {
904 let value = if self.eat_if('\'') {
905 let value = self.quoted_value()?;
906 self.eat('\'')?;
907 value
908 } else {
909 self.simple_value()?
910 };
911
912 Ok(value)
913 }
914
915 fn simple_value(&mut self) -> Result<String, Error> {
916 let mut value = String::new();
917
918 while let Some(&(_, c)) = self.it.peek() {
919 if c.is_whitespace() {
920 break;
921 }
922
923 self.it.next();
924 if c == '\\' {
925 if let Some((_, c2)) = self.it.next() {
926 value.push(c2);
927 }
928 } else {
929 value.push(c);
930 }
931 }
932
933 if value.is_empty() {
934 return Err(Error::config_parse("unexpected EOF".into()));
935 }
936
937 Ok(value)
938 }
939
940 fn quoted_value(&mut self) -> Result<String, Error> {
941 let mut value = String::new();
942
943 while let Some(&(_, c)) = self.it.peek() {
944 if c == '\'' {
945 return Ok(value);
946 }
947
948 self.it.next();
949 if c == '\\' {
950 if let Some((_, c2)) = self.it.next() {
951 value.push(c2);
952 }
953 } else {
954 value.push(c);
955 }
956 }
957
958 Err(Error::config_parse(
959 "unterminated quoted connection parameter value".into(),
960 ))
961 }
962
963 fn parameter(&mut self) -> Result<Option<(&'a str, String)>, Error> {
964 self.skip_ws();
965 let keyword = match self.keyword() {
966 Some(keyword) => keyword,
967 None => return Ok(None),
968 };
969 self.skip_ws();
970 self.eat('=')?;
971 self.skip_ws();
972 let value = self.value()?;
973
974 Ok(Some((keyword, value)))
975 }
976}
977
978struct UrlParser<'a> {
980 s: &'a str,
981 config: Config,
982}
983
984impl<'a> UrlParser<'a> {
985 fn parse(s: &'a str) -> Result<Option<Config>, Error> {
986 let s = match Self::remove_url_prefix(s) {
987 Some(s) => s,
988 None => return Ok(None),
989 };
990
991 let mut parser = UrlParser {
992 s,
993 config: Config::new(),
994 };
995
996 parser.parse_credentials()?;
997 parser.parse_host()?;
998 parser.parse_path()?;
999 parser.parse_params()?;
1000
1001 Ok(Some(parser.config))
1002 }
1003
1004 fn remove_url_prefix(s: &str) -> Option<&str> {
1005 for prefix in &["postgres://", "postgresql://"] {
1006 if let Some(stripped) = s.strip_prefix(prefix) {
1007 return Some(stripped);
1008 }
1009 }
1010
1011 None
1012 }
1013
1014 fn take_until(&mut self, end: &[char]) -> Option<&'a str> {
1015 match self.s.find(end) {
1016 Some(pos) => {
1017 let (head, tail) = self.s.split_at(pos);
1018 self.s = tail;
1019 Some(head)
1020 }
1021 None => None,
1022 }
1023 }
1024
1025 fn take_all(&mut self) -> &'a str {
1026 mem::take(&mut self.s)
1027 }
1028
1029 fn eat_byte(&mut self) {
1030 self.s = &self.s[1..];
1031 }
1032
1033 fn parse_credentials(&mut self) -> Result<(), Error> {
1034 let creds = match self.take_until(&['@']) {
1035 Some(creds) => creds,
1036 None => return Ok(()),
1037 };
1038 self.eat_byte();
1039
1040 let mut it = creds.splitn(2, ':');
1041 let user = self.decode(it.next().unwrap())?;
1042 self.config.user(user);
1043
1044 if let Some(password) = it.next() {
1045 let password = Cow::from(percent_encoding::percent_decode(password.as_bytes()));
1046 self.config.password(password);
1047 }
1048
1049 Ok(())
1050 }
1051
1052 fn parse_host(&mut self) -> Result<(), Error> {
1053 let host = match self.take_until(&['/', '?']) {
1054 Some(host) => host,
1055 None => self.take_all(),
1056 };
1057
1058 if host.is_empty() {
1059 return Ok(());
1060 }
1061
1062 for chunk in host.split(',') {
1063 let (host, port) = if chunk.starts_with('[') {
1064 let idx = match chunk.find(']') {
1065 Some(idx) => idx,
1066 None => return Err(Error::config_parse(InvalidValue("host").into())),
1067 };
1068
1069 let host = &chunk[1..idx];
1070 let remaining = &chunk[idx + 1..];
1071 let port = if let Some(port) = remaining.strip_prefix(':') {
1072 Some(port)
1073 } else if remaining.is_empty() {
1074 None
1075 } else {
1076 return Err(Error::config_parse(InvalidValue("host").into()));
1077 };
1078
1079 (host, port)
1080 } else {
1081 let mut it = chunk.splitn(2, ':');
1082 (it.next().unwrap(), it.next())
1083 };
1084
1085 self.host_param(host)?;
1086 let port = self.decode(port.unwrap_or("5432"))?;
1087 self.config.param("port", &port)?;
1088 }
1089
1090 Ok(())
1091 }
1092
1093 fn parse_path(&mut self) -> Result<(), Error> {
1094 if !self.s.starts_with('/') {
1095 return Ok(());
1096 }
1097 self.eat_byte();
1098
1099 let dbname = match self.take_until(&['?']) {
1100 Some(dbname) => dbname,
1101 None => self.take_all(),
1102 };
1103
1104 if !dbname.is_empty() {
1105 self.config.dbname(self.decode(dbname)?);
1106 }
1107
1108 Ok(())
1109 }
1110
1111 fn parse_params(&mut self) -> Result<(), Error> {
1112 if !self.s.starts_with('?') {
1113 return Ok(());
1114 }
1115 self.eat_byte();
1116
1117 while !self.s.is_empty() {
1118 let key = match self.take_until(&['=']) {
1119 Some(key) => self.decode(key)?,
1120 None => return Err(Error::config_parse("unterminated parameter".into())),
1121 };
1122 self.eat_byte();
1123
1124 let value = match self.take_until(&['&']) {
1125 Some(value) => {
1126 self.eat_byte();
1127 value
1128 }
1129 None => self.take_all(),
1130 };
1131
1132 if key == "host" {
1133 self.host_param(value)?;
1134 } else {
1135 let value = self.decode(value)?;
1136 self.config.param(&key, &value)?;
1137 }
1138 }
1139
1140 Ok(())
1141 }
1142
1143 #[cfg(unix)]
1144 fn host_param(&mut self, s: &str) -> Result<(), Error> {
1145 let decoded = Cow::from(percent_encoding::percent_decode(s.as_bytes()));
1146 if decoded.first() == Some(&b'/') {
1147 self.config.host_path(OsStr::from_bytes(&decoded));
1148 } else {
1149 let decoded = str::from_utf8(&decoded).map_err(|e| Error::config_parse(Box::new(e)))?;
1150 self.config.host(decoded);
1151 }
1152
1153 Ok(())
1154 }
1155
1156 #[cfg(not(unix))]
1157 fn host_param(&mut self, s: &str) -> Result<(), Error> {
1158 let s = self.decode(s)?;
1159 self.config.param("host", &s)
1160 }
1161
1162 fn decode(&self, s: &'a str) -> Result<Cow<'a, str>, Error> {
1163 percent_encoding::percent_decode(s.as_bytes())
1164 .decode_utf8()
1165 .map_err(|e| Error::config_parse(e.into()))
1166 }
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171 use std::net::IpAddr;
1172
1173 use crate::{Config, config::Host};
1174
1175 #[test]
1176 fn test_simple_parsing() {
1177 let s = "user=pass_user dbname=postgres host=host1,host2 hostaddr=127.0.0.1,127.0.0.2 port=26257";
1178 let config = s.parse::<Config>().unwrap();
1179 assert_eq!(Some("pass_user"), config.get_user());
1180 assert_eq!(Some("postgres"), config.get_dbname());
1181 assert_eq!(
1182 [
1183 Host::Tcp("host1".to_string()),
1184 Host::Tcp("host2".to_string())
1185 ],
1186 config.get_hosts(),
1187 );
1188
1189 assert_eq!(
1190 [
1191 "127.0.0.1".parse::<IpAddr>().unwrap(),
1192 "127.0.0.2".parse::<IpAddr>().unwrap()
1193 ],
1194 config.get_hostaddrs(),
1195 );
1196
1197 assert_eq!(1, 1);
1198 }
1199
1200 #[test]
1201 fn test_invalid_hostaddr_parsing() {
1202 let s = "user=pass_user dbname=postgres host=host1 hostaddr=127.0.0 port=26257";
1203 s.parse::<Config>().err().unwrap();
1204 }
1205}