112 lines
3.3 KiB
Rust
112 lines
3.3 KiB
Rust
use super::*;
|
|
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field, quoted, parse_with};
|
|
|
|
impl DnsName {
|
|
/// Parse text representation of a domain name
|
|
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"@" {
|
|
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();
|
|
let mut pos = 0;
|
|
while pos < raw.len() {
|
|
if raw[pos] == b'.' {
|
|
ensure!(!label.is_empty(), "empty label in name: {:?}", value);
|
|
name.push_back(DnsLabelRef::new(&label)?)?;
|
|
label.clear();
|
|
} else if raw[pos] == b'\\' {
|
|
ensure!(pos + 1 < raw.len(), "unexpected end of name after backslash: {:?}", value);
|
|
if raw[pos+1] >= b'0' && raw[pos+1] <= b'9' {
|
|
// \ddd escape
|
|
ensure!(pos + 3 < raw.len(), "unexpected end of name after backslash with digit: {:?}", value);
|
|
ensure!(raw[pos+2] >= b'0' && raw[pos+2] <= b'9' && raw[pos+3] >= b'0' && raw[pos+3] <= b'9', "expected three digits after backslash in name: {:?}", name);
|
|
let d1 = (raw[pos+1] - b'0') as u32;
|
|
let d2 = (raw[pos+2] - b'0') as u32;
|
|
let d3 = (raw[pos+3] - b'0') as u32;
|
|
let v = d1 * 100 + d2 * 10 + d3;
|
|
ensure!(v < 256, "invalid escape in name, {} > 255: {:?}", v, name);
|
|
label.push(v as u8);
|
|
} else {
|
|
ensure!(!quoted::is_ascii_whitespace(raw[pos+1]), "whitespace cannot be escaped with backslash prefix; encode it as \\{:03} in: {:?}", raw[pos+1], name);
|
|
label.push(raw[pos+1]);
|
|
}
|
|
} else {
|
|
ensure!(!quoted::is_ascii_whitespace(raw[pos]), "whitespace must be encoded as \\{:03} in: {:?}", raw[pos], name);
|
|
label.push(raw[pos]);
|
|
}
|
|
pos += 1;
|
|
}
|
|
|
|
if !label.is_empty() {
|
|
// no trailing dot, relative name
|
|
|
|
// push last label
|
|
name.push_back(DnsLabelRef::new(&label)?)?;
|
|
|
|
match context.origin() {
|
|
Some(o) => {
|
|
for l in o { name.push_back(l)?; }
|
|
},
|
|
None => bail!("missing trailing dot without $ORIGIN"),
|
|
}
|
|
}
|
|
|
|
Ok(name)
|
|
}
|
|
}
|
|
|
|
impl DnsTextData for DnsName {
|
|
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> Result<Self> {
|
|
let field = next_field(data)?;
|
|
DnsName::parse(context, field)
|
|
}
|
|
|
|
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
|
write!(f, "{}", self)
|
|
}
|
|
}
|
|
|
|
impl ::std::str::FromStr for DnsName {
|
|
type Err = ::failure::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
parse_with(s, |data| DnsName::dns_parse(&DnsTextContext::new(), data))
|
|
}
|
|
}
|
|
|
|
impl DnsCompressedName {
|
|
/// Parse text representation of a domain name
|
|
pub fn parse(context: &DnsTextContext, value: &str) -> Result<Self>
|
|
{
|
|
Ok(DnsCompressedName(DnsName::parse(context, value)?))
|
|
}
|
|
}
|
|
|
|
impl DnsTextData for DnsCompressedName {
|
|
fn dns_parse(context: &DnsTextContext, data: &mut &str) -> Result<Self> {
|
|
let field = next_field(data)?;
|
|
DnsCompressedName::parse(context, field)
|
|
}
|
|
|
|
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
|
self.0.dns_format(f)
|
|
}
|
|
}
|
|
|
|
impl ::std::str::FromStr for DnsCompressedName {
|
|
type Err = ::failure::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
parse_with(s, |data| DnsCompressedName::dns_parse(&DnsTextContext::new(), data))
|
|
}
|
|
}
|