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