use crate::config::{Config, DomainCheck};
use crate::error::{Error, ErrorKind};
use crate::normalize::Normalized;
use crate::parser::Parsed;
const MAX_LOCAL_PART_LEN: usize = 64;
const MAX_ADDRESS_LEN: usize = 254;
const MAX_LABEL_LEN: usize = 63;
pub(crate) fn validate(
parsed: &Parsed<'_>,
normalized: &Normalized,
config: &Config,
) -> Result<(), Error> {
let domain = &normalized.domain;
let local = parsed.local_part_str();
let domain_str = parsed.domain_str();
if local.len() > MAX_LOCAL_PART_LEN {
return Err(Error::new(
ErrorKind::LocalPartTooLong { len: local.len() },
parsed.local_part.start,
));
}
let total = local.len() + 1 + domain_str.len();
if total > MAX_ADDRESS_LEN {
return Err(Error::new(
ErrorKind::AddressTooLong { len: total },
parsed.local_part.start,
));
}
let is_domain_literal = domain_str.starts_with('[');
if config.require_tld_dot && !domain.contains('.') && !is_domain_literal {
return Err(Error::new(ErrorKind::DomainNoDot, parsed.domain.start));
}
if !is_domain_literal {
for label in domain.split('.') {
if label.len() > MAX_LABEL_LEN {
return Err(Error::new(
ErrorKind::DomainLabelTooLong {
label: label.to_string(),
len: label.len(),
},
parsed.domain.start,
));
}
}
match config.domain_check {
DomainCheck::Syntax => {}
DomainCheck::Tld => validate_tld(domain, parsed.domain.start)?,
DomainCheck::Psl => validate_psl(domain, parsed.domain.start)?,
}
}
Ok(())
}
fn validate_tld(domain: &str, pos: usize) -> Result<(), Error> {
let tld = domain.rsplit('.').next().unwrap_or(domain);
if tld.starts_with("xn--") && tld.len() > 4 {
return Ok(());
}
if tld.len() < 2 || !tld.chars().all(|c| c.is_ascii_alphabetic()) {
return Err(Error::new(ErrorKind::UnknownTld(tld.to_string()), pos));
}
Ok(())
}
#[cfg(feature = "psl")]
fn validate_psl(domain: &str, pos: usize) -> Result<(), Error> {
if structured_public_domains::is_known_suffix(domain) {
Ok(())
} else {
let tld = domain.rsplit('.').next().unwrap_or(domain);
Err(Error::new(ErrorKind::UnknownTld(tld.to_string()), pos))
}
}
#[cfg(not(feature = "psl"))]
fn validate_psl(domain: &str, pos: usize) -> Result<(), Error> {
validate_tld(domain, pos)
}
#[cfg(test)]
mod tests;