use crate::ipv6::{parse_ipv6_rev, Segment};
use std::io::Cursor;
fn is_ipv4(ip: &str) -> bool {
let mut nums = ip
.split('.')
.take_while(|num| num.len() <= 3)
.map(|num| num.parse::<u8>());
nums.by_ref()
.take(4)
.filter(std::result::Result::is_ok)
.count()
== 4
&& nums.next().is_none()
}
#[test]
fn is_ipv4_recognizes_ipv4_addresses() {
assert!(is_ipv4("000.001.00.255"));
}
#[test]
fn is_ipv4_rejects_invalid_ipv4_addresses() {
assert!(is_ipv4("000.001.00.255"));
assert!(!is_ipv4("000.001.00.256"));
assert!(!is_ipv4("000.001.00.256/32"));
assert!(!is_ipv4("0000.1.1.1"));
assert!(!is_ipv4("1.1.1"));
assert!(!is_ipv4("1.1.1.1.1"));
assert!(!is_ipv4("1.1._.1"));
assert!(!is_ipv4("_.1.1.1.1"));
assert!(!is_ipv4("1_.1.1.1.1"));
assert!(!is_ipv4("_1.1.1.1.1"));
assert!(!is_ipv4("1.1.1.1.1_"));
}
fn write_to_buf(
buf: &mut [u8],
mut writer: impl FnMut(&mut Cursor<&mut [u8]>) -> std::io::Result<()>,
) -> std::io::Result<&str> {
let end = {
let mut cursor = Cursor::new(&mut *buf);
writer(&mut cursor)?;
cursor.position() as usize
};
std::str::from_utf8(&buf[..end])
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}
pub(crate) fn sanitize_ip(input: &mut String) {
let ip = input.split('/').next().unwrap();
if is_ipv4(ip) {
let mut zeros_to_remove = [None, None, None, None];
let mut iter_zeros_to_remove = zeros_to_remove.iter_mut();
for pos in ip
.rmatch_indices('0')
.map(|(pos, _)| pos)
.filter(|&pos| pos == 0 || input.as_bytes()[pos - 1] == b'.')
{
let zero_count = input.as_bytes()[pos..]
.iter()
.position(|b| *b != b'0')
.unwrap_or(ip.len() - pos);
let zeros_to_remove =
if pos + zero_count == ip.len() || input.as_bytes()[pos..][zero_count] == b'.' {
zero_count - 1
} else {
zero_count
};
if zeros_to_remove > 0 {
*iter_zeros_to_remove.next().unwrap() =
Some(pos..pos + zeros_to_remove);
}
}
for modification in zeros_to_remove.into_iter().flatten() {
input.replace_range(modification, "");
}
} else if let Ok(reverse_parsed_ipv6) = parse_ipv6_rev(ip).map_err(|_| ()) {
let ip_len = ip.len();
use std::io::Write as _;
for segment in &reverse_parsed_ipv6 {
match segment {
Segment::Num(range) => {
let num = &input[range.clone()];
if (num.starts_with('0') && num.len() > 1)
|| num.bytes().any(|b| b.is_ascii_lowercase())
{
let mut buf = [0u8; 4];
let hex = write_to_buf(&mut buf, |cursor| {
write!(
cursor,
"{:X}",
u16::from_str_radix(num, 16).unwrap()
)
})
.unwrap();
input.replace_range(range.clone(), hex);
}
}
Segment::Colons(range) => {
if range.len() == 2 {
let number_count = reverse_parsed_ipv6
.iter()
.filter(|segment| {
!matches!(segment, Segment::Colons(_))
})
.count();
let missing_zero_count = 8 - number_count;
let mut buf = [0u8; 15];
let zeros = write_to_buf(&mut buf, |cursor| {
if range.start != 0 {
cursor.write_all(b":")?;
}
for i in 0..missing_zero_count {
cursor.write_all(if i == 0 {
b"0"
} else {
b":0"
})?;
}
if range.end != ip_len {
cursor.write_all(b":")?;
}
Ok(())
})
.unwrap();
input.replace_range(range.clone(), zeros);
}
}
}
}
}
}
#[cfg(test)]
fn test_sanitize_ip<const N: usize>(tests: [(&str, &str); N]) {
for (input, expected) in tests {
let mut output = input.to_string();
sanitize_ip(&mut output);
assert_eq!(output, expected, "{:?}", parse_ipv6_rev(input));
}
}
#[test]
fn sanitize_ip_replaces_double_colons_with_zeros() {
test_sanitize_ip([
("::1", "0:0:0:0:0:0:0:1"),
("0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:1"),
("::", "0:0:0:0:0:0:0:0"),
("0:0:0:1::", "0:0:0:1:0:0:0:0"),
("::1", "0:0:0:0:0:0:0:1"),
("::1", "0:0:0:0:0:0:0:1"),
("::1", "0:0:0:0:0:0:0:1"),
]);
}
#[test]
fn sanitize_ip_uppercases() {
test_sanitize_ip([
("cebc:2004:f::", "CEBC:2004:F:0:0:0:0:0"),
("3f:535::e:fbb", "3F:535:0:0:0:0:E:FBB"),
("::1/24", "0:0:0:0:0:0:0:1/24"),
]);
}
#[test]
fn sanitize_ip_recognizes_subpages_of_ipv6_address() {
test_sanitize_ip([
("1::1/IP_subpage", "1:0:0:0:0:0:0:1/IP_subpage"),
("1::1_/not_IP_subpage", "1::1_/not_IP_subpage"),
("1::g/not_IP_subpage", "1::g/not_IP_subpage"),
("1::1/24", "1:0:0:0:0:0:0:1/24"),
]);
}