mirror of
https://github.com/NLnetLabs/domain.git
synced 2026-09-24 02:34:56 +02:00
638 lines
18 KiB
Rust
638 lines
18 KiB
Rust
/// Uncompressed, absolute domain names.
|
||
|
||
use std::{cmp, fmt, hash, io, ops, str};
|
||
use std::ascii::AsciiExt;
|
||
use std::io::Write;
|
||
use bytes::{BufMut, Bytes};
|
||
use ::bits::compose::{Compose, Compress, Compressor};
|
||
use ::bits::parse::{Parse, ParseAll, Parser, ShortBuf};
|
||
use ::master::print::{Print, Printer};
|
||
use ::master::scan::{CharSource, Scan, Scanner, ScanError, SyntaxError};
|
||
use super::error::{FromStrError, IndexError, LabelTypeError,
|
||
SplitLabelError, RootNameError};
|
||
use super::label::Label;
|
||
use super::parsed::ParsedDname;
|
||
use super::relative::{RelativeDname, DnameIter};
|
||
use super::traits::{ToLabelIter, ToDname, ToRelativeDname};
|
||
use super::uncertain::UncertainDname;
|
||
|
||
|
||
//------------ Dname ---------------------------------------------------------
|
||
|
||
/// An uncompressed, absolute domain name.
|
||
///
|
||
/// The type wraps a [`Bytes`] value and guarantees that it always contains
|
||
/// a correctly encoded, absolute domain name. It derefs to [`Bytes`] and
|
||
/// therefore to `[u8]` allowing you direct access to the underlying byte
|
||
/// slice. It does overide all applicable methods providing access to parts
|
||
/// of the byte slice, though, returning either `Dname` or [`RelativeDname`]s
|
||
/// instead.
|
||
///
|
||
/// You can construct a domain name from a string via the `FromStr` trait or
|
||
/// manually via a [`DnameBuilder`]. In addition, you can also parse it from
|
||
/// a message. This will, however, require the name to be uncompressed.
|
||
///
|
||
/// [`Bytes`]: ../../../bytes/struct.Bytes.html
|
||
/// [`DnameBuilder`]: struct.DnameBuilder.html
|
||
#[derive(Clone)]
|
||
pub struct Dname {
|
||
bytes: Bytes
|
||
}
|
||
|
||
/// # Creation and Conversion
|
||
///
|
||
impl Dname {
|
||
/// Creates a domain name from the underlying bytes without any check.
|
||
///
|
||
/// Since this will allow to actually construct an incorrectly encoded
|
||
/// domain name value, the function is unsafe.
|
||
pub(super) unsafe fn from_bytes_unchecked(bytes: Bytes) -> Self {
|
||
Dname { bytes }
|
||
}
|
||
|
||
/// Creates a domain name representing the root.
|
||
///
|
||
/// The resulting domain name will consist of the root label only.
|
||
pub fn root() -> Self {
|
||
unsafe { Self::from_bytes_unchecked(Bytes::from_static(b"\0")) }
|
||
}
|
||
|
||
/// Creates a domain name from a bytes value.
|
||
///
|
||
/// This will only succeed if `bytes` contains a properly encoded
|
||
/// absolute domain name. Because the function checks, this will take
|
||
/// a wee bit of time.
|
||
pub fn from_bytes(bytes: Bytes) -> Result<Self, DnameBytesError> {
|
||
if bytes.len() > 255 {
|
||
return Err(DnameError::LongName.into());
|
||
}
|
||
{
|
||
let mut tmp = bytes.as_ref();
|
||
loop {
|
||
let (label, tail) = Label::split_from(tmp)?;
|
||
if label.is_root() {
|
||
if tail.is_empty() {
|
||
break;
|
||
}
|
||
else {
|
||
return Err(DnameBytesError::TrailingData)
|
||
}
|
||
}
|
||
if tail.is_empty() {
|
||
return Err(ShortBuf.into())
|
||
}
|
||
tmp = tail;
|
||
}
|
||
}
|
||
Ok(unsafe { Dname::from_bytes_unchecked(bytes) })
|
||
}
|
||
|
||
pub fn from_slice(s: &[u8]) -> Result<Self, DnameBytesError> {
|
||
Self::from_bytes(s.into())
|
||
}
|
||
|
||
/// Creates a domain name from a sequence of characters.
|
||
///
|
||
/// The sequence must result in a domain name in master format
|
||
/// representation. That is, its labels should be separated by dots,
|
||
/// actual dots, white space and backslashes should be escaped by a
|
||
/// preceeding backslash, and any byte value that is not a printable
|
||
/// ASCII character should be encoded by a backslash followed by its
|
||
/// three digit decimal value.
|
||
///
|
||
/// The name will always be an absolute name. If the last character in the
|
||
/// sequence is not a dot, the function will quietly add a root label,
|
||
/// anyway. In most cases, this is likely what you want. If it isn’t,
|
||
/// though, use [`UncertainDname`] instead to be able to check.
|
||
///
|
||
/// [`UncertainDname`]: enum.UncertainDname.html
|
||
pub fn from_chars<C>(chars: C) -> Result<Self, FromStrError>
|
||
where C: IntoIterator<Item=char> {
|
||
UncertainDname::from_chars(chars).map(|res| res.into_absolute())
|
||
}
|
||
|
||
/// Returns a reference to the underlying bytes value.
|
||
pub fn as_bytes(&self) -> &Bytes {
|
||
&self.bytes
|
||
}
|
||
|
||
/// Returns a reference to the underlying byte slice.
|
||
pub fn as_slice(&self) -> &[u8] {
|
||
self.bytes.as_ref()
|
||
}
|
||
|
||
/// Converts the domain name into its underlying bytes slice.
|
||
pub fn into_bytes(self) -> Bytes {
|
||
self.bytes
|
||
}
|
||
|
||
/// Converts the name into a relative name by dropping the root label.
|
||
pub fn into_relative(mut self) -> RelativeDname {
|
||
let len = self.bytes.len() - 1;
|
||
self.bytes.truncate(len);
|
||
unsafe { RelativeDname::from_bytes_unchecked(self.bytes) }
|
||
}
|
||
}
|
||
|
||
/// # Properties
|
||
///
|
||
impl Dname {
|
||
/// Returns whether the name is the root label only.
|
||
pub fn is_root(&self) -> bool {
|
||
self.len() == 1
|
||
}
|
||
}
|
||
|
||
|
||
/// # Working with Labels
|
||
///
|
||
impl Dname {
|
||
/// Returns an iterator over the labels of the domain name.
|
||
pub fn iter(&self) -> DnameIter {
|
||
DnameIter::new(self.bytes.as_ref())
|
||
}
|
||
|
||
/// Returns an iterator over the suffixes of the name.
|
||
///
|
||
/// The returned iterator starts with the full name and then for each
|
||
/// additional step returns a name with the left-most label stripped off
|
||
/// until it reaches the root label.
|
||
pub fn iter_suffixes(&self) -> SuffixIter {
|
||
SuffixIter::new(self)
|
||
}
|
||
|
||
/// Returns the number of labels in the domain name.
|
||
pub fn label_count(&self) -> usize {
|
||
self.iter().count()
|
||
}
|
||
|
||
/// Returns a reference to the first label.
|
||
pub fn first(&self) -> &Label {
|
||
self.iter().next().unwrap()
|
||
}
|
||
|
||
/// Returns a reference to the last label.
|
||
pub fn last(&self) -> &Label {
|
||
self.iter().next_back().unwrap()
|
||
}
|
||
|
||
/// Determines whether `base` is a prefix of `self`.
|
||
///
|
||
/// As this methods accepts only relative domain names, it will only
|
||
/// allow checking for a ‘strict’ prefix.
|
||
pub fn starts_with<N: ToRelativeDname>(&self, base: &N) -> bool {
|
||
<Self as ToLabelIter>::starts_with(self, base)
|
||
}
|
||
|
||
/// Determines whether `base` is a suffix of `self`.
|
||
pub fn ends_with<N: ToDname>(&self, base: &N) -> bool {
|
||
<Self as ToLabelIter>::ends_with(self, base)
|
||
}
|
||
|
||
/// Returns the part of the name indicated by start and end positions.
|
||
///
|
||
/// The returned name will start at position `begin` and end right before
|
||
/// position `end`. Both positions must point to the begining of a label
|
||
/// or an error will be returned.
|
||
///
|
||
/// Because the returned domain is a relative name, the method will also
|
||
/// return an error if the end equal to the length of the name. If you
|
||
/// want to slice the entire end of the name including the final root
|
||
/// label, you can use [`slice_from()`] instead.
|
||
///
|
||
/// # Panics
|
||
///
|
||
/// The method panics if either position points beyond the end of the
|
||
/// name.
|
||
///
|
||
/// [`slice_from()`]: #method.slice_from
|
||
pub fn slice(&self, begin: usize, end: usize)
|
||
-> Result<RelativeDname, IndexError> {
|
||
IndexError::check(&self.bytes, begin)?;
|
||
IndexError::check(&self.bytes, end)?;
|
||
if end == self.len() {
|
||
return Err(IndexError)
|
||
}
|
||
Ok(unsafe {
|
||
RelativeDname::from_bytes_unchecked(self.bytes.slice(begin, end))
|
||
})
|
||
}
|
||
|
||
/// Returns the part of the name starting at the given position.
|
||
///
|
||
/// This will fail if the position isn’t the start of a label.
|
||
///
|
||
/// # Panics
|
||
///
|
||
/// The method panics if either position points beyond the end of the
|
||
/// name.
|
||
pub fn slice_from(&self, begin: usize) -> Result<Self, IndexError> {
|
||
IndexError::check(&self.bytes, begin)?;
|
||
Ok(unsafe {
|
||
Self::from_bytes_unchecked(self.bytes.slice_from(begin))
|
||
})
|
||
}
|
||
|
||
/// Returns the part of the name ending at the given position.
|
||
///
|
||
/// This will fail if the position isn’t the start of a label.
|
||
///
|
||
/// # Panics
|
||
///
|
||
/// The method panics if either position points beyond the end of the
|
||
/// name.
|
||
pub fn slice_to(&self, end: usize) -> Result<RelativeDname, IndexError> {
|
||
IndexError::check(&self.bytes, end)?;
|
||
if end == self.len() {
|
||
return Err(IndexError)
|
||
}
|
||
Ok(unsafe {
|
||
RelativeDname::from_bytes_unchecked(self.bytes.slice_to(end))
|
||
})
|
||
}
|
||
|
||
// XXX No `split_off()` since that would require `self` to mysteriously
|
||
// change into a `RelativeDname`. Would could make this move `self`,
|
||
// but then you would loose it upon an error which is not nice,
|
||
// either.
|
||
|
||
/// Splits the name into two at the given position.
|
||
///
|
||
/// Afterwards, `self` will contain the name starting at the position
|
||
/// while the name ending right before it will be returned. The method
|
||
/// will fail if `mid` is not the start of a new label.
|
||
///
|
||
/// # Panics
|
||
///
|
||
/// The method will panic if `mid` is greater than the name’s length.
|
||
pub fn split_to(&mut self, mid: usize)
|
||
-> Result<RelativeDname, IndexError> {
|
||
IndexError::check(&self.bytes, mid)?;
|
||
Ok(unsafe {
|
||
RelativeDname::from_bytes_unchecked(self.bytes.split_to(mid))
|
||
})
|
||
}
|
||
|
||
// XXX No `truncate()` either.
|
||
|
||
/// Splits off the first label.
|
||
///
|
||
/// If this name is longer than just the root label, returns the first
|
||
/// label as a relative name and removes it from the name itself. If the
|
||
/// name is only the root label, returns an error and does nothing.
|
||
pub fn split_first(&mut self) -> Result<RelativeDname, RootNameError> {
|
||
if self.len() == 1 {
|
||
return Err(RootNameError)
|
||
}
|
||
let end = self.iter().next().unwrap().len() + 1;
|
||
Ok(unsafe {
|
||
RelativeDname::from_bytes_unchecked(self.bytes.split_to(end))
|
||
})
|
||
}
|
||
|
||
/// Reduces the name to the parent of the current name.
|
||
///
|
||
/// This will fail if the name consists of the root label only.
|
||
pub fn parent(&mut self) -> Result<(), RootNameError> {
|
||
self.split_first().map(|_| ())
|
||
}
|
||
|
||
// XXX And no `strip_suffix()`.
|
||
}
|
||
|
||
|
||
//--- Parse, ParseAll, and Compose
|
||
|
||
impl Parse for Dname {
|
||
type Err = DnameParseError;
|
||
|
||
fn parse(parser: &mut Parser) -> Result<Self, Self::Err> {
|
||
let len = {
|
||
let mut tmp = parser.peek_all();
|
||
loop {
|
||
if tmp.is_empty() {
|
||
return Err(ShortBuf.into())
|
||
}
|
||
let (label, tail) = Label::split_from(tmp)?;
|
||
tmp = tail;
|
||
if label.is_root() {
|
||
break;
|
||
}
|
||
}
|
||
parser.remaining() - tmp.len()
|
||
};
|
||
if len > 255 {
|
||
return Err(DnameError::LongName.into());
|
||
}
|
||
Ok(unsafe {
|
||
Self::from_bytes_unchecked(parser.parse_bytes(len).unwrap())
|
||
})
|
||
}
|
||
}
|
||
|
||
impl ParseAll for Dname {
|
||
type Err = DnameBytesError;
|
||
|
||
fn parse_all(parser: &mut Parser, len: usize) -> Result<Self, Self::Err> {
|
||
let mut tmp = parser.clone();
|
||
let end = tmp.pos() + len;
|
||
let res = Self::parse(&mut tmp)?;
|
||
if tmp.pos() < end {
|
||
return Err(DnameBytesError::TrailingData)
|
||
}
|
||
else if tmp.pos() > end {
|
||
return Err(ShortBuf.into())
|
||
}
|
||
parser.advance(len)?;
|
||
Ok(res)
|
||
}
|
||
}
|
||
|
||
|
||
impl Compose for Dname {
|
||
fn compose_len(&self) -> usize {
|
||
self.bytes.len()
|
||
}
|
||
|
||
fn compose<B: BufMut>(&self, buf: &mut B) {
|
||
buf.put_slice(self.as_ref())
|
||
}
|
||
}
|
||
|
||
impl Compress for Dname {
|
||
fn compress(&self, compressor: &mut Compressor) -> Result<(), ShortBuf> {
|
||
compressor.compress_name(self)
|
||
}
|
||
}
|
||
|
||
|
||
//--- FromStr
|
||
|
||
impl str::FromStr for Dname {
|
||
type Err = FromStrError;
|
||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||
UncertainDname::from_str(s).map(|res| res.into_absolute())
|
||
}
|
||
}
|
||
|
||
|
||
//--- ToLabelIter and ToDname
|
||
|
||
impl<'a> ToLabelIter<'a> for Dname {
|
||
type LabelIter = DnameIter<'a>;
|
||
|
||
fn iter_labels(&'a self) -> Self::LabelIter {
|
||
self.iter()
|
||
}
|
||
}
|
||
|
||
impl ToDname for Dname {
|
||
fn to_name(&self) -> Dname {
|
||
self.clone()
|
||
}
|
||
}
|
||
|
||
|
||
//--- Deref and AsRef
|
||
|
||
impl ops::Deref for Dname {
|
||
type Target = Bytes;
|
||
|
||
fn deref(&self) -> &Bytes {
|
||
self.as_ref()
|
||
}
|
||
}
|
||
|
||
impl AsRef<Bytes> for Dname {
|
||
fn as_ref(&self) -> &Bytes {
|
||
&self.bytes
|
||
}
|
||
}
|
||
|
||
impl AsRef<[u8]> for Dname {
|
||
fn as_ref(&self) -> &[u8] {
|
||
self.bytes.as_ref()
|
||
}
|
||
}
|
||
|
||
|
||
//--- IntoIterator
|
||
|
||
impl<'a> IntoIterator for &'a Dname {
|
||
type Item = &'a Label;
|
||
type IntoIter = DnameIter<'a>;
|
||
|
||
fn into_iter(self) -> Self::IntoIter {
|
||
self.iter()
|
||
}
|
||
}
|
||
|
||
|
||
//--- PartialEq and Eq
|
||
//
|
||
// XXX TODO Once specialization lands in stable, we can add a blanket
|
||
// impl for `ToDname`. For now, I’d rather keep the optimized
|
||
// versions for Dname, instead.
|
||
|
||
impl PartialEq for Dname {
|
||
fn eq(&self, other: &Self) -> bool {
|
||
self.as_slice().eq_ignore_ascii_case(other.as_slice())
|
||
}
|
||
}
|
||
|
||
impl PartialEq<ParsedDname> for Dname {
|
||
fn eq(&self, other: &ParsedDname) -> bool {
|
||
self.iter().eq(other.iter())
|
||
}
|
||
}
|
||
|
||
impl Eq for Dname { }
|
||
|
||
|
||
//--- PartialOrd and Ord
|
||
|
||
impl PartialOrd for Dname {
|
||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||
self.iter().partial_cmp(other.iter())
|
||
}
|
||
}
|
||
|
||
impl PartialOrd<ParsedDname> for Dname {
|
||
fn partial_cmp(&self, other: &ParsedDname) -> Option<cmp::Ordering> {
|
||
self.iter().partial_cmp(other.iter())
|
||
}
|
||
}
|
||
|
||
impl Ord for Dname {
|
||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||
self.iter().cmp(other.iter())
|
||
}
|
||
}
|
||
|
||
|
||
//--- Hash
|
||
|
||
impl hash::Hash for Dname {
|
||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||
for item in self.iter() {
|
||
item.hash(state)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//--- Display and Debug
|
||
|
||
impl fmt::Display for Dname {
|
||
/// Formats the domain name.
|
||
///
|
||
/// This will produce the domain name in common display format without
|
||
/// the trailing dot.
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
let mut iter = self.iter();
|
||
write!(f, "{}", iter.next().unwrap())?;
|
||
for label in iter {
|
||
if !label.is_root() {
|
||
write!(f, ".{}", label)?
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl fmt::Debug for Dname {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
write!(f, "Dname({}.)", self)
|
||
}
|
||
}
|
||
|
||
|
||
//--- Scan and Print
|
||
|
||
impl Scan for Dname {
|
||
fn scan<C: CharSource>(scanner: &mut Scanner<C>)
|
||
-> Result<Self, ScanError> {
|
||
scanner.try_scan(UncertainDname::scan, |res| {
|
||
res.try_into_absolute().map_err(|_| SyntaxError::RelativeName)
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Print for Dname {
|
||
fn print<W: io::Write>(&self, printer: &mut Printer<W>)
|
||
-> Result<(), io::Error> {
|
||
write!(printer.item()?, "{}.", self)
|
||
}
|
||
}
|
||
|
||
|
||
//------------ SuffixIter ----------------------------------------------------
|
||
|
||
#[derive(Clone, Debug)]
|
||
pub struct SuffixIter {
|
||
name: Option<Dname>,
|
||
}
|
||
|
||
impl SuffixIter {
|
||
fn new(name: &Dname) -> Self {
|
||
SuffixIter {
|
||
name: Some(name.clone())
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Iterator for SuffixIter {
|
||
type Item = Dname;
|
||
|
||
fn next(&mut self) -> Option<Self::Item> {
|
||
let (res, ok) = match self.name {
|
||
Some(ref mut name) => (name.clone(), name.parent().is_ok()),
|
||
None => return None
|
||
};
|
||
if !ok {
|
||
self.name = None
|
||
}
|
||
Some(res)
|
||
}
|
||
}
|
||
|
||
|
||
//------------ DnameError ----------------------------------------------------
|
||
|
||
/// A domain name wasn’t encoded correctly.
|
||
#[derive(Clone, Copy, Debug, Eq, Fail, PartialEq)]
|
||
pub enum DnameError {
|
||
#[fail(display="{}", _0)]
|
||
BadLabel(LabelTypeError),
|
||
|
||
#[fail(display="compressed domain name")]
|
||
CompressedName,
|
||
|
||
#[fail(display="long domain name")]
|
||
LongName,
|
||
}
|
||
|
||
impl From<LabelTypeError> for DnameError {
|
||
fn from(err: LabelTypeError) -> DnameError {
|
||
DnameError::BadLabel(err)
|
||
}
|
||
}
|
||
|
||
|
||
//------------ DnameParseError -----------------------------------------------
|
||
|
||
/// An error happened while parsing a domain name.
|
||
#[derive(Clone, Copy, Debug, Eq, Fail, PartialEq)]
|
||
pub enum DnameParseError {
|
||
#[fail(display="{}", _0)]
|
||
BadName(DnameError),
|
||
|
||
#[fail(display="unexpected end of buffer")]
|
||
ShortBuf,
|
||
}
|
||
|
||
impl<T: Into<DnameError>> From<T> for DnameParseError {
|
||
fn from(err: T) -> DnameParseError {
|
||
DnameParseError::BadName(err.into())
|
||
}
|
||
}
|
||
|
||
impl From<SplitLabelError> for DnameParseError {
|
||
fn from(err: SplitLabelError) -> DnameParseError {
|
||
match err {
|
||
SplitLabelError::Pointer(_)
|
||
=> DnameParseError::BadName(DnameError::CompressedName),
|
||
SplitLabelError::BadType(t)
|
||
=> DnameParseError::BadName(DnameError::BadLabel(t)),
|
||
SplitLabelError::ShortSlice => DnameParseError::ShortBuf,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<ShortBuf> for DnameParseError {
|
||
fn from(_: ShortBuf) -> DnameParseError {
|
||
DnameParseError::ShortBuf
|
||
}
|
||
}
|
||
|
||
|
||
//------------ DnameBytesError -----------------------------------------------
|
||
|
||
/// An error happened while converting a bytes value into a domain name.
|
||
#[derive(Clone, Copy, Debug, Eq, Fail, PartialEq)]
|
||
pub enum DnameBytesError {
|
||
#[fail(display="{}", _0)]
|
||
ParseError(DnameParseError),
|
||
|
||
#[fail(display="trailing data")]
|
||
TrailingData,
|
||
}
|
||
|
||
impl<T: Into<DnameParseError>> From<T> for DnameBytesError {
|
||
fn from(err: T) -> DnameBytesError {
|
||
DnameBytesError::ParseError(err.into())
|
||
}
|
||
}
|
||
|