trust_dns_proto/rr/rdata/txt.rs
1/*
2 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! text records for storing arbitrary data
18use std::fmt;
19use std::slice::Iter;
20
21#[cfg(feature = "serde-config")]
22use serde::{Deserialize, Serialize};
23
24use crate::error::*;
25use crate::serialize::binary::*;
26
27/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
28///
29/// ```text
30/// 3.3.14. TXT RDATA format
31///
32/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
33/// / TXT-DATA /
34/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
35///
36///
37/// TXT RRs are used to hold descriptive text. The semantics of the text
38/// depends on the domain where it is found.
39/// ```
40#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
41#[derive(Debug, PartialEq, Eq, Hash, Clone)]
42pub struct TXT {
43 txt_data: Box<[Box<[u8]>]>,
44}
45
46impl TXT {
47 /// Creates a new TXT record data.
48 ///
49 /// # Arguments
50 ///
51 /// * `txt_data` - the set of strings which make up the txt_data.
52 ///
53 /// # Return value
54 ///
55 /// The new TXT record data.
56 pub fn new(txt_data: Vec<String>) -> Self {
57 Self {
58 txt_data: txt_data
59 .into_iter()
60 .map(|s| s.into_bytes().into_boxed_slice())
61 .collect::<Vec<_>>()
62 .into_boxed_slice(),
63 }
64 }
65
66 /// Creates a new TXT record data from bytes.
67 /// Allows creating binary record data.
68 ///
69 /// # Arguments
70 ///
71 /// * `txt_data` - the set of bytes which make up the txt_data.
72 ///
73 /// # Return value
74 ///
75 /// The new TXT record data.
76 pub fn from_bytes(txt_data: Vec<&[u8]>) -> Self {
77 Self {
78 txt_data: txt_data
79 .into_iter()
80 .map(|s| s.to_vec().into_boxed_slice())
81 .collect::<Vec<_>>()
82 .into_boxed_slice(),
83 }
84 }
85
86 /// ```text
87 /// TXT-DATA One or more <character-string>s.
88 /// ```
89 pub fn txt_data(&self) -> &[Box<[u8]>] {
90 &self.txt_data
91 }
92
93 /// Returns an iterator over the arrays in the txt data
94 pub fn iter(&self) -> Iter<'_, Box<[u8]>> {
95 self.txt_data.iter()
96 }
97}
98
99/// Read the RData from the given Decoder
100pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<TXT> {
101 let data_len = decoder.len();
102 let mut strings = Vec::with_capacity(1);
103
104 // no unsafe usage of rdata length after this point
105 let rdata_length =
106 rdata_length.map(|u| u as usize).unverified(/*used as a higher bound, safely*/);
107 while data_len - decoder.len() < rdata_length {
108 let string =
109 decoder.read_character_data()?.unverified(/*any data should be validate in TXT usage*/);
110 strings.push(string.to_vec().into_boxed_slice());
111 }
112 Ok(TXT {
113 txt_data: strings.into_boxed_slice(),
114 })
115}
116
117/// Write the RData from the given Decoder
118pub fn emit(encoder: &mut BinEncoder<'_>, txt: &TXT) -> ProtoResult<()> {
119 for s in txt.txt_data() {
120 encoder.emit_character_data(s)?;
121 }
122
123 Ok(())
124}
125
126impl fmt::Display for TXT {
127 /// Format a [TXT] with lossy conversion of invalid utf8.
128 ///
129 /// ## Case of invalid utf8
130 ///
131 /// Invalid utf8 will be converted to:
132 /// `U+FFFD REPLACEMENT CHARACTER`, which looks like this: �
133 ///
134 /// Same behaviour as `alloc::string::String::from_utf8_lossy`.
135 /// ```rust
136 /// # use trust_dns_proto::rr::rdata::TXT;
137 /// let first_bytes = b"Invalid utf8 <\xF0\x90\x80>.";
138 /// let second_bytes = b" Valid utf8 <\xF0\x9F\xA4\xA3>";
139 /// let rdata: Vec<&[u8]> = vec![first_bytes, second_bytes];
140 /// let txt = TXT::from_bytes(rdata);
141 ///
142 /// let tested = format!("{}", txt);
143 /// assert_eq!(
144 /// tested.as_bytes(),
145 /// b"Invalid utf8 <\xEF\xBF\xBD>. Valid utf8 <\xF0\x9F\xA4\xA3>",
146 /// "Utf8 lossy conversion error! Mismatch between input and expected"
147 /// );
148 /// ```
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
150 for txt in self.txt_data.iter() {
151 f.write_str(&String::from_utf8_lossy(txt))?;
152 }
153
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 #![allow(clippy::dbg_macro, clippy::print_stdout)]
161
162 use super::*;
163
164 #[test]
165 fn test() {
166 let rdata = TXT::new(vec!["Test me some".to_string(), "more please".to_string()]);
167
168 let mut bytes = Vec::new();
169 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
170 assert!(emit(&mut encoder, &rdata).is_ok());
171 let bytes = encoder.into_bytes();
172
173 println!("bytes: {:?}", bytes);
174
175 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
176 let restrict = Restrict::new(bytes.len() as u16);
177 let read_rdata = read(&mut decoder, restrict).expect("Decoding error");
178 assert_eq!(rdata, read_rdata);
179 }
180
181 #[test]
182 fn publish_binary_txt_record() {
183 let bin_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
184 let rdata = TXT::from_bytes(vec![b"Test me some", &bin_data]);
185
186 let mut bytes = Vec::new();
187 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
188 assert!(emit(&mut encoder, &rdata).is_ok());
189 let bytes = encoder.into_bytes();
190
191 println!("bytes: {:?}", bytes);
192
193 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
194 let restrict = Restrict::new(bytes.len() as u16);
195 let read_rdata = read(&mut decoder, restrict).expect("Decoding error");
196 assert_eq!(rdata, read_rdata);
197 }
198}