From d90ca5c0d7df85874e92e9931850ec1077830043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Mon, 24 Jun 2024 14:16:21 +0200 Subject: [PATCH] add inet_addr-like IPv4Addr parser --- src/lib.rs | 1 + src/parsers/inetaddr.rs | 117 ++++++++++++++++++++++++++++++++++++++++ src/parsers/mod.rs | 10 ++++ 3 files changed, 128 insertions(+) create mode 100644 src/parsers/inetaddr.rs create mode 100644 src/parsers/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 20e89ca..06655dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ pub use self::{ }; pub mod errors; +pub mod parsers; mod serde_common; diff --git a/src/parsers/inetaddr.rs b/src/parsers/inetaddr.rs new file mode 100644 index 0000000..c050d6f --- /dev/null +++ b/src/parsers/inetaddr.rs @@ -0,0 +1,117 @@ +use std::net::{AddrParseError, IpAddr, Ipv4Addr}; + +/// Parse "loose" IPv4 address similar to POSIX `inet_addr` +/// +/// This also accepts inputs that are not 4 decimal represented octects separated by dots. +/// +/// * Allows less than 4 numbers separated by dots; the last number is +/// interpreted as an 32-bit (no dot), 24-bit (1 dot), 16-bit (2 dots) number. +/// Other numbers still must be octets (i.e. 8 bit) +/// * Allows hexadecimal ("0x" or "0X" prefix) and octal ("0" prefix) numbers +/// in all places. +/// +/// See +pub fn inet_addr(s: &str) -> Option { + let mut slots = [0u32; 4]; + let mut last_slot = 0; + for (ndx, part) in s.split('.').enumerate() { + if ndx >= 4 { + // too many '.' + return None; + } + let slot = if part.starts_with("0x") || part.starts_with("0X") { + let part = &part[2..]; + if part.starts_with("+") { + // we don't want to support "0x+..." + return None; + } + u32::from_str_radix(part, 16) + } else if part.starts_with("0") { + u32::from_str_radix(part, 8) + } else { + part.parse::() + }; + slots[ndx] = slot.ok()?; + last_slot = ndx; + } + debug_assert!(last_slot <= 3, "can't have more than 4"); + + let num = if last_slot == 0 { + slots[0] + } else { + let mut base: u32 = 0; + for ndx in 0..last_slot { + if slots[ndx] >= 256 { + // leading parts must be octects + return None; + } + // shift by 24, 16 or 8 + base = base | (slots[ndx] << (3 - ndx) * 8); + } + // last 3 => have 4 => 1 byte, last 2 => have 3 => 2 bytes, last 1 => have 2 => 3 bytes + let last_slot_bit_limit = (4 - last_slot) * 8; + if slots[last_slot] >= 1 << last_slot_bit_limit { + // last part too big + return None; + } + // never shift last slot + base | slots[last_slot] + }; + + Some(Ipv4Addr::from(num)) +} + +/// Parse IPv4 address; fall back to `inet_addr` if normal parser fails +pub fn parse_loose_ipv4(s: &str) -> Result { + match s.parse() { + Ok(a) => Ok(a), + Err(e) => { + if let Some(a) = inet_addr(s) { + Ok(a) + } else { + Err(e) + } + } + } +} + +/// Parse IP address; fall back to `inet_addr` if normal parser fails +pub fn parse_loose_ip(s: &str) -> Result { + match s.parse() { + Ok(a) => Ok(a), + Err(e) => { + if let Some(a) = inet_addr(s) { + Ok(IpAddr::V4(a)) + } else { + Err(e) + } + } + } +} + +#[cfg(test)] +mod tests { + use std::net::{AddrParseError, IpAddr, Ipv4Addr}; + + fn run(s: &str, expect: Ipv4Addr) { + assert_eq!(super::inet_addr(s), Some(expect)); + assert_eq!(super::parse_loose_ipv4(s), Ok::<_, AddrParseError>(expect)); + assert_eq!(super::parse_loose_ip(s), Ok::<_, AddrParseError>(IpAddr::V4(expect))); + } + + #[test] + fn test_loose_ipv4() { + run("10.0.1.2", Ipv4Addr::from([10, 0, 1, 2])); + run("10.0.258", Ipv4Addr::from([10, 0, 1, 2])); + run("0xA000102", Ipv4Addr::from([10, 0, 1, 2])); + run("0XA000102", Ipv4Addr::from([10, 0, 1, 2])); + run("012.0x102", Ipv4Addr::from([10, 0, 1, 2])); + + run("127.0.0.1", Ipv4Addr::from([127, 0, 0, 1])); + run("127.0.1", Ipv4Addr::from([127, 0, 0, 1])); + run("127.1", Ipv4Addr::from([127, 0, 0, 1])); + + run("0", Ipv4Addr::from([0, 0, 0, 0])); + run("1", Ipv4Addr::from([0, 0, 0, 1])); + } +} diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs new file mode 100644 index 0000000..63f2341 --- /dev/null +++ b/src/parsers/mod.rs @@ -0,0 +1,10 @@ +//! Extra parsers +//! +//! The `FromStr` implementations in this crate are rather strict +//! about the allowed input. Depending on the data format that needs +//! to be handled the functions here might help implementing custom +//! parsers. + +mod inetaddr; + +pub use self::inetaddr::{inet_addr, parse_loose_ip, parse_loose_ipv4};