Skip to content

Commit

Permalink
add inet_addr-like IPv4Addr parser
Browse files Browse the repository at this point in the history
  • Loading branch information
stbuehler committed Jun 24, 2024
1 parent de1c6b2 commit d90ca5c
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub use self::{
};

pub mod errors;
pub mod parsers;

mod serde_common;

Expand Down
117 changes: 117 additions & 0 deletions src/parsers/inetaddr.rs
Original file line number Diff line number Diff line change
@@ -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 <https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_addr.html>
pub fn inet_addr(s: &str) -> Option<Ipv4Addr> {
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::<u32>()
};
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<Ipv4Addr, AddrParseError> {
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<IpAddr, AddrParseError> {
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]));
}
}
10 changes: 10 additions & 0 deletions src/parsers/mod.rs
Original file line number Diff line number Diff line change
@@ -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};

0 comments on commit d90ca5c

Please sign in to comment.