hickory_proto/rr/rdata/
txt.rs

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