Skip to main content

robinpath_modules/modules/
ip_mod.rs

1use robinpath::{RobinPath, Value};
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4pub fn register(rp: &mut RobinPath) {
5    // ip.isValid(addr) -> bool (v4 or v6)
6    rp.register_builtin("ip.isValid", |args, _| {
7        let addr = args.first().map(|v| v.to_display_string()).unwrap_or_default();
8        Ok(Value::Bool(addr.parse::<IpAddr>().is_ok()))
9    });
10
11    // ip.isV4(addr) -> bool
12    rp.register_builtin("ip.isV4", |args, _| {
13        let addr = args.first().map(|v| v.to_display_string()).unwrap_or_default();
14        Ok(Value::Bool(addr.parse::<Ipv4Addr>().is_ok()))
15    });
16
17    // ip.isV6(addr) -> bool
18    rp.register_builtin("ip.isV6", |args, _| {
19        let addr = args.first().map(|v| v.to_display_string()).unwrap_or_default();
20        Ok(Value::Bool(addr.parse::<Ipv6Addr>().is_ok()))
21    });
22
23    // ip.toInt(v4addr) -> number
24    rp.register_builtin("ip.toInt", |args, _| {
25        let addr = args.first().map(|v| v.to_display_string()).unwrap_or_default();
26        match addr.parse::<Ipv4Addr>() {
27            Ok(ip) => {
28                let n: u32 = u32::from(ip);
29                Ok(Value::Number(n as f64))
30            }
31            Err(_) => Err(format!("ip.toInt: invalid IPv4 address \"{}\"", addr)),
32        }
33    });
34
35    // ip.fromInt(num) -> v4 string
36    rp.register_builtin("ip.fromInt", |args, _| {
37        let num = args.first().map(|v| v.to_number()).unwrap_or(0.0) as u32;
38        let ip = Ipv4Addr::from(num);
39        Ok(Value::String(ip.to_string()))
40    });
41
42    // ip.isPrivate(addr) -> bool
43    rp.register_builtin("ip.isPrivate", |args, _| {
44        let addr_str = args.first().map(|v| v.to_display_string()).unwrap_or_default();
45        match addr_str.parse::<IpAddr>() {
46            Ok(IpAddr::V4(ip)) => {
47                let octets = ip.octets();
48                let is_private =
49                    // 10.0.0.0/8
50                    octets[0] == 10 ||
51                    // 172.16.0.0/12
52                    (octets[0] == 172 && (16..=31).contains(&octets[1])) ||
53                    // 192.168.0.0/16
54                    (octets[0] == 192 && octets[1] == 168) ||
55                    // 127.0.0.0/8 (loopback)
56                    octets[0] == 127;
57                Ok(Value::Bool(is_private))
58            }
59            Ok(IpAddr::V6(ip)) => {
60                let segments = ip.segments();
61                let is_private =
62                    // ::1 (loopback)
63                    ip == Ipv6Addr::LOCALHOST ||
64                    // fc00::/7 (unique local)
65                    (segments[0] & 0xfe00) == 0xfc00;
66                Ok(Value::Bool(is_private))
67            }
68            Err(_) => Ok(Value::Bool(false)),
69        }
70    });
71
72    // ip.cidrMatch(addr, cidr) -> bool
73    rp.register_builtin("ip.cidrMatch", |args, _| {
74        let addr_str = args.first().map(|v| v.to_display_string()).unwrap_or_default();
75        let cidr_str = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
76
77        let addr: IpAddr = match addr_str.parse() {
78            Ok(a) => a,
79            Err(_) => return Ok(Value::Bool(false)),
80        };
81
82        // Parse CIDR notation: "prefix/length"
83        let parts: Vec<&str> = cidr_str.split('/').collect();
84        if parts.len() != 2 {
85            return Ok(Value::Bool(false));
86        }
87        let network: IpAddr = match parts[0].parse() {
88            Ok(a) => a,
89            Err(_) => return Ok(Value::Bool(false)),
90        };
91        let prefix_len: u32 = match parts[1].parse() {
92            Ok(n) => n,
93            Err(_) => return Ok(Value::Bool(false)),
94        };
95
96        let result = match (addr, network) {
97            (IpAddr::V4(a), IpAddr::V4(n)) => {
98                if prefix_len > 32 {
99                    false
100                } else if prefix_len == 0 {
101                    true
102                } else {
103                    let mask = if prefix_len == 32 {
104                        u32::MAX
105                    } else {
106                        u32::MAX << (32 - prefix_len)
107                    };
108                    let a_int = u32::from(a);
109                    let n_int = u32::from(n);
110                    (a_int & mask) == (n_int & mask)
111                }
112            }
113            (IpAddr::V6(a), IpAddr::V6(n)) => {
114                if prefix_len > 128 {
115                    false
116                } else if prefix_len == 0 {
117                    true
118                } else {
119                    let mask = if prefix_len == 128 {
120                        u128::MAX
121                    } else {
122                        u128::MAX << (128 - prefix_len)
123                    };
124                    let a_int = u128::from(a);
125                    let n_int = u128::from(n);
126                    (a_int & mask) == (n_int & mask)
127                }
128            }
129            _ => false, // Mixed v4/v6
130        };
131
132        Ok(Value::Bool(result))
133    });
134}