Skip to content

Commit fd5483d

Browse files
committed
extract short IPv4 address parser
* future versions won't accept short IPv4 addresses in FromStr * provide the IPv4 short parser as explicit function; the provided functions interpret short addresses as (non-host) networks, but a wrapper function could easily undo this. I.e currently "127" parses as 127.0.0.0/32, but parse_short_ipv4_address_as_cidr will return 127.0.0.0/8.
1 parent 1ef4b79 commit fd5483d

File tree

3 files changed

+157
-11
lines changed

3 files changed

+157
-11
lines changed

src/local_addr_parser.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,11 @@ pub trait ParseableAddress: Sized {
1111
}
1212

1313
fn special_ipv4_parser(s: &str) -> Option<Ipv4Addr> {
14-
// handle ipv4 short forms (e.g. '10' as '10.0.0.0')
15-
let mut octets = [0u8; 4];
16-
for (ndx, os) in s.split('.').enumerate() {
17-
if ndx >= 4 {
18-
// too many octets
19-
return None;
20-
}
21-
// abort on invalid octet
22-
octets[ndx] = os.parse().ok()?;
23-
}
24-
Some(Ipv4Addr::from(octets))
14+
Some(
15+
crate::parsers::parse_short_ipv4_address_as_cidr(s)
16+
.ok()?
17+
.first_address(),
18+
)
2519
}
2620

