Skip to main content

tor_config_path/
addr.rs

1//! Support for shell expansion in [`general::SocketAddr`].
2
3use crate::{CfgPath, CfgPathError};
4use serde::{Deserialize, Serialize};
5use std::{io, net, path::PathBuf, str::FromStr, sync::Arc};
6use tor_general_addr::{general, unix};
7
8/// A variation of [`general::SocketAddr`] that allows shell expansions in Unix paths.
9///
10/// The string representation for these addresses is the same as for [`general::SocketAddr`];
11/// but the shell expansion syntax is the same as for [`CfgPath`].
12///
13/// Shell expansion is only supported _within_ paths: Even if the user has set `${HOME}`
14/// to `127.0.0.1`, the address `inet:${HOME}:9999` is a syntax error.
15///
16/// In addition to the "inet:" and "unix:" schemas supported by `general::SocketAddr`,
17/// This type also supports a "unix-literal" schema,
18/// to indicate that no shell expansion should occur.
19#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
20#[serde(into = "CfgAddrSerde", try_from = "CfgAddrSerde")]
21pub struct CfgAddr(AddrInner);
22
23/// Implementation type for `CfgAddr`.
24///
25/// This is a separate type because we can't define an public enum with private members.
26#[derive(Clone, Debug, Eq, PartialEq)]
27enum AddrInner {
28    /// An internet address (which will not be expanded).
29    Inet(net::SocketAddr),
30    /// A unix domain socket path.
31    Unix(CfgPath),
32}
33
34impl CfgAddr {
35    /// Create a new [`CfgAddr`] that will produce an `AF_UNIX` address
36    /// corresponding to the provided path.
37    ///
38    /// Note that not all platforms support AF\_UNIX addresses;
39    /// on Windows, notably, expanding this path will produce an error.
40    pub fn new_unix(path: CfgPath) -> Self {
41        CfgAddr(AddrInner::Unix(path))
42    }
43
44    /// Return the [`general::SocketAddr`] produced by expanding this `CfgAddr`.
45    #[cfg_attr(not(unix), expect(unused_variables))]
46    pub fn address(
47        &self,
48        path_resolver: &crate::CfgPathResolver,
49    ) -> Result<general::SocketAddr, CfgAddrError> {
50        match &self.0 {
51            AddrInner::Inet(socket_addr) => {
52                // Easy case: This is an inet address.
53                Ok((*socket_addr).into())
54            }
55            AddrInner::Unix(cfg_path) => {
56                #[cfg(not(unix))]
57                {
58                    // Give this error early on non-unix platforms, so that we don't confuse the user.
59                    return Err(unix::NoAfUnixSocketSupport::default().into());
60                }
61                #[cfg(unix)]
62                {
63                    let addr = unix::SocketAddr::from_pathname(cfg_path.path(path_resolver)?)
64                        .map_err(|e| CfgAddrError::ConstructAfUnixAddress(Arc::new(e)))?;
65                    Ok(addr.into())
66                }
67            }
68        }
69    }
70
71    /// Return true if this address is of a type to which variable substitutions will apply.
72    ///
73    /// Currently, substitutions apply to AF\_UNIX addresses but not to Inet addresses.
74    pub fn substitutions_will_apply(&self) -> bool {
75        match &self.0 {
76            AddrInner::Inet(_) => false,
77            AddrInner::Unix(_) => true,
78        }
79    }
80
81    /// Helper: if possible, format this address as a String.
82    ///
83    /// (This will return Err(p) if this path is a literal unix domain socket path
84    /// that can't be represented as a string.)
85    //
86    // This is a separate function so that it can form the basis of a "display_lossy"
87    // implementation, assuming we need one.
88    fn try_to_string(&self) -> Result<String, &PathBuf> {
89        use crate::PathInner as PI;
90        use AddrInner as AI;
91        match &self.0 {
92            AI::Inet(socket_addr) => Ok(format!("inet:{}", socket_addr)),
93            AI::Unix(cfg_path) => match &cfg_path.0 {
94                PI::Shell(s) => Ok(format!("unix:{}", s)),
95                PI::Literal(path) => match path.literal.to_str() {
96                    Some(literal_as_str) => Ok(format!("unix-literal:{}", literal_as_str)),
97                    None => Err(&path.literal),
98                },
99            },
100        }
101    }
102}
103
104/// Error produced when trying to expand a [`CfgAddr`] into a [`general::SocketAddr`].
105#[derive(Clone, Debug, thiserror::Error)]
106#[non_exhaustive]
107pub enum CfgAddrError {
108    /// Tried to expand a `unix:` address on a platform where we don't support `AF_UNIX` addresses.
109    #[error("No support for AF_UNIX addresses on this platform")]
110    NoAfUnixSocketSupport(#[from] unix::NoAfUnixSocketSupport),
111    /// Unable to expand the underlying `CfgPath`, likely due to syntax or missing variables.
112    #[error("Could not expand path")]
113    Path(#[from] CfgPathError),
114    /// Unable to create an AF_UNIX address from a path.
115    ///
116    /// (This can happen if the path is too long, or contains internal NULs.)
117    #[error("Could not construct AF_UNIX address")]
118    ConstructAfUnixAddress(#[source] Arc<io::Error>),
119}
120
121impl FromStr for CfgAddr {
122    type Err = general::AddrParseError;
123
124    fn from_str(s: &str) -> Result<Self, Self::Err> {
125        // NOTE: This logic is mostly duplicated from <FromStr for general::SocketAddr>;
126        // I don't see an easy way to deduplicate it.
127        if s.starts_with(|c: char| c.is_ascii_digit() || c == '[') {
128            // This looks like an inet address, and cannot be a qualified address.
129            Ok(s.parse::<net::SocketAddr>()?.into())
130        } else if let Some((schema, remainder)) = s.split_once(':') {
131            match schema {
132                "unix" => {
133                    let path = CfgPath::new(remainder.to_string());
134                    Ok(CfgAddr::new_unix(path))
135                }
136                "unix-literal" => {
137                    let path = CfgPath::new_literal(remainder.to_string());
138                    Ok(CfgAddr::new_unix(path))
139                }
140                "inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
141                _ => Err(general::AddrParseError::UnrecognizedSchema(
142                    schema.to_string(),
143                )),
144            }
145        } else {
146            Err(general::AddrParseError::NoSchema)
147        }
148    }
149}
150
151impl From<net::SocketAddr> for CfgAddr {
152    fn from(value: net::SocketAddr) -> Self {
153        CfgAddr(AddrInner::Inet(value))
154    }
155}
156impl TryFrom<unix::SocketAddr> for CfgAddr {
157    type Error = UnixAddrNotAPath;
158
159    fn try_from(value: unix::SocketAddr) -> Result<Self, Self::Error> {
160        // We don't need to check `#[cfg(unix)]` here:
161        // if unix::SocketAddr is inhabited, then we can construct the Unix variant.
162        Ok(Self::new_unix(CfgPath::new_literal(
163            value.as_pathname().ok_or(UnixAddrNotAPath)?,
164        )))
165    }
166}
167// NOTE that we deliberately _don't_ implement From<Path> or From<CfgPath>;
168// we want to keep open the possibility that there may be non-AF\_UNIX path-based
169// addresses in the future!
170
171/// Error returned when trying to convert a non-path `unix::SocketAddr` into a `CfgAddr` .
172#[derive(Clone, Debug, Default, thiserror::Error)]
173#[non_exhaustive]
174#[error("Unix domain socket address was not a path.")]
175pub struct UnixAddrNotAPath;
176
177/// Serde helper: We convert CfgAddr through this format in order to serialize and deserialize it.
178#[derive(Serialize, Deserialize)]
179#[serde(untagged)]
180enum CfgAddrSerde {
181    /// We serialize most types as a string.
182    Str(String),
183    /// We have another format for representing AF\_UNIX address literals
184    /// that can't be represented as a string.
185    UnixLiteral {
186        /// A path that won't be expanded.
187        unix_literal: PathBuf,
188    },
189}
190
191impl TryFrom<CfgAddrSerde> for CfgAddr {
192    type Error = general::AddrParseError;
193
194    fn try_from(value: CfgAddrSerde) -> Result<Self, Self::Error> {
195        use CfgAddrSerde as S;
196        match value {
197            S::Str(s) => s.parse(),
198            S::UnixLiteral { unix_literal } => {
199                Ok(CfgAddr::new_unix(CfgPath::new_literal(unix_literal)))
200            }
201        }
202    }
203}
204impl From<CfgAddr> for CfgAddrSerde {
205    fn from(value: CfgAddr) -> Self {
206        match value.try_to_string() {
207            Ok(s) => CfgAddrSerde::Str(s),
208            Err(unix_literal) => CfgAddrSerde::UnixLiteral {
209                unix_literal: unix_literal.clone(),
210            },
211        }
212    }
213}
214
215#[cfg(test)]
216mod test {
217    // @@ begin test lint list maintained by maint/add_warning @@
218    #![allow(clippy::bool_assert_comparison)]
219    #![allow(clippy::clone_on_copy)]
220    #![allow(clippy::dbg_macro)]
221    #![allow(clippy::mixed_attributes_style)]
222    #![allow(clippy::print_stderr)]
223    #![allow(clippy::print_stdout)]
224    #![allow(clippy::single_char_pattern)]
225    #![allow(clippy::unwrap_used)]
226    #![allow(clippy::unchecked_time_subtraction)]
227    #![allow(clippy::useless_vec)]
228    #![allow(clippy::needless_pass_by_value)]
229    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
230
231    use super::*;
232    use assert_matches::assert_matches;
233    use std::path::PathBuf;
234
235    use crate::{CfgPathResolver, home};
236
237    #[test]
238    fn parse_inet_ok() {
239        fn check(s: &str) {
240            let resolv = CfgPathResolver::from_pairs([("FOO", "foo")]);
241            let a: general::SocketAddr = CfgAddr::from_str(s).unwrap().address(&resolv).unwrap();
242            assert_eq!(a, general::SocketAddr::from_str(s).unwrap());
243        }
244
245        check("127.0.0.1:9999");
246        check("inet:127.0.0.1:9999");
247        check("[2001:db8::413]:443");
248        check("inet:[2001:db8::413]:443");
249    }
250
251    #[test]
252    fn parse_inet_bad() {
253        assert_matches!(
254            CfgAddr::from_str("612"),
255            Err(general::AddrParseError::InvalidInetAddress(_))
256        );
257        assert_matches!(
258            CfgAddr::from_str("612unix:/home"),
259            Err(general::AddrParseError::InvalidInetAddress(_))
260        );
261        assert_matches!(
262            CfgAddr::from_str("127.0.0.1.1:99"),
263            Err(general::AddrParseError::InvalidInetAddress(_))
264        );
265        assert_matches!(
266            CfgAddr::from_str("inet:6"),
267            Err(general::AddrParseError::InvalidInetAddress(_))
268        );
269        assert_matches!(
270            CfgAddr::from_str("[[[[[]]]]]"),
271            Err(general::AddrParseError::InvalidInetAddress(_))
272        );
273    }
274
275    #[test]
276    fn parse_bad_schemas() {
277        assert_matches!(
278            CfgAddr::from_str("uranian:umbra"),
279            Err(general::AddrParseError::UnrecognizedSchema(_))
280        );
281    }
282
283    #[test]
284    #[cfg_attr(not(unix), expect(unused_variables))]
285    fn unix_literal() {
286        let resolv = CfgPathResolver::from_pairs([("USER_HOME", home().unwrap())]);
287        let pb = PathBuf::from("${USER_HOME}/.local/socket");
288        let a1 = CfgAddr::new_unix(CfgPath::new_literal(&pb));
289        let a2 = CfgAddr::from_str("unix-literal:${USER_HOME}/.local/socket").unwrap();
290        #[cfg(unix)]
291        {
292            assert_eq!(a1.address(&resolv).unwrap(), a2.address(&resolv).unwrap(),);
293            match a1.address(&resolv).unwrap() {
294                general::SocketAddr::Unix(socket_addr) => {
295                    // can't use assert_eq because these types are not Debug.
296                    assert!(socket_addr.as_pathname() == Some(pb.as_ref()));
297                }
298                _ => panic!("Expected a unix domain socket address"),
299            }
300        }
301        #[cfg(not(unix))]
302        assert_matches!(
303            a1.address(&resolv),
304            Err(CfgAddrError::NoAfUnixSocketSupport(_))
305        );
306    }
307
308    #[cfg_attr(not(unix), expect(unused_variables))]
309    fn try_unix(addr: &str, want: &str, path_resolver: &CfgPathResolver) {
310        let p = CfgPath::new(want.to_string());
311        let expansion = p.path(path_resolver).unwrap();
312        let cfg_addr = CfgAddr::from_str(addr).unwrap();
313        assert_matches!(&cfg_addr.0, AddrInner::Unix(_));
314        #[cfg(unix)]
315        {
316            let gen_addr = cfg_addr.address(path_resolver).unwrap();
317            let expected_addr = unix::SocketAddr::from_pathname(expansion).unwrap();
318            assert_eq!(gen_addr, expected_addr.into());
319        }
320        #[cfg(not(unix))]
321        {
322            assert_matches!(
323                cfg_addr.address(path_resolver),
324                Err(CfgAddrError::NoAfUnixSocketSupport(_))
325            );
326        }
327    }
328
329    #[test]
330    fn unix_no_substitution() {
331        let resolver = CfgPathResolver::from_pairs([("FOO", "foo")]);
332        try_unix("unix:/home/mayor/.socket", "/home/mayor/.socket", &resolver);
333    }
334
335    #[test]
336    #[cfg(feature = "expand-paths")]
337    fn unix_substitution() {
338        let resolver = CfgPathResolver::from_pairs([("FOO", "foo")]);
339        try_unix("unix:${FOO}/socket", "${FOO}/socket", &resolver);
340    }
341
342    #[test]
343    fn serde() {
344        fn testcase_with_provided_addr(json: &str, addr: &CfgAddr) {
345            let a1: CfgAddr = serde_json::from_str(json).unwrap();
346            assert_eq!(&a1, addr);
347            let encoded = serde_json::to_string(&a1).unwrap();
348            let a2: CfgAddr = serde_json::from_str(&encoded).unwrap();
349            assert_eq!(&a2, addr);
350        }
351        fn testcase(json: &str, addr: &str) {
352            let addr = CfgAddr::from_str(addr).unwrap();
353            testcase_with_provided_addr(json, &addr);
354        }
355
356        testcase(r#" "inet:127.0.0.1:443" "#, "inet:127.0.0.1:443");
357        testcase(r#" "unix:${HOME}/socket" "#, "unix:${HOME}/socket");
358        testcase(
359            r#" "unix-literal:${HOME}/socket" "#,
360            "unix-literal:${HOME}/socket",
361        );
362    }
363}