Skip to main content

ssh2_config/params/
remote_forward.rs

1//! Typed values for remote forwarding directives.
2
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6/// Describes a complete `RemoteForward` directive.
7///
8/// A missing destination represents SOCKS proxy mode.
9///
10/// # Examples
11///
12/// ```rust
13/// use ssh2_config::{RemoteForward, RemoteForwardDestination, RemoteForwardListen};
14///
15/// let forward = RemoteForward::new(
16///     RemoteForwardListen::Port(8080),
17///     Some(RemoteForwardDestination::Host {
18///         host: "localhost".to_string(),
19///         port: 80,
20///     }),
21/// );
22///
23/// assert_eq!(forward.to_string(), "8080 localhost:80");
24/// ```
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct RemoteForward {
27    /// Listening endpoint on the remote machine.
28    pub listen: RemoteForwardListen,
29    /// Optional destination on the local machine.
30    pub destination: Option<RemoteForwardDestination>,
31}
32
33impl RemoteForward {
34    /// Creates a remote forwarding specification.
35    pub fn new(listen: RemoteForwardListen, destination: Option<RemoteForwardDestination>) -> Self {
36        Self {
37            listen,
38            destination,
39        }
40    }
41}
42
43impl fmt::Display for RemoteForward {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{listen}", listen = self.listen)?;
46        if let Some(destination) = &self.destination {
47            write!(f, " {destination}")?;
48        }
49        Ok(())
50    }
51}
52
53/// Describes the listening endpoint of a remote forwarding directive.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum RemoteForwardListen {
56    /// Listen on a port using the server's default bind address.
57    Port(u16),
58    /// Listen on a host and port.
59    Host {
60        /// Bind host, address, wildcard, or empty string.
61        host: String,
62        /// Bind port.
63        port: u16,
64    },
65    /// Listen on a Unix-domain socket.
66    UnixSocket(PathBuf),
67}
68
69impl fmt::Display for RemoteForwardListen {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::Port(port) => write!(f, "{port}"),
73            Self::Host { host, port } => write_host_port(f, host, *port),
74            Self::UnixSocket(path) => write_socket_path(f, path),
75        }
76    }
77}
78
79/// Describes the destination endpoint of a remote forwarding directive.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum RemoteForwardDestination {
82    /// Connect to a host and port.
83    Host {
84        /// Destination host or address.
85        host: String,
86        /// Destination port.
87        port: u16,
88    },
89    /// Connect to a Unix-domain socket.
90    UnixSocket(PathBuf),
91}
92
93impl fmt::Display for RemoteForwardDestination {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::Host { host, port } => write_host_port(f, host, *port),
97            Self::UnixSocket(path) => write_socket_path(f, path),
98        }
99    }
100}
101
102fn write_host_port(f: &mut fmt::Formatter<'_>, host: &str, port: u16) -> fmt::Result {
103    if host.contains(':') {
104        write!(f, "[{host}]:{port}")
105    } else {
106        write!(f, "{host}:{port}")
107    }
108}
109
110fn write_socket_path(f: &mut fmt::Formatter<'_>, path: &Path) -> fmt::Result {
111    let path = path.display().to_string();
112    if path.chars().any(char::is_whitespace) {
113        write!(
114            f,
115            "\"{path}\"",
116            path = path.replace('\\', "\\\\").replace('"', "\\\"")
117        )
118    } else {
119        write!(f, "{path}")
120    }
121}