This commit is contained in:
Stefan Bühler 2017-12-26 22:23:51 +01:00
parent c4c84bd887
commit 302300c184
15 changed files with 148 additions and 70 deletions

View File

@ -40,7 +40,7 @@ impl DnsPacketData for HexShortBlob {
}
impl DnsTextData for HexShortBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let s = next_field(data)?;
if s == "-" {
Ok(HexShortBlob(Bytes::new()))
@ -76,7 +76,7 @@ impl DnsPacketData for Base64RemainingBlob {
}
impl DnsTextData for Base64RemainingBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
skip_whitespace(data);
let result = BASE64_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid base64: {:?}", data)))?;
@ -101,7 +101,7 @@ impl DnsPacketData for HexRemainingBlob {
}
impl DnsTextData for HexRemainingBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
skip_whitespace(data);
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;

View File

@ -3,7 +3,7 @@
use bytes::Bytes;
use errors::*;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field};
use std::fmt;
use std::io::Cursor;
use std::borrow::Cow;
@ -153,7 +153,7 @@ impl Class {
/// parses generic names of the form "CLASS..."
pub fn from_generic_name(name: &str) -> Option<Self> {
use std::ascii::AsciiExt;
if name.as_bytes()[0..5].eq_ignore_ascii_case(b"CLASS") {
if name.len() > 5 && name.as_bytes()[0..5].eq_ignore_ascii_case(b"CLASS") {
name[5..].parse::<u16>().ok().map(Class)
} else {
None
@ -204,7 +204,7 @@ impl DnsPacketData for Class {
}
impl DnsTextData for Class {
fn dns_parse(data: &mut &str) -> Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
Class::from_name(field).ok_or_else(|| format_err!("unknown CLASS {:?}", field))
}

View File

@ -1,19 +1,19 @@
use super::*;
use ser::text::{DnsTextData, DnsTextFormatter, next_field, quoted};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field, quoted};
impl DnsName {
/// Parse text representation of a domain name
pub fn parse<'a, O>(value: &str, origin: O) -> ::errors::Result<Self>
where
O: IntoIterator<Item = DnsLabelRef<'a>>
pub fn parse(context: &DnsTextContext, value: &str) -> Result<Self>
{
let raw = value.as_bytes();
let mut name = DnsName::new_root();
if raw == b"." {
return Ok(name);
} else if raw == b"@" {
for l in origin.into_iter() { name.push_back(l)?; }
return Ok(name);
match context.origin() {
Some(o) => return Ok(o.clone()),
None => bail!("@ invalid without $ORIGIN"),
}
}
ensure!(!raw.is_empty(), "invalid empty name");
let mut label = Vec::new();
@ -48,8 +48,16 @@ impl DnsName {
if !label.is_empty() {
// no trailing dot, relative name
// push last label
name.push_back(DnsLabelRef::new(&label)?)?;
for l in origin.into_iter() { name.push_back(l)?; }
match context.origin() {
Some(o) => {
for l in o { name.push_back(l)?; }
},
None => bail!("missing trailing dot without $ORIGIN"),
}
}
Ok(name)
@ -57,9 +65,9 @@ impl DnsName {
}
impl DnsTextData for DnsName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
DnsName::parse(field, &DnsName::new_root())
DnsName::parse(context, field)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
@ -69,18 +77,16 @@ impl DnsTextData for DnsName {
impl DnsCompressedName {
/// Parse text representation of a domain name
pub fn parse<'a, O>(value: &str, origin: O) -> ::errors::Result<Self>
where
O: IntoIterator<Item = DnsLabelRef<'a>>
pub fn parse(context: &DnsTextContext, value: &str) -> Result<Self>
{
Ok(DnsCompressedName(DnsName::parse(value, origin)?))
Ok(DnsCompressedName(DnsName::parse(context, value)?))
}
}
impl DnsTextData for DnsCompressedName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
DnsCompressedName::parse(field, &DnsName::new_root())
DnsCompressedName::parse(context, field)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {

View File

@ -4,7 +4,7 @@ use data_encoding;
use errors::*;
use failure::{Fail, ResultExt};
use ser::packet::{DnsPacketData, remaining_bytes, short_blob};
use ser::text::{DnsTextData, DnsTextFormatter, skip_whitespace, next_field};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, skip_whitespace, next_field};
use std::collections::BTreeSet;
use std::fmt;
use std::io::Cursor;
@ -103,11 +103,11 @@ impl DnsPacketData for NsecTypeBitmap {
}
impl DnsTextData for NsecTypeBitmap {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let mut set = BTreeSet::new();
skip_whitespace(data);
while !data.is_empty() {
let t = Type::dns_parse(data)?;
let t = Type::dns_parse(context, data)?;
set.insert(t);
}
Ok(NsecTypeBitmap::from_set(set))
@ -137,7 +137,7 @@ impl DnsPacketData for NextHashedOwnerName {
}
impl DnsTextData for NextHashedOwnerName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
let raw = BASE32HEX_NOPAD_ALLOW_WS.decode(field.as_bytes())
.with_context(|e| e.context(format!("invalid base32hex (no padding): {:?}", field)))?;

View File

@ -2,7 +2,7 @@ use bytes::{Bytes, Buf};
use common_types::Type;
use errors::*;
use ser::packet::{DnsPacketData, remaining_bytes};
use ser::text::{DnsTextData, DnsTextFormatter, skip_whitespace};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, skip_whitespace};
use std::collections::BTreeSet;
use std::fmt;
use std::io::Cursor;
@ -75,11 +75,11 @@ impl DnsPacketData for NxtTypeBitmap {
}
impl DnsTextData for NxtTypeBitmap {
fn dns_parse(data: &mut &str) -> Result<Self> {
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> Result<Self> {
let mut set = BTreeSet::new();
skip_whitespace(data);
while !data.is_empty() {
let t = Type::dns_parse(data)?;
let t = Type::dns_parse(context, data)?;
set.insert(t);
}
NxtTypeBitmap::from_set(set)

View File

@ -16,7 +16,7 @@ impl DnsPacketData for ShortText {
}
impl DnsTextData for ShortText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(raw.len() < 256, "short text must be at most 255 bytes long");
Ok(ShortText(raw.into()))
@ -46,7 +46,7 @@ impl DnsPacketData for LongText {
}
impl DnsTextData for LongText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let mut result = Vec::new();
// `next_quoted_field` should skip trailing whitespace, we only
// need to skip the beginning whitespace for the first
@ -80,7 +80,7 @@ impl DnsPacketData for UnquotedShortText {
}
impl DnsTextData for UnquotedShortText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(raw.len() < 256, "short text must be at most 255 bytes long");
Ok(UnquotedShortText(raw.into()))
@ -105,7 +105,7 @@ impl DnsPacketData for RemainingText {
}
impl DnsTextData for RemainingText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
Ok(RemainingText(next_quoted_field(data)?.into()))
}

View File

@ -1,7 +1,7 @@
use bytes::Bytes;
use errors::*;
use ser::packet::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field};
use std::fmt;
use std::io::Cursor;
@ -18,7 +18,7 @@ impl DnsPacketData for Time {
}
impl DnsTextData for Time {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
let epoch = field.parse::<u32>();
if field.len() == 14 && epoch.is_err() {

View File

@ -4,7 +4,7 @@ use bytes::Bytes;
use errors::*;
use records::registry::{lookup_type_to_name, lookup_type_name};
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field};
use std::borrow::Cow;
use std::fmt;
use std::io::Cursor;
@ -475,7 +475,7 @@ impl Type {
/// parses generic names of the form "TYPE..."
pub fn from_generic_name(name: &str) -> Option<Self> {
use std::ascii::AsciiExt;
if name.as_bytes()[0..4].eq_ignore_ascii_case(b"TYPE") {
if name.len() > 4 && name.as_bytes()[0..4].eq_ignore_ascii_case(b"TYPE") {
name[4..].parse::<u16>().ok().map(Type)
} else {
None
@ -576,7 +576,7 @@ impl DnsPacketData for Type {
}
impl DnsTextData for Type {
fn dns_parse(data: &mut &str) -> Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
Type::from_name(field).ok_or_else(|| format_err!("unknown TYPE {:?}", field))
}

View File

@ -24,7 +24,7 @@ impl DnsPacketData for UriText {
}
impl DnsTextData for UriText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(!raw.is_empty(), "URI must not be empty");
Ok(UriText(raw.into()))

View File

@ -20,11 +20,13 @@ fn rrdata_parse<T>(data: &str) -> ::errors::Result<T>
where
T: StaticRRData
{
let mut data = data;
let result = T::dns_parse_rr_data(3600, classes::IN, T::TYPE, &mut data)?;
let data = data.trim();
ensure!(data.is_empty(), "didn't parse complete rrdata text, remaining: {:?}", data);
Ok(result)
let mut ctx = text::DnsTextContext::new();
ctx.set_zone_class(classes::IN);
ctx.set_record_type(T::TYPE);
ctx.set_last_ttl(3600);
text::parse_with(data, |data| {
T::dns_parse_rr_data(&ctx, data)
})
}
fn check<T>(txt: &str, data: &'static [u8]) -> ::errors::Result<()>
@ -109,8 +111,8 @@ fn test_nsec3() {
check::<structs::NSEC3>("1 2 300 ab vs A NS", b"\x01\x02\x01\x2c\x01\xab\x01\xff\x00\x01\x60").unwrap();
// invalid base32 texts
text::parse::<structs::NSEC3>("1 2 300 - v").unwrap_err();
text::parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err();
rrdata_parse::<structs::NSEC3>("1 2 300 - v").unwrap_err();
rrdata_parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err();
// invalid (empty) next-hashed values
packet::deserialize::<structs::NSEC3>(Bytes::from_static(b"\x01\x02\x01\x2c\x00\x00")).unwrap_err();
@ -121,7 +123,7 @@ fn test_nsec3param() {
check::<structs::NSEC3PARAM>("1 2 300 -", b"\x01\x02\x01\x2c\x00").unwrap();
check::<structs::NSEC3PARAM>("1 2 300 ab", b"\x01\x02\x01\x2c\x01\xab").unwrap();
// `salt` hex string must not contain spaces
text::parse::<structs::NSEC3PARAM>("1 2 300 a b").unwrap_err();
rrdata_parse::<structs::NSEC3PARAM>("1 2 300 a b").unwrap_err();
}
#[test]

View File

@ -2,7 +2,7 @@ use bytes::{Bytes, Buf};
use common_types::*;
use failure::ResultExt;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext};
use std::fmt;
use std::io::Read;
use std::net::Ipv6Addr;
@ -36,7 +36,7 @@ impl DnsPacketData for LOC {
}
impl DnsTextData for LOC {
fn dns_parse(_data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, _data: &mut &str) -> ::errors::Result<Self> {
unimplemented!()
}
@ -101,15 +101,15 @@ impl DnsPacketData for A6 {
}
impl DnsTextData for A6 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let prefix: u8 = DnsTextData::dns_parse(data)
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
let prefix: u8 = DnsTextData::dns_parse(context, data)
.context("failed parsing field A6::prefix")?;
ensure!(prefix <= 128, "invalid A6::prefix {}", prefix);
let suffix_offset = (prefix / 8) as usize;
debug_assert!(suffix_offset <= 16);
let suffix: Ipv6Addr = DnsTextData::dns_parse(data)
let suffix: Ipv6Addr = DnsTextData::dns_parse(context, data)
.context("failed parsing field A6::suffix")?;
// clear prefix bits
@ -124,7 +124,7 @@ impl DnsTextData for A6 {
let suffix = Ipv6Addr::from(suffix);
let prefix_name = if !data.is_empty() {
Some(DnsTextData::dns_parse(data)
Some(DnsTextData::dns_parse(context, data)
.context("failed parsing field A6::prefix_name")?)
} else {
None

View File

@ -2,7 +2,7 @@ use bytes::Bytes;
use common_types::{Class, Type, classes};
use errors::*;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext};
use std::borrow::Cow;
use std::fmt;
use std::io::Cursor;
@ -31,7 +31,7 @@ impl<T: DnsPacketData + StaticRRData> RRDataPacket for T {
}
pub trait RRDataText {
fn dns_parse_rr_data(ttl: u32, rr_class: Class, rr_type: Type, data: &mut &str) -> Result<Self>
fn dns_parse_rr_data(context: &DnsTextContext, data: &mut &str) -> Result<Self>
where
Self: Sized,
;
@ -59,15 +59,17 @@ pub trait RRDataText {
}
impl<T: DnsTextData + StaticRRData> RRDataText for T {
fn dns_parse_rr_data(_ttl: u32, rr_class: Class, rr_type: Type, data: &mut &str) -> Result<Self>
fn dns_parse_rr_data(context: &DnsTextContext, data: &mut &str) -> Result<Self>
where
Self: Sized,
{
ensure!(rr_type == T::TYPE, "type mismatch");
ensure!(context.record_type() == Some(T::TYPE), "type mismatch");
let rr_class = context.zone_class().expect("require zone CLASS to parse record");
if T::CLASS != classes::ANY {
ensure!(rr_class == T::CLASS, "class mismatch: got {}, need {}", rr_class, T::CLASS);
}
T::dns_parse(data)
ensure!(context.last_ttl().is_some(), "require TTL to parse record");
T::dns_parse(context, data)
}
fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result {

View File

@ -1,3 +1,4 @@
use common_types;
use std::fmt;
mod std_impls;
@ -94,8 +95,74 @@ impl<'a, 'b> DnsTextFormatter<'a, 'b> {
}
}
#[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 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 trait DnsTextData {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self>
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self>
where
Self: Sized,
;
@ -103,12 +170,12 @@ pub trait DnsTextData {
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result;
}
pub fn parse<T>(data: &str) -> ::errors::Result<T>
pub fn parse_with<'a, F, O>(data: &'a str, parser: F) -> ::errors::Result<O>
where
T: DnsTextData
for<'b> F: FnOnce(&'b mut &'a str) -> ::errors::Result<O>,
{
let mut data = data;
let result = T::dns_parse(&mut data)?;
let result = parser(&mut data)?;
let data = data.trim();
ensure!(data.is_empty(), "didn't parse complete text, remaining: {:?}", data);
Ok(result)

View File

@ -1,10 +1,10 @@
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field};
/* only decimal representations are used for numbers */
impl DnsTextData for u8 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
@ -14,7 +14,7 @@ impl DnsTextData for u8 {
}
impl DnsTextData for u16 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
@ -24,7 +24,7 @@ impl DnsTextData for u16 {
}
impl DnsTextData for u32 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
@ -35,7 +35,7 @@ impl DnsTextData for u32 {
/* only decimal representations are needed for octets */
impl DnsTextData for Ipv4Addr {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
@ -46,7 +46,7 @@ impl DnsTextData for Ipv4Addr {
/* representation as in RFC 3513: https://tools.ietf.org/html/rfc3513#section-2.2 */
impl DnsTextData for Ipv6Addr {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
@ -60,8 +60,9 @@ mod tests {
use super::*;
fn deserialize<T: DnsTextData>(data: &str) -> ::errors::Result<T> {
let context = DnsTextContext::new();
let mut data = data;
let res = T::dns_parse(&mut data)?;
let res = T::dns_parse(&context, &mut data)?;
let data = data.trim();
ensure!(data.is_empty(), "didn't read data completely");
Ok(res)

View File

@ -28,7 +28,7 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
let field_name = field.ident.as_ref().unwrap();
parse_fields = quote!{#parse_fields
#field_name: DnsTextData::dns_parse(_data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?,
#field_name: DnsTextData::dns_parse(_context, _data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?,
};
format_fields = quote!{#format_fields
@ -39,7 +39,7 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
quote!{
#[allow(unused_imports)]
impl ::dnsbox_base::ser::DnsTextData for #name {
fn dns_parse(_data: &mut &str) -> ::dnsbox_base::errors::Result<Self> {
fn dns_parse(_context: &::dnsbox_base::ser::text::DnsTextContext, _data: &mut &str) -> ::dnsbox_base::errors::Result<Self> {
use dnsbox_base::failure::ResultExt;
use dnsbox_base::ser::DnsTextData;
Ok(#name{ #parse_fields })