quick_file_transfer/config/
ssh.rs

1use std::path::PathBuf;
2
3use anyhow::bail;
4use clap::{arg, ArgAction, Args};
5
6use super::Compression;
7
8#[derive(Debug, Args)]
9#[command(flatten_help = true)]
10#[cfg(feature = "ssh")]
11pub struct SendSshArgs {
12    /// Source files or directories
13    #[arg(required(true),
14    value_hint = clap::ValueHint::AnyPath)]
15    pub sources: Vec<String>,
16
17    /// Destination file or directory
18    #[arg(required(true))]
19    pub destination: String,
20
21    /// Port that will be used to do the transfer via TCP. Prefer leaving this value empty. If no port is specified, the remote will attempt to find a free port. Don't use this unless you have very specific needs.
22    #[arg(long)]
23    pub tcp_port: Option<u16>,
24
25    /// Maximum time (ms) to attempt to resolve IP of mDNS hostname
26    #[arg(long, default_value_t = 5000)]
27    pub mdns_resolve_timeout_ms: u64,
28
29    /// Maximum time (ms) to attempt to establish an SSH connection
30    #[arg(long, default_value_t = 10000)]
31    pub ssh_timeout_ms: u64,
32
33    /// Preferred IP version (attempts to fall back to another variant if the preferred version is not found)
34    #[cfg(feature = "mdns")]
35    #[arg(long, default_value_t = crate::config::misc::IpVersion::V4)]
36    pub ip_version: crate::config::misc::IpVersion,
37
38    /// Port for SSH
39    #[arg(short('p'), long, default_value_t = 22)]
40    pub ssh_port: u16,
41
42    /// Compression format
43    #[command(subcommand)]
44    pub compression: Option<Compression>,
45
46    /// Path to the SSH private key to use for authorization (default: looks for a key in ~/.ssh)
47    #[arg(long, env(crate::ssh::ENV_SSH_PRIVATE_KEY))]
48    pub ssh_private_key_path: Option<PathBuf>,
49
50    /// Provide a path to a directory containing SSH key(s) to use for auth. Default: $HOME/.ssh on Unix and $APP_DATA/.ssh on windows
51    #[arg(long, env(crate::ssh::ENV_SSH_KEY_DIR))]
52    pub ssh_key_dir: Option<PathBuf>,
53
54    /// Start of the port range to look for free ports for TCP transfer. IANA recommends: 49152-65535 for dynamic use.
55    #[arg(short, long, default_value_t = 49152)]
56    pub start_port: u16,
57
58    /// end of the port range to look for free ports for TCP transfer
59    #[arg(short, long, requires("start_port"), default_value_t = u16::MAX)]
60    pub end_port: u16,
61
62    /// Use memory mapping mode
63    #[arg(long, action = ArgAction::SetTrue, global(true))]
64    pub mmap: bool,
65}
66
67/// The components in the target args (if present) e.g. user@hostname:/home/user/f.txt
68#[derive(Debug, Clone)]
69pub struct TargetComponents {
70    pub user: String,
71    pub host: String,
72    pub destination: PathBuf,
73}
74
75pub fn parse_scp_style_uri(input: &str) -> anyhow::Result<TargetComponents> {
76    let parts: Vec<&str> = input.split('@').collect();
77    if parts.len() != 2 {
78        bail!("Invalid SSH argument format: {input}")
79    }
80    let user = parts[0].to_string();
81    let host_and_dest: Vec<&str> = parts[1].split(':').collect();
82    if host_and_dest.len() != 2 {
83        bail!("Invalid SSH argument format: {input}")
84    }
85    let host = host_and_dest[0].to_string();
86    let destination = PathBuf::from(host_and_dest[1]);
87
88    Ok(TargetComponents {
89        user,
90        host,
91        destination,
92    })
93}
94
95impl SendSshArgs {
96    /// Returns whether the configuration is to send data to a remote
97    pub fn is_sending(&self) -> bool {
98        // If destination doesn't contain '@', it must be the source that has the `<user>@<hostname>:<path>` syntax instaed
99        self.destination.contains('@')
100    }
101}