fix: expand tilde in file path validation for SSH keys and TLS certs

`validate_file_path` now expands leading `~` to the user's home directory
before checking file existence, so paths like `~/.ssh/id_rsa` work on macOS
and Linux. Also fixes error messages that incorrectly said "Database file".
This commit is contained in:
t8y2
2026-06-08 11:14:11 +08:00
parent b200d5b041
commit c38994d02b
+69 -6
View File
@@ -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<String> {
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('~'));
}
}