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 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}