rustdns 0.4.0

A DNS parsing library
/// 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
}