use crate::{CfgPath, CfgPathError};
use serde::{Deserialize, Serialize};
use std::{io, net, path::PathBuf, str::FromStr, sync::Arc};
use tor_general_addr::{general, unix};
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(into = "CfgAddrSerde", try_from = "CfgAddrSerde")]
pub struct CfgAddr(AddrInner);
#[derive(Clone, Debug, Eq, PartialEq)]
enum AddrInner {
Inet(net::SocketAddr),
Unix(CfgPath),
}
impl CfgAddr {
pub fn new_unix(path: CfgPath) -> Self {
CfgAddr(AddrInner::Unix(path))
}
#[cfg_attr(not(unix), expect(unused_variables))]
pub fn address(
&self,
path_resolver: &crate::CfgPathResolver,
) -> Result<general::SocketAddr, CfgAddrError> {
match &self.0 {
AddrInner::Inet(socket_addr) => {
Ok((*socket_addr).into())
}
AddrInner::Unix(cfg_path) => {
#[cfg(not(unix))]
{
Err(unix::NoAfUnixSocketSupport::default().into())
}
#[cfg(unix)]
{
let addr = unix::SocketAddr::from_pathname(cfg_path.path(path_resolver)?)
.map_err(|e| CfgAddrError::ConstructAfUnixAddress(Arc::new(e)))?;
Ok(addr.into())
}
}
}
}
pub fn substitutions_will_apply(&self) -> bool {
match &self.0 {
AddrInner::Inet(_) => false,
AddrInner::Unix(_) => true,
}
}
fn try_to_string(&self) -> Result<String, &PathBuf> {
use crate::PathInner as PI;
use AddrInner as AI;
match &self.0 {
AI::Inet(socket_addr) => Ok(format!("inet:{}", socket_addr)),
AI::Unix(cfg_path) => match &cfg_path.0 {
PI::Shell(s) => Ok(format!("unix:{}", s)),
PI::Literal(path) => match path.literal.to_str() {
Some(literal_as_str) => Ok(format!("unix-literal:{}", literal_as_str)),
None => Err(&path.literal),
},
},
}
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CfgAddrError {
#[error("No support for AF_UNIX addresses on this platform")]
NoAfUnixSocketSupport(#[from] unix::NoAfUnixSocketSupport),
#[error("Could not expand path")]
Path(#[from] CfgPathError),
#[error("Could not construct AF_UNIX address")]
ConstructAfUnixAddress(#[source] Arc<io::Error>),
}
impl FromStr for CfgAddr {
type Err = general::AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with(|c: char| c.is_ascii_digit() || c == '[') {
Ok(s.parse::<net::SocketAddr>()?.into())
} else if let Some((schema, remainder)) = s.split_once(':') {
match schema {
"unix" => {
let path = CfgPath::new(remainder.to_string());
Ok(CfgAddr::new_unix(path))
}
"unix-literal" => {
let path = CfgPath::new_literal(remainder.to_string());
Ok(CfgAddr::new_unix(path))
}
"inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
_ => Err(general::AddrParseError::UnrecognizedSchema(
schema.to_string(),
)),
}
} else {
Err(general::AddrParseError::NoSchema)
}
}
}
impl From<net::SocketAddr> for CfgAddr {
fn from(value: net::SocketAddr) -> Self {
CfgAddr(AddrInner::Inet(value))
}
}
impl TryFrom<unix::SocketAddr> for CfgAddr {
type Error = UnixAddrNotAPath;
fn try_from(value: unix::SocketAddr) -> Result<Self, Self::Error> {
Ok(Self::new_unix(CfgPath::new_literal(
value.as_pathname().ok_or(UnixAddrNotAPath)?,
)))
}
}
#[derive(Clone, Debug, Default, thiserror::Error)]
#[non_exhaustive]
#[error("Unix domain socket address was not a path.")]
pub struct UnixAddrNotAPath;
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum CfgAddrSerde {
Str(String),
UnixLiteral {
unix_literal: PathBuf,
},
}
impl TryFrom<CfgAddrSerde> for CfgAddr {
type Error = general::AddrParseError;
fn try_from(value: CfgAddrSerde) -> Result<Self, Self::Error> {
use CfgAddrSerde as S;
match value {
S::Str(s) => s.parse(),
S::UnixLiteral { unix_literal } => {
Ok(CfgAddr::new_unix(CfgPath::new_literal(unix_literal)))
}
}
}
}
impl From<CfgAddr> for CfgAddrSerde {
fn from(value: CfgAddr) -> Self {
match value.try_to_string() {
Ok(s) => CfgAddrSerde::Str(s),
Err(unix_literal) => CfgAddrSerde::UnixLiteral {
unix_literal: unix_literal.clone(),
},
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use assert_matches::assert_matches;
use std::path::PathBuf;
use crate::{CfgPathResolver, home};
#[test]
fn parse_inet_ok() {
fn check(s: &str) {
let resolv = CfgPathResolver::from_pairs([("FOO", "foo")]);
let a: general::SocketAddr = CfgAddr::from_str(s).unwrap().address(&resolv).unwrap();
assert_eq!(a, general::SocketAddr::from_str(s).unwrap());
}
check("127.0.0.1:9999");
check("inet:127.0.0.1:9999");
check("[2001:db8::413]:443");
check("inet:[2001:db8::413]:443");
}
#[test]
fn parse_inet_bad() {
assert_matches!(
CfgAddr::from_str("612"),
Err(general::AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
CfgAddr::from_str("612unix:/home"),
Err(general::AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
CfgAddr::from_str("127.0.0.1.1:99"),
Err(general::AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
CfgAddr::from_str("inet:6"),
Err(general::AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
CfgAddr::from_str("[[[[[]]]]]"),
Err(general::AddrParseError::InvalidInetAddress(_))
);
}
#[test]
fn parse_bad_schemas() {
assert_matches!(
CfgAddr::from_str("uranian:umbra"),
Err(general::AddrParseError::UnrecognizedSchema(_))
);
}
#[test]
#[cfg_attr(not(unix), expect(unused_variables))]
fn unix_literal() {
let resolv = CfgPathResolver::from_pairs([("USER_HOME", home().unwrap())]);
let pb = PathBuf::from("${USER_HOME}/.local/socket");
let a1 = CfgAddr::new_unix(CfgPath::new_literal(&pb));
let a2 = CfgAddr::from_str("unix-literal:${USER_HOME}/.local/socket").unwrap();
#[cfg(unix)]
{
assert_eq!(a1.address(&resolv).unwrap(), a2.address(&resolv).unwrap(),);
match a1.address(&resolv).unwrap() {
general::SocketAddr::Unix(socket_addr) => {
assert!(socket_addr.as_pathname() == Some(pb.as_ref()));
}
_ => panic!("Expected a unix domain socket address"),
}
}
#[cfg(not(unix))]
assert_matches!(
a1.address(&resolv),
Err(CfgAddrError::NoAfUnixSocketSupport(_))
);
}
#[cfg_attr(not(unix), expect(unused_variables))]
fn try_unix(addr: &str, want: &str, path_resolver: &CfgPathResolver) {
let p = CfgPath::new(want.to_string());
let expansion = p.path(path_resolver).unwrap();
let cfg_addr = CfgAddr::from_str(addr).unwrap();
assert_matches!(&cfg_addr.0, AddrInner::Unix(_));
#[cfg(unix)]
{
let gen_addr = cfg_addr.address(path_resolver).unwrap();
let expected_addr = unix::SocketAddr::from_pathname(expansion).unwrap();
assert_eq!(gen_addr, expected_addr.into());
}
#[cfg(not(unix))]
{
assert_matches!(
cfg_addr.address(path_resolver),
Err(CfgAddrError::NoAfUnixSocketSupport(_))
);
}
}
#[test]
fn unix_no_substitution() {
let resolver = CfgPathResolver::from_pairs([("FOO", "foo")]);
try_unix("unix:/home/mayor/.socket", "/home/mayor/.socket", &resolver);
}
#[test]
#[cfg(feature = "expand-paths")]
fn unix_substitution() {
let resolver = CfgPathResolver::from_pairs([("FOO", "foo")]);
try_unix("unix:${FOO}/socket", "${FOO}/socket", &resolver);
}
#[test]
fn serde() {
fn testcase_with_provided_addr(json: &str, addr: &CfgAddr) {
let a1: CfgAddr = serde_json::from_str(json).unwrap();
assert_eq!(&a1, addr);
let encoded = serde_json::to_string(&a1).unwrap();
let a2: CfgAddr = serde_json::from_str(&encoded).unwrap();
assert_eq!(&a2, addr);
}
fn testcase(json: &str, addr: &str) {
let addr = CfgAddr::from_str(addr).unwrap();
testcase_with_provided_addr(json, &addr);
}
testcase(r#" "inet:127.0.0.1:443" "#, "inet:127.0.0.1:443");
testcase(r#" "unix:${HOME}/socket" "#, "unix:${HOME}/socket");
testcase(
r#" "unix-literal:${HOME}/socket" "#,
"unix-literal:${HOME}/socket",
);
}
}