You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
5.3 KiB
235 lines
5.3 KiB
use crate::common_types; |
|
use std::fmt; |
|
|
|
mod std_impls; |
|
pub mod quoted; |
|
|
|
pub use dnsbox_derive::DnsTextData; |
|
|
|
pub fn skip_whitespace(data: &mut &str) { |
|
*data = (*data).trim_start(); |
|
} |
|
|
|
pub fn next_field<'a>(data: &mut &'a str) -> crate::errors::Result<&'a str> { |
|
*data = (*data).trim_start(); |
|
if data.is_empty() { failure::bail!("missing field"); } |
|
match data.find(char::is_whitespace) { |
|
None => { |
|
let result = *data; |
|
*data = ""; |
|
Ok(result) |
|
}, |
|
Some(next) => { |
|
let result = &(*data)[..next]; |
|
*data = &(*data)[next..].trim_start(); |
|
Ok(result) |
|
}, |
|
} |
|
} |
|
|
|
pub fn next_quoted_field(data: &mut &str) -> crate::errors::Result<Vec<u8>> { |
|
*data = (*data).trim_start(); |
|
if data.is_empty() { failure::bail!("missing field"); } |
|
|
|
let result = quoted::UnquoteIterator::new(data).collect::<Result<Vec<_>, _>>()?; |
|
Ok(result) |
|
} |
|
|
|
pub fn quote(data: &[u8]) -> String { |
|
let mut result = String::with_capacity(data.len() + 2); |
|
result.push('"'); |
|
for qc in quoted::EncodeIterator::new_quoted(data) { |
|
result += &qc; |
|
} |
|
result.push('"'); |
|
result |
|
} |
|
|
|
// also escapes whitespace, but doesn't use quotes to surround it |
|
pub fn escape(data: &[u8]) -> String { |
|
let mut result = String::with_capacity(data.len()); |
|
for qc in quoted::EncodeIterator::new_encode_whitespace(data) { |
|
result += &qc; |
|
} |
|
result |
|
} |
|
|
|
/// Each call to write!() makes sure a space is emitted to separate it |
|
/// from previous fields. |
|
pub struct DnsTextFormatter<'a> { |
|
w: &'a mut dyn fmt::Write, |
|
need_space: bool, |
|
} |
|
|
|
impl<'a> DnsTextFormatter<'a> { |
|
pub fn new(w: &'a mut dyn fmt::Write) -> Self { |
|
DnsTextFormatter { |
|
w: w, |
|
need_space: false, |
|
} |
|
} |
|
|
|
/// make sure a field separator was emitted |
|
pub fn next_field(&mut self) -> fmt::Result { |
|
if self.need_space { |
|
write!(self.w, " ")?; |
|
self.need_space = false; |
|
} |
|
Ok(()) |
|
} |
|
|
|
/// a field was emitted through `inner`; next field needs a separator |
|
pub fn end_field(&mut self) { |
|
self.need_space = true; |
|
} |
|
|
|
/// direct access to underlying output; you'll need to call |
|
/// `next_field` and `end_field` manually. |
|
pub fn inner(&mut self) -> &mut dyn fmt::Write { |
|
self.w |
|
} |
|
|
|
pub fn format_field<'b>(&'b mut self) -> Result<DnsTextFormatField<'a, 'b>, fmt::Error> { |
|
self.next_field()?; |
|
Ok(DnsTextFormatField{ inner: self }) |
|
} |
|
|
|
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { |
|
self.next_field()?; |
|
self.w.write_fmt(args)?; |
|
self.end_field(); |
|
Ok(()) |
|
} |
|
} |
|
|
|
pub struct DnsTextFormatField<'a: 'b, 'b> { |
|
inner: &'b mut DnsTextFormatter<'a>, |
|
} |
|
|
|
impl<'a, 'b> ::std::ops::Deref for DnsTextFormatField<'a, 'b> { |
|
type Target = dyn fmt::Write + 'a; |
|
|
|
fn deref(&self) -> &Self::Target { |
|
self.inner.w |
|
} |
|
} |
|
|
|
impl<'a, 'b> ::std::ops::DerefMut for DnsTextFormatField<'a, 'b> { |
|
fn deref_mut(&mut self) -> &mut Self::Target { |
|
self.inner.w |
|
} |
|
} |
|
|
|
impl<'a, 'b> Drop for DnsTextFormatField<'a, 'b> { |
|
fn drop(&mut self) { |
|
self.inner.end_field(); |
|
} |
|
} |
|
|
|
#[derive(Clone, Debug, Default)] |
|
pub struct DnsTextContext { |
|
zone_class: Option<common_types::Class>, |
|
origin: Option<common_types::DnsName>, |
|
record_type: Option<common_types::Type>, |
|
last_ttl: Option<u32>, |
|
} |
|
|
|
impl DnsTextContext { |
|
pub fn new() -> Self { |
|
Self::default() |
|
} |
|
|
|
pub fn new_root_origin() -> Self { |
|
let mut this = Self::default(); |
|
this.set_origin(common_types::DnsName::new_root()); |
|
this |
|
} |
|
|
|
pub fn zone_class(&self) -> Option<common_types::Class> { |
|
self.zone_class |
|
} |
|
|
|
pub fn set_zone_class(&mut self, zone_class: common_types::Class) -> &mut Self { |
|
self.zone_class = Some(zone_class); |
|
self |
|
} |
|
|
|
pub fn unset_zone_class(&mut self) -> &mut Self { |
|
self |
|
} |
|
|
|
pub fn origin(&self) -> Option<&common_types::DnsName> { |
|
self.origin.as_ref() |
|
} |
|
|
|
pub fn set_origin(&mut self, origin: common_types::DnsName) -> &mut Self { |
|
self.origin = Some(origin); |
|
self |
|
} |
|
|
|
pub fn unset_origin(&mut self) -> &mut Self { |
|
self |
|
} |
|
|
|
pub fn record_type(&self) -> Option<common_types::Type> { |
|
self.record_type |
|
} |
|
|
|
pub fn set_record_type(&mut self, record_type: common_types::Type) -> &mut Self { |
|
self.record_type = Some(record_type); |
|
self |
|
} |
|
|
|
pub fn unset_record_type(&mut self) -> &mut Self { |
|
self |
|
} |
|
|
|
pub fn last_ttl(&self) -> Option<u32> { |
|
self.last_ttl |
|
} |
|
|
|
pub fn set_last_ttl(&mut self, last_ttl: u32) -> &mut Self { |
|
self.last_ttl = Some(last_ttl); |
|
self |
|
} |
|
|
|
pub fn unset_last_ttl(&mut self) -> &mut Self { |
|
self |
|
} |
|
|
|
pub fn parse<T>(&self, data: &str) -> crate::errors::Result<T> |
|
where |
|
T: DnsTextData, |
|
{ |
|
parse_with(data, |data| T::dns_parse(self, data)) |
|
} |
|
} |
|
|
|
pub trait DnsTextData { |
|
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> crate::errors::Result<Self> |
|
where |
|
Self: Sized, |
|
; |
|
// format might fail if there is no (known) text representation. |
|
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result; |
|
} |
|
|
|
pub fn parse_with<'a, F, O>(data: &'a str, parser: F) -> crate::errors::Result<O> |
|
where |
|
for<'b> F: FnOnce(&'b mut &'a str) -> crate::errors::Result<O>, |
|
{ |
|
let mut data = data; |
|
let result = parser(&mut data)?; |
|
let data = data.trim(); |
|
failure::ensure!(data.is_empty(), "didn't parse complete text, remaining: {:?}", data); |
|
Ok(result) |
|
} |
|
|
|
#[derive(Debug)] |
|
pub struct DnsDisplay<'a, T: DnsTextData + ?Sized + 'a>(pub &'a T); |
|
|
|
impl<'a, T: DnsTextData + ?Sized + 'a> fmt::Display for DnsDisplay<'a, T> { |
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
|
self.0.dns_format(&mut DnsTextFormatter::new(f)) |
|
} |
|
}
|
|
|