2721
impl ParseableAddress for Ipv4Addr {

src/parsers/ipv4_short.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use core::str::FromStr;
2+
use std::net::{
3+
IpAddr,
4+
Ipv4Addr,
5+
};
6+
7+
use crate::{
8+
errors::NetworkParseError,
9+
AnyIpCidr,
10+
IpCidr,
11+
Ipv4Cidr,
12+
};
13+
14+
// parse normal IPv4 addresses as host cidr, and short forms
15+
// with a cidr that indicates how many octets were present.
16+
fn _parse_short_ipv4_address_as_cidr(s: &str) -> Option<Ipv4Cidr> {
17+
let mut octets = [0u8; 4];
18+
let mut last = 0;
19+
for (ndx, os) in s.split('.').enumerate() {
20+
if ndx >= 4 {
21+
// too many octets
22+
return None;
23+
}
24+
// abort on invalid octet
25+
octets[ndx] = os.parse().ok()?;
26+
last = ndx;
27+
}
28+
let bits = (last as u8 + 1) * 8;
29+
Some(Ipv4Cidr::new(Ipv4Addr::from(octets), bits).expect("host bits are zero"))
30+
}
31+
32+
/// Parse "short" IPv4 addresses as networks with octet-aligned length
33+
///
34+
/// * parse `"10"` as `10.0.0.0/8`
35+
/// * parse `"192.168"` as `192.168.0.0/16`
36+
/// * parse `"192.0.2"` as `192.0.2.0/24`
37+
/// * parse `"127"` as `127.0.0.0/8`
38+
/// * parse `"127.0.0.1"` as `127.0.0.1/32`
39+
///
40+
/// The returned prefix length indicates how many octets were present.
41+
///
42+
/// This is very different from [`inet_addr`][`super::inet_addr`] which would
43+
/// interpret `"192.168"` as `192.0.0.168`!
44+
///
45+
/// This function doesn't accept normal CIDR notations, so you will probably
46+
/// need to combine it with other functions.
47+
pub fn parse_short_ipv4_address_as_cidr(s: &str) -> Result<Ipv4Cidr, NetworkParseError> {
48+
match _parse_short_ipv4_address_as_cidr(s) {
49+
Some(n) => Ok(n),
50+
None => {
51+
// address parser should fail here (to generate proper error result)
52+
// (but if it works the result should actually be correct)
53+
Ok(Ipv4Cidr::new_host(s.parse()?))
54+
},
55+
}
56+
}
57+
58+
/// Parses normal IPv4 CIDR notations or short forms via [`parse_short_ipv4_address_as_cidr`]
59+
pub fn parse_short_ipv4_cidr(s: &str) -> Result<Ipv4Cidr, NetworkParseError> {
60+
super::parse_cidr_full(s, FromStr::from_str, parse_short_ipv4_address_as_cidr)
61+
}
62+
63+
/// Parses normal IP addresses as host addresses, and short IPv4 addresses via [`parse_short_ipv4_address_as_cidr`]
64+
///
65+
/// This function doesn't accept normal CIDR notations, so you will probably
66+
/// need to combine it with other functions.
67+
pub fn parse_short_ip_address_as_cidr(s: &str) -> Result<IpCidr, NetworkParseError> {
68+
match s.parse::<IpAddr>() {
69+
Ok(a) => Ok(IpCidr::new_host(a)),
70+
// only try short IPv4 as fallback
71+
Err(e) => match _parse_short_ipv4_address_as_cidr(s) {
72+
Some(n) => Ok(n.into()),
73+
None => Err(e.into()),
74+
},
75+
}
76+
}
77+
78+
/// Parses normal IP CIDR notations or short IPv4 forms via [`parse_short_ipv4_address_as_cidr`]
79+
pub fn parse_short_ip_cidr(s: &str) -> Result<IpCidr, NetworkParseError> {
80+
super::parse_cidr_full(s, FromStr::from_str, parse_short_ip_address_as_cidr)
81+
}
82+
83+
/// Parses normal IP CIDR notations, `"any"` or short IPv4 forms via [`parse_short_ipv4_address_as_cidr`]
84+
pub fn parse_short_any_ip_cidr(s: &str) -> Result<AnyIpCidr, NetworkParseError> {
85+
super::parse_any_cidr_full(s, FromStr::from_str, parse_short_ip_address_as_cidr)
86+
}
87+
88+
#[cfg(test)]
89+
mod tests {
90+
use super::{
91+
parse_short_ip_cidr,
92+
parse_short_ipv4_cidr,
93+
};
94+
use crate::{
95+
IpCidr,
96+
Ipv4Cidr,
97+
};
98+
99+
fn test(s: &str, expect: &str) {
100+
// check against standard parser of canonical form
101+
let expect_v4 = expect.parse::<Ipv4Cidr>().unwrap();
102+
let expect = expect.parse::<IpCidr>().unwrap();
103+
104+
assert_eq!(parse_short_ipv4_cidr(s).unwrap(), expect_v4);
105+
106+
assert_eq!(parse_short_ip_cidr(s).unwrap(), expect);
107+
}
108+
109+
#[test]
110+
fn invalid_short() {
111+
assert!(parse_short_ipv4_cidr("").is_err());
112+
assert!(parse_short_ip_cidr("").is_err());
113+
}
114+
115+
#[test]
116+
fn short_10() {
117+
test("10.0.0.0/8", "10.0.0.0/8");
118+
test("10", "10.0.0.0/8");
119+
test("10.42.0.0/16", "10.42.0.0/16");
120+
test("10.42", "10.42.0.0/16");
121+
test("10.0.42.0/24", "10.0.42.0/24");
122+
test("10.0.42", "10.0.42.0/24");
123+
test("10.0.0.42/32", "10.0.0.42/32");
124+
test("10.0.0.42", "10.0.0.42/32");
125+
}
126+
127+
#[test]
128+
fn short_192_168() {
129+
test("192.168.0.0/16", "192.168.0.0/16");
130+
test("192.168", "192.168.0.0/16");
131+
test("192.168.42.0/24", "192.168.42.0/24");
132+
test("192.168.42", "192.168.42.0/24");
133+
test("192.168.0.42/32", "192.168.0.42/32");
134+
test("192.168.0.42", "192.168.0.42/32");
135+
}
136+
137+
#[test]
138+
fn short_192_0_2() {
139+
test("192.0.2.0/24", "192.0.2.0/24");
140+
test("192.0.2", "192.0.2.0/24");
141+
test("192.0.2.42/32", "192.0.2.42/32");
142+
test("192.0.2.42", "192.0.2.42/32");
143+
}
144+
}

src/parsers/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
mod combinators;
99
mod inetaddr;
10+
mod ipv4_short;
1011

1112
pub use self::{
1213
combinators::{
@@ -26,4 +27,11 @@ pub use self::{
2627
parse_loose_ip,
2728
parse_loose_ipv4,
2829
},
30+
ipv4_short::{
31+
parse_short_any_ip_cidr,
32+
parse_short_ip_address_as_cidr,
33+
parse_short_ip_cidr,
34+
parse_short_ipv4_address_as_cidr,
35+
parse_short_ipv4_cidr,
36+
},
2937
};

0 commit comments

Comments
 (0)