use super::{compute, Parts};
pub use super::Type;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Suffix<'a> {
bytes: &'a [u8],
typ: Option<Type>,
fqdn: bool,
}
impl<'a> Suffix<'a> {
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
#[inline]
pub fn typ(&self) -> Option<Type> {
self.typ
}
#[inline]
pub fn is_known(&self) -> bool {
self.typ.is_some()
}
#[inline]
pub fn is_fqdn(&self) -> bool {
self.fqdn
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Domain<'a> {
bytes: &'a [u8],
suffix: Suffix<'a>,
}
impl<'a> Domain<'a> {
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
#[inline]
pub fn suffix(&self) -> Suffix<'a> {
self.suffix
}
}
fn parse(name: &[u8]) -> Option<(&str, Parts, bool)> {
let s = core::str::from_utf8(name).ok()?;
if s.is_empty() {
return None;
}
let fqdn = s.as_bytes().last() == Some(&b'.');
let host = if fqdn { &s[..s.len() - 1] } else { s };
if host.is_empty() {
return None;
}
let parts = compute(host)?;
Some((host, parts, fqdn))
}
#[inline]
pub fn suffix(name: &[u8]) -> Option<Suffix<'_>> {
let (host, parts, fqdn) = parse(name)?;
Some(Suffix {
bytes: &host.as_bytes()[parts.suffix_off..],
typ: parts.typ,
fqdn,
})
}
#[inline]
pub fn suffix_str(name: &str) -> Option<Suffix<'_>> {
suffix(name.as_bytes())
}
#[inline]
pub fn domain(name: &[u8]) -> Option<Domain<'_>> {
let (host, parts, fqdn) = parse(name)?;
let domain_off = parts.domain_off?;
Some(Domain {
bytes: &host.as_bytes()[domain_off..],
suffix: Suffix {
bytes: &host.as_bytes()[parts.suffix_off..],
typ: parts.typ,
fqdn,
},
})
}
#[inline]
pub fn domain_str(name: &str) -> Option<Domain<'_>> {
domain(name.as_bytes())
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[test]
fn domain_and_suffix() {
let d = domain_str("www.example.co.uk").unwrap();
assert_eq!(d.as_bytes(), b"example.co.uk");
assert_eq!(d.suffix().as_bytes(), b"co.uk");
assert!(d.suffix().is_known());
assert_eq!(d.suffix().typ(), Some(Type::Icann));
assert_eq!(suffix_str("com").unwrap().as_bytes(), b"com");
assert_eq!(domain_str("com"), None); }
#[test]
fn unknown_tld_uses_default_rule() {
let s = suffix_str("foo.example").unwrap();
assert_eq!(s.as_bytes(), b"example");
assert!(!s.is_known());
assert_eq!(
domain_str("foo.example").unwrap().as_bytes(),
b"foo.example"
);
}
#[test]
fn fully_qualified() {
let s = suffix_str("example.com.").unwrap();
assert_eq!(s.as_bytes(), b"com");
assert!(s.is_fqdn());
assert!(!suffix_str("example.com").unwrap().is_fqdn());
}
#[test]
fn byte_and_str_agree() {
assert_eq!(domain(b"a.b.example.com"), domain_str("a.b.example.com"));
assert_eq!(
domain_str("a.b.example.com").unwrap().as_bytes(),
b"example.com"
);
}
#[test]
fn invalid() {
assert_eq!(suffix(b""), None);
assert_eq!(domain(b""), None);
assert_eq!(suffix(&[0xff, 0xfe]), None); }
}