bind-conf/dns_zone/src/name.rs

362 lines
11 KiB
Rust

use crate::error::{Error, ErrorType, Result};
use crate::context::CtxString;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
pub struct Name {
labels: Vec<String>,
absolute: bool,
}
trait DomainCheck {
fn is_domain_char(&self) -> bool;
fn is_outer_char(&self) -> bool;
fn is_inner_char(&self) -> bool;
}
impl DomainCheck for char {
#[inline]
fn is_domain_char(&self) -> bool {
match *self {
'\x21'..='\x7e' => true,
_ => false,
}
}
#[inline]
fn is_outer_char(&self) -> bool {
self.is_ascii_alphanumeric()
}
#[inline]
fn is_inner_char(&self) -> bool {
self.is_outer_char() || *self == '-'
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let domain = self.labels.iter()
.map(|s| s.replace('.', "\\."))
.collect::<Vec<_>>()
.join(".");
write!(f, "{}", domain)
}
}
impl FromStr for Name {
type Err = Error<ErrorType>;
fn from_str(sname: &str) -> Result<Self> {
Name::from_ctx_string(&sname.into())
}
}
impl Name {
pub fn from_ctx_string(sname: &CtxString) -> Result<Self> {
if **sname == "." {
return Ok(Name::root());
}
if **sname == "@" || **sname == "" {
return Ok(Name::new());
}
let mut labels = Vec::new();
let mut label = String::new();
let mut chars = sname.chars();
let mut absolute = false;
let mut escaped = false;
let mut escaped_digits = false;
let mut escaped_digits_str = String::new();
loop {
match chars.next() {
Some(ch) if escaped && ch.is_ascii_digit() => {
escaped_digits_str = ch.to_string();
escaped = false;
escaped_digits = true;
},
Some(ch) if escaped => {
label.push(ch.to_ascii_lowercase());
escaped = false;
},
Some(ch) if escaped_digits => {
escaped_digits_str.push(ch);
if escaped_digits_str.len() == 3 {
let ch_num: u8 = escaped_digits_str.parse().map_err(|_| Error::from(ErrorType::BadEscape))?;
label.push((ch_num as char).to_ascii_lowercase());
escaped_digits = false;
}
},
Some('.') => {
if label.len() == 0 {
return Err(ErrorType::EmptyLabel.into());
}
if label.len() > 63 {
return Err(ErrorType::LabelTooLong.into());
}
labels.push(label);
label = String::new();
},
Some('\\') => {
escaped = true;
},
Some(ch) if ch.is_ascii() => {
label.push(ch.to_ascii_lowercase())
},
Some(_) => {
return Err(ErrorType::NonAsciiChar.into());
},
None => {
labels.push(label);
break;
},
}
}
if escaped || escaped_digits {
return Err(ErrorType::BadEscape.into());
}
if labels.last() == Some(&String::new()) {
absolute = true;
}
Ok(Name {
labels,
absolute,
})
}
}
impl Name {
pub fn new() -> Self {
Name {
labels: Vec::new(),
absolute: false,
}
}
pub fn root() -> Self {
Name {
labels: vec![String::new()],
absolute: true,
}
}
pub fn prefixed_by(&self, prefix: Name) -> Self {
if prefix.absolute {
prefix.clone()
} else {
let mut labels = prefix.labels.clone();
labels.extend_from_slice(self.labels.as_slice());
Name {
labels,
absolute: self.absolute
}
}
}
pub fn len(&self) -> usize {
self.labels.iter().fold(0, |acc, label| acc + label.len() + 1)
}
pub fn ensure_is_valid(&self) -> Result<&Self> {
let domain_length_check = self.len() < 256 && self.labels.len() < 128;
let labels_length_check = self.labels.iter().all(|label| label.len() < 64);
let empty_label_check = if let Some(pos) = self.labels.iter().position(|l| l == "") {
pos == self.labels.len() - 1
} else {
true
};
let is_valid = domain_length_check && labels_length_check && empty_label_check;
if is_valid {
Ok(self)
} else {
Err(ErrorType::InvalidDomain.into())
}
}
pub fn ensure_is_mailbox(&self) -> Result<&Self> {
if !self.absolute || self.labels.len() == 0 {
return Err(ErrorType::InvalidMailbox.into());
}
if self.labels.len() == 1 {
return Ok(self);
}
let username = &self.labels[0];
let valid_username_lenth = username.len() < 64;
let valid_username_chars = username.chars().all(|c| c.is_domain_char());
let labels_check = self.labels.iter().skip(1).all(|label| Self::is_hostname_label(label));
let is_mailbox = valid_username_lenth && valid_username_chars && labels_check;
if is_mailbox {
Ok(self)
} else {
Err(ErrorType::InvalidMailbox.into())
}
}
pub fn ensure_is_hostname(&self, allow_wildcard: bool) -> Result<&Self> {
if !self.absolute || self.labels.len() == 0 {
return Err(ErrorType::InvalidHostname.into());
}
if self.labels.len() == 1 {
return Ok(self);
}
let mut label_iter = if allow_wildcard && self.labels[0] == "*" {
self.labels.iter().skip(1)
} else {
self.labels.iter().skip(0)
};
if label_iter.all(|label| Self::is_hostname_label(label)) {
Ok(self)
} else {
Err(ErrorType::InvalidHostname.into())
}
}
fn is_hostname_label(label: &String) -> bool {
if label.len() > 1 {
let first = label.chars().next().unwrap().is_outer_char();
let last = label.chars().last().unwrap().is_outer_char();
let inner = label[1..(label.len() - 1)].chars().all(|c| c.is_inner_char());
label.len() < 64 && inner && first && last
} else if label.len() == 1 {
label.chars().next().unwrap().is_outer_char()
} else {
true
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_create_names() {
assert_eq!(
Name {
labels: vec!["admin.domains".into(), "example".into(), "com".into(), "".into()],
absolute: true,
},
"admin\\.domains.example.com.".parse().unwrap()
);
assert_eq!(
Name {
labels: vec!["example".into(), "com".into()],
absolute: false,
},
"example.com".parse().unwrap()
);
assert_eq!(Name::new(), "".parse().unwrap());
assert_eq!(Name::new(), "@".parse().unwrap());
assert_eq!(Name::root(), ".".parse().unwrap());
assert_eq!(ErrorType::EmptyLabel, ".test".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::EmptyLabel, "a...test".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::LabelTooLong, "totototototototototototototototototototototototototototototototo.xyz".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::NonAsciiChar, "😀".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::BadEscape, "\\08".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::BadEscape, "\\321".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::BadEscape, "\\08A".parse::<Name>().unwrap_err().error_type);
assert_eq!(ErrorType::BadEscape, "\\".parse::<Name>().unwrap_err().error_type);
assert_eq!("example.com.", "ExaMpLe.Com.".parse::<Name>().unwrap().to_string());
assert_eq!("test", "\\084\\101\\115\\116".parse::<Name>().unwrap().to_string());
}
#[test]
fn test_prefixed_by() {
let origin = Name::root();
let res1 = origin.prefixed_by("example.com".parse::<Name>().unwrap());
let res2 = res1.prefixed_by("srv1".parse::<Name>().unwrap());
let res3 = res1.prefixed_by("not.example.com.".parse::<Name>().unwrap());
let origin2: Name = "com".parse().unwrap();
let res4 = origin2.prefixed_by("example".parse::<Name>().unwrap());
assert_eq!(res1.to_string(), "example.com.");
assert_eq!(res2.to_string(), "srv1.example.com.");
assert_eq!(res3.to_string(), "not.example.com.");
assert_eq!(res4.to_string(), "example.com");
}
#[test]
fn test_valid_domain() {
assert!("example.com.".parse::<Name>().unwrap().ensure_is_valid().is_ok());
assert!("example.com".parse::<Name>().unwrap().ensure_is_valid().is_ok());
assert!("".parse::<Name>().unwrap().ensure_is_valid().is_ok());
assert!(".".parse::<Name>().unwrap().ensure_is_valid().is_ok());
}
#[test]
fn test_valid_hostname() {
assert!("1-23.xx.".parse::<Name>().unwrap().ensure_is_hostname(false).is_ok());
assert!(".".parse::<Name>().unwrap().ensure_is_hostname(false).is_ok());
assert!("*.test.".parse::<Name>().unwrap().ensure_is_hostname(true).is_ok());
assert!("1.b.".parse::<Name>().unwrap().ensure_is_hostname(true).is_ok());
assert_eq!(
ErrorType::InvalidHostname,
"example.com".parse::<Name>().unwrap().ensure_is_hostname(false).unwrap_err().error_type
);
assert_eq!(
ErrorType::InvalidHostname,
"-test.".parse::<Name>().unwrap().ensure_is_hostname(false).unwrap_err().error_type
);
assert_eq!(
ErrorType::InvalidHostname,
"12-.".parse::<Name>().unwrap().ensure_is_hostname(false).unwrap_err().error_type
);
assert_eq!(
ErrorType::InvalidHostname,
"_test.".parse::<Name>().unwrap().ensure_is_hostname(false).unwrap_err().error_type
);
assert_eq!(
ErrorType::InvalidHostname,
"*.test.".parse::<Name>().unwrap().ensure_is_hostname(false).unwrap_err().error_type
);
assert_eq!(
ErrorType::InvalidHostname,
"a.*.b.".parse::<Name>().unwrap().ensure_is_hostname(true).unwrap_err().error_type
);
}
#[test]
fn test_valid_mailbox() {
assert!(".".parse::<Name>().unwrap().ensure_is_hostname(false).is_ok());
assert!("test.".parse::<Name>().unwrap().ensure_is_hostname(false).is_ok());
assert!("firstname\\.lastname+domain.example.com.".parse::<Name>().unwrap().ensure_is_mailbox().is_ok());
assert_eq!(
ErrorType::InvalidMailbox,
"test+abc.example\\.test.com".parse::<Name>().unwrap().ensure_is_mailbox().unwrap_err().error_type
);
assert_eq!(
ErrorType::InvalidMailbox,
"admin domain.example.com".parse::<Name>().unwrap().ensure_is_mailbox().unwrap_err().error_type
);
}
}