diff --git a/src/file_transfer.rs b/src/file_transfer.rs index 03165db..63f5ff1 100644 --- a/src/file_transfer.rs +++ b/src/file_transfer.rs @@ -164,21 +164,21 @@ pub fn encode_frame(payload: &[u8]) -> Result> { pub fn parse_copy_endpoint(raw: &str) -> CopyEndpoint { if looks_like_windows_path(raw) { - return CopyEndpoint::Local(PathBuf::from(raw)); + return CopyEndpoint::Local(local_copy_path(raw)); } if let Some((host, path)) = parse_bracketed_remote_endpoint(raw) { return CopyEndpoint::Remote { host, path }; } if raw.starts_with('[') { - return CopyEndpoint::Local(PathBuf::from(raw)); + return CopyEndpoint::Local(local_copy_path(raw)); } let Some(index) = raw.find(':') else { - return CopyEndpoint::Local(PathBuf::from(raw)); + return CopyEndpoint::Local(local_copy_path(raw)); }; let host = &raw[..index]; let path = &raw[index + 1..]; if host.is_empty() || host.contains('/') || host.contains('\\') { - return CopyEndpoint::Local(PathBuf::from(raw)); + return CopyEndpoint::Local(local_copy_path(raw)); } CopyEndpoint::Remote { host: host.to_string(), @@ -266,6 +266,23 @@ fn looks_like_windows_path(raw: &str) -> bool { bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':' } +fn local_copy_path(raw: &str) -> PathBuf { + let rest = if raw == "~" { + Some("") + } else { + raw.strip_prefix("~/").or_else(|| raw.strip_prefix("~\\")) + }; + if let Some(rest) = rest + && let Some(home) = dirs::home_dir() + { + if rest.is_empty() { + return home; + } + return home.join(rest); + } + PathBuf::from(raw) +} + #[cfg(test)] mod tests { use super::*; @@ -346,4 +363,27 @@ mod tests { CopyEndpoint::Local(PathBuf::from("[::1]")) ); } + + #[test] + fn copy_endpoint_expands_local_home_paths() { + let Some(home) = dirs::home_dir() else { + return; + }; + assert_eq!(parse_copy_endpoint("~"), CopyEndpoint::Local(home.clone())); + assert_eq!( + parse_copy_endpoint("~/Downloads/file.txt"), + CopyEndpoint::Local(home.join("Downloads/file.txt")) + ); + assert_eq!( + parse_copy_endpoint("~\\Downloads\\file.txt"), + CopyEndpoint::Local(home.join("Downloads\\file.txt")) + ); + assert_eq!( + parse_copy_endpoint("host:~/Downloads/file.txt"), + CopyEndpoint::Remote { + host: "host".to_string(), + path: "~/Downloads/file.txt".to_string() + } + ); + } }