use std::net::IpAddr;
use std::net::SocketAddr;
use std::str::FromStr;
use ipnetwork::IpNetwork;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Environment {
source_ips: Vec<String>,
}
impl Environment {
pub fn of(source_ip: &str) -> Option<Self> {
if source_ip.trim().is_empty() {
None
} else {
Some(Self {
source_ips: vec![source_ip.to_string()],
})
}
}
pub fn of_list<I, S>(source_ips: I) -> Option<Self>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let v: Vec<String> = source_ips.into_iter().map(Into::into).collect();
if v.is_empty() {
None
} else {
Some(Self { source_ips: v })
}
}
pub fn is_match(&self, other: &Environment) -> bool {
if self.source_ips.is_empty() {
return true;
}
if other.source_ips.is_empty() {
return false;
}
let target = &other.source_ips[0];
for pattern in &self.source_ips {
if source_ip_matches(pattern, target) {
return true;
}
}
false
}
pub fn source_ips(&self) -> &Vec<String> {
&self.source_ips
}
pub fn set_source_ips<I, S>(&mut self, ips: I)
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.source_ips = ips.into_iter().map(Into::into).collect();
}
}
pub fn source_ip_matches(pattern: &str, target: &str) -> bool {
let pattern = pattern.trim();
let target = normalize_ip_text(target);
if target.is_empty() || pattern.is_empty() {
return false;
}
if pattern == "*" {
return true;
}
if pattern.contains('*') {
return wildcard_match(pattern, target.as_str());
}
if pattern.contains('/') {
if let (Ok(net), Ok(ip)) = (IpNetwork::from_str(pattern), IpAddr::from_str(target.as_str())) {
return net.contains(ip);
}
return false;
}
if pattern.contains('-') {
let parts: Vec<&str> = pattern.splitn(2, '-').collect();
if parts.len() == 2 {
if let (Ok(start), Ok(end), Ok(ip)) = (
IpAddr::from_str(parts[0].trim()),
IpAddr::from_str(parts[1].trim()),
IpAddr::from_str(target.as_str()),
) {
return ip_in_range(ip, start, end);
}
}
return false;
}
target == pattern
}
fn normalize_ip_text(value: &str) -> String {
let value = value.trim();
if let Ok(socket_addr) = SocketAddr::from_str(value) {
return socket_addr.ip().to_string();
}
value.to_owned()
}
fn wildcard_match(pattern: &str, target: &str) -> bool {
let pattern_parts: Vec<&str> = pattern.split('.').collect();
let target_parts: Vec<&str> = target.split('.').collect();
if pattern_parts.len() != target_parts.len() {
return false;
}
pattern_parts
.iter()
.zip(target_parts.iter())
.all(|(pattern_part, target_part)| *pattern_part == "*" || pattern_part == target_part)
}
fn ip_to_u128(ip: IpAddr) -> u128 {
match ip {
IpAddr::V4(a) => u32::from(a) as u128,
IpAddr::V6(b) => {
let octets = b.octets();
let mut acc = 0u128;
for byte in &octets {
acc = (acc << 8) | (*byte as u128);
}
acc
}
}
}
fn ip_in_range(ip: IpAddr, start: IpAddr, end: IpAddr) -> bool {
match (ip, start, end) {
(IpAddr::V4(a), IpAddr::V4(b), IpAddr::V4(c)) => {
let a = u32::from(a);
let b = u32::from(b);
let c = u32::from(c);
b <= a && a <= c
}
(IpAddr::V6(a), IpAddr::V6(b), IpAddr::V6(c)) => {
let a = ip_to_u128(IpAddr::V6(a));
let b = ip_to_u128(IpAddr::V6(b));
let c = ip_to_u128(IpAddr::V6(c));
b <= a && a <= c
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_of_and_list() {
assert!(Environment::of("").is_none());
assert!(Environment::of("127.0.0.1").is_some());
assert!(Environment::of_list(Vec::<String>::new()).is_none());
assert!(Environment::of_list(vec!["1.2.3.4"]).is_some());
}
#[test]
fn test_exact_match() {
let a = Environment::of("1.2.3.4").unwrap();
let b = Environment::of("1.2.3.4").unwrap();
assert!(a.is_match(&b));
let c = Environment::of("1.2.3.5").unwrap();
assert!(!a.is_match(&c));
}
#[test]
fn test_cidr_match() {
let a = Environment::of_list(vec!["192.168.0.0/16"]).unwrap();
let b = Environment::of("192.168.1.5").unwrap();
assert!(a.is_match(&b));
let c = Environment::of("10.0.0.1").unwrap();
assert!(!a.is_match(&c));
}
#[test]
fn test_range_match() {
let a = Environment::of_list(vec!["192.168.0.1-192.168.0.10"]).unwrap();
assert!(a.is_match(&Environment::of("192.168.0.5").unwrap()));
assert!(!a.is_match(&Environment::of("192.168.0.11").unwrap()));
}
#[test]
fn test_java_plain_acl_wildcard_match() {
let a = Environment::of_list(vec!["192.168.0.*", "10.10.*.*"]).unwrap();
assert!(a.is_match(&Environment::of("192.168.0.5").unwrap()));
assert!(a.is_match(&Environment::of("10.10.1.2").unwrap()));
assert!(!a.is_match(&Environment::of("10.11.1.2").unwrap()));
}
#[test]
fn test_source_ip_match_normalizes_socket_address() {
assert!(source_ip_matches("127.0.0.1", "127.0.0.1:10911"));
assert!(source_ip_matches("*", "127.0.0.1:10911"));
assert!(!source_ip_matches("127.0.0.2", "127.0.0.1:10911"));
}
#[test]
fn test_empty_behavior() {
let a = Environment::default();
let b = Environment::of("1.2.3.4").unwrap();
assert!(a.is_match(&b));
let c = Environment::default();
assert!(!b.is_match(&c));
}
}