diff --git a/Cargo.lock b/Cargo.lock index 6eaa136..340e88f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,13 +461,19 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -650,12 +656,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -839,6 +845,7 @@ name = "nextcloud-config-parser" version = "0.14.1" dependencies = [ "form_urlencoded", + "indexmap", "itertools", "miette", "php-literal-parser", @@ -1393,7 +1400,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown", + "hashbrown 0.15.2", "hashlink", "indexmap", "log", diff --git a/Cargo.toml b/Cargo.toml index 7697ada..1396d71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ miette = "7.4.0" urlencoding = "2.1.3" form_urlencoded = "1.2.1" itertools = "0.14.0" +indexmap = "2.13.0" [dev-dependencies] miette = { version = "7.4.0", features = ["fancy"] } diff --git a/src/lib.rs b/src/lib.rs index 333cf9d..8989a01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ mod nc; use form_urlencoded::Serializer; +use indexmap::IndexMap; use itertools::Either; use miette::Diagnostic; use std::iter::once; @@ -192,7 +193,7 @@ pub enum NotAConfigError { NotAnArray(PathBuf), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum SslOptions { Enabled { key: String, @@ -204,7 +205,7 @@ pub enum SslOptions { Default, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Database { Sqlite { database: PathBuf, @@ -221,11 +222,11 @@ pub enum Database { username: String, password: String, connect: DbConnect, - ssl_options: SslOptions, + options: IndexMap, }, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum DbConnect { Tcp { host: String, port: u16 }, Socket(PathBuf), @@ -300,21 +301,11 @@ impl Database { username, password, connect, - ssl_options, + options, } => { let mut params = Serializer::new(String::new()); - match ssl_options { - SslOptions::Default => {} - SslOptions::Disabled => { - params.append_pair("sslmode", "disable"); - } - SslOptions::Enabled { ca, verify, .. } => { - params.append_pair( - "ssl-mode", - if *verify { "verify-full" } else { "verify-ca" }, - ); - params.append_pair("sslrootcert", ca.as_str()); - } + for (key, value) in options { + params.append_pair(key.as_str(), value.as_str()); } let (host, port) = match connect { DbConnect::Socket(socket) => { diff --git a/src/nc.rs b/src/nc.rs index 02755f2..a1a2426 100644 --- a/src/nc.rs +++ b/src/nc.rs @@ -3,6 +3,7 @@ use crate::{ RedisClusterConnectionInfo, RedisConnectionInfo, RedisTlsParams, Result, SslOptions, }; use crate::{RedisConfig, RedisConnectionAddr}; +use indexmap::IndexMap; use php_literal_parser::Value; use std::collections::HashMap; use std::fs::DirEntry; @@ -215,8 +216,10 @@ fn parse_db_options(parsed: &Value) -> Result { Some("pgsql") => { let username = parsed["dbuser"].as_str().ok_or(DbError::NoUsername)?; let password = parsed["dbpassword"].as_str().unwrap_or_default(); + let db_host = parsed["dbhost"].as_str().unwrap_or_default(); + let mut host_parts = db_host.split(';'); let (mut connect, disable_ssl) = - match split_host(parsed["dbhost"].as_str().unwrap_or_default()) { + match split_host(host_parts.next().expect("empty split")) { (addr, None, None) => ( DbConnect::Tcp { host: addr.into(), @@ -248,6 +251,14 @@ fn parse_db_options(parsed: &Value) -> Result { unreachable!() } }; + + let mut options = IndexMap::new(); + for part in host_parts { + if let Some((key, value)) = part.split_once('=') { + options.insert(key.into(), value.into()); + } + } + if let Some(port) = parsed["dbport"].clone().into_int() { if let DbConnect::Tcp { port: connect_port, .. @@ -256,20 +267,21 @@ fn parse_db_options(parsed: &Value) -> Result { *connect_port = port as u16; } } - let database = parsed["dbname"].as_str().unwrap_or("owncloud"); + if disable_ssl { + options.insert("sslmode".into(), "disable".into()); + } - let ssl_options = if disable_ssl { - SslOptions::Disabled - } else { - SslOptions::Default - }; + let database = parsed["dbname"] + .as_str() + .or_else(|| options.get("dbname").map(String::as_str)) + .unwrap_or("owncloud"); Ok(Database::Postgres { database: database.into(), username: username.into(), password: password.into(), connect, - ssl_options, + options, }) } Some("sqlite3") | Some("sqlite") | None => { @@ -377,3 +389,36 @@ fn test_redis_empty_password_none() { let redis = parse_redis_options(&config, "redis"); assert_eq!(redis.passwd(), None); } + +#[test] +fn test_postgres_options() { + use indexmap::indexmap; + + let config = + php_literal_parser::from_str(r#"[ + 'dbtype' => 'pgsql', + 'dbhost' => 'db.example.org;sslmode=verify-ca;sslrootcert=/etc/ssl/certs/ca-certificates.crt;dbname=nextcloud', + 'dbuser' => 'nextcloud', + 'dbpassword' => 'nextcloud', + ]"#) + .unwrap(); + let db = parse_db_options(&config).unwrap(); + assert_eq!( + db, + Database::Postgres { + database: "nextcloud".to_string(), + username: "nextcloud".to_string(), + password: "nextcloud".to_string(), + connect: DbConnect::Tcp { + host: "db.example.org".into(), + port: 5432, + }, + options: indexmap! { + "sslmode".into() => "verify-ca".into(), + "sslrootcert".into() => "/etc/ssl/certs/ca-certificates.crt".into(), + "dbname".into() => "nextcloud".into(), + }, + } + ); + assert_eq!(db.url(), "postgresql://nextcloud:nextcloud@db.example.org/nextcloud?sslmode=verify-ca&sslrootcert=/etc/ssl/certs/ca-certificates.crt&dbname=nextcloud"); +} diff --git a/tests/configs.rs b/tests/configs.rs index e9ac2f6..8c83fa1 100644 --- a/tests/configs.rs +++ b/tests/configs.rs @@ -4,6 +4,7 @@ use nextcloud_config_parser::{ }; use std::fmt::Debug; +use indexmap::{indexmap, IndexMap}; use redis::{ConnectionAddr, ConnectionInfo}; use sqlx::mysql::{MySqlConnectOptions, MySqlSslMode}; use sqlx::sqlite::SqliteConnectOptions; @@ -249,7 +250,7 @@ fn test_parse_postgres_socket() { username: "redacted".to_string(), password: "redacted".to_string(), connect: DbConnect::Socket("/var/run/postgresql".into()), - ssl_options: SslOptions::Default, + options: IndexMap::default(), }, &config.database, ); @@ -279,7 +280,7 @@ fn test_parse_postgres_socket_empty_hostname() { username: "nextcloud".to_string(), password: "redacted".to_string(), connect: DbConnect::Socket("/run/postgresql".into()), - ssl_options: SslOptions::Default, + options: IndexMap::default(), }, &config.database, ); @@ -309,7 +310,7 @@ fn test_parse_postgres_socket_no_pass() { username: "redacted".to_string(), password: "".to_string(), connect: DbConnect::Socket("/var/run/postgresql".into()), - ssl_options: SslOptions::Default, + options: IndexMap::default(), }, &config.database, ); @@ -337,7 +338,7 @@ fn test_parse_postgres_socket_folder() { username: "redacted".to_string(), password: "redacted".to_string(), connect: DbConnect::Socket("/var/run/postgresql".into()), - ssl_options: SslOptions::Default, + options: IndexMap::default(), }, &config.database, ); @@ -590,7 +591,9 @@ fn test_parse_postgres_ip() { host: "1.2.3.4".to_string(), port: 5432, }, - ssl_options: SslOptions::Disabled, + options: indexmap! { + "sslmode".into() => "disable".into() + }, }, &config.database, ); @@ -623,7 +626,7 @@ fn test_parse_postgres_fqdn() { host: "pg.example.com".to_string(), port: 5432, }, - ssl_options: SslOptions::Default, + options: IndexMap::default(), }, &config.database, ); @@ -691,7 +694,9 @@ fn test_parse_postgres_escaped_credentials() { host: "1.2.3.4".to_string(), port: 5432, }, - ssl_options: SslOptions::Disabled, + options: indexmap! { + "sslmode".into() => "disable".into() + }, }, &config.database, );