1use crate::CharacterString;
2use crate::{
3 dns::{WireFormat, MAX_CHARACTER_STRING_LENGTH},
4 lib::Write,
5 lib::{vec, FromUtf8Error, String, Vec},
6};
7
8use super::RR;
9
10#[derive(Debug, PartialEq, Eq, Hash, Clone)]
12pub struct TXT<'a> {
13 strings: Vec<CharacterString<'a>>,
14 size: usize,
15}
16
17impl RR for TXT<'_> {
18 const TYPE_CODE: u16 = 16;
19}
20
21impl Default for TXT<'_> {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl<'a> TXT<'a> {
28 pub fn new() -> Self {
30 Self {
31 strings: vec![],
32 size: 0,
33 }
34 }
35
36 pub fn add_string(&mut self, char_string: &'a str) -> crate::Result<()> {
38 self.add_char_string(char_string.try_into()?);
39 Ok(())
40 }
41
42 pub fn add_char_string(&mut self, char_string: CharacterString<'a>) {
44 self.size += char_string.len();
45 self.strings.push(char_string);
46 }
47
48 pub fn with_string(mut self, char_string: &'a str) -> crate::Result<Self> {
50 self.add_char_string(char_string.try_into()?);
51 Ok(self)
52 }
53
54 pub fn with_char_string(mut self, char_string: CharacterString<'a>) -> Self {
56 self.add_char_string(char_string);
57 self
58 }
59
60 pub fn iter_raw(&self) -> impl Iterator<Item = (&[u8], Option<&[u8]>)> {
65 self.strings.iter().filter_map(|char_str| {
66 let mut splited = char_str.data.splitn(2, |c| *c == b'=');
67 let key = splited.next()?;
68 if key.is_empty() {
69 return None;
70 }
71 let value = splited.next();
72 Some((key, value))
73 })
74 }
75
76 pub fn add_attribute(&mut self, key: &str, value: Option<&[u8]>) -> crate::Result<()> {
82 if key.is_empty()
83 || !key
84 .bytes()
85 .all(|b| (0x20..=0x7E).contains(&b) && b != b'=')
86 {
87 return Err(crate::SimpleDnsError::InvalidCharacterString);
88 }
89
90 let total_len = key.len() + value.map_or(0, |v| 1 + v.len());
91 if total_len > MAX_CHARACTER_STRING_LENGTH {
92 return Err(crate::SimpleDnsError::InvalidCharacterString);
93 }
94
95 let mut data = Vec::with_capacity(total_len);
96 data.extend_from_slice(key.as_bytes());
97 if let Some(value) = value {
98 data.push(b'=');
99 data.extend_from_slice(value);
100 }
101
102 self.add_char_string(CharacterString::new(&data)?.into_owned());
103 Ok(())
104 }
105
106 pub fn with_attribute(mut self, key: &str, value: Option<&[u8]>) -> crate::Result<Self> {
108 self.add_attribute(key, value)?;
109 Ok(self)
110 }
111
112 #[cfg(feature = "std")]
120 pub fn attributes(&self) -> crate::lib::HashMap<String, Option<String>> {
121 let mut attributes = crate::lib::HashMap::new();
122 let iter = self.iter_raw().filter_map(|(key, value)| {
123 let key = match crate::lib::str::from_utf8(key) {
124 Ok(key) => key.to_ascii_lowercase(),
125 Err(_) => return None,
126 };
127
128 let value = match value {
129 Some(value) if !value.is_empty() => match crate::lib::str::from_utf8(value) {
130 Ok(v) => Some(v.to_owned()),
131 Err(_) => Some(String::new()),
132 },
133 Some(_) => Some(String::new()),
134 _ => None,
135 };
136
137 Some((key, value))
138 });
139
140 for (key, value) in iter {
141 attributes.entry(key).or_insert(value);
142 }
143
144 attributes
145 }
146
147 #[cfg(feature = "std")]
150 pub fn long_attributes(self) -> crate::Result<crate::lib::HashMap<String, Option<String>>> {
151 let mut attributes = crate::lib::HashMap::new();
152
153 let full_string: String = match self.try_into() {
154 Ok(string) => string,
155 Err(err) => return Err(crate::SimpleDnsError::InvalidUtf8String(err)),
156 };
157
158 let parts = full_string.split(|c| (c as u8) == b';');
159
160 for part in parts {
161 let key_value = part.splitn(2, |c| (c as u8) == b'=').collect::<Vec<&str>>();
162
163 let key = key_value[0];
164
165 let value = match key_value.len() > 1 {
166 true => Some(key_value[1].to_owned()),
167 _ => None,
168 };
169
170 if !key.is_empty() {
171 attributes.entry(key.to_owned()).or_insert(value);
172 }
173 }
174
175 Ok(attributes)
176 }
177
178 pub fn into_owned<'b>(self) -> TXT<'b> {
180 TXT {
181 strings: self.strings.into_iter().map(|s| s.into_owned()).collect(),
182 size: self.size,
183 }
184 }
185}
186
187#[cfg(feature = "std")]
188impl TryFrom<crate::lib::HashMap<String, Option<String>>> for TXT<'_> {
189 type Error = crate::SimpleDnsError;
190
191 fn try_from(value: crate::lib::HashMap<String, Option<String>>) -> Result<Self, Self::Error> {
192 let mut txt = TXT::new();
193 for (key, value) in value {
194 match value {
195 Some(value) => {
196 txt.add_char_string(format!("{key}={value}").try_into()?);
197 }
198 None => txt.add_char_string(key.try_into()?),
199 }
200 }
201 Ok(txt)
202 }
203}
204
205impl<'a> TryFrom<&'a str> for TXT<'a> {
206 type Error = crate::SimpleDnsError;
207
208 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
209 let mut txt = TXT::new();
210 for v in value.as_bytes().chunks(MAX_CHARACTER_STRING_LENGTH - 1) {
211 txt.add_char_string(CharacterString::new(v)?);
212 }
213 Ok(txt)
214 }
215}
216
217impl<'a> TryFrom<TXT<'a>> for String {
218 type Error = FromUtf8Error;
219
220 fn try_from(val: TXT<'a>) -> Result<Self, Self::Error> {
221 let init = Vec::with_capacity(val.len());
222
223 let bytes = val.strings.into_iter().fold(init, |mut acc, val| {
224 acc.extend(val.data.as_ref());
225 acc
226 });
227 String::from_utf8(bytes)
228 }
229}
230
231impl<'a> WireFormat<'a> for TXT<'a> {
232 const MINIMUM_LEN: usize = 1;
233
234 fn parse(data: &mut crate::bytes_buffer::BytesBuffer<'a>) -> crate::Result<Self>
235 where
236 Self: Sized,
237 {
238 let mut strings = Vec::new();
239 let mut size = 0;
240
241 while data.has_remaining() {
242 let char_str = CharacterString::parse(data)?;
243 size += char_str.len();
244 strings.push(char_str);
245 }
246
247 Ok(Self { strings, size })
248 }
249
250 fn len(&self) -> usize {
251 if self.strings.is_empty() {
252 Self::MINIMUM_LEN
253 } else {
254 self.size
255 }
256 }
257
258 fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
259 if self.strings.is_empty() {
260 out.write_all(&[0])?;
261 } else {
262 for string in &self.strings {
263 string.write_to(out)?;
264 }
265 }
266 Ok(())
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::lib::Error;
274
275 #[test]
276 pub fn parse_and_write_txt() -> Result<(), crate::lib::Box<dyn Error>> {
277 let mut out = vec![];
278 let txt = TXT::new()
279 .with_char_string("version=0.1".try_into()?)
280 .with_char_string("proto=123".try_into()?);
281
282 txt.write_to(&mut out)?;
283 assert_eq!(out.len(), txt.len());
284
285 let txt2 = TXT::parse(&mut out[..].into())?;
286 assert_eq!(2, txt2.strings.len());
287 assert_eq!(txt.strings[0], txt2.strings[0]);
288 assert_eq!(txt.strings[1], txt2.strings[1]);
289
290 Ok(())
291 }
292
293 #[test]
294 pub fn iter_raw() -> Result<(), crate::lib::Box<dyn Error>> {
295 let txt = TXT::new()
296 .with_string("version=0.1")?
297 .with_string("flag")?
298 .with_string("with_eq=eq=")?
299 .with_string("version=dup")?
300 .with_string("empty=")?;
301
302 assert_eq!(
303 txt.iter_raw().collect::<Vec<_>>(),
304 vec![
305 ("version".as_bytes(), Some("0.1".as_bytes())),
306 ("flag".as_bytes(), None),
307 ("with_eq".as_bytes(), Some("eq=".as_bytes())),
308 ("version".as_bytes(), Some("dup".as_bytes())),
309 ("empty".as_bytes(), Some("".as_bytes()))
310 ]
311 );
312 Ok(())
313 }
314
315 #[test]
316 #[cfg(feature = "std")]
317 pub fn get_attributes() -> Result<(), Box<dyn Error>> {
318 let attributes = TXT::new()
319 .with_string("version=0.1")?
320 .with_string("flag")?
321 .with_string("with_eq=eq=")?
322 .with_string("version=dup")?
323 .with_string("empty=")?
324 .attributes();
325
326 assert_eq!(4, attributes.len());
327 assert_eq!(Some("0.1".to_owned()), attributes["version"]);
328 assert_eq!(Some("eq=".to_owned()), attributes["with_eq"]);
329 assert_eq!(Some(String::new()), attributes["empty"]);
330 assert_eq!(None, attributes["flag"]);
331
332 Ok(())
333 }
334
335 #[test]
336 #[cfg(feature = "std")]
337 fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
338 use crate::{rdata::RData, ResourceRecord};
339 let sample_file = std::fs::read("samples/zonefile/TXT.sample")?;
340
341 let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata {
342 RData::TXT(rdata) => rdata,
343 _ => unreachable!(),
344 };
345
346 let strings = vec!["\"foo\nbar\"".try_into()?];
347 assert_eq!(sample_rdata.strings, strings);
348
349 Ok(())
350 }
351
352 #[test]
353 fn write_and_parse_large_txt() -> Result<(), crate::lib::Box<dyn Error>> {
354 let string = "X".repeat(1000);
355 let txt: TXT = string.as_str().try_into()?;
356
357 let mut bytes = Vec::new();
358 assert!(txt.write_to(&mut bytes).is_ok());
359
360 let parsed_txt = TXT::parse(&mut bytes[..].into())?;
361 let parsed_string: String = parsed_txt.try_into()?;
362
363 assert_eq!(parsed_string, string);
364
365 Ok(())
366 }
367
368 #[test]
369 #[cfg(feature = "std")]
370 fn write_and_parse_large_attributes() -> Result<(), Box<dyn Error>> {
371 let big_value = "f".repeat(1000);
372
373 let string = format!("foo={big_value};;flag;bar={big_value}");
374 let txt: TXT = string.as_str().try_into()?;
375 let attributes = txt.long_attributes()?;
376
377 assert_eq!(Some(big_value.to_owned()), attributes["bar"]);
378
379 Ok(())
380 }
381
382 #[test]
383 #[cfg(feature = "std")]
384 fn test_rfc6763_empty_keys_and_case_insensitivity() -> Result<(), Box<dyn Error>> {
385 let txt = TXT::new()
386 .with_char_string(CharacterString::new(b"").unwrap())
387 .with_char_string(CharacterString::new(b"=ignored").unwrap())
388 .with_string("Vers=1")?
389 .with_string("vers=2")?
390 .with_string("PATH=/test")?;
391
392 let raw: Vec<_> = txt.iter_raw().collect();
393 assert_eq!(3, raw.len());
394 assert_eq!(b"Vers", raw[0].0);
395 assert_eq!(Some(b"1".as_slice()), raw[0].1);
396
397 let attrs = txt.attributes();
398 assert_eq!(2, attrs.len());
399 assert_eq!(Some("1".to_owned()), attrs["vers"]);
400 assert_eq!(Some("/test".to_owned()), attrs["path"]);
401
402 Ok(())
403 }
404
405 #[test]
406 fn test_add_attribute() -> Result<(), Box<dyn Error>> {
407 let mut txt = TXT::new();
408 txt.add_attribute("binary", Some(&[0x00, 0xFF, 0xFE]))?;
409 txt.add_attribute("flag", None)?;
410
411 let raw: Vec<_> = txt.iter_raw().collect();
412 assert_eq!(2, raw.len());
413 assert_eq!(b"binary", raw[0].0);
414 assert_eq!(Some(&[0x00, 0xFF, 0xFE][..]), raw[0].1);
415 assert_eq!(b"flag", raw[1].0);
416 assert_eq!(None, raw[1].1);
417
418 assert!(txt.clone().with_attribute("", None).is_err());
419 assert!(txt.with_attribute("bad=key", None).is_err());
420
421 Ok(())
422 }
423}