diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index 10d6a34..58000d5 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -4711,13 +4711,46 @@ fn shell_word(value: &str) -> String { } fn ssh_config_word(value: &str) -> String { - if cfg!(windows) { - format!("\"{}\"", value.replace('"', "\\\"")) + ssh_config_word_for_os(std::env::consts::OS, value) +} + +fn ssh_config_word_for_os(os: &str, value: &str) -> String { + if os == "windows" { + windows_command_word(value) } else { shell_word(value) } } +fn windows_command_word(value: &str) -> String { + let mut out = String::from("\""); + let mut backslashes = 0usize; + for ch in value.chars() { + if ch == '\\' { + backslashes += 1; + continue; + } + if ch == '"' { + for _ in 0..(backslashes * 2 + 1) { + out.push('\\'); + } + out.push('"'); + backslashes = 0; + continue; + } + for _ in 0..backslashes { + out.push('\\'); + } + backslashes = 0; + out.push(ch); + } + for _ in 0..(backslashes * 2) { + out.push('\\'); + } + out.push('"'); + out +} + fn local_bootstrap( session: &str, mode: &str, @@ -10135,12 +10168,13 @@ mod tests { should_hold_post_submit_input, should_reconnect_before_input_for_local_sleep, should_repaint_idle_terminal, should_strip_unowned_terminal_reports, split_after_command_submit, split_trace_tokens, ssh_command_target, ssh_config_uses_proxy, - ssh_destination_host, ssh_username, ssh_with_user, startup_command, status_ssh_target, - strip_stale_mouse_reports, strip_terminal_focus_reports, strip_unowned_terminal_reports, - summarize_trace_file, summarize_trace_file_with_mode, terminal_private_mode_transition, - toml_bare_key_or_quoted, top_trace_events, trace_report_warnings, unix_update_script, - update_binary_version_for_installer, update_installer_url, update_version_status, - upsert_managed_block, valid_forward_host, vscode_safe_alias, wake_repaint_retry_deadline, + ssh_config_word_for_os, ssh_destination_host, ssh_username, ssh_with_user, startup_command, + status_ssh_target, strip_stale_mouse_reports, strip_terminal_focus_reports, + strip_unowned_terminal_reports, summarize_trace_file, summarize_trace_file_with_mode, + terminal_private_mode_transition, toml_bare_key_or_quoted, top_trace_events, + trace_report_warnings, unix_update_script, update_binary_version_for_installer, + update_installer_url, update_version_status, upsert_managed_block, valid_forward_host, + vscode_safe_alias, wake_repaint_retry_deadline, windows_command_word, windows_mode_from_readonly, windows_readonly_from_mode, windows_update_script, }; use dosh::config::{ClientConfig, CommandExtension, HostConfig}; @@ -10503,6 +10537,31 @@ mod tests { ); } + #[test] + fn ssh_config_word_quotes_for_unix_and_windows() { + assert_eq!( + ssh_config_word_for_os("linux", "/Users/palav/My App/dosh"), + "'/Users/palav/My App/dosh'" + ); + assert_eq!( + ssh_config_word_for_os("windows", r#"C:\Users\palav\My App\dosh.exe"#), + r#""C:\Users\palav\My App\dosh.exe""# + ); + } + + #[test] + fn windows_command_word_escapes_quotes_and_trailing_backslashes() { + assert_eq!( + windows_command_word(r#"C:\Program Files\dosh\"#), + r#""C:\Program Files\dosh\\""# + ); + assert_eq!(windows_command_word(r#"say "hi""#), r#""say \"hi\"""#); + assert_eq!( + windows_command_word(r#"C:\path\before"quote"#), + r#""C:\path\before\"quote""# + ); + } + #[test] fn parses_ssh_config_native_parity_options() { let parsed = parse_ssh_config(