adns_proto/types/
data_parser.rs

1use std::{borrow::Cow, fmt, net::AddrParseError, num::ParseIntError};
2
3use hex::FromHexError;
4use thiserror::Error;
5
6use crate::{NameParseError, SoaData, TsigData, Type, TypeData};
7
8#[derive(Error, Debug)]
9pub enum TypeDataParseError {
10    #[error("argument string is malformed")]
11    MalformedString,
12    #[error("no arguments specified")]
13    NoArguments,
14    #[error("missing expected argument")]
15    MissingArgument,
16
17    #[error("invalid UTF8 in name: {0}")]
18    UTF8Error(#[from] std::str::Utf8Error),
19    #[error("failed to parse domain name: {0}")]
20    NameParseError(#[from] NameParseError),
21    #[error("failed to parse address: {0}")]
22    AddrParseError(#[from] AddrParseError),
23    #[error("failed to parse integer: {0}")]
24    ParseIntError(#[from] ParseIntError),
25    #[error("failed to parse hex: {0}")]
26    FromHexError(#[from] FromHexError),
27}
28
29fn fmt_arg(input: &str) -> Cow<'_, str> {
30    if needs_escape(input) {
31        Cow::Owned(do_escape(input))
32    } else {
33        Cow::Borrowed(input)
34    }
35}
36
37fn needs_escape(input: &str) -> bool {
38    input
39        .chars()
40        .any(|x| x == '"' || x.is_ascii_whitespace() || x == '\\')
41}
42
43fn do_escape(input: &str) -> String {
44    let mut out = "\"".to_string();
45    for c in input.chars() {
46        if c == '"' || c == '\\' || c.is_ascii_whitespace() {
47            out.push('\\');
48        }
49        out.push(c);
50    }
51    out.push('"');
52    out
53}
54
55fn parse_args(input: &str) -> Result<Vec<String>, TypeDataParseError> {
56    let mut out = vec![];
57    let mut escaped = false;
58    let mut quoted = false;
59    let mut current = String::new();
60    for c in input.trim().chars() {
61        if escaped {
62            current.push(c);
63            escaped = false;
64            continue;
65        }
66        if c == '\\' {
67            escaped = true;
68            continue;
69        } else if c == '"' && !quoted {
70            if !current.is_empty() {
71                out.push(std::mem::take(&mut current));
72            }
73            quoted = true;
74        } else if c == '"' && quoted {
75            out.push(std::mem::take(&mut current));
76            quoted = false;
77        } else if c.is_ascii_whitespace() {
78            if !current.is_empty() {
79                out.push(std::mem::take(&mut current));
80            }
81        } else {
82            current.push(c);
83        }
84    }
85    if quoted || escaped {
86        return Err(TypeDataParseError::MalformedString);
87    }
88    if !current.is_empty() {
89        out.push(std::mem::take(&mut current));
90    }
91    Ok(out)
92}
93
94impl fmt::Display for TypeData {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self {
97            TypeData::A(x) => write!(f, "{x}")?,
98            TypeData::DNAME(x) | TypeData::NS(x) | TypeData::CNAME(x) | TypeData::PTR(x) => {
99                write!(f, "{x}")?
100            }
101            TypeData::SOA(SoaData {
102                mname,
103                rname,
104                serial,
105                refresh,
106                retry,
107                expire,
108                minimum,
109            }) => {
110                write!(
111                    f,
112                    "{} {} {} {} {} {} {}",
113                    mname, rname, serial, refresh, retry, expire, minimum
114                )?;
115            }
116            TypeData::HINFO { cpu, os } => {
117                write!(f, "{} {}", fmt_arg(cpu), fmt_arg(os))?;
118            }
119            TypeData::MX {
120                preference,
121                exchange,
122            } => {
123                write!(f, "{} {}", preference, exchange)?;
124            }
125            TypeData::TXT(texts) => {
126                if texts.is_empty() {
127                    return Ok(());
128                }
129                write!(f, "{}", fmt_arg(texts.first().unwrap()))?;
130                for text in texts[1..].iter() {
131                    write!(f, " {}", fmt_arg(text))?;
132                }
133            }
134            TypeData::AAAA(x) => write!(f, "{x}")?,
135            TypeData::LOC {
136                version,
137                size,
138                horiz_pre,
139                vert_pre,
140                latitude,
141                longitude,
142                altitude,
143            } => {
144                write!(
145                    f,
146                    "{} {} {} {} {} {} {}",
147                    version, size, horiz_pre, vert_pre, latitude, longitude, altitude
148                )?;
149            }
150            TypeData::SRV {
151                priority,
152                weight,
153                port,
154                target,
155            } => {
156                write!(f, "{} {} {} {}", priority, weight, port, target)?;
157            }
158            TypeData::CERT {
159                type_,
160                key_tag,
161                algorithm,
162                data,
163            } => {
164                write!(
165                    f,
166                    "{} {} {} {}",
167                    type_,
168                    key_tag,
169                    algorithm,
170                    hex::encode(data)
171                )?;
172            }
173            TypeData::SSHFP {
174                algorithm,
175                fp_type,
176                fingerprint,
177            } => {
178                write!(f, "{} {} {}", algorithm, fp_type, hex::encode(fingerprint))?;
179            }
180            TypeData::TSIG(TsigData {
181                algorithm,
182                time_signed,
183                fudge,
184                mac,
185                original_id,
186                error,
187                other_data,
188            }) => write!(
189                f,
190                "{algorithm} {time_signed} {fudge} {} {original_id} {error:?} {}",
191                hex::encode(mac),
192                hex::encode(other_data)
193            )?,
194            TypeData::URI {
195                priority,
196                weight,
197                target,
198            } => {
199                write!(f, "{} {} {}", priority, weight, target)?;
200            }
201            TypeData::Other(_, x) => write!(f, "{}", hex::encode(x))?,
202        }
203        Ok(())
204    }
205}
206
207impl TypeData {
208    pub fn parse_str(type_: Type, input: &str) -> Result<TypeData, TypeDataParseError> {
209        let args = parse_args(input)?;
210        let Some(first) = args.first() else {
211            return Err(TypeDataParseError::NoArguments);
212        };
213
214        Ok(match type_ {
215            Type::A => TypeData::A(first.parse()?),
216            Type::NS => TypeData::NS(first.parse()?),
217            Type::CNAME => TypeData::CNAME(first.parse()?),
218            Type::SOA => TypeData::SOA(SoaData {
219                mname: first.parse()?,
220                rname: args
221                    .get(1)
222                    .ok_or(TypeDataParseError::MissingArgument)?
223                    .parse()?,
224                serial: args
225                    .get(2)
226                    .ok_or(TypeDataParseError::MissingArgument)?
227                    .parse()?,
228                refresh: args
229                    .get(3)
230                    .ok_or(TypeDataParseError::MissingArgument)?
231                    .parse()?,
232                retry: args
233                    .get(4)
234                    .ok_or(TypeDataParseError::MissingArgument)?
235                    .parse()?,
236                expire: args
237                    .get(5)
238                    .ok_or(TypeDataParseError::MissingArgument)?
239                    .parse()?,
240                minimum: args
241                    .get(6)
242                    .ok_or(TypeDataParseError::MissingArgument)?
243                    .parse()?,
244            }),
245            Type::PTR => TypeData::PTR(first.parse()?),
246            Type::HINFO => TypeData::HINFO {
247                cpu: first.clone(),
248                os: args
249                    .get(1)
250                    .ok_or(TypeDataParseError::MissingArgument)?
251                    .clone(),
252            },
253            Type::MX => TypeData::MX {
254                preference: first.parse()?,
255                exchange: args
256                    .get(1)
257                    .ok_or(TypeDataParseError::MissingArgument)?
258                    .parse()?,
259            },
260            Type::TXT => TypeData::TXT(args.into()),
261            Type::AAAA => TypeData::AAAA(first.parse()?),
262            Type::LOC => TypeData::LOC {
263                version: first.parse()?,
264                size: args
265                    .get(1)
266                    .ok_or(TypeDataParseError::MissingArgument)?
267                    .parse()?,
268                horiz_pre: args
269                    .get(2)
270                    .ok_or(TypeDataParseError::MissingArgument)?
271                    .parse()?,
272                vert_pre: args
273                    .get(3)
274                    .ok_or(TypeDataParseError::MissingArgument)?
275                    .parse()?,
276                latitude: args
277                    .get(4)
278                    .ok_or(TypeDataParseError::MissingArgument)?
279                    .parse()?,
280                longitude: args
281                    .get(5)
282                    .ok_or(TypeDataParseError::MissingArgument)?
283                    .parse()?,
284                altitude: args
285                    .get(6)
286                    .ok_or(TypeDataParseError::MissingArgument)?
287                    .parse()?,
288            },
289            Type::SRV => TypeData::SRV {
290                priority: first.parse()?,
291                weight: args
292                    .get(1)
293                    .ok_or(TypeDataParseError::MissingArgument)?
294                    .parse()?,
295                port: args
296                    .get(2)
297                    .ok_or(TypeDataParseError::MissingArgument)?
298                    .parse()?,
299                target: args
300                    .get(3)
301                    .ok_or(TypeDataParseError::MissingArgument)?
302                    .parse()?,
303            },
304            Type::CERT => TypeData::CERT {
305                type_: first.parse()?,
306                key_tag: args
307                    .get(1)
308                    .ok_or(TypeDataParseError::MissingArgument)?
309                    .parse()?,
310                algorithm: args
311                    .get(2)
312                    .ok_or(TypeDataParseError::MissingArgument)?
313                    .parse()?,
314                data: hex::decode(args.get(3).ok_or(TypeDataParseError::MissingArgument)?)?,
315            },
316            Type::DNAME => TypeData::DNAME(first.parse()?),
317            Type::SSHFP => TypeData::SSHFP {
318                algorithm: first.parse()?,
319                fp_type: args
320                    .get(1)
321                    .ok_or(TypeDataParseError::MissingArgument)?
322                    .parse()?,
323                fingerprint: hex::decode(args.get(2).ok_or(TypeDataParseError::MissingArgument)?)?,
324            },
325            // TSIG cannot be parsed
326            Type::URI => TypeData::URI {
327                priority: first.parse()?,
328                weight: args
329                    .get(1)
330                    .ok_or(TypeDataParseError::MissingArgument)?
331                    .parse()?,
332                target: args
333                    .get(1)
334                    .ok_or(TypeDataParseError::MissingArgument)?
335                    .clone(),
336            },
337            type_ => TypeData::Other(type_, hex::decode(first)?.into()),
338        })
339    }
340}