use crate::std::vec::Vec;
use super::{Domain, Label};
use rama_core::bytes::Bytes;
mod sealed {
pub trait Sealed {}
}
pub trait DomainLabels: sealed::Sealed {
type LabelIter<'a>: Iterator<Item = &'a Label> + DoubleEndedIterator + Clone
where
Self: 'a;
fn labels(&self) -> Self::LabelIter<'_>;
fn label_count(&self) -> usize {
self.labels().count()
}
fn starts_with<D: DomainLabels + ?Sized>(&self, prefix: &D) -> bool {
let mut a = self.labels();
let mut b = prefix.labels();
loop {
match (b.next(), a.next()) {
(None, _) => return true,
(Some(_), None) => return false,
(Some(y), Some(x)) => {
if x != y {
return false;
}
}
}
}
}
fn ends_with<D: DomainLabels + ?Sized>(&self, suffix: &D) -> bool {
let mut a = self.labels().rev();
let mut b = suffix.labels().rev();
loop {
match (b.next(), a.next()) {
(None, _) => return true,
(Some(_), None) => return false,
(Some(y), Some(x)) => {
if x != y {
return false;
}
}
}
}
}
fn is_subdomain_of<D: DomainLabels + ?Sized>(&self, parent: &D) -> bool {
let mut a = self.labels().rev();
let mut b = parent.labels().rev();
let mut matched_any = false;
loop {
match (b.next(), a.next()) {
(None, _) => return matched_any,
(Some(_), None) => return false,
(Some(y), Some(x)) => {
if x != y {
return false;
}
matched_any = true;
}
}
}
}
fn parent(&self) -> Option<Domain> {
let mut it = self.labels();
let _leaf = it.next()?;
let rest: Vec<&str> = it.map(Label::as_str).collect();
if rest.is_empty() {
return None;
}
let joined = rest.join(".");
Some(unsafe { Domain::from_maybe_borrowed_unchecked(joined) })
}
fn suffix_iter(&self) -> SuffixIter<'_, Self>
where
Self: Sized,
{
SuffixIter {
iter: Some(self.labels()),
}
}
}
#[derive(Clone)]
pub struct SuffixIter<'a, D: DomainLabels + ?Sized + 'a> {
iter: Option<D::LabelIter<'a>>,
}
impl<'a, D: DomainLabels + ?Sized + 'a> Iterator for SuffixIter<'a, D> {
type Item = Domain;
fn next(&mut self) -> Option<Domain> {
let it = self.iter.as_ref()?;
let parts: Vec<&str> = it.clone().map(Label::as_str).collect();
if parts.is_empty() {
self.iter = None;
return None;
}
let mut next_it = it.clone();
let _ = next_it.next();
self.iter = Some(next_it);
let joined = parts.join(".");
Some(unsafe { Domain::from_maybe_borrowed_unchecked(joined) })
}
}
#[derive(Clone)]
pub struct DomainLabelIter<'a>(core::str::Split<'a, char>);
impl<'a> DomainLabelIter<'a> {
pub(super) fn new(buf: &'a str) -> Self {
let s = buf.strip_prefix('.').unwrap_or(buf);
let s = s.strip_suffix('.').unwrap_or(s);
Self(s.split('.'))
}
}
impl<'a> Iterator for DomainLabelIter<'a> {
type Item = &'a Label;
fn next(&mut self) -> Option<&'a Label> {
self.0
.next()
.map(|s| unsafe { Label::from_str_unchecked(s) })
}
}
impl<'a> DoubleEndedIterator for DomainLabelIter<'a> {
fn next_back(&mut self) -> Option<&'a Label> {
self.0
.next_back()
.map(|s| unsafe { Label::from_str_unchecked(s) })
}
}
impl sealed::Sealed for Domain {}
impl sealed::Sealed for super::super::Host {}
impl DomainLabels for Domain {
type LabelIter<'a> = DomainLabelIter<'a>;
fn labels(&self) -> Self::LabelIter<'_> {
DomainLabelIter::new(self.as_str())
}
fn parent(&self) -> Option<Domain> {
let s = self.as_str();
let start = if s.starts_with('.') { 1 } else { 0 };
let end = if s.len() > start && s.ends_with('.') {
s.len() - 1
} else {
s.len()
};
let trimmed = &s[start..end];
let dot = trimmed.find('.')?;
let rest = &trimmed[dot + 1..];
if rest.is_empty() {
return None;
}
Some(unsafe {
Self::from_maybe_borrowed_unchecked(Bytes::copy_from_slice(rest.as_bytes()))
})
}
}
#[cfg(test)]
mod tests {
use super::super::Domain;
use super::DomainLabels;
fn labels_of(s: &str) -> Vec<String> {
Domain::try_from(s.to_owned())
.unwrap()
.labels()
.map(|l| l.as_str().to_owned())
.collect()
}
#[test]
fn forward_iter_order() {
assert_eq!(labels_of("www.example.com"), vec!["www", "example", "com"]);
assert_eq!(labels_of("example.com"), vec!["example", "com"]);
assert_eq!(labels_of("com"), vec!["com"]);
assert_eq!(labels_of("*.example.com"), vec!["*", "example", "com"]);
assert_eq!(labels_of("example.com."), vec!["example", "com"]);
assert_eq!(labels_of(".example.com"), vec!["example", "com"]);
assert_eq!(labels_of(".example.com."), vec!["example", "com"]);
}
#[test]
fn reverse_iter() {
let d = Domain::from_static("www.example.com");
let rev: Vec<&str> = d.labels().rev().map(|l| l.as_str()).collect();
assert_eq!(rev, vec!["com", "example", "www"]);
}
#[test]
fn iter_is_clone_double_ended() {
let d = Domain::from_static("a.b.c");
let it = d.labels();
let cloned = it.clone();
assert_eq!(it.count(), 3);
assert_eq!(cloned.count(), 3);
}
#[test]
fn label_count() {
assert_eq!(Domain::from_static("com").label_count(), 1);
assert_eq!(Domain::from_static("example.com").label_count(), 2);
assert_eq!(Domain::from_static("a.b.c.d").label_count(), 4);
assert_eq!(Domain::from_static("example.com.").label_count(), 2);
}
#[test]
fn starts_with() {
let d = Domain::from_static("www.example.com");
assert!(d.starts_with(&Domain::from_static("www")));
assert!(d.starts_with(&Domain::from_static("www.example")));
assert!(d.starts_with(&Domain::from_static("www.example.com")));
assert!(!d.starts_with(&Domain::from_static("example")));
assert!(!d.starts_with(&Domain::from_static("www.example.com.uk")));
assert!(d.starts_with(&Domain::from_static("WWW.eXaMpLe")));
}
#[test]
fn ends_with() {
let d = Domain::from_static("www.example.com");
assert!(d.ends_with(&Domain::from_static("com")));
assert!(d.ends_with(&Domain::from_static("example.com")));
assert!(d.ends_with(&Domain::from_static("www.example.com")));
assert!(!d.ends_with(&Domain::from_static("foo.example.com")));
assert!(!d.ends_with(&Domain::from_static("www")));
assert!(d.ends_with(&Domain::from_static("ExAmPlE.CoM")));
assert!(d.ends_with(&Domain::from_static("example.com.")));
}
#[test]
fn is_subdomain_of_table() {
let cases: &[(&str, &str, bool)] = &[
("www.example.com", "example.com", true),
("example.com", "example.com", true), ("a.b.example.com", "example.com", true),
("example.com", "www.example.com", false),
("example.org", "example.com", false),
("www.example.com", "ample.com", false), ];
for (a, b, want) in cases {
let da = Domain::from_static(a);
let db = Domain::from_static(b);
assert_eq!(
da.is_subdomain_of(&db),
*want,
"{a}.is_subdomain_of({b}) — want {want}"
);
}
}
#[test]
fn parent_chain() {
let d = Domain::from_static("a.b.c.example.com");
let p1 = d.parent().expect("p1");
assert_eq!(p1.as_str(), "b.c.example.com");
let p2 = p1.parent().expect("p2");
assert_eq!(p2.as_str(), "c.example.com");
let p3 = p2.parent().expect("p3");
assert_eq!(p3.as_str(), "example.com");
let p4 = p3.parent().expect("p4");
assert_eq!(p4.as_str(), "com");
assert!(p4.parent().is_none(), "tld has no parent");
}
#[test]
fn suffix_iter_order() {
let d = Domain::from_static("a.b.example.com");
let got: Vec<String> = d.suffix_iter().map(|s| s.as_str().to_owned()).collect();
assert_eq!(
got,
vec!["a.b.example.com", "b.example.com", "example.com", "com"]
);
let tld = Domain::from_static("com");
let got_tld: Vec<String> = tld.suffix_iter().map(|s| s.as_str().to_owned()).collect();
assert_eq!(got_tld, vec!["com"]);
}
}