use core::mem;
use crate::parser::char;
use crate::parser::str::{
find_split_hole, get_wrapped_inner, rfind_split_hole, satisfy_chars_with_pct_encoded,
strip_ascii_char_prefix,
};
use crate::spec::Spec;
use crate::validate::Error;
pub(crate) fn validate_userinfo<S: Spec>(i: &str) -> Result<(), Error> {
let is_valid = satisfy_chars_with_pct_encoded(
i,
char::is_ascii_userinfo_ipvfutureaddr,
char::is_nonascii_userinfo::<S>,
);
if is_valid {
Ok(())
} else {
Err(Error::new())
}
}
#[must_use]
fn is_dec_octet(i: &str) -> bool {
matches!(
i.as_bytes(),
[b'0'..=b'9']
| [b'1'..=b'9', b'0'..=b'9']
| [b'1', b'0'..=b'9', b'0'..=b'9']
| [b'2', b'0'..=b'4', b'0'..=b'9']
| [b'2', b'5', b'0'..=b'5']
)
}
fn validate_ipv4address(i: &str) -> Result<(), Error> {
let (first, rest) = find_split_hole(i, b'.').ok_or_else(Error::new)?;
if !is_dec_octet(first) {
return Err(Error::new());
}
let (second, rest) = find_split_hole(rest, b'.').ok_or_else(Error::new)?;
if !is_dec_octet(second) {
return Err(Error::new());
}
let (third, fourth) = find_split_hole(rest, b'.').ok_or_else(Error::new)?;
if is_dec_octet(third) && is_dec_octet(fourth) {
Ok(())
} else {
Err(Error::new())
}
}
#[derive(Clone, Copy)]
enum V6AddrPart {
H16Omit,
H16Cont,
H16End,
V4,
Omit,
}
fn split_v6_addr_part(i: &str) -> Result<(&str, V6AddrPart), Error> {
debug_assert!(!i.is_empty());
match find_split_hole(i, b':') {
Some((prefix, rest)) => {
if prefix.len() >= 5 {
return Err(Error::new());
}
if prefix.is_empty() {
return match strip_ascii_char_prefix(rest, b':') {
Some(rest) => Ok((rest, V6AddrPart::Omit)),
None => Err(Error::new()),
};
}
debug_assert!((1..=4).contains(&prefix.len()));
if !prefix.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(Error::new());
}
match strip_ascii_char_prefix(rest, b':') {
Some(rest) => Ok((rest, V6AddrPart::H16Omit)),
None => Ok((rest, V6AddrPart::H16Cont)),
}
}
None => {
if i.len() >= 5 {
validate_ipv4address(i)?;
return Ok(("", V6AddrPart::V4));
}
if i.bytes().all(|b| b.is_ascii_hexdigit()) {
Ok(("", V6AddrPart::H16End))
} else {
Err(Error::new())
}
}
}
}
fn validate_ipv6address(mut i: &str) -> Result<(), Error> {
let mut h16_count = 0;
let mut is_omitted = false;
while !i.is_empty() {
let (rest, part) = split_v6_addr_part(i)?;
match part {
V6AddrPart::H16Omit => {
h16_count += 1;
if mem::replace(&mut is_omitted, true) {
return Err(Error::new());
}
}
V6AddrPart::H16Cont => {
h16_count += 1;
if rest.is_empty() {
return Err(Error::new());
}
}
V6AddrPart::H16End => {
h16_count += 1;
break;
}
V6AddrPart::V4 => {
debug_assert!(rest.is_empty());
h16_count += 2;
break;
}
V6AddrPart::Omit => {
if mem::replace(&mut is_omitted, true) {
return Err(Error::new());
}
}
}
if h16_count > 8 {
return Err(Error::new());
}
i = rest;
}
let is_valid = if is_omitted {
h16_count < 8
} else {
h16_count == 8
};
if is_valid {
Ok(())
} else {
Err(Error::new())
}
}
pub(super) fn validate_authority<S: Spec>(i: &str) -> Result<(), Error> {
let (i, _userinfo) = match find_split_hole(i, b'@') {
Some((maybe_userinfo, i)) => {
validate_userinfo::<S>(maybe_userinfo)?;
(i, Some(maybe_userinfo))
}
None => (i, None),
};
let (maybe_host, _port) = match rfind_split_hole(i, b':') {
Some((maybe_host, maybe_port)) => {
if maybe_port.bytes().all(|b| b.is_ascii_digit()) {
(maybe_host, Some(maybe_port))
} else {
(i, None)
}
}
None => (i, None),
};
validate_host::<S>(maybe_host)
}
pub(crate) fn validate_host<S: Spec>(i: &str) -> Result<(), Error> {
match get_wrapped_inner(i, b'[', b']') {
Some(maybe_addr) => {
if let Some(maybe_addr_rest) = strip_ascii_char_prefix(maybe_addr, b'v')
.or_else(|| strip_ascii_char_prefix(maybe_addr, b'V'))
{
let (maybe_ver, maybe_addr) =
find_split_hole(maybe_addr_rest, b'.').ok_or_else(Error::new)?;
if maybe_ver.is_empty() || !maybe_ver.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(Error::new());
}
if !maybe_addr.is_empty()
&& maybe_addr.is_ascii()
&& maybe_addr
.bytes()
.all(char::is_ascii_userinfo_ipvfutureaddr)
{
Ok(())
} else {
Err(Error::new())
}
} else {
validate_ipv6address(maybe_addr)
}
}
None => {
let is_valid = satisfy_chars_with_pct_encoded(
i,
char::is_ascii_regname,
char::is_nonascii_regname::<S>,
);
if is_valid {
Ok(())
} else {
Err(Error::new())
}
}
}
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::*;
use alloc::format;
macro_rules! assert_validate {
($parser:expr, $($input:expr),* $(,)?) => {{
$({
let input = $input;
let input: &str = input.as_ref();
assert!($parser(input).is_ok(), "input={:?}", input);
})*
}};
}
#[test]
fn test_ipv6address() {
use core::cmp::Ordering;
assert_validate!(validate_ipv6address, "a:bB:cCc:dDdD:e:F:a:B");
assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1:1");
assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1.1.1.1");
assert_validate!(validate_ipv6address, "2001:db8::7");
let make_sub = |n: usize| {
let mut s = "1:".repeat(n);
s.pop();
s
};
for len_pref in 0..=7 {
let prefix = make_sub(len_pref);
for len_suf in 1..=(7 - len_pref) {
assert_validate!(
validate_ipv6address,
&format!("{}::{}", prefix, make_sub(len_suf))
);
match len_suf.cmp(&2) {
Ordering::Greater => assert_validate!(
validate_ipv6address,
&format!("{}::{}:1.1.1.1", prefix, make_sub(len_suf - 2))
),
Ordering::Equal => {
assert_validate!(validate_ipv6address, &format!("{}::1.1.1.1", prefix))
}
Ordering::Less => {}
}
}
}
}
}