/// DNS Zone file parsing
///
/// Format is defined in rfc1035 Section 5, extended rfc2308 Section 4.
///
/// <domain-name><rr> [<comment>]
/// <blank><rr> [<comment>]
/// ```
///
/// <rr> contents take one of the following forms:
/// ```text
/// [<TTL>] [<class>] <type> <RDATA>
/// [<class>] [<TTL>] <type> <RDATA>
/// ```
// We use explict WHITESPACE to ensure there is whitespace between matching tokens
// as opposed to optional WHITESPACE.
ws = _{
( " "
| "\t"
// Also match ( and ) because after preprocessing, we can effectively
// ignore them. We only leave them in to make error printing, etc a
// little nicer.
| "(" | ")"
)+
}
// Standard comment until end of line
COMMENT = _{";" ~ (!NEWLINE ~ ANY)*}
// TODO Merge domain and string together
domain = @{
"@"
| (ASCII_ALPHANUMERIC | "." | "-" )+
// TODO Handle escape characters
// TODO Handle quoted strings
}
string = @{ (ASCII_ALPHANUMERIC | "." | "-" | "\\")+ }
ip4 = @{ (ASCII_DIGIT | ".")+ }
ip6 = @{ (ASCII_HEX_DIGIT | ":")+ }
number = @{ ASCII_DIGIT+ }
duration = @{ ASCII_DIGIT+ }
class = @{ ^"IN" | ^"CS" | ^"CH" | ^"HS" }
resource = _{
resource_a
| resource_aaaa
| resource_cname
| resource_ns
| resource_mx
| resource_ptr
| resource_soa
}
resource_a = {^"A" ~ ws ~ ip4}
resource_aaaa = {^"AAAA" ~ ws ~ ip6}
resource_cname = {^"CNAME" ~ ws ~ domain}
resource_ns = {^"NS" ~ ws ~ domain}
resource_mx = {^"MX" ~ ws ~ number ~ ws ~ domain}
resource_ptr = {^"PTR" ~ ws ~ domain}
resource_soa = {^"SOA" ~ ws ~ domain ~ ws ~ string ~ ws ~ number ~ ws ~ duration ~ ws ~ duration ~ ws ~ duration ~ ws ~ duration}
// Entry for full file.
file = {
// TODO records can be split across many lines
//NEWLINE* ~ (entry? ~ NEWLINE?)* ~ EOI
entry ~ (NEWLINE ~ entry)* ~ EOI
}
// Entry for a single resource record.
single_record = {
SOI ~ ws? ~ record ~ ws? ~ EOI
}
entry = _{
ws? ~ (
origin
| ttl
| record
| ws? // blank record
) ~ ws?
}
origin = {
^"$ORIGIN" ~ ws ~ domain
}
ttl = {
^"$TTL" ~ ws ~ duration
}
record = {
// This is perhaps more verbose than needed, but this ensures
// we parse this ambiguous text in a well defined order.
// For example, "IN" could be a domain, or a class.
// All arguments are provided
(domain ~ ws ~ duration ~ ws ~ class ~ ws ~ resource)
| (domain ~ ws ~ class ~ ws ~ duration ~ ws ~ resource)
// No domain provided, but match these first, we assume IN means class, not domain
| (duration ~ ws ~ class ~ ws ~ resource)
| (duration ~ ws ~ resource)
| (class ~ ws ~ duration ~ ws ~ resource)
| (class ~ ws ~ resource)
// Match the ones listing domains
| (domain ~ ws ~ class ~ ws ~ resource)
| (domain ~ ws ~ duration ~ ws ~ resource)
| (domain ~ ws ~ resource)
// Finally no domain.
| resource
}