meowping 2.0.8

A flexible ping utility Tool written in Rust, that is focused on being size efficient and fast.
pub struct Parser {
    pub scheme: String,
    pub host: String,
    pub port: Option<u16>,
    pub path: String,
}

impl Parser {
    pub fn parse(url: &str) -> Result<Self, &'static str> {
        let scheme_end = url.find("://");
        let (scheme, url) = match scheme_end {
            Some(end) => (&url[..end], &url[end + 3..]),
            None => {
                return Err("Missing scheme");
            }
        };

        let host_end = url.find('/').unwrap_or(url.len());

        let host_port = &url[..host_end];
        let path = if host_end < url.len() {
            &url[host_end..]
        } else {
            "/"
        };

        let mut host = host_port;
        let mut port = None;

        if host_port.starts_with('[') {
            if let Some(bracket_end) = host_port.find(']') {
                host = &host_port[1..bracket_end];
                let after = &host_port[bracket_end + 1..];
                if let Some(port_str) = after.strip_prefix(':') {
                    port = port_str.parse().ok();
                }
            } else {
                return Err("Invalid IPv6 address format");
            }
        } else if let Some(colon_pos) = host_port.find(':') {
            host = &host_port[..colon_pos];
            port = host_port[colon_pos + 1..].parse().ok();
        }

        if host.is_empty() {
            return Err("Invalid host");
        }

        Ok(Self {
            scheme: scheme.to_string(),
            host: host.to_string(),
            port,
            path: path.to_string(),
        })
    }

    pub fn extract_url(input: &str) -> Extracted {
        if let Ok(parsed) = Self::parse(input) {
            return Extracted::Success(parsed.host);
        }

        if Self::is_resolvable_hostname(input) {
            return Extracted::Success(input.to_string());
        }

        Extracted::Error
    }

    fn is_resolvable_hostname(hostname: &str) -> bool {
        use std::net::ToSocketAddrs;
        let socket_str = if hostname.contains(':') {
            format!("[{hostname}]:80")
        } else {
            format!("{hostname}:80")
        };
        socket_str
            .to_socket_addrs()
            .is_ok_and(|mut addrs| addrs.next().is_some())
    }
}

pub enum Extracted {
    Success(String),
    Error,
}

pub fn parse_multiple_destinations(input: &str) -> Vec<String> {
    let trimmed = input.trim();
    if trimmed.starts_with('[') && trimmed.ends_with(']') {
        trimmed[1..trimmed.len() - 1]
            .split(',')
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect()
    } else if trimmed.contains(',') {
        trimmed
            .split(',')
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect()
    } else {
        vec![trimmed.to_string()]
    }
}

pub fn parse_ports(input: &str) -> Result<Vec<u16>, String> {
    let trimmed = input.trim();
    let body = if trimmed.starts_with('[') && trimmed.ends_with(']') {
        &trimmed[1..trimmed.len() - 1]
    } else {
        trimmed
    };

    if body.trim().is_empty() {
        return Err("No ports specified".to_string());
    }

    let mut ports: Vec<u16> = Vec::new();
    for token in body.split(',') {
        let token = token.trim();
        if token.is_empty() {
            continue;
        }
        if let Some((start_s, end_s)) = token.split_once('-') {
            let start: u32 = start_s
                .trim()
                .parse()
                .map_err(|_| format!("Invalid port range start: {token}"))?;
            let end: u32 = end_s
                .trim()
                .parse()
                .map_err(|_| format!("Invalid port range end: {token}"))?;
            if end < start {
                return Err(format!("Port range end before start: {token}"));
            }
            if end > u16::MAX.into() {
                return Err(format!("Port out of range (0-65535): {token}"));
            }
            for p in start..=end {
                ports.push(u16::try_from(p).expect("range bounded by u16::MAX"));
            }
        } else {
            let p: u32 = token
                .parse()
                .map_err(|_| format!("Invalid port: {token}"))?;
            if p > u16::MAX.into() {
                return Err(format!("Port out of range (0-65535): {token}"));
            }
            ports.push(u16::try_from(p).expect("single port bounded by u16::MAX"));
        }
    }

    if ports.is_empty() {
        return Err("No ports specified".to_string());
    }

    ports.sort_unstable();
    ports.dedup();
    Ok(ports)
}