Skip to main content

tiny_proxy/config/
address.rs

1use std::net::SocketAddr;
2
3/// Resolve a config address string to a [`SocketAddr`] suitable for binding.
4///
5/// - `"example.com:443"` → `"0.0.0.0:443"` (bind to all interfaces)
6/// - `"0.0.0.0:9090"` → `"0.0.0.0:9090"`
7pub fn resolve_listen_addr(address: &str) -> anyhow::Result<SocketAddr> {
8    if let Ok(addr) = address.parse::<SocketAddr>() {
9        return Ok(addr);
10    }
11
12    let port = address
13        .rsplit(':')
14        .next()
15        .and_then(|p| p.parse::<u16>().ok())
16        .ok_or_else(|| anyhow::anyhow!("Cannot parse address: {}", address))?;
17
18    Ok(SocketAddr::from(([0, 0, 0, 0], port)))
19}
20
21/// Extract the hostname portion from an address string (used as SNI key).
22///
23/// - `"example.com:443"` → `"example.com"`
24/// - `"[::1]:443"` → `"::1"`
25/// - `"0.0.0.0:9090"` → `"0.0.0.0"`
26pub fn extract_hostname(address: &str) -> &str {
27    if address.starts_with('[') {
28        if let Some(end) = address.find(']') {
29            return &address[1..end];
30        }
31    }
32    address.rsplit(':').next_back().unwrap_or(address)
33}
34
35/// HTTP port for redirect listeners derived from the TLS listen port.
36///
37/// Formula: `tls_port - 443 + 80` (443→80, 8443→8080, etc.)
38pub fn tls_redirect_port(tls_port: u16) -> u16 {
39    tls_port.saturating_sub(443).saturating_add(80)
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn test_resolve_listen_addr_ip_port() {
48        let addr = resolve_listen_addr("0.0.0.0:8443").unwrap();
49        assert_eq!(addr, "0.0.0.0:8443".parse::<SocketAddr>().unwrap());
50    }
51
52    #[test]
53    fn test_resolve_listen_addr_hostname_port() {
54        let addr = resolve_listen_addr("example.com:443").unwrap();
55        assert_eq!(addr.port(), 443);
56        assert_eq!(
57            addr.ip(),
58            std::net::IpAddr::from(std::net::Ipv4Addr::new(0, 0, 0, 0))
59        );
60    }
61
62    #[test]
63    fn test_resolve_listen_addr_localhost() {
64        let addr = resolve_listen_addr("localhost:8080").unwrap();
65        assert_eq!(addr.port(), 8080);
66    }
67
68    #[test]
69    fn test_resolve_listen_addr_invalid() {
70        assert!(resolve_listen_addr("no-port-here").is_err());
71    }
72
73    #[test]
74    fn test_extract_hostname_ipv4() {
75        assert_eq!(extract_hostname("example.com:443"), "example.com");
76    }
77
78    #[test]
79    fn test_extract_hostname_ipv6() {
80        assert_eq!(extract_hostname("[::1]:443"), "::1");
81    }
82
83    #[test]
84    fn test_extract_hostname_ip() {
85        assert_eq!(extract_hostname("0.0.0.0:9090"), "0.0.0.0");
86    }
87
88    #[test]
89    fn test_extract_hostname_localhost() {
90        assert_eq!(extract_hostname("localhost:8080"), "localhost");
91    }
92
93    #[test]
94    fn test_tls_redirect_port() {
95        assert_eq!(tls_redirect_port(443), 80);
96        assert_eq!(tls_redirect_port(8443), 8080);
97        assert_eq!(tls_redirect_port(44300), 43937);
98    }
99}