simple_dns/dns/rdata/
txt.rs1use std::{
2 collections::HashMap,
3 convert::{TryFrom, TryInto},
4};
5
6use crate::dns::{WireFormat, MAX_CHARACTER_STRING_LENGTH};
7use crate::CharacterString;
8
9use super::RR;
10
11#[derive(Debug, PartialEq, Eq, Hash, Clone)]
13pub struct TXT<'a> {
14 strings: Vec<CharacterString<'a>>,
15 size: usize,
16}
17
18impl RR for TXT<'_> {
19 const TYPE_CODE: u16 = 16;
20}
21
22impl Default for TXT<'_> {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl<'a> TXT<'a> {
29 pub fn new() -> Self {
31 Self {
32 strings: vec![],
33 size: 0,
34 }
35 }
36
37 pub fn add_string(&mut self, char_string: &'a str) -> crate::Result<()> {
39 self.add_char_string(char_string.try_into()?);
40 Ok(())
41 }
42
43 pub fn add_char_string(&mut self, char_string: CharacterString<'a>) {
45 self.size += char_string.len();
46 self.strings.push(char_string);
47 }
48
49 pub fn with_string(mut self, char_string: &'a str) -> crate::Result<Self> {
51 self.add_char_string(char_string.try_into()?);
52 Ok(self)
53 }
54
55 pub fn with_char_string(mut self, char_string: CharacterString<'a>) -> Self {
57 self.add_char_string(char_string);
58 self
59 }
60
61 pub fn attributes(&self) -> HashMap<String, Option<String>> {
68 let mut attributes = HashMap::new();
69
70 for char_str in &self.strings {
71 let mut splited = char_str.data.splitn(2, |c| *c == b'=');
72 let key = match splited.next() {
73 Some(key) => match std::str::from_utf8(key) {
74 Ok(key) => key.to_owned(),
75 Err(_) => continue,
76 },
77 None => continue,
78 };
79
80 let value = match splited.next() {
81 Some(value) if !value.is_empty() => match std::str::from_utf8(value) {
82 Ok(v) => Some(v.to_owned()),
83 Err(_) => Some(String::new()),
84 },
85 Some(_) => Some(String::new()),
86 _ => None,
87 };
88
89 attributes.entry(key).or_insert(value);
90 }
91
92 attributes
93 }
94
95 pub fn long_attributes(self) -> crate::Result<HashMap<String, Option<String>>> {
98 let mut attributes = HashMap::new();
99
100 let full_string: String = match self.try_into() {
101 Ok(string) => string,
102 Err(err) => return Err(crate::SimpleDnsError::InvalidUtf8String(err)),
103 };
104
105 let parts = full_string.split(|c| (c as u8) == b';');
106
107 for part in parts {
108 let key_value = part.splitn(2, |c| (c as u8) == b'=').collect::<Vec<&str>>();
109
110 let key = key_value[0];
111
112 let value = match key_value.len() > 1 {
113 true => Some(key_value[1].to_owned()),
114 _ => None,
115 };
116
117 if !key.is_empty() {
118 attributes.entry(key.to_owned()).or_insert(value);
119 }
120 }
121
122 Ok(attributes)
123 }
124
125 pub fn into_owned<'b>(self) -> TXT<'b> {
127 TXT {
128 strings: self.strings.into_iter().map(|s| s.into_owned()).collect(),
129 size: self.size,
130 }
131 }
132}
133
134impl TryFrom<HashMap<String, Option<String>>> for TXT<'_> {
135 type Error = crate::SimpleDnsError;
136
137 fn try_from(value: HashMap<String, Option<String>>) -> Result<Self, Self::Error> {
138 let mut txt = TXT::new();
139 for (key, value) in value {
140 match value {
141 Some(value) => {
142 txt.add_char_string(format!("{}={}", &key, &value).try_into()?);
143 }
144 None => txt.add_char_string(key.try_into()?),
145 }
146 }
147 Ok(txt)
148 }
149}
150
151impl<'a> TryFrom<&'a str> for TXT<'a> {
152 type Error = crate::SimpleDnsError;
153
154 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
155 let mut txt = TXT::new();
156 for v in value.as_bytes().chunks(MAX_CHARACTER_STRING_LENGTH - 1) {
157 txt.add_char_string(CharacterString::new(v)?);
158 }
159 Ok(txt)
160 }
161}
162
163impl<'a> TryFrom<TXT<'a>> for String {
164 type Error = std::string::FromUtf8Error;
165
166 fn try_from(val: TXT<'a>) -> Result<Self, Self::Error> {
167 let init = Vec::with_capacity(val.len());
168
169 let bytes = val.strings.into_iter().fold(init, |mut acc, val| {
170 acc.extend(val.data.as_ref());
171 acc
172 });
173 String::from_utf8(bytes)
174 }
175}
176
177impl<'a> WireFormat<'a> for TXT<'a> {
178 const MINIMUM_LEN: usize = 1;
179
180 fn parse(data: &mut crate::bytes_buffer::BytesBuffer<'a>) -> crate::Result<Self>
181 where
182 Self: Sized,
183 {
184 let mut strings = Vec::new();
185 let mut size = 0;
186
187 while data.has_remaining() {
188 let char_str = CharacterString::parse(data)?;
189 size += char_str.len();
190 strings.push(char_str);
191 }
192
193 Ok(Self { strings, size })
194 }
195
196 fn len(&self) -> usize {
197 if self.strings.is_empty() {
198 Self::MINIMUM_LEN
199 } else {
200 self.size
201 }
202 }
203
204 fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
205 if self.strings.is_empty() {
206 out.write_all(&[0])?;
207 } else {
208 for string in &self.strings {
209 string.write_to(out)?;
210 }
211 }
212 Ok(())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use crate::{rdata::RData, ResourceRecord};
219 use std::convert::TryInto;
220
221 use super::*;
222
223 #[test]
224 pub fn parse_and_write_txt() -> Result<(), Box<dyn std::error::Error>> {
225 let mut out = vec![];
226 let txt = TXT::new()
227 .with_char_string("version=0.1".try_into()?)
228 .with_char_string("proto=123".try_into()?);
229
230 txt.write_to(&mut out)?;
231 assert_eq!(out.len(), txt.len());
232
233 let txt2 = TXT::parse(&mut out[..].into())?;
234 assert_eq!(2, txt2.strings.len());
235 assert_eq!(txt.strings[0], txt2.strings[0]);
236 assert_eq!(txt.strings[1], txt2.strings[1]);
237
238 Ok(())
239 }
240
241 #[test]
242 pub fn get_attributes() -> Result<(), Box<dyn std::error::Error>> {
243 let attributes = TXT::new()
244 .with_string("version=0.1")?
245 .with_string("flag")?
246 .with_string("with_eq=eq=")?
247 .with_string("version=dup")?
248 .with_string("empty=")?
249 .attributes();
250
251 assert_eq!(4, attributes.len());
252 assert_eq!(Some("0.1".to_owned()), attributes["version"]);
253 assert_eq!(Some("eq=".to_owned()), attributes["with_eq"]);
254 assert_eq!(Some(String::new()), attributes["empty"]);
255 assert_eq!(None, attributes["flag"]);
256
257 Ok(())
258 }
259
260 #[test]
261 fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
262 let sample_file = std::fs::read("samples/zonefile/TXT.sample")?;
263
264 let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata {
265 RData::TXT(rdata) => rdata,
266 _ => unreachable!(),
267 };
268
269 let strings = vec!["\"foo\nbar\"".try_into()?];
270 assert_eq!(sample_rdata.strings, strings);
271
272 Ok(())
273 }
274
275 #[test]
276 fn write_and_parse_large_txt() -> Result<(), Box<dyn std::error::Error>> {
277 let string = "X".repeat(1000);
278 let txt: TXT = string.as_str().try_into()?;
279
280 let mut bytes = Vec::new();
281 assert!(txt.write_to(&mut bytes).is_ok());
282
283 let parsed_txt = TXT::parse(&mut bytes[..].into())?;
284 let parsed_string: String = parsed_txt.try_into()?;
285
286 assert_eq!(parsed_string, string);
287
288 Ok(())
289 }
290
291 #[test]
292 fn write_and_parse_large_attributes() -> Result<(), Box<dyn std::error::Error>> {
293 let big_value = "f".repeat(1000);
294
295 let string = format!("foo={};;flag;bar={}", big_value, big_value);
296 let txt: TXT = string.as_str().try_into()?;
297 let attributes = txt.long_attributes()?;
298
299 assert_eq!(Some(big_value.to_owned()), attributes["bar"]);
300
301 Ok(())
302 }
303}