Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions sqlx-postgres/src/options/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::borrow::Cow;
use std::env::var;
use std::ffi::OsStr;
use std::fmt::{self, Display, Write};
use std::path::{Path, PathBuf};

Expand All @@ -20,6 +21,7 @@ pub struct PgConnectOptions {
pub(crate) socket: Option<PathBuf>,
pub(crate) username: String,
pub(crate) password: Option<String>,
pub(crate) passfile_paths: Vec<PathBuf>,
pub(crate) database: Option<String>,
pub(crate) ssl_mode: PgSslMode,
pub(crate) ssl_root_cert: Option<CertificateInput>,
Expand Down Expand Up @@ -74,6 +76,7 @@ impl PgConnectOptions {
socket: None,
username,
password: var("PGPASSWORD").ok(),
passfile_paths: vec![],
database,
ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from),
Expand All @@ -100,6 +103,7 @@ impl PgConnectOptions {
self.port,
&self.username,
self.database.as_deref(),
&self.passfile_paths,
);
}

Expand Down Expand Up @@ -184,6 +188,23 @@ impl PgConnectOptions {
self
}

/// Sets the paths to try when looking for the pgpass file.
///
/// # Example
///
/// ```rust
/// # use sqlx_postgres::PgConnectOptions;
/// let options = PgConnectOptions::new()
/// .passfile_paths(&["/non/default/pgpass"]);
/// ```
pub fn passfile_paths<P>(mut self, paths: impl IntoIterator<Item = P>) -> Self
where
P: Into<PathBuf> + AsRef<OsStr>,
{
self.passfile_paths = paths.into_iter().map(Into::into).collect();
self
}

/// Sets the database name. Defaults to be the same as the user name.
///
/// # Example
Expand Down
17 changes: 17 additions & 0 deletions sqlx-postgres/src/options/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{PgConnectOptions, PgSslMode};
use sqlx_core::percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC};
use sqlx_core::Url;
use std::net::IpAddr;
use std::path::PathBuf;
use std::str::FromStr;

impl PgConnectOptions {
Expand Down Expand Up @@ -87,6 +88,8 @@ impl PgConnectOptions {

"password" => options = options.password(&value),

"passfile" => options.passfile_paths.insert(0, PathBuf::from(&*value)),

"application_name" => options = options.application_name(&value),

"options" => {
Expand Down Expand Up @@ -242,6 +245,20 @@ fn it_parses_password_correctly_from_parameter() {
assert_eq!(Some("some_pass"), opts.password.as_deref());
}

#[test]
fn it_parses_passfile_correctly_from_parameter() {
let url = "postgres:///?passfile=/non%20default/pgpass&passfile=.pgpass";
let opts = PgConnectOptions::from_str(url).unwrap();

assert_eq!(
vec![
PathBuf::from(".pgpass"),
PathBuf::from("/non default/pgpass"),
],
opts.passfile_paths
);
}

#[test]
fn it_parses_application_name_correctly_from_parameter() {
let url = "postgres:///?application_name=some_name";
Expand Down
59 changes: 36 additions & 23 deletions sqlx-postgres/src/options/pgpass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,59 @@ use std::borrow::Cow;
use std::env::var_os;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::path::{Path, PathBuf};

/// try to load a password from the various pgpass file locations
/// Try to load a password from the various pgpass file locations.
///
/// Loading is attempted in the following order:
/// 1. Path given via the `PGPASSFILE` environment variable.
/// 2. Paths given via custom_paths.
/// 3. Default path (`~/.pgpass` on Linux and `%APPDATA%/postgres/pgpass.conf`
/// on Windows)
pub fn load_password(
host: &str,
port: u16,
username: &str,
database: Option<&str>,
custom_paths: &[impl AsRef<Path>],
) -> Option<String> {
let custom_file = var_os("PGPASSFILE");
if let Some(file) = custom_file {
if let Some(password) =
load_password_from_file(PathBuf::from(file), host, port, username, database)
{
return Some(password);
}
}
let env_path = var_os("PGPASSFILE").map(PathBuf::from);
let default_path = default_path();

let path_iter = env_path
.as_deref()
.into_iter()
.chain(custom_paths.iter().map(AsRef::as_ref))
.chain(default_path.as_deref());

path_iter
.filter_map(|path| load_password_from_file(path, host, port, username, database))
.next()
}

#[cfg(not(target_os = "windows"))]
fn default_path() -> Option<PathBuf> {
home::home_dir().map(|path| path.join(".pgpass"))
}

#[cfg(target_os = "windows")]
fn default_path() -> Option<PathBuf> {
use etcetera::BaseStrategy;

#[cfg(not(target_os = "windows"))]
let default_file = home::home_dir().map(|path| path.join(".pgpass"));
#[cfg(target_os = "windows")]
let default_file = {
use etcetera::BaseStrategy;

etcetera::base_strategy::Windows::new()
.ok()
.map(|basedirs| basedirs.data_dir().join("postgres").join("pgpass.conf"))
};
load_password_from_file(default_file?, host, port, username, database)
etcetera::base_strategy::Windows::new()
.ok()
.map(|basedirs| basedirs.data_dir().join("postgres").join("pgpass.conf"))
}

/// try to extract a password from a pgpass file
fn load_password_from_file(
path: PathBuf,
path: &Path,
host: &str,
port: u16,
username: &str,
database: Option<&str>,
) -> Option<String> {
let file = File::open(&path)
let file = File::open(path)
.map_err(|e| {
match e.kind() {
std::io::ErrorKind::NotFound => {
Expand Down
Loading