diff --git a/crates/dbx-core/src/db/file_validator.rs b/crates/dbx-core/src/db/file_validator.rs index 48f4ff0739..0a5cc8e778 100644 --- a/crates/dbx-core/src/db/file_validator.rs +++ b/crates/dbx-core/src/db/file_validator.rs @@ -1,5 +1,28 @@ use std::path::Path; +fn expand_tilde(path: &str) -> String { + if !path.starts_with('~') { + return path.to_string(); + } + if path.len() == 1 { + // path is "~" + return home_dir().unwrap_or_else(|| path.to_string()); + } + if path.as_bytes().get(1) == Some(&b'/') { + // path is "~/..." + return match home_dir() { + Some(home) => home + &path[1..], + None => path.to_string(), + }; + } + // "~user" or "~something-without-slash" — not a home-dir expansion + path.to_string() +} + +fn home_dir() -> Option { + std::env::var("HOME").or_else(|_| std::env::var("USERPROFILE")).ok() +} + /// Validates a file path for database connections. /// /// Performs comprehensive checks including: @@ -22,30 +45,31 @@ where { // Check if path is empty if path.is_empty() { - return Err("Database file path cannot be empty".to_string()); + return Err("File path cannot be empty".to_string()); } // Check if path contains invalid characters if path.contains('\0') { - return Err("Database file path contains null characters".to_string()); + return Err("File path contains null characters".to_string()); } - let path_obj = Path::new(path); + let expanded = expand_tilde(path); + let path_obj = Path::new(&expanded); // For non-network paths, perform file system checks if !is_network_path(path) { if !path_obj.exists() { - return Err(format!("Database file does not exist: {}", path)); + return Err(format!("File does not exist: {}", path)); } // Check if path is actually a file, not a directory if path_obj.is_dir() { - return Err(format!("Database file path is a directory, not a file: {}", path)); + return Err(format!("File path is a directory, not a file: {}", path)); } // Check if path is a valid file if !path_obj.is_file() { - return Err(format!("Database file path is not a valid file: {}", path)); + return Err(format!("File path is not a valid file: {}", path)); } } @@ -86,4 +110,43 @@ mod tests { assert!(result.is_err()); assert!(result.unwrap_err().contains("does not exist")); } + + #[test] + fn test_tilde_expands_to_home() { + let home = std::env::var("HOME").unwrap_or_default(); + if home.is_empty() { + return; // skip if no HOME + } + let result = validate_file_path(&format!("{}/.bashrc", home), is_network_path_test); + // $HOME/.bashrc may or may not exist, but the path shouldn't contain literal ~ + // We just verify no error mentioning tilde + if let Err(e) = &result { + assert!(!e.contains('~'), "Error should not contain literal tilde: {e}"); + } + } + + #[test] + fn test_tilde_only() { + let home = std::env::var("HOME").unwrap_or_default(); + if home.is_empty() { + return; + } + let result = validate_file_path("~", |_| true); // treat as network to skip fs check + assert!(result.is_ok()); + } + + #[test] + fn test_no_tilde_unchanged() { + let result = validate_file_path("/absolute/path", is_network_path_test); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("/absolute/path")); + } + + #[test] + fn test_tilde_without_slash_not_expanded() { + // ~user form is not expanded, treated literally + let result = validate_file_path("~nonexistentuser", is_network_path_test); + assert!(result.is_err()); + assert!(result.unwrap_err().contains('~')); + } }