Skip to content

Commit 1cc1f41

Browse files
committed
Add support for parsing IPv6 addresses and update tests for IPv4/6
1 parent b0eb28f commit 1cc1f41

12 files changed

Lines changed: 260 additions & 2 deletions

src/lib.rs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
mod structures;
66

77
use crate::structures::{flatten_ranges, Part, RangeOutput};
8+
use combine::parser::token::satisfy;
89
use combine::{
910
attempt, between, choice, eof,
1011
error::{ParseError, StreamError},
@@ -66,7 +67,7 @@ where
6667
I: Stream<Token = char>,
6768
I::Error: ParseError<I::Token, I::Range, I::Position>,
6869
{
69-
many1(alpha_num().or(dash()).or(token('.')))
70+
many1(alpha_num().or(dash()).or(token('.')).or(token(':')))
7071
}
7172

7273
fn digits<I>() -> impl Parser<I, Output = String>
@@ -77,6 +78,14 @@ where
7778
many1(digit())
7879
}
7980

81+
fn hex_digits<I>() -> impl Parser<I, Output = String>
82+
where
83+
I: Stream<Token = char>,
84+
I::Error: ParseError<I::Token, I::Range, I::Position>,
85+
{
86+
many1(satisfy(|c: char| c.is_ascii_hexdigit()))
87+
}
88+
8089
fn leading_zeros<I>() -> impl Parser<I, Output = (usize, u64)>
8190
where
8291
I: Stream<Token = char>,
@@ -95,6 +104,28 @@ where
95104
})
96105
}
97106

107+
fn leading_hex<I>() -> impl Parser<I, Output = (usize, u64, bool)>
108+
where
109+
I: Stream<Token = char>,
110+
I::Error: ParseError<I::Token, I::Range, I::Position>,
111+
{
112+
hex_digits().and_then(|x| {
113+
let mut digits = x.chars().take_while(|x| x == &'0').count();
114+
115+
if x.len() == digits {
116+
digits -= 1;
117+
}
118+
119+
let has_alpha = x
120+
.chars()
121+
.any(|c| c.is_ascii_hexdigit() && !c.is_ascii_digit());
122+
123+
u64::from_str_radix(&x, 16)
124+
.map(|num| (digits, num, has_alpha))
125+
.map_err(StreamErrorFor::<I>::other)
126+
})
127+
}
128+
98129
fn range_digits<I>() -> impl Parser<I, Output = RangeOutput>
99130
where
100131
I: Stream<Token = char>,
@@ -135,6 +166,49 @@ where
135166
})
136167
}
137168

169+
fn range_hex<I>() -> impl Parser<I, Output = RangeOutput>
170+
where
171+
I: Stream<Token = char>,
172+
I::Error: ParseError<I::Token, I::Range, I::Position>,
173+
{
174+
attempt((
175+
leading_hex(),
176+
optional_spaces().with(dash()),
177+
optional_spaces().with(leading_hex()),
178+
))
179+
.and_then(|((start_zeros, start, a1), _, (end_zeros, end, a2))| {
180+
if !(a1 || a2) {
181+
return Err(StreamErrorFor::<I>::unexpected_static_message("not hex"));
182+
}
183+
let mut xs = [start, end];
184+
xs.sort_unstable();
185+
186+
let same_prefix_len = start_zeros == end_zeros;
187+
188+
let (range, start_zeros, end_zeros) = if start > end {
189+
(
190+
RangeOutput::HexRangeReversed(end_zeros, same_prefix_len, end, start),
191+
end_zeros,
192+
start_zeros,
193+
)
194+
} else {
195+
(
196+
RangeOutput::HexRange(start_zeros, same_prefix_len, start, end),
197+
start_zeros,
198+
end_zeros,
199+
)
200+
};
201+
202+
if end_zeros > start_zeros {
203+
Err(StreamErrorFor::<I>::unexpected_static_message(
204+
"larger end padding",
205+
))
206+
} else {
207+
Ok(range)
208+
}
209+
})
210+
}
211+
138212
fn disjoint_digits<I>() -> impl Parser<I, Output = RangeOutput>
139213
where
140214
I: Stream<Token = char>,
@@ -157,6 +231,36 @@ where
157231
.map(RangeOutput::Disjoint)
158232
}
159233

