1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! A native Rust library for Mozilla's Public Suffix List

#![no_std]
#![forbid(unsafe_code)]

mod list;
#[cfg(feature = "serde")]
mod serde;

/// Get the public suffix of the domain
///
/// *NB:* `name` must be a valid domain name in lowercase
#[inline]
pub fn suffix(name: &[u8]) -> Option<Suffix<'_>> {
    let mut labels = name.rsplit(|x| *x == b'.');
    let fqdn = if name.ends_with(b".") {
        labels.next();
        true
    } else {
        false
    };
    let Info { mut len, typ } = list::lookup(labels);
    if fqdn {
        len += 1;
    }
    if len == 0 {
        return None;
    }
    let offset = name.len() - len;
    let bytes = &name[offset..];
    Some(Suffix { bytes, fqdn, typ })
}

/// Get the registrable domain
///
/// *NB:* `name` must be a valid domain name in lowercase
#[inline]
pub fn domain(name: &[u8]) -> Option<Domain<'_>> {
    let suffix = suffix(name)?;
    let name_len = name.len();
    let suffix_len = suffix.bytes.len();
    if name_len < suffix_len + 2 {
        return None;
    }
    let offset = name_len - (1 + suffix_len);
    let subdomain = &name[..offset];
    let root_label = subdomain.rsplitn(2, |x| *x == b'.').next()?;
    let registrable_len = root_label.len() + 1 + suffix_len;
    let offset = name_len - registrable_len;
    let bytes = &name[offset..];
    Some(Domain { bytes, suffix })
}

/// Type of suffix
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum Type {
    Icann,
    Private,
}

/// Information about the suffix
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
struct Info {
    len: usize,
    typ: Option<Type>,
}

/// The suffix of a domain name
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Suffix<'a> {
    bytes: &'a [u8],
    fqdn: bool,
    typ: Option<Type>,
}

impl Suffix<'_> {
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        &self.bytes
    }

    #[inline]
    pub fn is_fqdn(&self) -> bool {
        self.fqdn
    }

    #[inline]
    pub fn typ(&self) -> Option<Type> {
        self.typ
    }

    #[inline]
    pub fn is_known(&self) -> bool {
        self.typ.is_some()
    }
}

/// A registrable domain name
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Domain<'a> {
    bytes: &'a [u8],
    suffix: Suffix<'a>,
}

impl Domain<'_> {
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        &self.bytes
    }

    #[inline]
    pub fn suffix(&self) -> Suffix<'_> {
        self.suffix
    }
}