clawscan 1.0.0

OpenClaw/Moltbot/Clawdbot vulnerability scanner for prompt injection, supply chain, and RAG poisoning attacks
Documentation
//! Smart target parsing with multiple input format support

use anyhow::Result;

/// Parse target input with smart URL construction
///
/// Supports multiple input formats:
/// - `target.com` → `ws://target.com:18789`
/// - `192.168.1.100` → `ws://192.168.1.100:18789`
/// - `target.com:9999` → `ws://target.com:9999`
/// - `ws://target.com:18789` → passthrough
/// - `wss://target.com:18789` → passthrough (secure WebSocket)
pub fn parse_target(input: &str) -> Result<ParsedTarget> {
    use url::Url;

    // Handle explicit WebSocket URLs (passthrough)
    if input.starts_with("ws://") || input.starts_with("wss://") {
        let url = Url::parse(input)?;

        let host = url.host_str()
            .ok_or_else(|| anyhow::anyhow!("Missing host in URL"))?
            .to_string();

        // Get port, respecting URL-specified ports or inferring from scheme
        let port = url.port_or_known_default()
            .ok_or_else(|| anyhow::anyhow!("Cannot determine port"))?;

        return Ok(ParsedTarget {
            url: input.to_string(),
            host,
            port,
        });
    }

    // Parse host:port or just host
    let (host, port) = if let Some((h, p)) = input.split_once(':') {
        let port = p.parse::<u16>()?;
        (h.to_string(), port)
    } else {
        (input.to_string(), 18789)
    };

    // Construct WebSocket URL
    let url = format!("ws://{}:{}", host, port);

    Ok(ParsedTarget {
        url,
        host,
        port,
    })
}

#[derive(Debug, Clone, PartialEq)]
pub struct ParsedTarget {
    pub url: String,
    pub host: String,
    pub port: u16,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_simple_domain() {
        let target = parse_target("example.com").unwrap();
        assert_eq!(target.url, "ws://example.com:18789");
        assert_eq!(target.host, "example.com");
        assert_eq!(target.port, 18789);
    }

    #[test]
    fn test_parse_ip_address() {
        let target = parse_target("192.168.1.100").unwrap();
        assert_eq!(target.url, "ws://192.168.1.100:18789");
        assert_eq!(target.host, "192.168.1.100");
        assert_eq!(target.port, 18789);
    }

    #[test]
    fn test_parse_domain_with_custom_port() {
        let target = parse_target("example.com:9999").unwrap();
        assert_eq!(target.url, "ws://example.com:9999");
        assert_eq!(target.host, "example.com");
        assert_eq!(target.port, 9999);
    }

    #[test]
    fn test_parse_explicit_ws_url() {
        let target = parse_target("ws://example.com:18789").unwrap();
        assert_eq!(target.url, "ws://example.com:18789");
        assert_eq!(target.host, "example.com");
        assert_eq!(target.port, 18789);
    }

    #[test]
    fn test_parse_explicit_wss_url() {
        let target = parse_target("wss://secure.example.com:443").unwrap();
        assert_eq!(target.url, "wss://secure.example.com:443");
        assert_eq!(target.host, "secure.example.com");
        assert_eq!(target.port, 443);
    }

    #[test]
    fn test_parse_localhost() {
        let target = parse_target("localhost").unwrap();
        assert_eq!(target.url, "ws://localhost:18789");
        assert_eq!(target.host, "localhost");
        assert_eq!(target.port, 18789);
    }

    #[test]
    fn test_parse_localhost_with_port() {
        let target = parse_target("localhost:8080").unwrap();
        assert_eq!(target.url, "ws://localhost:8080");
        assert_eq!(target.host, "localhost");
        assert_eq!(target.port, 8080);
    }

    #[test]
    fn test_parse_subdomain() {
        let target = parse_target("gateway.internal.corp.com").unwrap();
        assert_eq!(target.url, "ws://gateway.internal.corp.com:18789");
        assert_eq!(target.host, "gateway.internal.corp.com");
        assert_eq!(target.port, 18789);
    }

    #[test]
    fn test_invalid_port_returns_error() {
        let result = parse_target("example.com:99999");
        assert!(result.is_err());
    }

    #[test]
    fn test_invalid_url_returns_error() {
        let result = parse_target("ws://");
        assert!(result.is_err());
    }
}