use crate::encode::is_unreserved;
use crate::error::{Error, ErrorKind};
use crate::hex::{has_lowercase_hex, is_hex};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rules {
pub enforce_reserved_masking: bool,
pub allow_lowercase_hex: bool,
}
impl Rules {
pub const fn strict_like() -> Self {
Self {
enforce_reserved_masking: true,
allow_lowercase_hex: false,
}
}
}
pub fn validate_dnp(input: &str, rules: &Rules) -> Result<(), Error> {
let bytes = input.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
let b = bytes[i];
if b == b',' {
if i + 2 >= bytes.len() {
return Err(Error::new(ErrorKind::LoneComma, Some(i)));
}
let h1 = bytes[i + 1];
let h2 = bytes[i + 2];
#[cfg(feature = "strict")]
{
if has_lowercase_hex(h1, h2) {
return Err(Error::new(ErrorKind::LowercaseHexInStrict, Some(i)));
}
}
if !rules.allow_lowercase_hex && has_lowercase_hex(h1, h2) {
return Err(Error::new(ErrorKind::LowercaseHexInStrict, Some(i)));
}
if !is_hex(h1) {
return Err(Error::new(
ErrorKind::InvalidHexDigit(h1 as char),
Some(i + 1),
));
}
if !is_hex(h2) {
return Err(Error::new(
ErrorKind::InvalidHexDigit(h2 as char),
Some(i + 2),
));
}
i += 3;
continue;
}
if b < 0x80 {
if rules.enforce_reserved_masking && !is_unreserved(b) {
return Err(Error::new(
ErrorKind::UnescapedReservedAscii(b as char),
Some(i),
));
}
i += 1;
} else {
let rest = &input[i..];
let ch = rest.chars().next().unwrap();
i += ch.len_utf8();
}
}
Ok(())
}