dns_types/zones/
deserialise.rs

1use bytes::{BufMut, Bytes, BytesMut};
2use std::iter::Peekable;
3use std::net::{Ipv4Addr, Ipv6Addr};
4use std::str::FromStr;
5
6use crate::protocol::types::*;
7use crate::zones::types::*;
8
9impl Zone {
10    /// Parse a string of zone data
11    ///
12    /// This implementation does not support `$INCLUDE` entries or
13    /// non-`IN` record classes.  These will raise an error.
14    ///
15    /// # Errors
16    ///
17    /// If the string cannot be parsed.
18    pub fn deserialise(data: &str) -> Result<Self, Error> {
19        let mut rrs = Vec::new();
20        let mut wildcard_rrs = Vec::new();
21        let mut apex_and_soa = None;
22        let mut origin = None;
23        let mut previous_domain = None;
24        let mut previous_ttl = None;
25        let mut stream = data.chars().peekable();
26        while let Some(entry) = parse_entry(
27            origin.as_ref(),
28            previous_domain.as_ref(),
29            previous_ttl,
30            &mut stream,
31        )? {
32            match entry {
33                Entry::Origin { name } => origin = Some(name),
34                Entry::Include { path, origin } => {
35                    return Err(Error::IncludeNotSupported { path, origin })
36                }
37                Entry::RR { rr } => {
38                    previous_domain = Some(MaybeWildcard::Normal {
39                        name: rr.name.clone(),
40                    });
41                    previous_ttl = Some(rr.ttl);
42
43                    if let RecordTypeWithData::SOA {
44                        mname,
45                        rname,
46                        serial,
47                        refresh,
48                        retry,
49                        expire,
50                        minimum,
51                    } = rr.rtype_with_data
52                    {
53                        if apex_and_soa.is_some() {
54                            return Err(Error::MultipleSOA);
55                        }
56                        apex_and_soa = Some((
57                            rr.name,
58                            SOA {
59                                mname,
60                                rname,
61                                serial,
62                                refresh,
63                                retry,
64                                expire,
65                                minimum,
66                            },
67                        ));
68                    } else {
69                        rrs.push(rr);
70                    }
71                }
72                Entry::WildcardRR { rr } => {
73                    previous_domain = Some(MaybeWildcard::Wildcard {
74                        name: rr.name.clone(),
75                    });
76                    previous_ttl = Some(rr.ttl);
77
78                    if rr.rtype_with_data.rtype() == RecordType::SOA {
79                        return Err(Error::WildcardSOA);
80                    }
81                    wildcard_rrs.push(rr);
82                }
83            }
84        }
85
86        let mut zone = if let Some((apex, soa)) = apex_and_soa {
87            Zone::new(apex, Some(soa))
88        } else {
89            Zone::default()
90        };
91
92        for rr in rrs {
93            if !rr.name.is_subdomain_of(zone.get_apex()) {
94                return Err(Error::NotSubdomainOfApex {
95                    apex: zone.get_apex().clone(),
96                    name: rr.name,
97                });
98            }
99            zone.insert(&rr.name, rr.rtype_with_data, rr.ttl);
100        }
101
102        for rr in wildcard_rrs {
103            if !rr.name.is_subdomain_of(zone.get_apex()) {
104                return Err(Error::NotSubdomainOfApex {
105                    apex: zone.get_apex().clone(),
106                    name: rr.name,
107                });
108            }
109            zone.insert_wildcard(&rr.name, rr.rtype_with_data, rr.ttl);
110        }
111
112        Ok(zone)
113    }
114}
115
116/// Parse a single entry, skipping comments and whitespace.  Entries
117/// are of the form:
118///
119/// ```text
120/// $ORIGIN <domain-name>
121/// $INCLUDE <file-name> [<domain-name>]
122/// <rr>
123/// ```
124///
125/// Where `<rr>` is one of these forms:
126///
127/// ```text
128/// <domain-name> <ttl>   <class> <type> <rdata>
129/// <domain-name> <class> <ttl>   <type> <rdata>
130/// <domain-name> <ttl>           <type> <rdata>
131/// <domain-name>         <class> <type> <rdata>
132/// <domain-name>                 <type> <rdata>
133///               <ttl>   <class> <type> <rdata>
134///               <class> <ttl>   <type> <rdata>
135///               <ttl>           <type> <rdata>
136///                       <class> <type> <rdata>
137///                               <type> <rdata>
138/// ```
139///
140/// This is annoyingly flexible:
141///
142/// - If the `<domain-name>`, `<ttl>`, or `<class>` are missing, the
143///   previous is used (so it is an error to omit it in the first RR).
144///
145/// - But since this implementation only supports `IN`-class records,
146///   if the class is missing in the first RR, `IN` is used.
147///
148/// - The `<domain-name>` can be an absolute domain, given as a dotted
149///   string ending in a `.`; or a relative domain, given as a dotted
150///   string not ending in a `.`, in which case the origin is prepended;
151///   or `@`, in which case it is the origin.
152///
153/// The `<rdata>` format depends on the record type.
154///
155/// Some special characters are:
156///
157/// - `@` - the current origin
158/// - `;` - the rest of the line is a comment
159/// - `" ... "` - a string (used for rdata)
160/// - `( ... )` - a group of data which crosses a newline
161/// - `\X` - quotes a character, where `X` is a non-digit
162/// - `\DDD` - an octet, given as a decimal number
163///
164/// Returns `None` if the stream is empty.
165///
166/// # Errors
167///
168/// If the string cannot be parsed.
169fn parse_entry<I: Iterator<Item = char>>(
170    origin: Option<&DomainName>,
171    previous_domain: Option<&MaybeWildcard>,
172    previous_ttl: Option<u32>,
173    stream: &mut Peekable<I>,
174) -> Result<Option<Entry>, Error> {
175    loop {
176        let tokens = tokenise_entry(stream)?;
177        if tokens.is_empty() {
178            if stream.peek().is_none() {
179                return Ok(None);
180            }
181        } else if tokens[0].0 == "$ORIGIN" {
182            return Ok(Some(parse_origin(origin, tokens)?));
183        } else if tokens[0].0 == "$INCLUDE" {
184            return Ok(Some(parse_include(origin, tokens)?));
185        } else {
186            return Ok(Some(parse_rr(
187                origin,
188                previous_domain,
189                previous_ttl,
190                tokens,
191            )?));
192        }
193    }
194}
195
196/// ```text
197/// $ORIGIN <domain-name>
198/// ```
199///
200/// # Errors
201///
202/// If the string cannot be parsed.
203fn parse_origin(origin: Option<&DomainName>, tokens: Vec<(String, Bytes)>) -> Result<Entry, Error> {
204    if tokens.len() != 2 {
205        return Err(Error::WrongLen { tokens });
206    }
207
208    if tokens[0].0 != "$ORIGIN" {
209        return Err(Error::Unexpected {
210            expected: "$ORIGIN".to_string(),
211            tokens,
212        });
213    }
214
215    let name = parse_domain(origin, &tokens[1].0)?;
216    Ok(Entry::Origin { name })
217}
218
219/// ```text
220/// $INCLUDE <file-name> [<domain-name>]
221/// ```
222///
223/// # Errors
224///
225/// If the string cannot be parsed.
226fn parse_include(
227    origin: Option<&DomainName>,
228    tokens: Vec<(String, Bytes)>,
229) -> Result<Entry, Error> {
230    if tokens.len() != 2 && tokens.len() != 3 {
231        return Err(Error::WrongLen { tokens });
232    }
233
234    if tokens[0].0 != "$INCLUDE" {
235        return Err(Error::Unexpected {
236            expected: "$INCLUDE".to_string(),
237            tokens,
238        });
239    }
240
241    let path = tokens[1].0.clone();
242    let name = if tokens.len() == 3 {
243        Some(parse_domain(origin, &tokens[2].0)?)
244    } else {
245        None
246    };
247    Ok(Entry::Include { path, origin: name })
248}
249
250/// ```text
251/// <domain-name> <ttl>   <class> <type> <rdata>
252/// <domain-name> <class> <ttl>   <type> <rdata>
253/// <domain-name> <ttl>           <type> <rdata>
254/// <domain-name>         <class> <type> <rdata>
255/// <domain-name>                 <type> <rdata>
256///               <ttl>   <class> <type> <rdata>
257///               <class> <ttl>   <type> <rdata>
258///               <ttl>           <type> <rdata>
259///                       <class> <type> <rdata>
260///                               <type> <rdata>
261/// ```
262///
263/// # Errors
264///
265/// If the string cannot be parsed.
266fn parse_rr(
267    origin: Option<&DomainName>,
268    previous_domain: Option<&MaybeWildcard>,
269    previous_ttl: Option<u32>,
270    tokens: Vec<(String, Bytes)>,
271) -> Result<Entry, Error> {
272    if tokens.is_empty() {
273        return Err(Error::WrongLen { tokens });
274    }
275
276    if tokens.len() >= 4 {
277        if let Some(rtype_with_data) = try_parse_rtype_with_data(origin, &tokens[3..]) {
278            // <domain-name> <ttl>   <class> <type> <rdata>
279            // <domain-name> <class> <ttl>   <type> <rdata>
280            let wname = parse_domain_or_wildcard(origin, &tokens[0].0)?;
281            let ttl = if tokens[2].0 == "IN" {
282                parse_u32(&tokens[1].0)?
283            } else if tokens[1].0 == "IN" {
284                parse_u32(&tokens[2].0)?
285            } else {
286                return Err(Error::Unexpected {
287                    expected: "IN".to_string(),
288                    tokens,
289                });
290            };
291
292            return Ok(to_rr(wname, rtype_with_data, ttl));
293        }
294    }
295
296    if tokens.len() >= 3 {
297        if let Some(rtype_with_data) = try_parse_rtype_with_data(origin, &tokens[2..]) {
298            // <domain-name> <ttl>           <type> <rdata>
299            // <domain-name>         <class> <type> <rdata>
300            //               <ttl>   <class> <type> <rdata>
301            //               <class> <ttl>   <type> <rdata>
302            return if tokens[1].0 == "IN" {
303                if tokens[0].0.chars().all(|c| c.is_ascii_digit()) {
304                    let ttl = parse_u32(&tokens[0].0)?;
305                    if let Some(wname) = previous_domain {
306                        Ok(to_rr(wname.clone(), rtype_with_data, ttl))
307                    } else {
308                        Err(Error::MissingDomainName { tokens })
309                    }
310                } else {
311                    let wname = parse_domain_or_wildcard(origin, &tokens[0].0)?;
312                    if let Some(ttl) = previous_ttl {
313                        Ok(to_rr(wname, rtype_with_data, ttl))
314                    } else if rtype_with_data.rtype() == RecordType::SOA {
315                        Ok(to_rr(wname, rtype_with_data, 0))
316                    } else {
317                        Err(Error::MissingTTL { tokens })
318                    }
319                }
320            } else if tokens[0].0 == "IN" {
321                let ttl = parse_u32(&tokens[1].0)?;
322                if let Some(wname) = previous_domain {
323                    Ok(to_rr(wname.clone(), rtype_with_data, ttl))
324                } else {
325                    Err(Error::MissingDomainName { tokens })
326                }
327            } else {
328                let wname = parse_domain_or_wildcard(origin, &tokens[0].0)?;
329                let ttl = parse_u32(&tokens[1].0)?;
330                Ok(to_rr(wname, rtype_with_data, ttl))
331            };
332        }
333    }
334
335    if tokens.len() >= 2 {
336        if let Some(rtype_with_data) = try_parse_rtype_with_data(origin, &tokens[1..]) {
337            // <domain-name>                 <type> <rdata>
338            //               <ttl>           <type> <rdata>
339            //                       <class> <type> <rdata>
340            return if tokens[0].0 == "IN" {
341                if let Some(wname) = previous_domain {
342                    if let Some(ttl) = previous_ttl {
343                        Ok(to_rr(wname.clone(), rtype_with_data, ttl))
344                    } else if rtype_with_data.rtype() == RecordType::SOA {
345                        Ok(to_rr(wname.clone(), rtype_with_data, 0))
346                    } else {
347                        Err(Error::MissingTTL { tokens })
348                    }
349                } else {
350                    Err(Error::MissingDomainName { tokens })
351                }
352            } else if tokens[0].0.chars().all(|c| c.is_ascii_digit()) {
353                let ttl = parse_u32(&tokens[0].0)?;
354                if let Some(wname) = previous_domain {
355                    Ok(to_rr(wname.clone(), rtype_with_data, ttl))
356                } else {
357                    Err(Error::MissingDomainName { tokens })
358                }
359            } else {
360                let wname = parse_domain_or_wildcard(origin, &tokens[0].0)?;
361                if let Some(ttl) = previous_ttl {
362                    Ok(to_rr(wname, rtype_with_data, ttl))
363                } else if rtype_with_data.rtype() == RecordType::SOA {
364                    Ok(to_rr(wname, rtype_with_data, 0))
365                } else {
366                    Err(Error::MissingTTL { tokens })
367                }
368            };
369        }
370    }
371
372    if !tokens.is_empty() {
373        if let Some(rtype_with_data) = try_parse_rtype_with_data(origin, &tokens[0..]) {
374            //                               <type> <rdata>
375            return if let Some(wname) = previous_domain {
376                if let Some(ttl) = previous_ttl {
377                    Ok(to_rr(wname.clone(), rtype_with_data, ttl))
378                } else if rtype_with_data.rtype() == RecordType::SOA {
379                    Ok(to_rr(wname.clone(), rtype_with_data, 0))
380                } else {
381                    Err(Error::MissingTTL { tokens })
382                }
383            } else {
384                Err(Error::MissingDomainName { tokens })
385            };
386        }
387    }
388
389    Err(Error::MissingType { tokens })
390}
391
392/// Try to parse a record type with data.  Returns `None` if there is
393/// no parse, since this does not necessarily indicate a fatal error.
394fn try_parse_rtype_with_data(
395    origin: Option<&DomainName>,
396    tokens: &[(String, Bytes)],
397) -> Option<RecordTypeWithData> {
398    if tokens.is_empty() {
399        return None;
400    }
401
402    match RecordType::from_str(tokens[0].0.as_str()) {
403        Ok(RecordType::A) if tokens.len() == 2 => match Ipv4Addr::from_str(&tokens[1].0) {
404            Ok(address) => Some(RecordTypeWithData::A { address }),
405            _ => None,
406        },
407        Ok(RecordType::NS) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
408            Ok(nsdname) => Some(RecordTypeWithData::NS { nsdname }),
409            _ => None,
410        },
411        Ok(RecordType::MD) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
412            Ok(madname) => Some(RecordTypeWithData::MD { madname }),
413            _ => None,
414        },
415        Ok(RecordType::MF) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
416            Ok(madname) => Some(RecordTypeWithData::MF { madname }),
417            _ => None,
418        },
419        Ok(RecordType::CNAME) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
420            Ok(cname) => Some(RecordTypeWithData::CNAME { cname }),
421            _ => None,
422        },
423        Ok(RecordType::SOA) if tokens.len() == 8 => match (
424            parse_domain(origin, &tokens[1].0),
425            parse_domain(origin, &tokens[2].0),
426            u32::from_str(&tokens[3].0),
427            u32::from_str(&tokens[4].0),
428            u32::from_str(&tokens[5].0),
429            u32::from_str(&tokens[6].0),
430            u32::from_str(&tokens[7].0),
431        ) {
432            (Ok(mname), Ok(rname), Ok(serial), Ok(refresh), Ok(retry), Ok(expire), Ok(minimum)) => {
433                Some(RecordTypeWithData::SOA {
434                    mname,
435                    rname,
436                    serial,
437                    refresh,
438                    retry,
439                    expire,
440                    minimum,
441                })
442            }
443            _ => None,
444        },
445        Ok(RecordType::MB) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
446            Ok(madname) => Some(RecordTypeWithData::MB { madname }),
447            _ => None,
448        },
449        Ok(RecordType::MG) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
450            Ok(mdmname) => Some(RecordTypeWithData::MG { mdmname }),
451            _ => None,
452        },
453        Ok(RecordType::MR) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
454            Ok(newname) => Some(RecordTypeWithData::MR { newname }),
455            _ => None,
456        },
457        Ok(RecordType::NULL) if tokens.len() == 2 => Some(RecordTypeWithData::NULL {
458            octets: tokens[1].1.clone(),
459        }),
460        Ok(RecordType::WKS) if tokens.len() == 2 => Some(RecordTypeWithData::WKS {
461            octets: tokens[1].1.clone(),
462        }),
463        Ok(RecordType::PTR) if tokens.len() == 2 => match parse_domain(origin, &tokens[1].0) {
464            Ok(ptrdname) => Some(RecordTypeWithData::PTR { ptrdname }),
465            _ => None,
466        },
467        Ok(RecordType::HINFO) if tokens.len() == 2 => Some(RecordTypeWithData::HINFO {
468            octets: tokens[1].1.clone(),
469        }),
470        Ok(RecordType::MINFO) if tokens.len() == 3 => match (
471            parse_domain(origin, &tokens[1].0),
472            parse_domain(origin, &tokens[2].0),
473        ) {
474            (Ok(rmailbx), Ok(emailbx)) => Some(RecordTypeWithData::MINFO { rmailbx, emailbx }),
475            _ => None,
476        },
477        Ok(RecordType::MX) if tokens.len() == 3 => {
478            match (
479                u16::from_str(&tokens[1].0),
480                parse_domain(origin, &tokens[2].0),
481            ) {
482                (Ok(preference), Ok(exchange)) => Some(RecordTypeWithData::MX {
483                    preference,
484                    exchange,
485                }),
486                _ => None,
487            }
488        }
489        Ok(RecordType::TXT) if tokens.len() == 2 => Some(RecordTypeWithData::TXT {
490            octets: tokens[1].1.clone(),
491        }),
492        Ok(RecordType::AAAA) if tokens.len() == 2 => match Ipv6Addr::from_str(&tokens[1].0) {
493            Ok(address) => Some(RecordTypeWithData::AAAA { address }),
494            _ => None,
495        },
496        Ok(RecordType::SRV) if tokens.len() == 5 => match (
497            u16::from_str(&tokens[1].0),
498            u16::from_str(&tokens[2].0),
499            u16::from_str(&tokens[3].0),
500            parse_domain(origin, &tokens[4].0),
501        ) {
502            (Ok(priority), Ok(weight), Ok(port), Ok(target)) => Some(RecordTypeWithData::SRV {
503                priority,
504                weight,
505                port,
506                target,
507            }),
508            _ => None,
509        },
510        _ => None,
511    }
512}
513
514/// Parse a regular or wildcard domain name.
515///
516/// # Errors
517///
518/// If the string cannot be parsed.
519fn parse_domain_or_wildcard(
520    origin: Option<&DomainName>,
521    dotted_string: &str,
522) -> Result<MaybeWildcard, Error> {
523    let dotted_string_vec = dotted_string.chars().collect::<Vec<char>>();
524
525    if dotted_string_vec.is_empty() {
526        return Err(Error::ExpectedDomainName {
527            dotted_string: dotted_string.to_string(),
528        });
529    }
530
531    if dotted_string == "*" {
532        if let Some(name) = origin {
533            Ok(MaybeWildcard::Wildcard { name: name.clone() })
534        } else {
535            Err(Error::ExpectedOrigin)
536        }
537    } else if dotted_string_vec.len() >= 2
538        && dotted_string_vec[0] == '*'
539        && dotted_string_vec[1] == '.'
540    {
541        let name = if dotted_string_vec.len() == 2 {
542            DomainName::root_domain()
543        } else {
544            parse_domain(origin, &dotted_string_vec[2..].iter().collect::<String>())?
545        };
546        Ok(MaybeWildcard::Wildcard { name })
547    } else {
548        let name = parse_domain(origin, dotted_string)?;
549        Ok(MaybeWildcard::Normal { name })
550    }
551}
552
553/// Parse a domain name, appending the origin if it's not absolute.
554///
555/// # Errors
556///
557/// If the string cannot be parsed.
558fn parse_domain(origin: Option<&DomainName>, dotted_string: &str) -> Result<DomainName, Error> {
559    let dotted_string_vec = dotted_string.chars().collect::<Vec<char>>();
560
561    if dotted_string_vec.is_empty() {
562        return Err(Error::ExpectedDomainName {
563            dotted_string: dotted_string.to_string(),
564        });
565    }
566
567    if !dotted_string_vec.iter().all(char::is_ascii) {
568        return Err(Error::ExpectedDomainName {
569            dotted_string: dotted_string.to_string(),
570        });
571    }
572
573    if dotted_string == "@" {
574        if let Some(name) = origin {
575            Ok(name.clone())
576        } else {
577            Err(Error::ExpectedOrigin)
578        }
579    } else if dotted_string_vec[dotted_string_vec.len() - 1] == '.' {
580        if let Some(domain) = DomainName::from_dotted_string(dotted_string) {
581            Ok(domain)
582        } else {
583            Err(Error::ExpectedDomainName {
584                dotted_string: dotted_string.to_string(),
585            })
586        }
587    } else if let Some(name) = origin {
588        if let Some(domain) = DomainName::from_relative_dotted_string(name, dotted_string) {
589            Ok(domain)
590        } else {
591            Err(Error::ExpectedDomainName {
592                dotted_string: dotted_string.to_string(),
593            })
594        }
595    } else {
596        Err(Error::ExpectedOrigin)
597    }
598}
599
600/// Parse a decimal number into a u32.
601///
602/// # Errors
603///
604/// If the string cannot be parsed.
605fn parse_u32(digits: &str) -> Result<u32, Error> {
606    if let Ok(val) = u32::from_str(digits) {
607        Ok(val)
608    } else {
609        Err(Error::ExpectedU32 {
610            digits: digits.to_string(),
611        })
612    }
613}
614
615/// Helper for `parse_rr`
616fn to_rr(wname: MaybeWildcard, rtype_with_data: RecordTypeWithData, ttl: u32) -> Entry {
617    let ttl = if let RecordTypeWithData::SOA { minimum, .. } = rtype_with_data {
618        minimum
619    } else {
620        ttl
621    };
622
623    match wname {
624        MaybeWildcard::Normal { name } => Entry::RR {
625            rr: ResourceRecord {
626                name,
627                rtype_with_data,
628                rclass: RecordClass::IN,
629                ttl,
630            },
631        },
632        MaybeWildcard::Wildcard { name } => Entry::WildcardRR {
633            rr: ResourceRecord {
634                name,
635                rtype_with_data,
636                rclass: RecordClass::IN,
637                ttl,
638            },
639        },
640    }
641}
642
643/// Split an entry into tokens: split on whitespace, taking quoting
644/// into account, and if there are parentheses or quotes continue to
645/// the matched delimiter.
646///
647/// # Errors
648///
649/// If the string cannot be parsed.
650fn tokenise_entry<I: Iterator<Item = char>>(
651    stream: &mut Peekable<I>,
652) -> Result<Vec<(String, Bytes)>, Error> {
653    let mut tokens = Vec::new();
654    let mut token_string = String::new();
655    let mut token_octets = BytesMut::new();
656    let mut state = State::Initial;
657    let mut line_continuation = false;
658
659    while let Some(c) = stream.next() {
660        state = match (state, c) {
661            (State::Initial, '\n') => {
662                if line_continuation {
663                    State::Initial
664                } else {
665                    break;
666                }
667            }
668            (State::Initial, ';') => State::SkipToEndOfComment,
669            (State::Initial, '(') => {
670                if line_continuation {
671                    return Err(Error::TokeniserUnexpected { unexpected: '(' });
672                }
673                line_continuation = true;
674                State::Initial
675            }
676            (State::Initial, ')') => {
677                if line_continuation {
678                    line_continuation = false;
679                    State::Initial
680                } else {
681                    return Err(Error::TokeniserUnexpected { unexpected: ')' });
682                }
683            }
684            (State::Initial, '"') => State::QuotedString,
685            (State::Initial, '\\') => {
686                let octet = tokenise_escape(stream)?;
687                token_string.push(octet as char);
688                token_octets.put_u8(octet);
689                State::UnquotedString
690            }
691            (State::Initial, c) => {
692                if c.is_whitespace() {
693                    State::Initial
694                } else if c.is_ascii() {
695                    token_string.push(c);
696                    token_octets.put_u8(c as u8);
697                    State::UnquotedString
698                } else {
699                    return Err(Error::TokeniserUnexpected { unexpected: c });
700                }
701            }
702
703            (State::UnquotedString, '\n') => {
704                if !token_string.is_empty() {
705                    tokens.push((token_string, token_octets.freeze()));
706                    token_string = String::new();
707                    token_octets = BytesMut::new();
708                }
709                if line_continuation {
710                    State::Initial
711                } else {
712                    break;
713                }
714            }
715            (State::UnquotedString, ';') => {
716                if !token_string.is_empty() {
717                    tokens.push((token_string, token_octets.freeze()));
718                    token_string = String::new();
719                    token_octets = BytesMut::new();
720                }
721                State::SkipToEndOfComment
722            }
723            (State::UnquotedString, '\\') => {
724                let octet = tokenise_escape(stream)?;
725                token_string.push(octet as char);
726                token_octets.put_u8(octet);
727                State::UnquotedString
728            }
729            (State::UnquotedString, c) => {
730                if c.is_whitespace() {
731                    if !token_string.is_empty() {
732                        tokens.push((token_string, token_octets.freeze()));
733                        token_string = String::new();
734                        token_octets = BytesMut::new();
735                    }
736                    State::Initial
737                } else if c.is_ascii() {
738                    token_string.push(c);
739                    token_octets.put_u8(c as u8);
740                    State::UnquotedString
741                } else {
742                    return Err(Error::TokeniserUnexpected { unexpected: c });
743                }
744            }
745
746            (State::SkipToEndOfComment, '\n') => {
747                if line_continuation {
748                    State::Initial
749                } else {
750                    break;
751                }
752            }
753            (State::SkipToEndOfComment, _) => State::SkipToEndOfComment,
754
755            (State::QuotedString, '"') => {
756                tokens.push((token_string, token_octets.freeze()));
757                token_string = String::new();
758                token_octets = BytesMut::new();
759                State::Initial
760            }
761            (State::QuotedString, '\\') => {
762                let octet = tokenise_escape(stream)?;
763                token_string.push(octet as char);
764                token_octets.put_u8(octet);
765                State::QuotedString
766            }
767            (State::QuotedString, c) => {
768                if c.is_ascii() {
769                    token_string.push(c);
770                    token_octets.put_u8(c as u8);
771                } else {
772                    return Err(Error::TokeniserUnexpected { unexpected: c });
773                }
774                State::QuotedString
775            }
776        }
777    }
778
779    if !token_string.is_empty() {
780        tokens.push((token_string, token_octets.freeze()));
781    }
782
783    Ok(tokens)
784}
785
786/// Tokenise an escape sequence
787///
788/// # Errors
789///
790/// If the string cannot be parsed.
791fn tokenise_escape<I: Iterator<Item = char>>(stream: &mut I) -> Result<u8, Error> {
792    if let Some(c1) = stream.next() {
793        match c1.to_digit(10) {
794            Some(d1) => {
795                if let Some(c2) = stream.next() {
796                    match c2.to_digit(10) {
797                        Some(d2) => {
798                            if let Some(c3) = stream.next() {
799                                match c3.to_digit(10) {
800                                    Some(d3) => match u8::try_from(d1 * 100 + d2 * 10 + d3) {
801                                        Ok(num) => Ok(num),
802                                        _ => Err(Error::TokeniserUnexpectedEscape {
803                                            unexpected: vec![c1, c2, c3],
804                                        }),
805                                    },
806                                    _ => Err(Error::TokeniserUnexpectedEscape {
807                                        unexpected: vec![c1, c2, c3],
808                                    }),
809                                }
810                            } else {
811                                Err(Error::TokeniserUnexpectedEscape {
812                                    unexpected: vec![c1, c2],
813                                })
814                            }
815                        }
816                        _ => Err(Error::TokeniserUnexpectedEscape {
817                            unexpected: vec![c1, c2],
818                        }),
819                    }
820                } else {
821                    Err(Error::TokeniserUnexpectedEscape {
822                        unexpected: vec![c1],
823                    })
824                }
825            }
826            _ => {
827                if c1.is_ascii() {
828                    Ok(c1 as u8)
829                } else {
830                    Err(Error::TokeniserUnexpected { unexpected: c1 })
831                }
832            }
833        }
834    } else {
835        Err(Error::TokeniserUnexpectedEscape {
836            unexpected: Vec::new(),
837        })
838    }
839}
840
841/// States the tokeniser can be in
842enum State {
843    Initial,
844    SkipToEndOfComment,
845    UnquotedString,
846    QuotedString,
847}
848
849/// A regular or wildcard domain
850#[derive(Debug, Clone, PartialEq, Eq)]
851enum MaybeWildcard {
852    Normal { name: DomainName },
853    Wildcard { name: DomainName },
854}
855
856/// An entry.
857#[derive(Debug, Clone, PartialEq, Eq)]
858enum Entry {
859    Origin {
860        name: DomainName,
861    },
862    Include {
863        path: String,
864        origin: Option<DomainName>,
865    },
866    RR {
867        rr: ResourceRecord,
868    },
869    WildcardRR {
870        rr: ResourceRecord,
871    },
872}
873
874/// An error that can occur reading a zone file.
875#[derive(Debug, Clone, PartialEq, Eq)]
876pub enum Error {
877    TokeniserUnexpected {
878        unexpected: char,
879    },
880    TokeniserUnexpectedEscape {
881        unexpected: Vec<char>,
882    },
883    IncludeNotSupported {
884        path: String,
885        origin: Option<DomainName>,
886    },
887    MultipleSOA,
888    WildcardSOA,
889    NotSubdomainOfApex {
890        apex: DomainName,
891        name: DomainName,
892    },
893    Unexpected {
894        expected: String,
895        tokens: Vec<(String, Bytes)>,
896    },
897    ExpectedU32 {
898        digits: String,
899    },
900    ExpectedOrigin,
901    ExpectedDomainName {
902        dotted_string: String,
903    },
904    WrongLen {
905        tokens: Vec<(String, Bytes)>,
906    },
907    MissingType {
908        tokens: Vec<(String, Bytes)>,
909    },
910    MissingTTL {
911        tokens: Vec<(String, Bytes)>,
912    },
913    MissingDomainName {
914        tokens: Vec<(String, Bytes)>,
915    },
916}
917
918impl std::fmt::Display for Error {
919    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
920        match self {
921            Error::TokeniserUnexpected { unexpected } => write!(f, "unexpected '{unexpected:?}'"),
922            Error::TokeniserUnexpectedEscape { unexpected } => {
923                write!(f, "unexpected escape '{unexpected:?}'")
924            }
925            Error::IncludeNotSupported { .. } => write!(f, "'$INCLUDE' directive not supported"),
926            Error::MultipleSOA => write!(f, "multiple SOA records, expected one or zero"),
927            Error::WildcardSOA => write!(f, "wildcard SOA record not allowed"),
928            Error::NotSubdomainOfApex { apex, name } => {
929                write!(
930                    f,
931                    "domain name '{name}' not a subdomain of the apex '{apex}'"
932                )
933            }
934            Error::Unexpected { expected, .. } => write!(f, "expected '{expected:?}'"),
935            Error::ExpectedU32 { digits } => write!(f, "expected u32, got '{digits:?}'"),
936            Error::ExpectedOrigin => write!(f, "relative domain name used without origin"),
937            Error::ExpectedDomainName { dotted_string } => {
938                write!(f, "could not parse domain name '{dotted_string}'")
939            }
940            Error::WrongLen { .. } => write!(f, "zone file incomplete"),
941            Error::MissingType { .. } => write!(f, "missing type in record definition"),
942            Error::MissingTTL { .. } => write!(f, "missing TTL in record definition"),
943            Error::MissingDomainName { .. } => {
944                write!(f, "missing domain name in record definition")
945            }
946        }
947    }
948}
949
950impl std::error::Error for Error {
951    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
952        None
953    }
954}
955
956#[cfg(test)]
957mod tests {
958    use crate::protocol::types::test_util::*;
959
960    use super::*;
961
962    #[test]
963    fn parse_zone() {
964        let zone_data = "$ORIGIN lan.\n\
965                         \n\
966                         @    IN    SOA    nyarlathotep.lan. barrucadu.nyarlathotep.lan. 1 30 30 30 30\n\
967                         \n\
968                         nyarlathotep      300    IN    A        10.0.0.3\n\
969                         *.nyarlathotep    300    IN    CNAME    nyarlathotep.lan.";
970        let zone = Zone::deserialise(zone_data).unwrap();
971
972        let soa_record = ResourceRecord {
973            name: domain("lan."),
974            rtype_with_data: RecordTypeWithData::SOA {
975                mname: domain("nyarlathotep.lan."),
976                rname: domain("barrucadu.nyarlathotep.lan."),
977                serial: 1,
978                refresh: 30,
979                retry: 30,
980                expire: 30,
981                minimum: 30,
982            },
983            rclass: RecordClass::IN,
984            ttl: 30,
985        };
986
987        let mut expected_all_records = vec![
988            soa_record,
989            a_record("nyarlathotep.lan.", Ipv4Addr::new(10, 0, 0, 3)),
990        ];
991        expected_all_records.sort();
992
993        let expected_all_wildcard_records =
994            vec![cname_record("nyarlathotep.lan.", "nyarlathotep.lan.")];
995
996        let mut actual_all_records = Vec::with_capacity(expected_all_records.capacity());
997        for (name, zrs) in &zone.all_records() {
998            for zr in zrs {
999                actual_all_records.push(zr.to_rr(name));
1000            }
1001        }
1002        actual_all_records.sort();
1003
1004        let mut actual_all_wildcard_records =
1005            Vec::with_capacity(expected_all_wildcard_records.capacity());
1006        for (name, zrs) in &zone.all_wildcard_records() {
1007            for zr in zrs {
1008                actual_all_wildcard_records.push(zr.to_rr(name));
1009            }
1010        }
1011        actual_all_wildcard_records.sort();
1012
1013        assert_eq!(expected_all_records, actual_all_records);
1014        assert_eq!(expected_all_wildcard_records, actual_all_wildcard_records);
1015    }
1016
1017    #[test]
1018    fn parse_rr_origin() {
1019        let tokens = tokenise_str("* IN 300 A 10.0.0.2");
1020
1021        assert!(matches!(
1022            parse_rr(None, None, None, tokens.clone()),
1023            Err(Error::ExpectedOrigin)
1024        ));
1025
1026        if let Ok(parsed) = parse_rr(Some(&domain("example.com.")), None, None, tokens) {
1027            assert_eq!(
1028                Entry::WildcardRR {
1029                    rr: ResourceRecord {
1030                        name: domain("example.com."),
1031                        rtype_with_data: RecordTypeWithData::A {
1032                            address: Ipv4Addr::new(10, 0, 0, 2)
1033                        },
1034                        rclass: RecordClass::IN,
1035                        ttl: 300
1036                    }
1037                },
1038                parsed
1039            );
1040        } else {
1041            panic!("expected successful parse");
1042        }
1043    }
1044
1045    #[test]
1046    fn parse_rr_previous_domain() {
1047        let tokens = tokenise_str("IN 300 A 10.0.0.2");
1048
1049        assert!(matches!(
1050            parse_rr(None, None, None, tokens.clone()),
1051            Err(Error::MissingDomainName { .. })
1052        ));
1053
1054        if let Ok(parsed) = parse_rr(
1055            None,
1056            Some(&MaybeWildcard::Normal {
1057                name: domain("example.com."),
1058            }),
1059            None,
1060            tokens,
1061        ) {
1062            assert_eq!(
1063                Entry::RR {
1064                    rr: ResourceRecord {
1065                        name: domain("example.com."),
1066                        rtype_with_data: RecordTypeWithData::A {
1067                            address: Ipv4Addr::new(10, 0, 0, 2)
1068                        },
1069                        rclass: RecordClass::IN,
1070                        ttl: 300
1071                    }
1072                },
1073                parsed
1074            );
1075        } else {
1076            panic!("expected successful parse");
1077        }
1078    }
1079
1080    #[test]
1081    fn parse_rr_previous_ttl() {
1082        let tokens = tokenise_str("nyarlathotep.lan. IN A 10.0.0.2");
1083
1084        assert!(matches!(
1085            parse_rr(None, None, None, tokens.clone()),
1086            Err(Error::MissingTTL { .. })
1087        ));
1088
1089        if let Ok(parsed) = parse_rr(None, None, Some(42), tokens) {
1090            assert_eq!(
1091                Entry::RR {
1092                    rr: ResourceRecord {
1093                        name: domain("nyarlathotep.lan."),
1094                        rtype_with_data: RecordTypeWithData::A {
1095                            address: Ipv4Addr::new(10, 0, 0, 2)
1096                        },
1097                        rclass: RecordClass::IN,
1098                        ttl: 42
1099                    }
1100                },
1101                parsed
1102            );
1103        } else {
1104            panic!("expected successful parse");
1105        }
1106    }
1107
1108    #[test]
1109    fn parse_rr_a() {
1110        let tokens = tokenise_str("nyarlathotep.lan. IN 300 A 10.0.0.2");
1111        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1112            assert_eq!(
1113                Entry::RR {
1114                    rr: ResourceRecord {
1115                        name: domain("nyarlathotep.lan."),
1116                        rtype_with_data: RecordTypeWithData::A {
1117                            address: Ipv4Addr::new(10, 0, 0, 2)
1118                        },
1119                        rclass: RecordClass::IN,
1120                        ttl: 300
1121                    }
1122                },
1123                parsed
1124            );
1125        } else {
1126            panic!("expected successful parse");
1127        }
1128    }
1129
1130    #[test]
1131    fn parse_rr_ns() {
1132        let tokens = tokenise_str("nyarlathotep.lan. IN 300 NS ns1.lan.");
1133        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1134            assert_eq!(
1135                Entry::RR {
1136                    rr: ResourceRecord {
1137                        name: domain("nyarlathotep.lan."),
1138                        rtype_with_data: RecordTypeWithData::NS {
1139                            nsdname: domain("ns1.lan."),
1140                        },
1141                        rclass: RecordClass::IN,
1142                        ttl: 300
1143                    }
1144                },
1145                parsed
1146            );
1147        } else {
1148            panic!("expected successful parse");
1149        }
1150    }
1151
1152    #[test]
1153    fn parse_rr_md() {
1154        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MD madname.lan.");
1155        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1156            assert_eq!(
1157                Entry::RR {
1158                    rr: ResourceRecord {
1159                        name: domain("nyarlathotep.lan."),
1160                        rtype_with_data: RecordTypeWithData::MD {
1161                            madname: domain("madname.lan."),
1162                        },
1163                        rclass: RecordClass::IN,
1164                        ttl: 300
1165                    }
1166                },
1167                parsed
1168            );
1169        } else {
1170            panic!("expected successful parse");
1171        }
1172    }
1173
1174    #[test]
1175    fn parse_rr_mf() {
1176        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MF madname.lan.");
1177        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1178            assert_eq!(
1179                Entry::RR {
1180                    rr: ResourceRecord {
1181                        name: domain("nyarlathotep.lan."),
1182                        rtype_with_data: RecordTypeWithData::MF {
1183                            madname: domain("madname.lan."),
1184                        },
1185                        rclass: RecordClass::IN,
1186                        ttl: 300
1187                    }
1188                },
1189                parsed
1190            );
1191        } else {
1192            panic!("expected successful parse");
1193        }
1194    }
1195
1196    #[test]
1197    fn parse_rr_cname() {
1198        let tokens = tokenise_str("nyarlathotep.lan. IN 300 CNAME cname.lan.");
1199        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1200            assert_eq!(
1201                Entry::RR {
1202                    rr: ResourceRecord {
1203                        name: domain("nyarlathotep.lan."),
1204                        rtype_with_data: RecordTypeWithData::CNAME {
1205                            cname: domain("cname.lan."),
1206                        },
1207                        rclass: RecordClass::IN,
1208                        ttl: 300
1209                    }
1210                },
1211                parsed
1212            );
1213        } else {
1214            panic!("expected successful parse");
1215        }
1216    }
1217
1218    #[test]
1219    fn parse_rr_soa() {
1220        let tokens =
1221            tokenise_str("nyarlathotep.lan. IN 300 SOA mname.lan. rname.lan. 100 200 300 400 500");
1222        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1223            assert_eq!(
1224                Entry::RR {
1225                    rr: ResourceRecord {
1226                        name: domain("nyarlathotep.lan."),
1227                        rtype_with_data: RecordTypeWithData::SOA {
1228                            mname: domain("mname.lan."),
1229                            rname: domain("rname.lan."),
1230                            serial: 100,
1231                            refresh: 200,
1232                            retry: 300,
1233                            expire: 400,
1234                            minimum: 500,
1235                        },
1236                        rclass: RecordClass::IN,
1237                        ttl: 500
1238                    }
1239                },
1240                parsed
1241            );
1242        } else {
1243            panic!("expected successful parse");
1244        }
1245    }
1246
1247    #[test]
1248    fn parse_rr_mb() {
1249        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MB madname.lan.");
1250        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1251            assert_eq!(
1252                Entry::RR {
1253                    rr: ResourceRecord {
1254                        name: domain("nyarlathotep.lan."),
1255                        rtype_with_data: RecordTypeWithData::MB {
1256                            madname: domain("madname.lan."),
1257                        },
1258                        rclass: RecordClass::IN,
1259                        ttl: 300
1260                    }
1261                },
1262                parsed
1263            );
1264        } else {
1265            panic!("expected successful parse");
1266        }
1267    }
1268
1269    #[test]
1270    fn parse_rr_mg() {
1271        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MG mdmname.lan.");
1272        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1273            assert_eq!(
1274                Entry::RR {
1275                    rr: ResourceRecord {
1276                        name: domain("nyarlathotep.lan."),
1277                        rtype_with_data: RecordTypeWithData::MG {
1278                            mdmname: domain("mdmname.lan."),
1279                        },
1280                        rclass: RecordClass::IN,
1281                        ttl: 300
1282                    }
1283                },
1284                parsed
1285            );
1286        } else {
1287            panic!("expected successful parse");
1288        }
1289    }
1290
1291    #[test]
1292    fn parse_rr_mr() {
1293        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MR newname.lan.");
1294        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1295            assert_eq!(
1296                Entry::RR {
1297                    rr: ResourceRecord {
1298                        name: domain("nyarlathotep.lan."),
1299                        rtype_with_data: RecordTypeWithData::MR {
1300                            newname: domain("newname.lan."),
1301                        },
1302                        rclass: RecordClass::IN,
1303                        ttl: 300
1304                    }
1305                },
1306                parsed
1307            );
1308        } else {
1309            panic!("expected successful parse");
1310        }
1311    }
1312
1313    #[test]
1314    fn parse_rr_null() {
1315        let tokens = tokenise_str("nyarlathotep.lan. IN 300 NULL 123");
1316        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1317            assert_eq!(
1318                Entry::RR {
1319                    rr: ResourceRecord {
1320                        name: domain("nyarlathotep.lan."),
1321                        rtype_with_data: RecordTypeWithData::NULL {
1322                            octets: Bytes::copy_from_slice(b"123"),
1323                        },
1324                        rclass: RecordClass::IN,
1325                        ttl: 300
1326                    }
1327                },
1328                parsed
1329            );
1330        } else {
1331            panic!("expected successful parse");
1332        }
1333    }
1334
1335    #[test]
1336    fn parse_rr_wks() {
1337        let tokens = tokenise_str("nyarlathotep.lan. IN 300 WKS 123");
1338        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1339            assert_eq!(
1340                Entry::RR {
1341                    rr: ResourceRecord {
1342                        name: domain("nyarlathotep.lan."),
1343                        rtype_with_data: RecordTypeWithData::WKS {
1344                            octets: Bytes::copy_from_slice(b"123"),
1345                        },
1346                        rclass: RecordClass::IN,
1347                        ttl: 300
1348                    }
1349                },
1350                parsed
1351            );
1352        } else {
1353            panic!("expected successful parse");
1354        }
1355    }
1356
1357    #[test]
1358    fn parse_rr_ptr() {
1359        let tokens = tokenise_str("nyarlathotep.lan. IN 300 PTR ptrdname.lan.");
1360        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1361            assert_eq!(
1362                Entry::RR {
1363                    rr: ResourceRecord {
1364                        name: domain("nyarlathotep.lan."),
1365                        rtype_with_data: RecordTypeWithData::PTR {
1366                            ptrdname: domain("ptrdname.lan."),
1367                        },
1368                        rclass: RecordClass::IN,
1369                        ttl: 300
1370                    }
1371                },
1372                parsed
1373            );
1374        } else {
1375            panic!("expected successful parse");
1376        }
1377    }
1378
1379    #[test]
1380    fn parse_rr_hinfo() {
1381        let tokens = tokenise_str("nyarlathotep.lan. IN 300 HINFO 123");
1382        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1383            assert_eq!(
1384                Entry::RR {
1385                    rr: ResourceRecord {
1386                        name: domain("nyarlathotep.lan."),
1387                        rtype_with_data: RecordTypeWithData::HINFO {
1388                            octets: Bytes::copy_from_slice(b"123"),
1389                        },
1390                        rclass: RecordClass::IN,
1391                        ttl: 300
1392                    }
1393                },
1394                parsed
1395            );
1396        } else {
1397            panic!("expected successful parse");
1398        }
1399    }
1400
1401    #[test]
1402    fn parse_rr_minfo() {
1403        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MINFO rmailbx.lan. emailbx.lan.");
1404        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1405            assert_eq!(
1406                Entry::RR {
1407                    rr: ResourceRecord {
1408                        name: domain("nyarlathotep.lan."),
1409                        rtype_with_data: RecordTypeWithData::MINFO {
1410                            rmailbx: domain("rmailbx.lan."),
1411                            emailbx: domain("emailbx.lan."),
1412                        },
1413                        rclass: RecordClass::IN,
1414                        ttl: 300
1415                    }
1416                },
1417                parsed
1418            );
1419        } else {
1420            panic!("expected successful parse");
1421        }
1422    }
1423
1424    #[test]
1425    fn parse_rr_mx() {
1426        let tokens = tokenise_str("nyarlathotep.lan. IN 300 MX 42 exchange.lan.");
1427        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1428            assert_eq!(
1429                Entry::RR {
1430                    rr: ResourceRecord {
1431                        name: domain("nyarlathotep.lan."),
1432                        rtype_with_data: RecordTypeWithData::MX {
1433                            preference: 42,
1434                            exchange: domain("exchange.lan."),
1435                        },
1436                        rclass: RecordClass::IN,
1437                        ttl: 300
1438                    }
1439                },
1440                parsed
1441            );
1442        } else {
1443            panic!("expected successful parse");
1444        }
1445    }
1446
1447    #[test]
1448    fn parse_rr_txt() {
1449        let tokens = tokenise_str("nyarlathotep.lan. IN 300 TXT 123");
1450        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1451            assert_eq!(
1452                Entry::RR {
1453                    rr: ResourceRecord {
1454                        name: domain("nyarlathotep.lan."),
1455                        rtype_with_data: RecordTypeWithData::TXT {
1456                            octets: Bytes::copy_from_slice(b"123"),
1457                        },
1458                        rclass: RecordClass::IN,
1459                        ttl: 300
1460                    }
1461                },
1462                parsed
1463            );
1464        } else {
1465            panic!("expected successful parse");
1466        }
1467    }
1468
1469    #[test]
1470    fn parse_rr_aaaa() {
1471        let tokens = tokenise_str("nyarlathotep.lan. IN 300 AAAA ::1:2:3");
1472        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1473            assert_eq!(
1474                Entry::RR {
1475                    rr: ResourceRecord {
1476                        name: domain("nyarlathotep.lan."),
1477                        rtype_with_data: RecordTypeWithData::AAAA {
1478                            address: Ipv6Addr::new(0, 0, 0, 0, 0, 1, 2, 3)
1479                        },
1480                        rclass: RecordClass::IN,
1481                        ttl: 300
1482                    }
1483                },
1484                parsed
1485            );
1486        } else {
1487            panic!("expected successful parse");
1488        }
1489    }
1490
1491    #[test]
1492    fn parse_rr_srv() {
1493        let tokens =
1494            tokenise_str("_service._tcp.nyarlathotep.lan. IN 300 SRV 0 0 8080 game-server.lan.");
1495        if let Ok(parsed) = parse_rr(None, None, None, tokens) {
1496            assert_eq!(
1497                Entry::RR {
1498                    rr: ResourceRecord {
1499                        name: domain("_service._tcp.nyarlathotep.lan."),
1500                        rtype_with_data: RecordTypeWithData::SRV {
1501                            priority: 0,
1502                            weight: 0,
1503                            port: 8080,
1504                            target: domain("game-server.lan."),
1505                        },
1506                        rclass: RecordClass::IN,
1507                        ttl: 300
1508                    }
1509                },
1510                parsed
1511            );
1512        } else {
1513            panic!("expected successful parse");
1514        }
1515    }
1516
1517    #[test]
1518    fn parse_domain_or_wildcard_origin() {
1519        assert!(matches!(
1520            parse_domain_or_wildcard(None, "@"),
1521            Err(Error::ExpectedOrigin)
1522        ));
1523
1524        if let Ok(name) = parse_domain_or_wildcard(Some(&domain("example.com.")), "@") {
1525            assert_eq!(
1526                MaybeWildcard::Normal {
1527                    name: domain("example.com.")
1528                },
1529                name
1530            );
1531        } else {
1532            panic!("expected parse");
1533        }
1534    }
1535
1536    #[test]
1537    fn parse_domain_or_wildcard_wildcard_origin() {
1538        assert!(matches!(
1539            parse_domain_or_wildcard(None, "*.@"),
1540            Err(Error::ExpectedOrigin)
1541        ));
1542
1543        if let Ok(name) = parse_domain_or_wildcard(Some(&domain("example.com.")), "*.@") {
1544            assert_eq!(
1545                MaybeWildcard::Wildcard {
1546                    name: domain("example.com.")
1547                },
1548                name
1549            );
1550        } else {
1551            panic!("expected parse");
1552        }
1553    }
1554
1555    #[test]
1556    fn parse_domain_or_wildcard_relative() {
1557        assert!(matches!(
1558            parse_domain_or_wildcard(None, "www"),
1559            Err(Error::ExpectedOrigin)
1560        ));
1561
1562        if let Ok(name) = parse_domain_or_wildcard(Some(&domain("example.com.")), "www") {
1563            assert_eq!(
1564                MaybeWildcard::Normal {
1565                    name: domain("www.example.com.")
1566                },
1567                name
1568            );
1569        } else {
1570            panic!("expected parse");
1571        }
1572    }
1573
1574    #[test]
1575    fn parse_domain_or_wildcard_wildcard_relative() {
1576        assert!(matches!(
1577            parse_domain_or_wildcard(None, "*"),
1578            Err(Error::ExpectedOrigin)
1579        ));
1580
1581        if let Ok(name) = parse_domain_or_wildcard(Some(&domain("example.com.")), "*") {
1582            assert_eq!(
1583                MaybeWildcard::Wildcard {
1584                    name: domain("example.com.")
1585                },
1586                name
1587            );
1588        } else {
1589            panic!("expected parse");
1590        }
1591    }
1592
1593    #[test]
1594    fn parse_domain_or_wildcard_absolute() {
1595        if let Ok(name) = parse_domain_or_wildcard(None, "www.example.com.") {
1596            assert_eq!(
1597                MaybeWildcard::Normal {
1598                    name: domain("www.example.com.")
1599                },
1600                name
1601            );
1602        } else {
1603            panic!("expected parse");
1604        }
1605
1606        if let Ok(name) =
1607            parse_domain_or_wildcard(Some(&domain("example.com.")), "www.example.com.")
1608        {
1609            assert_eq!(
1610                MaybeWildcard::Normal {
1611                    name: domain("www.example.com.")
1612                },
1613                name
1614            );
1615        } else {
1616            panic!("expected parse");
1617        }
1618    }
1619
1620    #[test]
1621    fn parse_domain_or_wildcard_wildcard_absolute() {
1622        if let Ok(name) = parse_domain_or_wildcard(None, "*.example.com.") {
1623            assert_eq!(
1624                MaybeWildcard::Wildcard {
1625                    name: domain("example.com.")
1626                },
1627                name
1628            );
1629        } else {
1630            panic!("expected parse");
1631        }
1632
1633        if let Ok(name) = parse_domain_or_wildcard(Some(&domain("example.com.")), "*.example.com.")
1634        {
1635            assert_eq!(
1636                MaybeWildcard::Wildcard {
1637                    name: domain("example.com.")
1638                },
1639                name
1640            );
1641        } else {
1642            panic!("expected parse");
1643        }
1644    }
1645
1646    #[test]
1647    fn parse_domain_or_wildcard_wildcard_root() {
1648        if let Ok(name) = parse_domain_or_wildcard(None, "*.") {
1649            assert_eq!(
1650                MaybeWildcard::Wildcard {
1651                    name: DomainName::root_domain()
1652                },
1653                name
1654            );
1655        } else {
1656            panic!("expected parse");
1657        }
1658    }
1659
1660    #[test]
1661    fn tokenise_entry_single() {
1662        let mut stream = "a b c \" quoted string 1 \" \"quoted string 2\" \\\" unquoted! \\("
1663            .chars()
1664            .peekable();
1665        if let Ok(tokens) = tokenise_entry(&mut stream) {
1666            assert_eq!(8, tokens.len());
1667            assert_eq!("a".to_string(), tokens[0].0);
1668            assert_eq!("b".to_string(), tokens[1].0);
1669            assert_eq!("c".to_string(), tokens[2].0);
1670            assert_eq!(" quoted string 1 ".to_string(), tokens[3].0);
1671            assert_eq!("quoted string 2".to_string(), tokens[4].0);
1672            assert_eq!("\"".to_string(), tokens[5].0);
1673            assert_eq!("unquoted!".to_string(), tokens[6].0);
1674            assert_eq!("(".to_string(), tokens[7].0);
1675            assert_eq!(None, stream.next());
1676        } else {
1677            panic!("expected tokenisation");
1678        }
1679    }
1680
1681    #[test]
1682    fn tokenise_entry_multi() {
1683        let mut stream = "entry one\nentry two".chars().peekable();
1684        if let Ok(tokens1) = tokenise_entry(&mut stream) {
1685            assert_eq!(2, tokens1.len());
1686            assert_eq!("entry".to_string(), tokens1[0].0);
1687            assert_eq!("one".to_string(), tokens1[1].0);
1688
1689            if let Ok(tokens2) = tokenise_entry(&mut stream) {
1690                assert_eq!(2, tokens2.len());
1691                assert_eq!("entry".to_string(), tokens2[0].0);
1692                assert_eq!("two".to_string(), tokens2[1].0);
1693
1694                assert_eq!(None, stream.next());
1695            } else {
1696                panic!("expected tokenisation of entry 1");
1697            }
1698        } else {
1699            panic!("expected tokenisation of entry 1");
1700        }
1701    }
1702
1703    #[test]
1704    fn tokenise_entry_multiline_continuation() {
1705        let mut stream = "line ( with \n continuation )".chars().peekable();
1706        if let Ok(tokens) = tokenise_entry(&mut stream) {
1707            assert_eq!(3, tokens.len());
1708            assert_eq!("line".to_string(), tokens[0].0);
1709            assert_eq!("with".to_string(), tokens[1].0);
1710            assert_eq!("continuation".to_string(), tokens[2].0);
1711        } else {
1712            panic!("expected tokenisation");
1713        }
1714    }
1715
1716    #[test]
1717    fn tokenise_entry_multiline_string() {
1718        let mut stream = "line \"with \n continuation\"".chars().peekable();
1719        if let Ok(tokens) = tokenise_entry(&mut stream) {
1720            assert_eq!(2, tokens.len());
1721            assert_eq!("line".to_string(), tokens[0].0);
1722            assert_eq!("with \n continuation".to_string(), tokens[1].0);
1723        } else {
1724            panic!("expected tokenisation");
1725        }
1726    }
1727
1728    #[test]
1729    fn tokenise_entry_handles_embedded_quotes() {
1730        let entry = "foo\"bar\"baz";
1731        if let Ok(tokens) = tokenise_entry(&mut entry.chars().peekable()) {
1732            assert!(!tokens.is_empty());
1733            assert_eq!(entry, tokens[0].0);
1734        } else {
1735            panic!("expected tokenisation");
1736        }
1737    }
1738
1739    #[test]
1740    fn tokenise_escape_non_numeric() {
1741        let mut stream = "ab".chars().peekable();
1742        if let Ok(c) = tokenise_escape(&mut stream) {
1743            assert_eq!(b'a', c);
1744            assert_eq!(Some('b'), stream.next());
1745        } else {
1746            panic!("expected tokenisation");
1747        }
1748    }
1749
1750    #[test]
1751    fn tokenise_escape_one_digits() {
1752        assert!(matches!(
1753            tokenise_escape(&mut "1".chars().peekable()),
1754            Err(Error::TokeniserUnexpectedEscape { .. })
1755        ));
1756    }
1757
1758    #[test]
1759    fn tokenise_escape_two_digits() {
1760        assert!(matches!(
1761            tokenise_escape(&mut "12".chars().peekable()),
1762            Err(Error::TokeniserUnexpectedEscape { .. })
1763        ));
1764    }
1765
1766    #[test]
1767    fn tokenise_escape_three_digits() {
1768        let mut stream = "123".chars().peekable();
1769        if let Ok(c) = tokenise_escape(&mut stream) {
1770            assert_eq!(123, c);
1771            assert_eq!(None, stream.next());
1772        } else {
1773            panic!("expected tokenisation");
1774        }
1775    }
1776
1777    #[test]
1778    fn tokenise_escape_three_digits_too_big() {
1779        assert!(matches!(
1780            tokenise_escape(&mut "999".chars().peekable()),
1781            Err(Error::TokeniserUnexpectedEscape { .. })
1782        ));
1783    }
1784
1785    #[test]
1786    fn tokenise_escape_four_digits() {
1787        let mut stream = "1234".chars().peekable();
1788        if let Ok(c) = tokenise_escape(&mut stream) {
1789            assert_eq!(123, c);
1790            assert_eq!(Some('4'), stream.next());
1791        } else {
1792            panic!("expected tokenisation");
1793        }
1794    }
1795
1796    fn tokenise_str(s: &str) -> Vec<(String, Bytes)> {
1797        tokenise_entry(&mut s.chars().peekable()).unwrap()
1798    }
1799}