domain/rdata/rfc1035/
txt.rs

1//! Record data for the TXT record.
2//!
3//! This is a private module. It’s content is re-exported by the parent.
4
5use crate::base::charstr::CharStr;
6#[cfg(feature = "serde")]
7use crate::base::charstr::DeserializeCharStrSeed;
8use crate::base::cmp::CanonicalOrd;
9use crate::base::iana::Rtype;
10use crate::base::rdata::{
11    ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
12};
13use crate::base::scan::Scanner;
14#[cfg(feature = "serde")]
15use crate::base::scan::Symbol;
16use crate::base::wire::{Composer, FormError, ParseError};
17use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
18#[cfg(feature = "bytes")]
19use bytes::BytesMut;
20use core::cmp::Ordering;
21use core::convert::{Infallible, TryFrom};
22use core::{fmt, hash, mem, str};
23use octseq::builder::{
24    infallible, EmptyBuilder, FreezeBuilder, FromBuilder, OctetsBuilder,
25    ShortBuf,
26};
27use octseq::octets::{Octets, OctetsFrom, OctetsInto};
28use octseq::parse::Parser;
29#[cfg(feature = "serde")]
30use octseq::serde::{DeserializeOctets, SerializeOctets};
31
32//------------ Txt ----------------------------------------------------------
33
34/// TXT record data.
35///
36/// TXT records hold descriptive text. While it may appear as a single text,
37/// it internally consists of a sequence of one or more
38/// [character strings][CharStr]. The type holds this sequence in its encoded
39/// form, i.e., each character string is at most 255 octets long and preceded
40/// by an octet with its length.
41///
42/// The type provides means to iterate over these strings, either as
43/// [`CharStr`s][CharStr] via [`iter_charstrs`][Self::iter_charstrs] or
44/// as plain octets slices via [`iter`][Self::iter]. There is a short cut for
45/// the most common case of there being exactly one character string in
46/// [`as_flat_slice`][Self::as_flat_slice]. Finally, the two methods
47/// [`text`][Self::text] and [`try_text`][Self::try_text] allow combining the
48/// content into one single octets sequence.
49///
50/// The TXT record type is defined in [RFC 1035, section 3.3.14].
51///
52/// # Presentation format
53///
54/// TXT record data appears in zone files as the white-space delimited
55/// sequence of its constituent [character strings][CharStr]. This means that
56/// if these strings are not quoted, each “word” results in a character string
57/// of its own. Thus, the quoted form of the character string’s presentation
58/// format is preferred.
59///
60/// # `Display`
61///
62/// The `Display` implementation prints the sequence of character strings in
63/// their quoted presentation format separated by a single space.
64///
65/// # Serde support
66///
67/// When the `serde` feature is enabled, the type supports serialization and
68/// deserialization. The format differs for human readable and compact
69/// serialization formats.
70///
71/// For human-readable formats, the type serializes into a newtype `Txt`
72/// wrapping a sequence of serialized [`CharStr`]s. The deserializer supports
73/// a non-canonical form as a single string instead of the sequence. In this
74/// case the string is broken up into chunks of 255 octets if it is longer.
75/// However, not all format implementations support alternative
76/// deserialization based on the encountered type. In particular,
77/// _serde-json_ doesn’t, so it will only accept sequences.
78///
79/// For compact formats, the type serializes as a newtype `Txt` that contains
80/// a byte array of the wire format representation of the content.
81///
82/// [RFC 1035, section 3.3.14]: https://tools.ietf.org/html/rfc1035#section-3.3.14
83#[derive(Clone)]
84#[repr(transparent)]
85pub struct Txt<Octs: ?Sized>(Octs);
86
87impl Txt<()> {
88    /// The rtype of this record data type.
89    pub(crate) const RTYPE: Rtype = Rtype::TXT;
90}
91
92impl<Octs: FromBuilder> Txt<Octs> {
93    /// Creates a new Txt record from a single slice.
94    ///
95    /// If the slice is longer than 255 octets, it will be broken up into
96    /// multiple character strings where all but the last string will be
97    /// 255 octets long.
98    ///
99    /// If the slice is longer than 65,535 octets or longer than what fits
100    /// into the octets type used, an error is returned.
101    pub fn build_from_slice(text: &[u8]) -> Result<Self, TxtAppendError>
102    where
103        <Octs as FromBuilder>::Builder:
104            EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
105    {
106        let mut builder = TxtBuilder::<Octs::Builder>::new();
107        builder.append_slice(text)?;
108        builder.finish()
109    }
110}
111
112impl<Octs> Txt<Octs> {
113    /// Creates new TXT record data from its encoded content.
114    ///
115    /// The `octets` sequence most contain correctly encoded TXT record
116    /// data. That is, it must contain a sequence of at least one character
117    /// string of at most 255 octets each preceded by a length octet. An
118    /// empty sequence is not allowed.
119    ///
120    /// Returns an error if `octets` does not contain correctly encoded TXT
121    /// record data.
122    pub fn from_octets(octets: Octs) -> Result<Self, TxtError>
123    where
124        Octs: AsRef<[u8]>,
125    {
126        Txt::check_slice(octets.as_ref())?;
127        Ok(unsafe { Txt::from_octets_unchecked(octets) })
128    }
129
130    /// Creates new TXT record data without checking.
131    ///
132    /// # Safety
133    ///
134    /// The passed octets must contain correctly encoded TXT record data.
135    /// See [`from_octets][Self::from_octets] for the required content.
136    unsafe fn from_octets_unchecked(octets: Octs) -> Self {
137        Txt(octets)
138    }
139}
140
141impl Txt<[u8]> {
142    /// Creates new TXT record data on an octets slice.
143    ///
144    /// The slice must contain correctly encoded TXT record data,
145    /// that is a sequence of encoded character strings. See
146    pub fn from_slice(slice: &[u8]) -> Result<&Self, TxtError> {
147        Txt::check_slice(slice)?;
148        Ok(unsafe { Txt::from_slice_unchecked(slice) })
149    }
150
151    /// Creates new TXT record data on an octets slice without checking.
152    ///
153    /// # Safety
154    ///
155    /// The passed octets must contain correctly encoded TXT record data.
156    /// See [`from_octets][Self::from_octets] for the required content.
157    unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
158        // SAFETY: Txt has repr(transparent)
159        mem::transmute(slice)
160    }
161
162    /// Checks that a slice contains correctly encoded TXT data.
163    fn check_slice(mut slice: &[u8]) -> Result<(), TxtError> {
164        if slice.is_empty() {
165            return Err(TxtError(TxtErrorInner::Empty));
166        }
167        LongRecordData::check_len(slice.len())?;
168        while let Some(&len) = slice.first() {
169            let len = usize::from(len);
170            if slice.len() <= len {
171                return Err(TxtError(TxtErrorInner::ShortInput));
172            }
173            slice = &slice[len + 1..];
174        }
175        Ok(())
176    }
177}
178
179impl<Octs> Txt<Octs> {
180    /// Parses TXT record data from the beginning of a parser.
181    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
182        parser: &mut Parser<'a, Src>,
183    ) -> Result<Self, ParseError>
184    where
185        Octs: AsRef<[u8]>,
186    {
187        let len = parser.remaining();
188        let text = parser.parse_octets(len)?;
189        let mut tmp = Parser::from_ref(text.as_ref());
190        while tmp.remaining() != 0 {
191            CharStr::skip(&mut tmp)?
192        }
193        Ok(Txt(text))
194    }
195
196    /// Scans TXT record data.
197    pub fn scan<S: Scanner<Octets = Octs>>(
198        scanner: &mut S,
199    ) -> Result<Self, S::Error> {
200        scanner.scan_charstr_entry().map(Txt)
201    }
202}
203
204impl<Octs: AsRef<[u8]> + ?Sized> Txt<Octs> {
205    /// Returns an iterator over the character strings as slices.
206    ///
207    /// The returned iterator will always return at least one octets slice.
208    pub fn iter(&self) -> TxtIter<'_> {
209        TxtIter(self.iter_charstrs())
210    }
211
212    /// Returns an iterator over the character strings.
213    ///
214    /// The returned iterator will always return at least one octets slice.
215    pub fn iter_charstrs(&self) -> TxtCharStrIter<'_> {
216        TxtCharStrIter(Parser::from_ref(self.0.as_ref()))
217    }
218
219    /// Returns the content if it consists of a single character string.
220    pub fn as_flat_slice(&self) -> Option<&[u8]> {
221        if usize::from(self.0.as_ref()[0]) == self.0.as_ref().len() - 1 {
222            Some(&self.0.as_ref()[1..])
223        } else {
224            None
225        }
226    }
227
228    /// Returns the length of the TXT record data.
229    ///
230    /// Note that this is the length of the encoded record data and therefore
231    /// never the length of the text, not even if there is only a single
232    /// character string – it is still preceded by a length octet.
233    ///
234    /// Note further that TXT record data is not allowed to be empty, so there
235    /// is no `is_empty` method.
236    #[allow(clippy::len_without_is_empty)]
237    pub fn len(&self) -> usize {
238        self.0.as_ref().len()
239    }
240
241    /// Returns the text content.
242    ///
243    /// The method appends the content of each character string to a newly
244    /// created octets builder. It does not add any delimiters between the
245    /// character string.
246    ///
247    /// If your octets builder is not space limited, you can use
248    /// [`text`][Self::text] instead.
249    pub fn try_text<T: FromBuilder>(
250        &self,
251    ) -> Result<T, <<T as FromBuilder>::Builder as OctetsBuilder>::AppendError>
252    where
253        <T as FromBuilder>::Builder: EmptyBuilder,
254    {
255        // Capacity will be a few bytes too much. Probably better than
256        // re-allocating.
257        let mut res = T::Builder::with_capacity(self.len());
258        for item in self.iter() {
259            res.append_slice(item)?;
260        }
261        Ok(res.freeze())
262    }
263
264    /// Returns the text content.
265    ///
266    /// The method appends the content of each character string to a newly
267    /// created octets builder. It does not add any delimiters between the
268    /// character string.
269    ///
270    /// This method is only available for octets builder types that are not
271    /// space limited. You can use [`try_text`][Self::try_text] with all
272    /// builder types.
273    pub fn text<T: FromBuilder>(&self) -> T
274    where
275        <T as FromBuilder>::Builder: EmptyBuilder,
276        <<T as FromBuilder>::Builder as OctetsBuilder>::AppendError:
277            Into<Infallible>,
278    {
279        infallible(self.try_text())
280    }
281}
282
283impl<SrcOcts> Txt<SrcOcts> {
284    /// Converts the octets type.
285    ///
286    /// This is used by the macros that create enum types.
287    pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<SrcOcts>>(
288        self,
289    ) -> Result<Txt<Target>, Target::Error> {
290        Ok(Txt(self.0.try_octets_into()?))
291    }
292
293    /// Flattens the contents.
294    ///
295    /// This is used by the macros that create enum types.
296    pub(in crate::rdata) fn flatten<Octs: OctetsFrom<SrcOcts>>(
297        self,
298    ) -> Result<Txt<Octs>, Octs::Error> {
299        self.convert_octets()
300    }
301}
302
303//--- OctetsFrom
304
305impl<Octs, SrcOcts> OctetsFrom<Txt<SrcOcts>> for Txt<Octs>
306where
307    Octs: OctetsFrom<SrcOcts>,
308{
309    type Error = Octs::Error;
310
311    fn try_octets_from(source: Txt<SrcOcts>) -> Result<Self, Self::Error> {
312        Octs::try_octets_from(source.0).map(Self)
313    }
314}
315
316//--- IntoIterator
317
318impl<'a, Octs: AsRef<[u8]>> IntoIterator for &'a Txt<Octs> {
319    type Item = &'a [u8];
320    type IntoIter = TxtIter<'a>;
321
322    fn into_iter(self) -> Self::IntoIter {
323        self.iter()
324    }
325}
326
327//--- PartialEq and Eq
328
329impl<Octs, Other> PartialEq<Txt<Other>> for Txt<Octs>
330where
331    Octs: AsRef<[u8]>,
332    Other: AsRef<[u8]>,
333{
334    fn eq(&self, other: &Txt<Other>) -> bool {
335        self.0.as_ref().eq(other.0.as_ref())
336    }
337}
338
339impl<Octs: AsRef<[u8]>> Eq for Txt<Octs> {}
340
341//--- PartialOrd, CanonicalOrd, and Ord
342
343impl<Octs, Other> PartialOrd<Txt<Other>> for Txt<Octs>
344where
345    Octs: AsRef<[u8]>,
346    Other: AsRef<[u8]>,
347{
348    fn partial_cmp(&self, other: &Txt<Other>) -> Option<Ordering> {
349        self.0.as_ref().partial_cmp(other.0.as_ref())
350    }
351}
352
353impl<Octs, Other> CanonicalOrd<Txt<Other>> for Txt<Octs>
354where
355    Octs: AsRef<[u8]>,
356    Other: AsRef<[u8]>,
357{
358    fn canonical_cmp(&self, other: &Txt<Other>) -> Ordering {
359        self.0.as_ref().cmp(other.0.as_ref())
360    }
361}
362
363impl<Octs: AsRef<[u8]>> Ord for Txt<Octs> {
364    fn cmp(&self, other: &Self) -> Ordering {
365        self.0.as_ref().cmp(other.0.as_ref())
366    }
367}
368
369//--- Hash
370
371impl<Octs: AsRef<[u8]>> hash::Hash for Txt<Octs> {
372    fn hash<H: hash::Hasher>(&self, state: &mut H) {
373        self.0.as_ref().hash(state)
374    }
375}
376
377//--- RecordData, ParseRecordData, ComposeRecordData
378
379impl<Octs> RecordData for Txt<Octs> {
380    fn rtype(&self) -> Rtype {
381        Txt::RTYPE
382    }
383}
384
385impl<'a, Octs> ParseRecordData<'a, Octs> for Txt<Octs::Range<'a>>
386where
387    Octs: Octets + ?Sized,
388{
389    fn parse_rdata(
390        rtype: Rtype,
391        parser: &mut Parser<'a, Octs>,
392    ) -> Result<Option<Self>, ParseError> {
393        if rtype == Txt::RTYPE {
394            Self::parse(parser).map(Some)
395        } else {
396            Ok(None)
397        }
398    }
399}
400
401impl<Octs: AsRef<[u8]>> ComposeRecordData for Txt<Octs> {
402    fn rdlen(&self, _compress: bool) -> Option<u16> {
403        Some(u16::try_from(self.0.as_ref().len()).expect("long TXT rdata"))
404    }
405
406    fn compose_rdata<Target: Composer + ?Sized>(
407        &self,
408        target: &mut Target,
409    ) -> Result<(), Target::AppendError> {
410        target.append_slice(self.0.as_ref())
411    }
412
413    fn compose_canonical_rdata<Target: Composer + ?Sized>(
414        &self,
415        target: &mut Target,
416    ) -> Result<(), Target::AppendError> {
417        self.compose_rdata(target)
418    }
419}
420
421//--- Display
422
423impl<Octs: AsRef<[u8]>> fmt::Display for Txt<Octs> {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        let mut first = true;
426        for slice in self.iter_charstrs() {
427            if !first {
428                f.write_str(" ")?;
429            } else {
430                first = false;
431            }
432            write!(f, "{}", slice.display_quoted())?;
433        }
434        Ok(())
435    }
436}
437
438//--- Debug
439
440impl<Octs: AsRef<[u8]>> fmt::Debug for Txt<Octs> {
441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442        f.write_str("Txt(")?;
443        fmt::Display::fmt(self, f)?;
444        f.write_str(")")
445    }
446}
447
448//--- ZonefileFmt
449
450impl<Octs> ZonefileFmt for Txt<Octs>
451where
452    Octs: AsRef<[u8]>,
453{
454    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
455        p.block(|p| {
456            for slice in self.iter_charstrs() {
457                p.write_token(slice.display_quoted())?;
458            }
459            Ok(())
460        })
461    }
462}
463
464//--- Serialize and Deserialize
465
466#[cfg(feature = "serde")]
467impl<Octs> serde::Serialize for Txt<Octs>
468where
469    Octs: AsRef<[u8]> + SerializeOctets,
470{
471    fn serialize<S: serde::Serializer>(
472        &self,
473        serializer: S,
474    ) -> Result<S::Ok, S::Error> {
475        use serde::ser::SerializeSeq;
476
477        struct TxtSeq<'a, Octs>(&'a Txt<Octs>);
478
479        impl<Octs> serde::Serialize for TxtSeq<'_, Octs>
480        where
481            Octs: AsRef<[u8]> + SerializeOctets,
482        {
483            fn serialize<S: serde::Serializer>(
484                &self,
485                serializer: S,
486            ) -> Result<S::Ok, S::Error> {
487                let mut serializer = serializer.serialize_seq(None)?;
488                for item in self.0.iter_charstrs() {
489                    serializer.serialize_element(item)?;
490                }
491                serializer.end()
492            }
493        }
494
495        if serializer.is_human_readable() {
496            serializer.serialize_newtype_struct("Txt", &TxtSeq(self))
497        } else {
498            serializer.serialize_newtype_struct(
499                "Txt",
500                &self.0.as_serialized_octets(),
501            )
502        }
503    }
504}
505
506#[cfg(feature = "serde")]
507impl<'de, Octs> serde::Deserialize<'de> for Txt<Octs>
508where
509    Octs: FromBuilder + DeserializeOctets<'de>,
510    <Octs as FromBuilder>::Builder: EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
511{
512    fn deserialize<D: serde::Deserializer<'de>>(
513        deserializer: D,
514    ) -> Result<Self, D::Error> {
515        use core::marker::PhantomData;
516
517        struct NewtypeVisitor<T>(PhantomData<T>);
518
519        impl<'de, Octs> serde::de::Visitor<'de> for NewtypeVisitor<Octs>
520        where
521            Octs: FromBuilder + DeserializeOctets<'de>,
522            <Octs as FromBuilder>::Builder:
523                OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
524        {
525            type Value = Txt<Octs>;
526
527            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528                f.write_str("TXT record data")
529            }
530
531            fn visit_newtype_struct<D: serde::Deserializer<'de>>(
532                self,
533                deserializer: D,
534            ) -> Result<Self::Value, D::Error> {
535                if deserializer.is_human_readable() {
536                    deserializer.deserialize_seq(ReadableVisitor(PhantomData))
537                } else {
538                    Octs::deserialize_with_visitor(
539                        deserializer,
540                        CompactVisitor(Octs::visitor()),
541                    )
542                }
543            }
544        }
545
546        struct ReadableVisitor<Octs>(PhantomData<Octs>);
547
548        impl<'de, Octs> serde::de::Visitor<'de> for ReadableVisitor<Octs>
549        where
550            Octs: FromBuilder,
551            <Octs as FromBuilder>::Builder:
552                OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
553        {
554            type Value = Txt<Octs>;
555
556            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557                f.write_str("TXT record data")
558            }
559
560            fn visit_str<E: serde::de::Error>(
561                self,
562                v: &str,
563            ) -> Result<Self::Value, E> {
564                // This is a non-canonical serialization. We accept strings
565                // of any length and break them down into chunks.
566                let mut builder =
567                    TxtBuilder::<<Octs as FromBuilder>::Builder>::new();
568                let mut chars = v.chars();
569                while let Some(ch) =
570                    Symbol::from_chars(&mut chars).map_err(E::custom)?
571                {
572                    builder
573                        .append_u8(ch.into_octet().map_err(E::custom)?)
574                        .map_err(E::custom)?;
575                }
576                builder.finish().map_err(E::custom)
577            }
578
579            fn visit_seq<A: serde::de::SeqAccess<'de>>(
580                self,
581                mut seq: A,
582            ) -> Result<Self::Value, A::Error> {
583                let mut builder = <Octs as FromBuilder>::Builder::empty();
584                while seq
585                    .next_element_seed(DeserializeCharStrSeed::new(
586                        &mut builder,
587                    ))?
588                    .is_some()
589                {
590                    LongRecordData::check_len(builder.as_ref().len())
591                        .map_err(serde::de::Error::custom)?;
592                }
593                if builder.as_ref().is_empty() {
594                    builder
595                        .append_slice(b"\0")
596                        .map_err(|_| serde::de::Error::custom(ShortBuf))?;
597                }
598                Ok(Txt(builder.freeze()))
599            }
600        }
601
602        struct CompactVisitor<'de, T: DeserializeOctets<'de>>(T::Visitor);
603
604        impl<'de, Octs> serde::de::Visitor<'de> for CompactVisitor<'de, Octs>
605        where
606            Octs: FromBuilder + DeserializeOctets<'de>,
607            <Octs as FromBuilder>::Builder:
608                OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
609        {
610            type Value = Txt<Octs>;
611
612            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613                f.write_str("TXT record data")
614            }
615
616            fn visit_borrowed_bytes<E: serde::de::Error>(
617                self,
618                value: &'de [u8],
619            ) -> Result<Self::Value, E> {
620                self.0.visit_borrowed_bytes(value).and_then(|octets| {
621                    Txt::from_octets(octets).map_err(E::custom)
622                })
623            }
624
625            #[cfg(feature = "std")]
626            fn visit_byte_buf<E: serde::de::Error>(
627                self,
628                value: std::vec::Vec<u8>,
629            ) -> Result<Self::Value, E> {
630                self.0.visit_byte_buf(value).and_then(|octets| {
631                    Txt::from_octets(octets).map_err(E::custom)
632                })
633            }
634        }
635
636        deserializer
637            .deserialize_newtype_struct("Txt", NewtypeVisitor(PhantomData))
638    }
639}
640
641//------------ TxtCharStrIter ------------------------------------------------
642
643/// An iterator over the character strings of a Txt record.
644#[derive(Clone)]
645pub struct TxtCharStrIter<'a>(Parser<'a, [u8]>);
646
647impl<'a> Iterator for TxtCharStrIter<'a> {
648    type Item = &'a CharStr<[u8]>;
649
650    fn next(&mut self) -> Option<Self::Item> {
651        if self.0.remaining() == 0 {
652            None
653        } else {
654            Some(CharStr::parse_slice(&mut self.0).unwrap())
655        }
656    }
657}
658
659//------------ TxtIter -------------------------------------------------------
660
661/// An iterator over the character strings of a Txt record.
662#[derive(Clone)]
663pub struct TxtIter<'a>(TxtCharStrIter<'a>);
664
665impl<'a> Iterator for TxtIter<'a> {
666    type Item = &'a [u8];
667
668    fn next(&mut self) -> Option<Self::Item> {
669        self.0.next().map(CharStr::as_slice)
670    }
671}
672
673//------------ TxtBuilder ---------------------------------------------------
674
675/// Iteratively build TXT record data.
676///
677/// This type allows building TXT record data by starting with empty data
678/// and appending either complete character strings or slices of data.
679#[derive(Clone, Debug)]
680pub struct TxtBuilder<Builder> {
681    /// The underlying builder.
682    builder: Builder,
683
684    /// The index of the start of the current char string.
685    ///
686    /// If this is `None`, there currently is no char string being worked on.
687    start: Option<usize>,
688}
689
690impl<Builder: OctetsBuilder + EmptyBuilder> TxtBuilder<Builder> {
691    /// Creates a new, empty TXT builder.
692    #[must_use]
693    pub fn new() -> Self {
694        TxtBuilder {
695            builder: Builder::empty(),
696            start: None,
697        }
698    }
699}
700
701#[cfg(feature = "bytes")]
702impl TxtBuilder<BytesMut> {
703    /// Creates a new, empty TXT builder using `BytesMut`.
704    pub fn new_bytes() -> Self {
705        Self::new()
706    }
707}
708
709impl<Builder: OctetsBuilder + AsRef<[u8]> + AsMut<[u8]>> TxtBuilder<Builder> {
710    /// Tries appending a slice.
711    ///
712    /// Errors out if either appending the slice would result in exceeding the
713    /// record data length limit or the underlying builder runs out of space.
714    fn builder_append_slice(
715        &mut self,
716        slice: &[u8],
717    ) -> Result<(), TxtAppendError> {
718        LongRecordData::check_append_len(
719            self.builder.as_ref().len(),
720            slice.len(),
721        )?;
722        self.builder.append_slice(slice)?;
723        Ok(())
724    }
725
726    /// Appends a slice to the builder.
727    ///
728    /// The method breaks up the slice into individual octets strings if
729    /// necessary. If a previous call has started a new octets string, it
730    /// fills this one up first before creating a new one. Thus, by using
731    /// this method only, the resulting TXT record data will consist of
732    /// character strings where all but the last one are 255 octets long.
733    ///
734    /// You can force a character string break by calling
735    /// [`close_charstr`][Self::close_charstr].
736    ///
737    /// The method will return an error if appending the slice would result
738    /// in exceeding the record data length limit or the underlying builder
739    /// runs out of space. In this case, the method may have appended some
740    /// data already. I.e., you should consider the builder corrupt if the
741    /// method returns an error.
742    pub fn append_slice(
743        &mut self,
744        mut slice: &[u8],
745    ) -> Result<(), TxtAppendError> {
746        if let Some(start) = self.start {
747            let left = 255 - (self.builder.as_ref().len() - (start + 1));
748            if slice.len() < left {
749                self.builder_append_slice(slice)?;
750                return Ok(());
751            }
752            let (append, left) = slice.split_at(left);
753            self.builder_append_slice(append)?;
754            self.builder.as_mut()[start] = 255;
755            slice = left;
756        }
757        for chunk in slice.chunks(255) {
758            // Remember offset of this incomplete chunk
759            self.start = if chunk.len() == 255 {
760                None
761            } else {
762                Some(self.builder.as_ref().len())
763            };
764            self.builder_append_slice(&[chunk.len() as u8])?;
765            self.builder_append_slice(chunk)?;
766        }
767        Ok(())
768    }
769
770    /// Appends a single octet.
771    ///
772    /// This method calls [`append_slice`][Self::append_slice], so all the
773    /// caveats described there apply.
774    pub fn append_u8(&mut self, ch: u8) -> Result<(), TxtAppendError> {
775        self.append_slice(&[ch])
776    }
777
778    /// Appends a complete character string.
779    ///
780    /// If a character string had previously been started by a call to
781    /// [`append_slice`][Self::append_slice], this string is closed before
782    /// appending the provided character string.
783    ///
784    /// The method will return an error if appending the slice would result
785    /// in exceeding the record data length limit or the underlying builder
786    /// runs out of space. In this case, the method may have appended some
787    /// data already. I.e., you should consider the builder corrupt if the
788    /// method returns an error.
789    pub fn append_charstr<Octs: AsRef<[u8]> + ?Sized>(
790        &mut self,
791        s: &CharStr<Octs>,
792    ) -> Result<(), TxtAppendError> {
793        self.close_charstr();
794        LongRecordData::check_append_len(
795            self.builder.as_ref().len(),
796            usize::from(s.compose_len()),
797        )?;
798        s.compose(&mut self.builder)?;
799        Ok(())
800    }
801
802    /// Ends a character string.
803    ///
804    /// If a previous call to [`append_slice`][Self::append_slice] started a
805    /// new character string, a call to this method will close it.
806    pub fn close_charstr(&mut self) {
807        if let Some(start) = self.start {
808            let last_slice_len = self.builder.as_ref().len() - (start + 1);
809            self.builder.as_mut()[start] = last_slice_len as u8;
810            self.start = None;
811        }
812    }
813
814    /// Finishes the builder and returns TXT record data.
815    ///
816    /// If the builder is empty, appends an empty character string before
817    /// returning. If that fails because the builder does not have enough
818    /// space, returns an error.
819    pub fn finish(mut self) -> Result<Txt<Builder::Octets>, TxtAppendError>
820    where
821        Builder: FreezeBuilder,
822    {
823        self.close_charstr();
824        if self.builder.as_ref().is_empty() {
825            self.builder.append_slice(b"\0")?;
826        }
827        Ok(Txt(self.builder.freeze()))
828    }
829}
830
831impl<Builder: OctetsBuilder + EmptyBuilder> Default for TxtBuilder<Builder> {
832    fn default() -> Self {
833        Self::new()
834    }
835}
836
837//============ Error Types ===================================================
838
839//------------ TxtError ------------------------------------------------------
840
841/// An octets sequence does not form valid TXT record data.
842#[derive(Clone, Copy, Debug)]
843pub struct TxtError(TxtErrorInner);
844
845#[derive(Clone, Copy, Debug)]
846enum TxtErrorInner {
847    Empty,
848    Long(LongRecordData),
849    ShortInput,
850}
851
852impl TxtError {
853    #[must_use]
854    pub fn as_str(self) -> &'static str {
855        match self.0 {
856            TxtErrorInner::Empty => "empty TXT record",
857            TxtErrorInner::Long(err) => err.as_str(),
858            TxtErrorInner::ShortInput => "short input",
859        }
860    }
861}
862
863impl From<LongRecordData> for TxtError {
864    fn from(err: LongRecordData) -> TxtError {
865        TxtError(TxtErrorInner::Long(err))
866    }
867}
868
869impl From<TxtError> for FormError {
870    fn from(err: TxtError) -> FormError {
871        FormError::new(err.as_str())
872    }
873}
874
875impl fmt::Display for TxtError {
876    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877        f.write_str(self.as_str())
878    }
879}
880
881//------------ TxtAppendError ------------------------------------------------
882
883/// An error occurred while append to TXT record data.
884#[derive(Clone, Copy, Debug)]
885pub enum TxtAppendError {
886    /// Appending would have caused the record data to be too long.
887    LongRecordData,
888
889    /// The octets builder did not have enough space.
890    ShortBuf,
891}
892
893impl TxtAppendError {
894    /// Returns a static string with the error reason.
895    #[must_use]
896    pub fn as_str(self) -> &'static str {
897        match self {
898            TxtAppendError::LongRecordData => "record data too long",
899            TxtAppendError::ShortBuf => "buffer size exceeded",
900        }
901    }
902}
903
904impl From<LongRecordData> for TxtAppendError {
905    fn from(_: LongRecordData) -> TxtAppendError {
906        TxtAppendError::LongRecordData
907    }
908}
909
910impl<T: Into<ShortBuf>> From<T> for TxtAppendError {
911    fn from(_: T) -> TxtAppendError {
912        TxtAppendError::ShortBuf
913    }
914}
915
916impl fmt::Display for TxtAppendError {
917    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
918        f.write_str(self.as_str())
919    }
920}
921
922//============ Testing =======================================================
923
924#[cfg(test)]
925#[cfg(all(feature = "std", feature = "bytes"))]
926mod test {
927    use super::*;
928    use crate::base::rdata::test::{
929        test_compose_parse, test_rdlen, test_scan,
930    };
931    use std::vec::Vec;
932
933    #[test]
934    #[allow(clippy::redundant_closure)] // lifetimes ...
935    fn txt_compose_parse_scan() {
936        let rdata = Txt::from_octets(b"\x03foo\x03bar".as_ref()).unwrap();
937        test_rdlen(&rdata);
938        test_compose_parse(&rdata, |parser| Txt::parse(parser));
939        test_scan(&["foo", "bar"], Txt::scan, &rdata);
940    }
941
942    #[test]
943    fn txt_from_slice() {
944        assert!(Txt::from_octets(b"").is_err());
945
946        let short = b"01234";
947        let txt: Txt<Vec<u8>> = Txt::build_from_slice(short).unwrap();
948        assert_eq!(Some(&short[..]), txt.as_flat_slice());
949        assert_eq!(short.to_vec(), txt.text::<Vec<u8>>());
950
951        // One full slice
952        let full = short.repeat(51);
953        let txt: Txt<Vec<u8>> = Txt::build_from_slice(&full).unwrap();
954        assert_eq!(Some(&full[..]), txt.as_flat_slice());
955        assert_eq!(full.to_vec(), txt.text::<Vec<u8>>());
956
957        // Two slices: 255, 5
958        let long = short.repeat(52);
959        let txt: Txt<Vec<u8>> = Txt::build_from_slice(&long).unwrap();
960        assert_eq!(None, txt.as_flat_slice());
961        assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
962
963        // Partial
964        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
965        for chunk in long.chunks(9) {
966            builder.append_slice(chunk).unwrap();
967        }
968        let txt = builder.finish().unwrap();
969        assert_eq!(None, txt.as_flat_slice());
970        assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
971
972        // Empty
973        let builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
974        let txt = builder.finish().unwrap();
975        assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
976
977        // Empty
978        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
979        builder.append_slice(b"").unwrap();
980        let txt = builder.finish().unwrap();
981        assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
982
983        // Invalid
984        let mut parser = Parser::from_static(b"\x01");
985        assert!(Txt::parse(&mut parser).is_err());
986
987        // Too long
988        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
989        assert!(builder
990            .append_slice(&b"\x00".repeat(u16::MAX as usize))
991            .is_err());
992
993        // Incremental, reserve space for offsets
994        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
995        assert!(builder
996            .append_slice(&b"\x00".repeat(u16::MAX as usize - 512))
997            .is_ok());
998        assert!(builder.append_slice(&b"\x00".repeat(512)).is_err());
999    }
1000
1001    #[test]
1002    fn txt_canonical_compare() {
1003        let data = [
1004            "mailru-verification: 14505c6eb222c847",
1005            "yandex-verification: 6059b187e78de544",
1006            "v=spf1 include:_spf.protonmail.ch ~all",
1007            "swisssign-check=CF0JHMTlTDNoES3rrknIRggocffSwqmzMb9X8YbjzK",
1008            "google-site-\
1009                verification=aq9zJnp3H3bNE0Y4D4rH5I5Dhj8VMaLYx0uQ7Rozfgg",
1010            "ahrefs-site-verification_\
1011                4bdac6bbaa81e0d591d7c0f3ef238905c0521b69bf3d74e64d3775bc\
1012                b2743afd",
1013            "brave-ledger-verification=\
1014                66a7f27fb99949cc0c564ab98efcc58ea1bac3e97eb557c782ab2d44b\
1015                49aefd7",
1016        ];
1017
1018        let records = data
1019            .iter()
1020            .map(|e| {
1021                let mut builder = TxtBuilder::<Vec<u8>>::new();
1022                builder.append_slice(e.as_bytes()).unwrap();
1023                builder.finish().unwrap()
1024            })
1025            .collect::<Vec<_>>();
1026
1027        // The canonical sort must sort by TXT labels which are prefixed by
1028        // length byte first.
1029        let mut sorted = records.clone();
1030        sorted.sort_by(|a, b| a.canonical_cmp(b));
1031
1032        for (a, b) in records.iter().zip(sorted.iter()) {
1033            assert_eq!(a, b);
1034        }
1035    }
1036
1037    #[test]
1038    fn txt_strings_eq() {
1039        let records = [["foo", "bar"], ["foob", "ar"], ["foo", "bar"]];
1040
1041        let records = records
1042            .iter()
1043            .map(|strings| {
1044                let mut builder = TxtBuilder::<Vec<u8>>::new();
1045                for string in strings {
1046                    builder
1047                        .append_charstr(
1048                            CharStr::from_slice(string.as_bytes()).unwrap(),
1049                        )
1050                        .unwrap();
1051                }
1052                builder.finish().unwrap()
1053            })
1054            .collect::<Vec<_>>();
1055
1056        assert_ne!(records[0], records[1]);
1057        assert_eq!(records[0], records[2]);
1058    }
1059
1060    #[cfg(all(feature = "serde", feature = "std"))]
1061    #[test]
1062    fn txt_ser_de() {
1063        use serde_test::{assert_tokens, Configure, Token};
1064
1065        let txt = Txt::from_octets(Vec::from(b"\x03foo".as_ref())).unwrap();
1066        assert_tokens(
1067            &txt.clone().compact(),
1068            &[
1069                Token::NewtypeStruct { name: "Txt" },
1070                Token::ByteBuf(b"\x03foo"),
1071            ],
1072        );
1073        assert_tokens(
1074            &txt.readable(),
1075            &[
1076                Token::NewtypeStruct { name: "Txt" },
1077                Token::Seq { len: None },
1078                Token::NewtypeStruct { name: "CharStr" },
1079                Token::BorrowedStr("foo"),
1080                Token::SeqEnd,
1081            ],
1082        );
1083
1084        let txt = Txt::from_octets(Vec::from(b"\x03foo\x04\\bar".as_ref()))
1085            .unwrap();
1086        assert_tokens(
1087            &txt.clone().compact(),
1088            &[
1089                Token::NewtypeStruct { name: "Txt" },
1090                Token::ByteBuf(b"\x03foo\x04\\bar"),
1091            ],
1092        );
1093        assert_tokens(
1094            &txt.readable(),
1095            &[
1096                Token::NewtypeStruct { name: "Txt" },
1097                Token::Seq { len: None },
1098                Token::NewtypeStruct { name: "CharStr" },
1099                Token::BorrowedStr("foo"),
1100                Token::NewtypeStruct { name: "CharStr" },
1101                Token::BorrowedStr("\\\\bar"),
1102                Token::SeqEnd,
1103            ],
1104        );
1105    }
1106
1107    #[cfg(all(feature = "serde", feature = "std"))]
1108    #[test]
1109    fn txt_de_str() {
1110        use serde_test::{assert_de_tokens, Configure, Token};
1111
1112        assert_de_tokens(
1113            &Txt::from_octets(Vec::from(b"\x03foo".as_ref()))
1114                .unwrap()
1115                .readable(),
1116            &[
1117                Token::NewtypeStruct { name: "Txt" },
1118                Token::BorrowedStr("foo"),
1119            ],
1120        );
1121    }
1122
1123    #[test]
1124    fn txt_display() {
1125        fn cmp(input: &[u8], output: &str) {
1126            assert_eq!(
1127                format!("{}", Txt::from_octets(input).unwrap()),
1128                output
1129            );
1130        }
1131
1132        cmp(b"\x03foo", "\"foo\"");
1133        cmp(b"\x03foo\x03bar", "\"foo\" \"bar\"");
1134        cmp(b"\x03fo\"\x04bar ", "\"fo\\\"\" \"bar \"");
1135        // I don’t think we need more escaping tests since the impl defers
1136        // to CharStr::display_quoted which is tested ...
1137    }
1138}