hickory_proto/serialize/txt/
parse_rdata.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! record data enum variants
9
10#[cfg(feature = "dnssec")]
11use crate::rr::dnssec::rdata::DNSSECRData;
12use crate::{
13    rr::{
14        rdata::{ANAME, CNAME, HTTPS, NS, PTR},
15        Name, RData, RecordType,
16    },
17    serialize::txt::{
18        errors::{ParseError, ParseErrorKind, ParseResult},
19        rdata_parsers::*,
20        zone_lex::Lexer,
21    },
22};
23
24use super::Token;
25
26/// Extension on RData for text parsing
27pub trait RDataParser: Sized {
28    /// Attempts to parse a stream of tokenized strs into the RData of the specified record type
29    fn parse<'i, I: Iterator<Item = &'i str>>(
30        record_type: RecordType,
31        tokens: I,
32        origin: Option<&Name>,
33    ) -> ParseResult<Self>;
34
35    /// Parse RData from a string
36    fn try_from_str(record_type: RecordType, s: &str) -> ParseResult<Self> {
37        let mut lexer = Lexer::new(s);
38        let mut rdata = Vec::new();
39
40        while let Some(token) = lexer.next_token()? {
41            match token {
42                Token::List(list) => rdata.extend(list),
43                Token::CharData(s) => rdata.push(s),
44                Token::EOL | Token::Blank => (),
45                _ => {
46                    return Err(ParseError::from(format!(
47                        "unexpected token in record data: {token:?}"
48                    )))
49                }
50            }
51        }
52
53        Self::parse(record_type, rdata.iter().map(AsRef::as_ref), None)
54    }
55}
56
57#[warn(clippy::wildcard_enum_match_arm)] // make sure all cases are handled
58impl RDataParser for RData {
59    /// Parse the RData from a set of Tokens
60    fn parse<'i, I: Iterator<Item = &'i str>>(
61        record_type: RecordType,
62        tokens: I,
63        origin: Option<&Name>,
64    ) -> ParseResult<Self> {
65        let rdata = match record_type {
66            RecordType::A => Self::A(a::parse(tokens)?),
67            RecordType::AAAA => Self::AAAA(aaaa::parse(tokens)?),
68            RecordType::ANAME => Self::ANAME(ANAME(name::parse(tokens, origin)?)),
69            RecordType::ANY => return Err(ParseError::from("parsing ANY doesn't make sense")),
70            RecordType::AXFR => return Err(ParseError::from("parsing AXFR doesn't make sense")),
71            RecordType::CAA => caa::parse(tokens).map(Self::CAA)?,
72            RecordType::CNAME => Self::CNAME(CNAME(name::parse(tokens, origin)?)),
73            RecordType::CSYNC => csync::parse(tokens).map(Self::CSYNC)?,
74            RecordType::HINFO => Self::HINFO(hinfo::parse(tokens)?),
75            RecordType::HTTPS => svcb::parse(tokens).map(HTTPS).map(Self::HTTPS)?,
76            RecordType::IXFR => return Err(ParseError::from("parsing IXFR doesn't make sense")),
77            RecordType::MX => Self::MX(mx::parse(tokens, origin)?),
78            RecordType::NAPTR => Self::NAPTR(naptr::parse(tokens, origin)?),
79            RecordType::NULL => Self::NULL(null::parse(tokens)?),
80            RecordType::NS => Self::NS(NS(name::parse(tokens, origin)?)),
81            RecordType::OPENPGPKEY => Self::OPENPGPKEY(openpgpkey::parse(tokens)?),
82            RecordType::OPT => return Err(ParseError::from("parsing OPT doesn't make sense")),
83            RecordType::PTR => Self::PTR(PTR(name::parse(tokens, origin)?)),
84            RecordType::SOA => Self::SOA(soa::parse(tokens, origin)?),
85            RecordType::SRV => Self::SRV(srv::parse(tokens, origin)?),
86            RecordType::SSHFP => Self::SSHFP(sshfp::parse(tokens)?),
87            RecordType::SVCB => svcb::parse(tokens).map(Self::SVCB)?,
88            RecordType::TLSA => Self::TLSA(tlsa::parse(tokens)?),
89            RecordType::TXT => Self::TXT(txt::parse(tokens)?),
90            RecordType::SIG => return Err(ParseError::from("parsing SIG doesn't make sense")),
91            RecordType::DNSKEY => {
92                return Err(ParseError::from("DNSKEY should be dynamically generated"))
93            }
94            RecordType::CDNSKEY => {
95                return Err(ParseError::from("CDNSKEY should be dynamically generated"))
96            }
97            RecordType::KEY => return Err(ParseError::from("KEY should be dynamically generated")),
98            #[cfg(feature = "dnssec")]
99            RecordType::DS => Self::DNSSEC(DNSSECRData::DS(ds::parse(tokens)?)),
100            #[cfg(not(feature = "dnssec"))]
101            RecordType::DS => return Err(ParseError::from("DS should be dynamically generated")),
102            RecordType::CDS => return Err(ParseError::from("CDS should be dynamically generated")),
103            RecordType::NSEC => {
104                return Err(ParseError::from("NSEC should be dynamically generated"))
105            }
106            RecordType::NSEC3 => {
107                return Err(ParseError::from("NSEC3 should be dynamically generated"))
108            }
109            RecordType::NSEC3PARAM => {
110                return Err(ParseError::from(
111                    "NSEC3PARAM should be dynamically generated",
112                ))
113            }
114            RecordType::RRSIG => {
115                return Err(ParseError::from("RRSIG should be dynamically generated"))
116            }
117            RecordType::TSIG => return Err(ParseError::from("TSIG is only used during AXFR")),
118            #[allow(deprecated)]
119            RecordType::ZERO => Self::ZERO,
120            r @ RecordType::Unknown(..) => {
121                // TODO: add a way to associate generic record types to the zone
122                return Err(ParseError::from(ParseErrorKind::UnsupportedRecordType(r)));
123            }
124        };
125
126        Ok(rdata)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    #![allow(clippy::dbg_macro, clippy::print_stdout)]
133
134    use super::*;
135    #[cfg(feature = "dnssec")]
136    use crate::rr::dnssec::rdata::DS;
137    use crate::rr::domain::Name;
138    use crate::rr::rdata::*;
139    use std::str::FromStr;
140
141    #[test]
142    fn test_a() {
143        let tokens = ["192.168.0.1"];
144        let name = Name::from_str("example.com.").unwrap();
145        let record =
146            RData::parse(RecordType::A, tokens.iter().map(AsRef::as_ref), Some(&name)).unwrap();
147
148        assert_eq!(record, RData::A("192.168.0.1".parse().unwrap()));
149    }
150
151    #[test]
152    fn test_a_parse() {
153        let data = "192.168.0.1";
154        let record = RData::try_from_str(RecordType::A, data).unwrap();
155
156        assert_eq!(record, RData::A("192.168.0.1".parse().unwrap()));
157    }
158
159    #[test]
160    fn test_aaaa() {
161        let tokens = ["::1"];
162        let name = Name::from_str("example.com.").unwrap();
163        let record = RData::parse(
164            RecordType::AAAA,
165            tokens.iter().map(AsRef::as_ref),
166            Some(&name),
167        )
168        .unwrap();
169
170        assert_eq!(record, RData::AAAA("::1".parse().unwrap()));
171    }
172
173    #[test]
174    fn test_aaaa_parse() {
175        let data = "::1";
176        let record = RData::try_from_str(RecordType::AAAA, data).unwrap();
177
178        assert_eq!(record, RData::AAAA("::1".parse().unwrap()));
179    }
180
181    #[test]
182    fn test_ns_parse() {
183        let data = "ns.example.com";
184        let record = RData::try_from_str(RecordType::NS, data).unwrap();
185
186        assert_eq!(
187            record,
188            RData::NS(NS(Name::from_str("ns.example.com.").unwrap()))
189        );
190    }
191
192    #[test]
193    fn test_csync() {
194        let tokens = ["123", "1", "A", "NS"];
195        let name = Name::from_str("example.com.").unwrap();
196        let record = RData::parse(
197            RecordType::CSYNC,
198            tokens.iter().map(AsRef::as_ref),
199            Some(&name),
200        )
201        .unwrap();
202
203        assert_eq!(
204            record,
205            RData::CSYNC(CSYNC::new(
206                123,
207                true,
208                false,
209                vec![RecordType::A, RecordType::NS]
210            ))
211        );
212    }
213
214    #[test]
215    fn test_csync_parse() {
216        let data = "123 1 A NS";
217        let record = RData::try_from_str(RecordType::CSYNC, data).unwrap();
218
219        assert_eq!(
220            record,
221            RData::CSYNC(CSYNC::new(
222                123,
223                true,
224                false,
225                vec![RecordType::A, RecordType::NS]
226            ))
227        );
228    }
229
230    #[cfg(feature = "dnssec")]
231    #[test]
232    #[allow(deprecated)]
233    fn test_ds() {
234        let tokens = [
235            "60485",
236            "5",
237            "1",
238            "2BB183AF5F22588179A53B0A",
239            "98631FAD1A292118",
240        ];
241        let name = Name::from_str("dskey.example.com.").unwrap();
242        let record = RData::parse(
243            RecordType::DS,
244            tokens.iter().map(AsRef::as_ref),
245            Some(&name),
246        )
247        .unwrap();
248
249        assert_eq!(
250            record,
251            RData::DNSSEC(DNSSECRData::DS(DS::new(
252                60485,
253                crate::rr::dnssec::Algorithm::RSASHA1,
254                crate::rr::dnssec::DigestType::SHA1,
255                vec![
256                    0x2B, 0xB1, 0x83, 0xAF, 0x5F, 0x22, 0x58, 0x81, 0x79, 0xA5, 0x3B, 0x0A, 0x98,
257                    0x63, 0x1F, 0xAD, 0x1A, 0x29, 0x21, 0x18
258                ]
259            )))
260        );
261    }
262
263    #[test]
264    fn test_any() {
265        let tokens = ["test"];
266        let name = Name::from_str("example.com.").unwrap();
267        let result = RData::parse(
268            RecordType::ANY,
269            tokens.iter().map(AsRef::as_ref),
270            Some(&name),
271        );
272
273        assert!(result.is_err());
274    }
275
276    #[test]
277    fn test_dynamically_generated() {
278        let dynamically_generated = vec![
279            RecordType::DS,
280            RecordType::CDS,
281            RecordType::DNSKEY,
282            RecordType::CDNSKEY,
283            RecordType::KEY,
284            RecordType::NSEC,
285            RecordType::NSEC3,
286            RecordType::NSEC3PARAM,
287            RecordType::RRSIG,
288        ];
289
290        let tokens = ["test"];
291
292        let name = Name::from_str("example.com.").unwrap();
293
294        for record_type in dynamically_generated {
295            let result = RData::parse(record_type, tokens.iter().map(AsRef::as_ref), Some(&name));
296            assert!(result.is_err());
297        }
298    }
299}