234+
fn disjoint_hex<I>() -> impl Parser<I, Output = RangeOutput>
235+
where
236+
I: Stream<Token = char>,
237+
I::Error: ParseError<I::Token, I::Range, I::Position>,
238+
{
239+
let not_name = not_followed_by(
240+
optional_spaces()
241+
.with(hex_digits())
242+
.skip(optional_spaces())
243+
.skip(dash())
244+
.map(|_| ""),
245+
);
246+
247+
sep_by1(
248+
optional_spaces()
249+
.with(leading_hex())
250+
.skip(optional_spaces()),
251+
attempt(comma().skip(not_name)),
252+
)
253+
.and_then(|xs: Vec<(usize, u64, bool)>| {
254+
if xs.iter().any(|(_, _, a)| *a) {
255+
Ok(RangeOutput::HexDisjoint(
256+
xs.into_iter().map(|(z, n, _)| (z, n)).collect(),
257+
))
258+
} else {
259+
Err(StreamErrorFor::<I>::unexpected_static_message("not hex"))
260+
}
261+
})
262+
}
263+
160264
fn range<I>() -> impl Parser<I, Output = Vec<RangeOutput>>
161265
where
162266
I: Stream<Token = char>,
@@ -165,7 +269,13 @@ where
165269
between(
166270
open_bracket(),
167271
close_bracket(),
168-
sep_by1(range_digits().or(disjoint_digits()), comma()),
272+
sep_by1(
273+
attempt(range_hex())
274+
.or(range_digits())
275+
.or(attempt(disjoint_hex()))
276+
.or(attempt(disjoint_digits())),
277+
comma(),
278+
),
169279
)
170280
}
171281

@@ -492,4 +602,16 @@ mod tests {
492602
fn test_parse_osts() {
493603
assert_debug_snapshot!("Leading 0s", parse("OST01[00,01]"));
494604
}
605+
606+
#[test]
607+
fn test_parse_ip_addresses() {
608+
assert_debug_snapshot!("IPv4 single", parse("192.168.0.1"));
609+
assert_debug_snapshot!("IPv6 compressed", parse("2001:db8::1"));
610+
assert_debug_snapshot!(
611+
"IPv6 full",
612+
parse("fe80:1234:5678:9abc:def0:1234:5678:9abc")
613+
);
614+
assert_debug_snapshot!("Multiple IPv6 literals", parse("2001:db8::1, 2001:db8::2"));
615+
assert_debug_snapshot!("IPv6 expansion", parse("2001:db8::[0-f]"));
616+
}
495617
}

src/snapshots/hostlist_parser__tests__Having a closing brace before an opening brace.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ Err(
3131
'.',
3232
),
3333
),
34+
Expected(
35+
Token(
36+
':',
37+
),
38+
),
3439
Unexpected(
3540
Token(
3641
'h',
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"192.168.0.1\")"
4+
---
5+
Ok(
6+
[
7+
"192.168.0.1",
8+
],
9+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"192.168.0.1\")"
4+
---
5+
Ok(
6+
[
7+
"192.168.0.1",
8+
],
9+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"2001:db8::1\")"
4+
---
5+
Ok(
6+
[
7+
"2001:db8::1",
8+
],
9+
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"2001:db8::[0-f]\")"
4+
---
5+
Ok(
6+
[
7+
"2001:db8::0",
8+
"2001:db8::1",
9+
"2001:db8::2",
10+
"2001:db8::3",
11+
"2001:db8::4",
12+
"2001:db8::5",
13+
"2001:db8::6",
14+
"2001:db8::7",
15+
"2001:db8::8",
16+
"2001:db8::9",
17+
"2001:db8::a",
18+
"2001:db8::b",
19+
"2001:db8::c",
20+
"2001:db8::d",
21+
"2001:db8::e",
22+
"2001:db8::f",
23+
],
24+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"fe80:1234:5678:9abc:def0:1234:5678:9abc\")"
4+
---
5+
Ok(
6+
[
7+
"fe80:1234:5678:9abc:def0:1234:5678:9abc",
8+
],
9+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"fe80::1\")"
4+
---
5+
Ok(
6+
[
7+
"fe80::1",
8+
],
9+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
source: src/lib.rs
3+
expression: "parse(\"2001:db8::1, 2001:db8::2\")"
4+
---
5+
Ok(
6+
[
7+
"2001:db8::1",
8+
"2001:db8::2",
9+
],
10+
)

src/snapshots/hostlist_parser__tests__No separation between comma and dash.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ Err(
2121
"digit",
2222
),
2323
),
24+
Unexpected(
25+
Token(
26+
'1',
27+
),
28+
),
2429
],
2530
},
2631
)

0 commit comments

Comments
 (0)