use std::collections::BTreeMap;
use std::str::FromStr;
use crate::error::*;
use crate::rr::{DNSClass, LowerName, Name, RData, Record, RecordSet, RecordType, RrKey};
use crate::serialize::txt::parse_rdata::RDataParser;
use crate::serialize::txt::zone_lex::{Lexer, Token};
#[derive(Clone, Copy, Default)]
pub struct Parser;
impl Parser {
pub fn new() -> Self {
Parser
}
pub fn parse(
&mut self,
lexer: Lexer<'_>,
origin: Option<Name>,
class: Option<DNSClass>,
) -> ParseResult<(Name, BTreeMap<RrKey, RecordSet>)> {
let mut lexer = lexer;
let mut records: BTreeMap<RrKey, RecordSet> = BTreeMap::new();
let mut origin: Option<Name> = origin;
let mut class: Option<DNSClass> = class;
let mut current_name: Option<Name> = None;
let mut rtype: Option<RecordType> = None;
let mut ttl: Option<u32> = None;
let mut state = State::StartLine;
while let Some(t) = lexer.next_token()? {
state = match state {
State::StartLine => {
rtype = None;
match t {
Token::Include => {
return Err(ParseError::from(ParseErrorKind::Message("The parser does not support $INCLUDE. Consider inlining file before parsing")))
},
Token::Origin => State::Origin,
Token::Ttl => State::Ttl,
Token::CharData(data) => {
current_name = Some(Name::parse(&data, origin.as_ref())?);
State::TtlClassType
}
Token::At => {
current_name = origin.clone(); State::TtlClassType
}
Token::Blank => State::TtlClassType,
Token::EOL => State::StartLine, _ => return Err(ParseErrorKind::UnexpectedToken(t).into()),
}
}
State::Ttl => match t {
Token::CharData(data) => {
ttl = Some(Self::parse_time(&data)?);
State::StartLine
}
_ => return Err(ParseErrorKind::UnexpectedToken(t).into()),
},
State::Origin => {
match t {
Token::CharData(data) => {
origin = Some(Name::parse(&data, None)?);
State::StartLine
}
_ => return Err(ParseErrorKind::UnexpectedToken(t).into()),
}
}
State::Include => return Err(ParseError::from(ParseErrorKind::Message(
"The parser does not support $INCLUDE. Consider inlining file before parsing",
))),
State::TtlClassType => {
match t {
Token::CharData(mut data) => {
let result: ParseResult<u32> = Self::parse_time(&data);
if result.is_ok() {
ttl = result.ok();
State::TtlClassType } else {
data.make_ascii_uppercase();
let result = DNSClass::from_str(&data);
if result.is_ok() {
class = result.ok();
State::TtlClassType
} else {
rtype = Some(RecordType::from_str(&data)?);
State::Record(vec![])
}
}
}
Token::EOL => {
State::StartLine }
_ => return Err(ParseErrorKind::UnexpectedToken(t).into()),
}
}
State::Record(record_parts) => {
match t {
Token::EOL => {
Self::flush_record(
record_parts,
&origin,
¤t_name,
rtype,
&mut ttl,
class,
&mut records,
)?;
State::StartLine
}
Token::CharData(part) => {
let mut record_parts = record_parts;
record_parts.push(part);
State::Record(record_parts)
}
Token::List(list) => {
let mut record_parts = record_parts;
record_parts.extend(list);
State::Record(record_parts)
}
_ => return Err(ParseErrorKind::UnexpectedToken(t).into()),
}
}
}
}
if let State::Record(record_parts) = state {
Self::flush_record(
record_parts,
&origin,
¤t_name,
rtype,
&mut ttl,
class,
&mut records,
)?;
}
let origin = origin.ok_or_else(|| {
ParseError::from(ParseErrorKind::Message("$ORIGIN was not specified"))
})?;
Ok((origin, records))
}
fn flush_record(
record_parts: Vec<String>,
origin: &Option<Name>,
current_name: &Option<Name>,
rtype: Option<RecordType>,
ttl: &mut Option<u32>,
class: Option<DNSClass>,
records: &mut BTreeMap<RrKey, RecordSet>,
) -> ParseResult<()> {
let rtype = rtype.ok_or_else(|| {
ParseError::from(ParseErrorKind::Message("record type not specified"))
})?;
let rdata = RData::parse(
rtype,
record_parts.iter().map(AsRef::as_ref),
origin.as_ref(),
)?;
let mut record = Record::new();
record.set_name(current_name.clone().ok_or_else(|| {
ParseError::from(ParseErrorKind::Message("record name not specified"))
})?);
record.set_rr_type(rtype);
record.set_dns_class(class.ok_or_else(|| {
ParseError::from(ParseErrorKind::Message("record class not specified"))
})?);
match rtype {
RecordType::SOA => {
if let RData::SOA(ref soa) = rdata {
record.set_ttl(soa.expire() as u32); if ttl.is_none() {
*ttl = Some(soa.minimum());
} } else {
let msg = format!("Invalid RData here, expected SOA: {:?}", rdata);
return ParseResult::Err(ParseError::from(ParseErrorKind::Msg(msg)));
}
}
_ => {
record.set_ttl(ttl.ok_or_else(|| {
ParseError::from(ParseErrorKind::Message("record ttl not specified"))
})?);
}
}
record.set_data(Some(rdata));
let key = RrKey::new(LowerName::new(record.name()), record.rr_type());
match rtype {
RecordType::SOA => {
let set = record.into();
if records.insert(key, set).is_some() {
return Err(ParseErrorKind::Message("SOA is already specified").into());
}
}
_ => {
let set = records
.entry(key)
.or_insert_with(|| RecordSet::new(record.name(), record.rr_type(), 0));
set.insert(record, 0);
}
}
Ok(())
}
pub fn parse_time(ttl_str: &str) -> ParseResult<u32> {
let mut value: u32 = 0;
let mut collect: Option<u32> = None;
for c in ttl_str.chars() {
match c {
'0'..='9' => {
let digit = c.to_digit(10).ok_or(ParseErrorKind::CharToInt(c))?;
collect = Some(collect.unwrap_or(0) * 10 + digit);
}
'S' | 's' => {
value += collect
.take()
.ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))?
}
'M' | 'm' => {
value += 60
* collect
.take()
.ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))?
}
'H' | 'h' => {
value += 3_600
* collect
.take()
.ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))?
}
'D' | 'd' => {
value += 86_400
* collect
.take()
.ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))?
}
'W' | 'w' => {
value += 604_800
* collect
.take()
.ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))?
}
_ => return Err(ParseErrorKind::ParseTime(ttl_str.to_string()).into()),
}
}
Ok(value + collect.unwrap_or(0)) }
}
#[allow(unused)]
enum State {
StartLine, TtlClassType, Ttl, Record(Vec<String>),
Include, Origin,
}