From f79d321b7829eb7872efdb12315bc285b68b8dee Mon Sep 17 00:00:00 2001 From: Rob Gilson Date: Wed, 29 Dec 2021 16:00:49 -0500 Subject: [PATCH] fix(postgres): Match `~/.pgpass` password after URL parsing and fix user and database ordering (#1566) * fix(postgres): Fixes pgpass so it applies after parsing and matches usernames & databases correctly * chore: Updated unit test * refactor: Previous semantics of PgConnectOptions::default() * refactor: formatting --- sqlx-core/src/postgres/options/mod.rs | 25 ++++++++++++++++++------ sqlx-core/src/postgres/options/parse.rs | 4 +++- sqlx-core/src/postgres/options/pgpass.rs | 14 +++++++------ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/sqlx-core/src/postgres/options/mod.rs b/sqlx-core/src/postgres/options/mod.rs index 1770959f..72a4b5cd 100644 --- a/sqlx-core/src/postgres/options/mod.rs +++ b/sqlx-core/src/postgres/options/mod.rs @@ -89,7 +89,7 @@ pub struct PgConnectOptions { impl Default for PgConnectOptions { fn default() -> Self { - Self::new() + Self::new_without_pgpass().apply_pgpass() } } @@ -115,6 +115,10 @@ impl PgConnectOptions { /// let options = PgConnectOptions::new(); /// ``` pub fn new() -> Self { + Self::new_without_pgpass().apply_pgpass() + } + + pub fn new_without_pgpass() -> Self { let port = var("PGPORT") .ok() .and_then(|v| v.parse().ok()) @@ -126,16 +130,12 @@ impl PgConnectOptions { let database = var("PGDATABASE").ok(); - let password = var("PGPASSWORD") - .ok() - .or_else(|| pgpass::load_password(&host, port, &username, database.as_deref())); - PgConnectOptions { port, host, socket: None, username, - password, + password: var("PGPASSWORD").ok(), database, ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), ssl_mode: var("PGSSLMODE") @@ -148,6 +148,19 @@ impl PgConnectOptions { } } + pub(crate) fn apply_pgpass(mut self) -> Self { + if self.password.is_none() { + self.password = pgpass::load_password( + &self.host, + self.port, + &self.username, + self.database.as_deref(), + ); + } + + self + } + /// Sets the name of the host to connect to. /// /// If a host name begins with a slash, it specifies diff --git a/sqlx-core/src/postgres/options/parse.rs b/sqlx-core/src/postgres/options/parse.rs index 5c5cd71e..04d2f222 100644 --- a/sqlx-core/src/postgres/options/parse.rs +++ b/sqlx-core/src/postgres/options/parse.rs @@ -11,7 +11,7 @@ impl FromStr for PgConnectOptions { fn from_str(s: &str) -> Result { let url: Url = s.parse().map_err(Error::config)?; - let mut options = Self::default(); + let mut options = Self::new_without_pgpass(); if let Some(host) = url.host_str() { let host_decoded = percent_decode_str(host); @@ -89,6 +89,8 @@ impl FromStr for PgConnectOptions { } } + let options = options.apply_pgpass(); + Ok(options) } } diff --git a/sqlx-core/src/postgres/options/pgpass.rs b/sqlx-core/src/postgres/options/pgpass.rs index 98cef7c5..253bc10c 100644 --- a/sqlx-core/src/postgres/options/pgpass.rs +++ b/sqlx-core/src/postgres/options/pgpass.rs @@ -90,13 +90,15 @@ fn load_password_from_line( ) -> Option { let whole_line = line; + // Pgpass line ordering: hostname, port, database, username, password + // See: https://www.postgresql.org/docs/9.3/libpq-pgpass.html match line.trim_start().chars().next() { None | Some('#') => None, _ => { matches_next_field(whole_line, &mut line, host)?; matches_next_field(whole_line, &mut line, &port.to_string())?; - matches_next_field(whole_line, &mut line, username)?; matches_next_field(whole_line, &mut line, database.unwrap_or_default())?; + matches_next_field(whole_line, &mut line, username)?; Some(line.to_owned()) } } @@ -219,7 +221,7 @@ mod tests { // normal assert_eq!( load_password_from_line( - "localhost:5432:foo:bar:baz", + "localhost:5432:bar:foo:baz", "localhost", 5432, "foo", @@ -229,19 +231,19 @@ mod tests { ); // wildcard assert_eq!( - load_password_from_line("*:5432:foo:bar:baz", "localhost", 5432, "foo", Some("bar")), + load_password_from_line("*:5432:bar:foo:baz", "localhost", 5432, "foo", Some("bar")), Some("baz".to_owned()) ); // accept wildcard with missing db assert_eq!( - load_password_from_line("localhost:5432:foo:*:baz", "localhost", 5432, "foo", None), + load_password_from_line("localhost:5432:*:foo:baz", "localhost", 5432, "foo", None), Some("baz".to_owned()) ); // doesn't match assert_eq!( load_password_from_line( - "thishost:5432:foo:bar:baz", + "thishost:5432:bar:foo:baz", "thathost", 5432, "foo", @@ -252,7 +254,7 @@ mod tests { // malformed entry assert_eq!( load_password_from_line( - "localhost:5432:foo:bar", + "localhost:5432:bar:foo", "localhost", 5432, "foo",