240 lines
5.2 KiB
Rust
240 lines
5.2 KiB
Rust
use bytes::Bytes;
|
|
use errors::*;
|
|
use std::fmt;
|
|
use std::cmp::Ordering;
|
|
|
|
#[inline]
|
|
fn check_label(label: &[u8]) -> Result<()> {
|
|
if label.len() == 0 {
|
|
bail!("label must not be empty")
|
|
}
|
|
if label.len() > 63 {
|
|
bail!("label must not be longer than 63 bytes")
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// A DNS label (any binary string with `0 < length < 64`)
|
|
#[derive(Clone)]
|
|
pub struct DnsLabel {
|
|
pub(super) label: Bytes, // 0 < len < 64
|
|
}
|
|
|
|
impl DnsLabel {
|
|
/// Create new label from existing storage
|
|
///
|
|
/// Fails when the length doesn't match the requirement `0 < length < 64`.
|
|
pub fn new(label: Bytes) -> Result<Self> {
|
|
check_label(&label)?;
|
|
Ok(DnsLabel{label})
|
|
}
|
|
|
|
/// Convert to a representation without storage
|
|
pub fn as_ref<'a>(&'a self) -> DnsLabelRef<'a> {
|
|
DnsLabelRef{label: self.label.as_ref()}
|
|
}
|
|
|
|
/// Access as raw bytes
|
|
pub fn as_bytes(&self) -> &Bytes {
|
|
&self.label
|
|
}
|
|
|
|
/// Access as raw bytes
|
|
pub fn as_raw(&self) -> &[u8] {
|
|
&self.label
|
|
}
|
|
|
|
/// Length of label (0 < len < 64)
|
|
pub fn len(&self) -> u8 {
|
|
self.label.len() as u8
|
|
}
|
|
|
|
/// compares two labels (ASCII case insensitive)
|
|
#[inline]
|
|
pub fn compare<'a, L: Into<DnsLabelRef<'a>>>(&self, rhs: L) -> Ordering {
|
|
compare_ascii_lc(self.as_raw(), rhs.into().as_raw())
|
|
}
|
|
}
|
|
|
|
impl<'a> From<DnsLabelRef<'a>> for DnsLabel {
|
|
fn from(label_ref: DnsLabelRef<'a>) -> Self {
|
|
DnsLabel{
|
|
label: Bytes::from(label_ref.label),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq<DnsLabel> for DnsLabel {
|
|
#[inline]
|
|
fn eq(&self, rhs: &DnsLabel) -> bool {
|
|
self.as_ref().eq(&rhs.as_ref())
|
|
}
|
|
}
|
|
|
|
impl<'a> PartialEq<DnsLabelRef<'a>> for DnsLabel {
|
|
#[inline]
|
|
fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool {
|
|
self.as_ref().eq(rhs)
|
|
}
|
|
}
|
|
|
|
impl Eq for DnsLabel{}
|
|
|
|
impl PartialOrd<DnsLabel> for DnsLabel {
|
|
#[inline]
|
|
fn partial_cmp(&self, rhs: &DnsLabel) -> Option<Ordering> {
|
|
Some(self.compare(rhs))
|
|
}
|
|
}
|
|
|
|
impl<'a> PartialOrd<DnsLabelRef<'a>> for DnsLabel {
|
|
#[inline]
|
|
fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option<Ordering> {
|
|
Some(self.compare(*rhs))
|
|
}
|
|
}
|
|
|
|
impl Ord for DnsLabel {
|
|
#[inline]
|
|
fn cmp(&self, rhs: &Self) -> Ordering {
|
|
self.compare(rhs)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for DnsLabel {
|
|
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
|
|
self.as_ref().fmt(w)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for DnsLabel {
|
|
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
|
|
self.as_ref().fmt(w)
|
|
}
|
|
}
|
|
|
|
/// A DNS label (any binary string with `0 < length < 64`)
|
|
///
|
|
/// Storage is provided through lifetime.
|
|
#[derive(Clone,Copy)]
|
|
pub struct DnsLabelRef<'a> {
|
|
pub(super) label: &'a [u8], // 0 < len < 64
|
|
}
|
|
|
|
impl<'a> DnsLabelRef<'a> {
|
|
/// Create new label from existing storage
|
|
///
|
|
/// Fails when the length doesn't match the requirement `0 < length < 64`.
|
|
pub fn new(label: &'a [u8]) -> Result<Self> {
|
|
check_label(label)?;
|
|
Ok(DnsLabelRef{label})
|
|
}
|
|
|
|
/// Access as raw bytes
|
|
pub fn as_raw(&self) -> &'a [u8] {
|
|
self.label
|
|
}
|
|
|
|
/// Length of label (0 < len < 64)
|
|
pub fn len(&self) -> u8 {
|
|
self.label.len() as u8
|
|
}
|
|
|
|
/// compares two labels (ASCII case insensitive)
|
|
#[inline]
|
|
pub fn compare<'b, L: Into<DnsLabelRef<'b>>>(&self, rhs: L) -> Ordering {
|
|
compare_ascii_lc(self.as_raw(), rhs.into().as_raw())
|
|
}
|
|
}
|
|
|
|
fn compare_ascii_lc(lhs: &[u8], rhs: &[u8]) -> Ordering {
|
|
use std::ascii::AsciiExt;
|
|
use std::cmp::min;
|
|
let l = min(lhs.len(), rhs.len());
|
|
for i in 0..l {
|
|
let a : u8 = AsciiExt::to_ascii_lowercase(&lhs[i]);
|
|
let b : u8 = AsciiExt::to_ascii_lowercase(&rhs[i]);
|
|
match Ord::cmp(&a, &b) {
|
|
Ordering::Equal => continue,
|
|
c => return c,
|
|
}
|
|
}
|
|
Ord::cmp(&lhs.len(), &rhs.len())
|
|
}
|
|
|
|
impl<'a> From<&'a DnsLabel> for DnsLabelRef<'a> {
|
|
fn from(label: &'a DnsLabel) -> Self {
|
|
label.as_ref()
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> PartialEq<DnsLabelRef<'a>> for DnsLabelRef<'b> {
|
|
fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool {
|
|
use std::ascii::AsciiExt;
|
|
AsciiExt::eq_ignore_ascii_case(self.as_raw(), rhs.as_raw())
|
|
}
|
|
}
|
|
|
|
impl<'a> PartialEq<DnsLabel> for DnsLabelRef<'a> {
|
|
#[inline]
|
|
fn eq(&self, rhs: &DnsLabel) -> bool {
|
|
self.eq(&rhs.as_ref())
|
|
}
|
|
}
|
|
|
|
impl<'a> Eq for DnsLabelRef<'a>{}
|
|
|
|
impl<'a, 'b> PartialOrd<DnsLabelRef<'a>> for DnsLabelRef<'b> {
|
|
#[inline]
|
|
fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option<Ordering> {
|
|
Some(self.compare(*rhs))
|
|
}
|
|
}
|
|
|
|
impl<'a> PartialOrd<DnsLabel> for DnsLabelRef<'a> {
|
|
#[inline]
|
|
fn partial_cmp(&self, rhs: &DnsLabel) -> Option<Ordering> {
|
|
Some(self.compare(rhs))
|
|
}
|
|
}
|
|
|
|
impl<'a> Ord for DnsLabelRef<'a> {
|
|
#[inline]
|
|
fn cmp(&self, rhs: &Self) -> Ordering {
|
|
self.compare(*rhs)
|
|
}
|
|
}
|
|
|
|
impl<'a> fmt::Debug for DnsLabelRef<'a> {
|
|
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
|
|
// just escape the display version
|
|
format!("{}", self).fmt(w)
|
|
}
|
|
}
|
|
|
|
impl<'a> fmt::Display for DnsLabelRef<'a> {
|
|
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
|
|
let mut done = 0;
|
|
for pos in 0..self.label.len() {
|
|
let c = self.label[pos];
|
|
if c <= 0x21 || c >= 0x7e || b'.' == c || b'\\' == c {
|
|
// flush
|
|
if done < pos {
|
|
w.write_str(::unsafe_ops::from_utf8_unchecked(&self.label[done..pos]))?;
|
|
}
|
|
match c {
|
|
b'.' => w.write_str(r#"\."#)?,
|
|
b'\\' => w.write_str(r#"\\"#)?,
|
|
_ => write!(w, r"\{:03}", c)?,
|
|
}
|
|
done = pos + 1;
|
|
}
|
|
}
|
|
// final flush
|
|
if done < self.label.len() {
|
|
w.write_str(::unsafe_ops::from_utf8_unchecked(&self.label[done..]))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|