Skip to main content

zond_engine/core/
parse.rs

1// Copyright (c) 2026 Erik Lening (hollowpointer) and Contributors
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4// If a copy of the MPL was not distributed with this file, You can obtain one at
5// https://mozilla.org/MPL/2.0/.
6
7//! # Parsing Utilities
8//!
9//! This module serves as the primary gateway for all parsing and resolution logic
10//! within the library. It abstracts the complexities of format-specific grammars
11//! into a clean, high-level API.
12//!
13//! Currently supported:
14//! * **IP Resolution**: Translating strings and keywords into [`IpSet`] models.
15
16pub mod ip;
17
18pub use ip::{IS_LAN_SCAN, IpParseError, to_set as to_ipset};
19
20use crate::core::models::port::PortSet;
21use crate::core::models::target::{TargetMap, TargetSet};
22
23/// Parses a list of target strings (e.g. `["1.1.1.1:80,443", "8.8.8.8"]`) into a `TargetMap`.
24/// Combines per-target specified ports, or falls back to `global_ports`.
25pub fn to_target_map(
26    targets: &[String],
27    global_ports: PortSet,
28    resolver: Option<ip::ResolverFn>,
29) -> Result<TargetMap, anyhow::Error> {
30    let mut map = TargetMap::new();
31
32    for target in targets {
33        if let Some((ip_str, port_str)) = target.split_once(':') {
34            let ip_set = to_ipset(&[ip_str], resolver)
35                .map_err(|e| anyhow::anyhow!("Invalid IP in '{}': {}", ip_str, e))?;
36            let port_set = PortSet::try_from(port_str)
37                .map_err(|e| anyhow::anyhow!("Invalid Port in '{}': {}", port_str, e))?;
38            map.add_unit(TargetSet::new(ip_set, port_set));
39        } else {
40            let ip_set = to_ipset(&[target.as_str()], resolver)
41                .map_err(|e| anyhow::anyhow!("Invalid IP '{}': {}", target, e))?;
42            map.add_unit(TargetSet::new(ip_set, global_ports.clone()));
43        }
44    }
45
46    Ok(map)
47}
48
49// ╔════════════════════════════════════════════╗
50// ║ ████████╗███████╗███████╗████████╗███████╗ ║
51// ║ ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝██╔════╝ ║
52// ║    ██║   █████╗  ███████╗   ██║   ███████╗ ║
53// ║    ██║   ██╔══╝  ╚════██║   ██║   ╚════██║ ║
54// ║    ██║   ███████╗███████║   ██║   ███████║ ║
55// ║    ╚═╝   ╚══════╝╚══════╝   ╚═╝   ╚══════╝ ║
56// ╚════════════════════════════════════════════╝
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use std::net::IpAddr;
62
63    #[test]
64    fn facade_ip_resolution() {
65        let inputs = vec!["127.0.0.1", "10.0.0.1-5"];
66
67        let set = to_ipset(&inputs, None).expect("Facade should resolve IP targets");
68
69        assert_eq!(set.len(), 6);
70        assert!(set.contains(&"127.0.0.1".parse::<IpAddr>().unwrap()));
71        assert!(set.contains(&"10.0.0.3".parse::<IpAddr>().unwrap()));
72    }
73
74    #[test]
75    fn facade_empty_input() {
76        let inputs: Vec<&str> = vec![];
77        let result = to_ipset(&inputs, None);
78
79        assert!(result.is_err());
80        assert_eq!(result.unwrap_err(), IpParseError::EmptySet);
81    }
82
83    #[test]
84    fn facade_comma_splitting() {
85        let inputs = vec!["1.1.1.1, 2.2.2.2"];
86        let set = to_ipset(&inputs, None).unwrap();
87
88        assert_eq!(set.len(), 2);
89    }
90}