Skip to main content

cossh/
ssh_args.rs

1//! Shared SSH argument helpers.
2//!
3//! These routines capture the CLI semantics we need in multiple places:
4//! identifying passthrough-only invocations and extracting the destination host
5//! for logging or vault lookups.
6
7const SSH_FLAGS_WITH_SEPARATE_VALUES: &[&str] = &[
8    "-b", "-B", "-c", "-D", "-E", "-e", "-F", "-I", "-i", "-J", "-L", "-l", "-m", "-O", "-o", "-p", "-P", "-Q", "-R", "-S", "-w", "-W",
9];
10
11const NON_INTERACTIVE_FLAGS: &[&str] = &["-G", "-V", "-O", "-Q"];
12
13/// Returns the target host from a forwarded SSH invocation, if present.
14pub fn extract_destination_host(ssh_args: &[String]) -> Option<String> {
15    let mut skip_next = false;
16
17    for arg in ssh_args {
18        if skip_next {
19            skip_next = false;
20            continue;
21        }
22
23        if arg.starts_with('-') {
24            if SSH_FLAGS_WITH_SEPARATE_VALUES.contains(&arg.as_str()) {
25                skip_next = true;
26            }
27            continue;
28        }
29
30        return Some(arg.split_once('@').map_or_else(|| arg.clone(), |(_, host)| host.to_string()));
31    }
32
33    None
34}
35
36/// Returns `true` when the forwarded SSH arguments should bypass the normal
37/// interactive output pipeline.
38pub fn is_non_interactive_ssh_invocation(ssh_args: &[String]) -> bool {
39    ssh_args.iter().any(|arg| NON_INTERACTIVE_FLAGS.contains(&arg.as_str()))
40}
41
42#[cfg(test)]
43#[path = "test/ssh_args.rs"]
44mod tests;