use super::funejson::Value; use super::net_port::is_port; use std::collections::HashSet; /// Spaces and tabs only: a newline inside a port list is a mistake, not padding. fn trim_blank(s: &str) -> &str { s.trim_matches(|c| c == ' ' || c == '\t') } /// Whole ASCII digits with no leading zero: "80", not "080", "+80" or "٨٠". fn is_number(s: &str) -> bool { !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) && (s.len() == 1 || !s.starts_with('0')) } fn port(token: &str) -> i64 { // More than five digits cannot be a port, and would overflow an i64 parse. let value = if token.len() > 5 { 0 } else { token.parse::().unwrap() }; if !is_port(value) { panic!("port \"{}\" is out of range 1-65535", token); } value } /// Parse a port list such as "22,80,8000-8010" into port numbers: each port /// once, in the order first given. Ranges are inclusive. /// /// # Panics /// Panics on an empty list or item, a malformed port or range, a port outside /// 1..=65535, or a range that runs backwards. pub fn parse_port_list(text: &str) -> Vec { if trim_blank(text).is_empty() { panic!("port list is empty"); } let mut out = Vec::new(); let mut seen = HashSet::new(); let mut add = |p: i64| { if seen.insert(p) { out.push(p); } }; for raw in text.split(',') { let item = trim_blank(raw); if item.is_empty() { panic!("empty item in port list"); } if item.contains('-') { let ends: Vec<&str> = item.split('-').collect(); if ends.len() != 2 || !is_number(ends[0]) || !is_number(ends[1]) { panic!("invalid port range \"{}\"", item); } let low = port(ends[0]); let high = port(ends[1]); if low > high { panic!("port range \"{}\" runs backwards", item); } for p in low..=high { add(p); } } else { if !is_number(item) { panic!("invalid port \"{}\"", item); } add(port(item)); } } out } pub fn fune_vector(args: &[Value]) -> Value { let text = match &args[0] { Value::Str(s) => s.as_str(), _ => panic!("text must be a string"), }; Value::Arr(parse_port_list(text).into_iter().map(Value::Int).collect()) }