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 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
116fn 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
196fn 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
219fn 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
250fn 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 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 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 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 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
392fn 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
514fn 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
553fn 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
600fn 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
615fn 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
643fn 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
786fn 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
841enum State {
843 Initial,
844 SkipToEndOfComment,
845 UnquotedString,
846 QuotedString,
847}
848
849#[derive(Debug, Clone, PartialEq, Eq)]
851enum MaybeWildcard {
852 Normal { name: DomainName },
853 Wildcard { name: DomainName },
854}
855
856#[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#[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}