breakmancer 0.1.0

Drop a breakpoint into any shell.
use std::net::IpAddr;
use std::str::FromStr;

use once_cell::sync::Lazy;
use regex::Regex;

// Not as strict as it could be, but close enough.
static DOMAIN_NAME_PATTERN: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.?$").unwrap());

pub fn check_host(host: &str) -> Result<(), String> {
    if IpAddr::from_str(host).is_err() {
        // Maybe it's a domain name?
        if !DOMAIN_NAME_PATTERN.is_match(host) {
            return Err("Host portion is not IPv4, IPv6, or domain name".to_string());
        }
    }
    Ok(())
}

/// Parse a HOST:PORT address into its two parts.
///
/// This also validates that the host portion is an IPv4 address, IPv6
/// address, or something that looks more or less like a domain name.
pub fn parse_address(addr: &str) -> Result<(&str, u16), String> {
    match addr.rsplit_once(':') {
        None => Err("Missing port. Use HOST:PORT format.".to_string()),
        Some((host, port)) => {
            check_host(host)?;
            let port = match port.parse::<u16>() {
                Ok(parsed) => parsed,
                Err(err) => return Err(format!("Invalid port: {err}")),
            };
            Ok((host, port))
        }
    }
}