From 07846687671c2801f4eb83a156b4e1d3dc10e5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Mon, 24 Jun 2024 16:00:33 +0200 Subject: [PATCH] add many inet/cidr parser combinators to allow custom address parsers and ignoring host bits --- src/cidr/any.rs | 8 +- src/cidr/from_str.rs | 11 +-- src/inet/from_str.rs | 11 +-- src/parsers/combinators.rs | 172 +++++++++++++++++++++++++++++++++++++ src/parsers/mod.rs | 21 ++++- 5 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 src/parsers/combinators.rs diff --git a/src/cidr/any.rs b/src/cidr/any.rs index 7b2f3dd..6cdf90b 100644 --- a/src/cidr/any.rs +++ b/src/cidr/any.rs @@ -4,7 +4,6 @@ use core::{ }; use std::net::IpAddr; -use super::from_str::cidr_from_str; use crate::{ errors::*, Family, @@ -244,11 +243,8 @@ impl FromStr for AnyIpCidr { type Err = NetworkParseError; fn from_str(s: &str) -> Result { - if s == "any" { - Ok(Self::Any) - } else { - cidr_from_str::(s).map(Self::from) - } + // TODO: use strict FromStr::from_str address parsing with version bump + crate::parsers::parse_any_cidr(s, crate::local_addr_parser::ParseableAddress::address_from_str) } } diff --git a/src/cidr/from_str.rs b/src/cidr/from_str.rs index a0feb69..c33b70c 100644 --- a/src/cidr/from_str.rs +++ b/src/cidr/from_str.rs @@ -1,5 +1,3 @@ -use core::str::FromStr; - use crate::{ errors::*, local_addr_parser::ParseableAddress, @@ -11,11 +9,6 @@ where C: Cidr, C::Address: ParseableAddress, { - match s.rfind('/') { - None => Ok(C::new_host(C::Address::address_from_str(s)?)), - Some(pos) => C::new( - C::Address::address_from_str(&s[0..pos])?, - u8::from_str(&s[pos + 1..])?, - ), - } + // TODO: use strict FromStr::from_str address parsing with version bump + crate::parsers::parse_cidr(s, C::Address::address_from_str) } diff --git a/src/inet/from_str.rs b/src/inet/from_str.rs index 273610d..6bbcb37 100644 --- a/src/inet/from_str.rs +++ b/src/inet/from_str.rs @@ -1,5 +1,3 @@ -use core::str::FromStr; - use crate::{ errors::NetworkParseError, local_addr_parser::ParseableAddress, @@ -11,11 +9,6 @@ where I: Inet, I::Address: ParseableAddress, { - Ok(match s.rfind('/') { - None => I::new_host(I::Address::address_from_str(s)?), - Some(pos) => I::new( - I::Address::address_from_str(&s[0..pos])?, - u8::from_str(&s[pos + 1..])?, - )?, - }) + // TODO: use strict FromStr::from_str address parsing with version bump + crate::parsers::parse_inet(s, I::Address::address_from_str) } diff --git a/src/parsers/combinators.rs b/src/parsers/combinators.rs new file mode 100644 index 0000000..8641009 --- /dev/null +++ b/src/parsers/combinators.rs @@ -0,0 +1,172 @@ +use std::net::{AddrParseError, IpAddr}; + +use crate::{errors::NetworkParseError, Address, AnyIpCidr, Cidr, Inet, IpInet}; + +/// Parse [`Cidr`] with custom address and network (when no '/' separator was found) parser +/// +/// If a '/' is found, parse trailing number as prefix length and leading address with `address_parser`. +/// Otherwise parse with `host_parser`. +pub fn parse_cidr_full(s: &str, address_parser: AP, host_parser: NP) -> Result +where + C: Cidr, + AP: FnOnce(&str) -> Result, + NP: FnOnce(&str) -> Result, +{ + match s.rfind('/') { + None => host_parser(s), + Some(pos) => C::new( + address_parser(&s[0..pos])?, + s[pos + 1..].parse()?, + ), + } +} + +/// Parse [`Cidr`] with custom address parser +/// +/// If a '/' is found, parse trailing number as prefix length and leading address with `address_parser`. +/// Otherwise parse `address_parser` and treat as host (maximum prefix length). +pub fn parse_cidr(s: &str, address_parser: AP) -> Result +where + C: Cidr, + AP: Fn(&str) -> Result, +{ + parse_cidr_full(s, &address_parser, |s| { + Ok(C::new_host(address_parser(s)?)) + }) +} + +/// Parse [`Cidr`] with custom address and network (when no '/' separator was found) parser +/// +/// Similar to [`parse_cidr_full`] but ignores host bits in addresses. +pub fn parse_cidr_full_ignore_hostbits(s: &str, address_parser: AP, host_parser: NP) -> Result +where + C: Cidr, + AP: FnOnce(&str) -> Result, + NP: FnOnce(&str) -> Result, +{ + match s.rfind('/') { + None => host_parser(s), + Some(pos) => { + let inet = ::Inet::new( + address_parser(&s[0..pos])?, + s[pos + 1..].parse()?, + )?; + Ok(inet.network()) + }, + } +} + +/// Parse [`Cidr`] with custom address parser +/// +/// Similar to [`parse_cidr`] but ignores host bits in addresses. +pub fn parse_cidr_ignore_hostbits(s: &str, address_parser: AP) -> Result +where + C: Cidr, + AP: Fn(&str) -> Result, +{ + parse_cidr_full_ignore_hostbits(s, &address_parser, |s| { + Ok(C::new_host(address_parser(s)?)) + }) +} + +/// Parse [`AnyIpCidr`] with custom address and network (when no '/' separator was found) parser +/// +/// Similar to [`parse_any_cidr_full`] but ignores host bits in addresses. +pub fn parse_any_cidr_full_ignore_hostbits(s: &str, address_parser: AP, host_parser: NP) -> Result +where + AP: FnOnce(&str) -> Result, + NP: FnOnce(&str) -> Result, +{ + match s.rfind('/') { + None => host_parser(s), + Some(pos) => Ok(IpInet::new( + address_parser(&s[0..pos])?, + s[pos + 1..].parse()?, + )?.network().into()), + } +} + +/// Parse [`AnyIpCidr`] with custom address parser +/// +/// Similar to [`parse_any_cidr`] but ignores host bits in addresses. +pub fn parse_any_cidr_ignore_hostbits(s: &str, address_parser: AP) -> Result +where + AP: Fn(&str) -> Result, +{ + parse_any_cidr_full(s, &address_parser, |s| { + if s == "any" { + Ok(AnyIpCidr::Any) + } else { + Ok(AnyIpCidr::new_host(address_parser(s)?)) + } + }) +} + +/// Parse [`AnyIpCidr`] with custom address and network (when no '/' separator was found) parser +/// +/// If a '/' is found, parse trailing number as prefix length and leading address with `address_parser`. +/// Otherwise parse with `host_parser`. +pub fn parse_any_cidr_full(s: &str, address_parser: AP, host_parser: NP) -> Result +where + AP: FnOnce(&str) -> Result, + NP: FnOnce(&str) -> Result, +{ + match s.rfind('/') { + None => host_parser(s), + Some(pos) => AnyIpCidr::new( + address_parser(&s[0..pos])?, + s[pos + 1..].parse()?, + ), + } +} + +/// Parse [`AnyIpCidr`] with custom address parser +/// +/// If a '/' is found, parse trailing number as prefix length and leading address with `address_parser`. +/// If input is just `"any"` returns [`AnyIpCidr::Any`]. +/// Otherwise parse `address_parser` and treat as host (maximum prefix length). +pub fn parse_any_cidr(s: &str, address_parser: AP) -> Result +where + AP: Fn(&str) -> Result, +{ + parse_any_cidr_full(s, &address_parser, |s| { + if s == "any" { + Ok(AnyIpCidr::Any) + } else { + Ok(AnyIpCidr::new_host(address_parser(s)?)) + } + }) +} + +/// Parse [`Inet`] with custom address and network (when no '/' separator was found) parser +/// +/// If a '/' is found, parse trailing number as prefix length and leading address with `address_parser`. +/// Otherwise parse with `host_parser`. +pub fn parse_inet_full(s: &str, address_parser: AP, host_parser: NP) -> Result +where + I: Inet, + AP: FnOnce(&str) -> Result, + NP: FnOnce(&str) -> Result, +{ + match s.rfind('/') { + None => host_parser(s), + Some(pos) => Ok(I::new( + address_parser(&s[0..pos])?, + s[pos + 1..].parse()?, + )?), + } +} + +/// Parse [`Inet`] with custom address parser +/// +/// If a '/' is found, parse trailing number as prefix length and leading address with `address_parser`. +/// Otherwise parse `address_parser` and treat as host (maximum prefix length). +pub fn parse_inet(s: &str, address_parser: AP) -> Result +where + I: Inet, + AP: Fn(&str) -> Result, +{ + parse_inet_full(s, &address_parser, |s| { + Ok(I::new_host(address_parser(s)?)) + }) +} diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs index 63f2341..86b7f28 100644 --- a/src/parsers/mod.rs +++ b/src/parsers/mod.rs @@ -5,6 +5,25 @@ //! to be handled the functions here might help implementing custom //! parsers. +mod combinators; mod inetaddr; -pub use self::inetaddr::{inet_addr, parse_loose_ip, parse_loose_ipv4}; +pub use self::{ + combinators::{ + parse_any_cidr_full_ignore_hostbits, + parse_any_cidr_full, + parse_any_cidr_ignore_hostbits, + parse_any_cidr, + parse_cidr_full_ignore_hostbits, + parse_cidr_full, + parse_cidr_ignore_hostbits, + parse_cidr, + parse_inet_full, + parse_inet, + }, + inetaddr::{ + inet_addr, + parse_loose_ip, + parse_loose_ipv4, + }, +};