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            }
430            else {
431                first = false;
432            }
433            write!(f, "{}", slice.display_quoted())?;
434        }
435        Ok(())
436    }
437}
438
439//--- Debug
440
441impl<Octs: AsRef<[u8]>> fmt::Debug for Txt<Octs> {
442    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
443        f.write_str("Txt(")?;
444        fmt::Display::fmt(self, f)?;
445        f.write_str(")")
446    }
447}
448
449//--- ZonefileFmt
450
451impl<Octs> ZonefileFmt for Txt<Octs>
452where
453    Octs: AsRef<[u8]>,
454{
455    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
456        p.block(|p| {
457            for slice in self.iter_charstrs() {
458                p.write_token(slice.display_quoted())?;
459            }
460            Ok(())
461        })
462    }
463}
464
465//--- Serialize and Deserialize
466
467#[cfg(feature = "serde")]
468impl<Octs> serde::Serialize for Txt<Octs>
469where
470    Octs: AsRef<[u8]> + SerializeOctets,
471{
472    fn serialize<S: serde::Serializer>(
473        &self,
474        serializer: S,
475    ) -> Result<S::Ok, S::Error> {
476        use serde::ser::SerializeSeq;
477
478        struct TxtSeq<'a, Octs>(&'a Txt<Octs>);
479
480        impl<Octs> serde::Serialize for TxtSeq<'_, Octs>
481        where
482            Octs: AsRef<[u8]> + SerializeOctets,
483        {
484            fn serialize<S: serde::Serializer>(
485                &self,
486                serializer: S,
487            ) -> Result<S::Ok, S::Error> {
488                let mut serializer = serializer.serialize_seq(None)?;
489                for item in self.0.iter_charstrs() {
490                    serializer.serialize_element(item)?;
491                }
492                serializer.end()
493            }
494        }
495
496        if serializer.is_human_readable() {
497            serializer.serialize_newtype_struct("Txt", &TxtSeq(self))
498        }
499        else {
500            serializer.serialize_newtype_struct(
501                "Txt",
502                &self.0.as_serialized_octets(),
503            )
504        }
505    }
506}
507
508#[cfg(feature = "serde")]
509impl<'de, Octs> serde::Deserialize<'de> for Txt<Octs>
510where
511    Octs: FromBuilder + DeserializeOctets<'de>,
512    <Octs as FromBuilder>::Builder: EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
513{
514    fn deserialize<D: serde::Deserializer<'de>>(
515        deserializer: D,
516    ) -> Result<Self, D::Error> {
517        use core::marker::PhantomData;
518
519        struct NewtypeVisitor<T>(PhantomData<T>);
520
521        impl<'de, Octs> serde::de::Visitor<'de> for NewtypeVisitor<Octs>
522        where
523            Octs: FromBuilder + DeserializeOctets<'de>,
524            <Octs as FromBuilder>::Builder:
525                OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
526        {
527            type Value = Txt<Octs>;
528
529            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
530                f.write_str("TXT record data")
531            }
532
533            fn visit_newtype_struct<D: serde::Deserializer<'de>>(
534                self,
535                deserializer: D,
536            ) -> Result<Self::Value, D::Error> {
537                if deserializer.is_human_readable() {
538                    deserializer.deserialize_seq(ReadableVisitor(PhantomData))
539                } else {
540                    Octs::deserialize_with_visitor(
541                        deserializer,
542                        CompactVisitor(Octs::visitor()),
543                    )
544                }
545            }
546        }
547
548        struct ReadableVisitor<Octs>(PhantomData<Octs>);
549
550        impl<'de, Octs> serde::de::Visitor<'de> for ReadableVisitor<Octs>
551        where
552            Octs: FromBuilder,
553            <Octs as FromBuilder>::Builder:
554                OctetsBuilder + EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>,
555        {
556            type Value = Txt<Octs>;
557
558            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
559                f.write_str("TXT record data")
560            }
561
562            fn visit_str<E: serde::de::Error>(
563                self,
564                v: &str,
565            ) -> Result<Self::Value, E> {
566                // This is a non-canonical serialization. We accept strings
567                // of any length and break them down into chunks.
568                let mut builder =
569                    TxtBuilder::<<Octs as FromBuilder>::Builder>::new();
570                let mut chars = v.chars();
571                while let Some(ch) =
572                    Symbol::from_chars(&mut chars).map_err(E::custom)?
573                {
574                    builder
575                        .append_u8(ch.into_octet().map_err(E::custom)?)
576                        .map_err(E::custom)?;
577                }
578                builder.finish().map_err(E::custom)
579            }
580
581            fn visit_seq<A: serde::de::SeqAccess<'de>>(
582                self,
583                mut seq: A,
584            ) -> Result<Self::Value, A::Error> {
585                let mut builder = <Octs as FromBuilder>::Builder::empty();
586                while seq.next_element_seed(
587                    DeserializeCharStrSeed::new(&mut builder)
588                )?.is_some() {
589                    LongRecordData::check_len(
590                        builder.as_ref().len()
591                    ).map_err(serde::de::Error::custom)?;
592                }
593                if builder.as_ref().is_empty() {
594                    builder.append_slice(b"\0").map_err(|_| {
595                        serde::de::Error::custom(ShortBuf)
596                    })?;
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.deserialize_newtype_struct(
637            "Txt", NewtypeVisitor(PhantomData)
638        )
639    }
640}
641
642//------------ TxtCharStrIter ------------------------------------------------
643
644/// An iterator over the character strings of a Txt record.
645#[derive(Clone)]
646pub struct TxtCharStrIter<'a>(Parser<'a, [u8]>);
647
648impl<'a> Iterator for TxtCharStrIter<'a> {
649    type Item = &'a CharStr<[u8]>;
650
651    fn next(&mut self) -> Option<Self::Item> {
652        if self.0.remaining() == 0 {
653            None
654        } else {
655            Some(CharStr::parse_slice(&mut self.0).unwrap())
656        }
657    }
658}
659
660//------------ TxtIter -------------------------------------------------------
661
662/// An iterator over the character strings of a Txt record.
663#[derive(Clone)]
664pub struct TxtIter<'a>(TxtCharStrIter<'a>);
665
666impl<'a> Iterator for TxtIter<'a> {
667    type Item = &'a [u8];
668
669    fn next(&mut self) -> Option<Self::Item> {
670        self.0.next().map(CharStr::as_slice)
671    }
672}
673
674//------------ TxtBuilder ---------------------------------------------------
675
676/// Iteratively build TXT record data.
677///
678/// This type allows building TXT record data by starting with empty data
679/// and appending either complete character strings or slices of data.
680#[derive(Clone, Debug)]
681pub struct TxtBuilder<Builder> {
682    /// The underlying builder.
683    builder: Builder,
684
685    /// The index of the start of the current char string.
686    ///
687    /// If this is `None`, there currently is no char string being worked on.
688    start: Option<usize>,
689}
690
691impl<Builder: OctetsBuilder + EmptyBuilder> TxtBuilder<Builder> {
692    /// Creates a new, empty TXT builder.
693    #[must_use]
694    pub fn new() -> Self {
695        TxtBuilder {
696            builder: Builder::empty(),
697            start: None,
698        }
699    }
700}
701
702#[cfg(feature = "bytes")]
703impl TxtBuilder<BytesMut> {
704    /// Creates a new, empty TXT builder using `BytesMut`.
705    pub fn new_bytes() -> Self {
706        Self::new()
707    }
708}
709
710impl<Builder: OctetsBuilder + AsRef<[u8]> + AsMut<[u8]>> TxtBuilder<Builder> {
711    /// Tries appending a slice.
712    ///
713    /// Errors out if either appending the slice would result in exceeding the
714    /// record data length limit or the underlying builder runs out of space.
715    fn builder_append_slice(
716        &mut self, slice: &[u8]
717    ) -> Result<(), TxtAppendError> {
718        LongRecordData::check_append_len(
719            self.builder.as_ref().len(), slice.len()
720        )?;
721        self.builder.append_slice(slice)?;
722        Ok(())
723    }
724
725    /// Appends a slice to the builder.
726    ///
727    /// The method breaks up the slice into individual octets strings if
728    /// necessary. If a previous call has started a new octets string, it
729    /// fills this one up first before creating a new one. Thus, by using
730    /// this method only, the resulting TXT record data will consist of
731    /// character strings where all but the last one are 255 octets long.
732    ///
733    /// You can force a character string break by calling
734    /// [`close_charstr`][Self::close_charstr].
735    ///
736    /// The method will return an error if appending the slice would result
737    /// in exceeding the record data length limit or the underlying builder
738    /// runs out of space. In this case, the method may have appended some
739    /// data already. I.e., you should consider the builder corrupt if the
740    /// method returns an error.
741    pub fn append_slice(
742        &mut self, mut slice: &[u8]
743    ) -> Result<(), TxtAppendError> {
744        if let Some(start) = self.start {
745            let left = 255 - (self.builder.as_ref().len() - (start + 1));
746            if slice.len() < left {
747                self.builder_append_slice(slice)?;
748                return Ok(());
749            }
750            let (append, left) = slice.split_at(left);
751            self.builder_append_slice(append)?;
752            self.builder.as_mut()[start] = 255;
753            slice = left;
754        }
755        for chunk in slice.chunks(255) {
756            // Remember offset of this incomplete chunk
757            self.start = if chunk.len() == 255 {
758                None
759            } else {
760                Some(self.builder.as_ref().len())
761            };
762            self.builder_append_slice(&[chunk.len() as u8])?;
763            self.builder_append_slice(chunk)?;
764        }
765        Ok(())
766    }
767
768    /// Appends a single octet.
769    ///
770    /// This method calls [`append_slice`][Self::append_slice], so all the
771    /// caveats described there apply.
772    pub fn append_u8(&mut self, ch: u8) -> Result<(), TxtAppendError> {
773        self.append_slice(&[ch])
774    }
775
776    /// Appends a complete character string.
777    ///
778    /// If a character string had previously been started by a call to
779    /// [`append_slice`][Self::append_slice], this string is closed before
780    /// appending the provided character string.
781    ///
782    /// The method will return an error if appending the slice would result
783    /// in exceeding the record data length limit or the underlying builder
784    /// runs out of space. In this case, the method may have appended some
785    /// data already. I.e., you should consider the builder corrupt if the
786    /// method returns an error.
787    pub fn append_charstr<Octs: AsRef<[u8]> + ?Sized>(
788        &mut self, s: &CharStr<Octs>
789    ) -> Result<(), TxtAppendError> {
790        self.close_charstr();
791        LongRecordData::check_append_len(
792            self.builder.as_ref().len(),
793            usize::from(s.compose_len())
794        )?;
795        s.compose(&mut self.builder)?;
796        Ok(())
797    }
798
799    /// Ends a character string.
800    ///
801    /// If a previous call to [`append_slice`][Self::append_slice] started a
802    /// new character string, a call to this method will close it.
803    pub fn close_charstr(&mut self) {
804        if let Some(start) = self.start {
805            let last_slice_len = self.builder.as_ref().len() - (start + 1);
806            self.builder.as_mut()[start] = last_slice_len as u8;
807            self.start = None;
808        }
809    }
810
811    /// Finishes the builder and returns TXT record data.
812    ///
813    /// If the builder is empty, appends an empty character string before
814    /// returning. If that fails because the builder does not have enough
815    /// space, returns an error.
816    pub fn finish(mut self) -> Result<Txt<Builder::Octets>, TxtAppendError>
817    where
818        Builder: FreezeBuilder,
819    {
820        self.close_charstr();
821        if self.builder.as_ref().is_empty() {
822            self.builder.append_slice(b"\0")?;
823        }
824        Ok(Txt(self.builder.freeze()))
825    }
826}
827
828impl<Builder: OctetsBuilder + EmptyBuilder> Default for TxtBuilder<Builder> {
829    fn default() -> Self {
830        Self::new()
831    }
832}
833
834//============ Error Types ===================================================
835
836//------------ TxtError ------------------------------------------------------
837
838/// An octets sequence does not form valid TXT record data.
839#[derive(Clone, Copy, Debug)]
840pub struct TxtError(TxtErrorInner);
841
842#[derive(Clone, Copy, Debug)]
843enum TxtErrorInner {
844    Empty,
845    Long(LongRecordData),
846    ShortInput,
847}
848
849impl TxtError {
850    #[must_use]
851    pub fn as_str(self) -> &'static str {
852        match self.0 {
853            TxtErrorInner::Empty => "empty TXT record",
854            TxtErrorInner::Long(err) => err.as_str(),
855            TxtErrorInner::ShortInput => "short input",
856        }
857    }
858}
859
860impl From<LongRecordData> for TxtError {
861    fn from(err: LongRecordData) -> TxtError {
862        TxtError(TxtErrorInner::Long(err))
863    }
864}
865
866impl From<TxtError> for FormError {
867    fn from(err: TxtError) -> FormError {
868        FormError::new(err.as_str())
869    }
870}
871
872impl fmt::Display for TxtError {
873    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
874        f.write_str(self.as_str())
875    }
876}
877
878//------------ TxtAppendError ------------------------------------------------
879
880/// An error occurred while append to TXT record data.
881#[derive(Clone, Copy, Debug)]
882pub enum TxtAppendError {
883    /// Appending would have caused the record data to be too long.
884    LongRecordData,
885
886    /// The octets builder did not have enough space.
887    ShortBuf
888}
889
890impl TxtAppendError {
891    /// Returns a static string with the error reason.
892    #[must_use]
893    pub fn as_str(self) -> &'static str {
894        match self {
895            TxtAppendError::LongRecordData => "record data too long",
896            TxtAppendError::ShortBuf => "buffer size exceeded"
897        }
898    }
899}
900
901impl From<LongRecordData> for TxtAppendError {
902    fn from(_: LongRecordData) -> TxtAppendError {
903        TxtAppendError::LongRecordData
904    }
905}
906
907impl<T: Into<ShortBuf>> From<T> for TxtAppendError {
908    fn from(_: T) -> TxtAppendError {
909        TxtAppendError::ShortBuf
910    }
911}
912
913impl fmt::Display for TxtAppendError {
914    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
915        f.write_str(self.as_str())
916    }
917}
918
919//============ Testing =======================================================
920
921#[cfg(test)]
922#[cfg(all(feature = "std", feature = "bytes"))]
923mod test {
924    use super::*;
925    use crate::base::rdata::test::{
926        test_compose_parse, test_rdlen, test_scan,
927    };
928    use std::vec::Vec;
929
930
931
932    #[test]
933    #[allow(clippy::redundant_closure)] // lifetimes ...
934    fn txt_compose_parse_scan() {
935        let rdata = Txt::from_octets(b"\x03foo\x03bar".as_ref()).unwrap();
936        test_rdlen(&rdata);
937        test_compose_parse(&rdata, |parser| Txt::parse(parser));
938        test_scan(&["foo", "bar"], Txt::scan, &rdata);
939    }
940
941    #[test]
942    fn txt_from_slice() {
943        assert!(Txt::from_octets(b"").is_err());
944
945        let short = b"01234";
946        let txt: Txt<Vec<u8>> = Txt::build_from_slice(short).unwrap();
947        assert_eq!(Some(&short[..]), txt.as_flat_slice());
948        assert_eq!(short.to_vec(), txt.text::<Vec<u8>>());
949
950        // One full slice
951        let full = short.repeat(51);
952        let txt: Txt<Vec<u8>> = Txt::build_from_slice(&full).unwrap();
953        assert_eq!(Some(&full[..]), txt.as_flat_slice());
954        assert_eq!(full.to_vec(), txt.text::<Vec<u8>>());
955
956        // Two slices: 255, 5
957        let long = short.repeat(52);
958        let txt: Txt<Vec<u8>> = Txt::build_from_slice(&long).unwrap();
959        assert_eq!(None, txt.as_flat_slice());
960        assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
961
962        // Partial
963        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
964        for chunk in long.chunks(9) {
965            builder.append_slice(chunk).unwrap();
966        }
967        let txt = builder.finish().unwrap();
968        assert_eq!(None, txt.as_flat_slice());
969        assert_eq!(long.to_vec(), txt.text::<Vec<u8>>());
970
971        // Empty
972        let builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
973        let txt = builder.finish().unwrap();
974        assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
975
976        // Empty
977        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
978        builder.append_slice(b"").unwrap();
979        let txt = builder.finish().unwrap();
980        assert_eq!(Some(b"".as_ref()), txt.as_flat_slice());
981
982        // Invalid
983        let mut parser = Parser::from_static(b"\x01");
984        assert!(Txt::parse(&mut parser).is_err());
985
986        // Too long
987        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
988        assert!(builder
989            .append_slice(&b"\x00".repeat(u16::MAX as usize))
990            .is_err());
991
992        // Incremental, reserve space for offsets
993        let mut builder: TxtBuilder<Vec<u8>> = TxtBuilder::new();
994        assert!(builder
995            .append_slice(&b"\x00".repeat(u16::MAX as usize - 512))
996            .is_ok());
997        assert!(builder.append_slice(&b"\x00".repeat(512)).is_err());
998    }
999
1000    #[test]
1001    fn txt_canonical_compare() {
1002        let data = [
1003            "mailru-verification: 14505c6eb222c847",
1004            "yandex-verification: 6059b187e78de544",
1005            "v=spf1 include:_spf.protonmail.ch ~all",
1006            "swisssign-check=CF0JHMTlTDNoES3rrknIRggocffSwqmzMb9X8YbjzK",
1007            "google-site-\
1008                verification=aq9zJnp3H3bNE0Y4D4rH5I5Dhj8VMaLYx0uQ7Rozfgg",
1009            "ahrefs-site-verification_\
1010                4bdac6bbaa81e0d591d7c0f3ef238905c0521b69bf3d74e64d3775bc\
1011                b2743afd",
1012            "brave-ledger-verification=\
1013                66a7f27fb99949cc0c564ab98efcc58ea1bac3e97eb557c782ab2d44b\
1014                49aefd7",
1015        ];
1016
1017        let records = data
1018            .iter()
1019            .map(|e| {
1020                let mut builder = TxtBuilder::<Vec<u8>>::new();
1021                builder.append_slice(e.as_bytes()).unwrap();
1022                builder.finish().unwrap()
1023            })
1024            .collect::<Vec<_>>();
1025
1026        // The canonical sort must sort by TXT labels which are prefixed by
1027        // length byte first.
1028        let mut sorted = records.clone();
1029        sorted.sort_by(|a, b| a.canonical_cmp(b));
1030
1031        for (a, b) in records.iter().zip(sorted.iter()) {
1032            assert_eq!(a, b);
1033        }
1034    }
1035
1036    #[test]
1037    fn txt_strings_eq() {
1038        let records = [["foo", "bar"], ["foob", "ar"], ["foo", "bar"]];
1039
1040        let records = records
1041            .iter()
1042            .map(|strings| {
1043                let mut builder = TxtBuilder::<Vec<u8>>::new();
1044                for string in strings {
1045                    builder
1046                        .append_charstr(
1047                            CharStr::from_slice(string.as_bytes()).unwrap(),
1048                        )
1049                        .unwrap();
1050                }
1051                builder.finish().unwrap()
1052            })
1053            .collect::<Vec<_>>();
1054
1055        assert_ne!(records[0], records[1]);
1056        assert_eq!(records[0], records[2]);
1057    }
1058
1059    #[cfg(all(feature = "serde", feature = "std"))]
1060    #[test]
1061    fn txt_ser_de() {
1062        use serde_test::{assert_tokens, Configure, Token};
1063
1064        let txt = Txt::from_octets(Vec::from(b"\x03foo".as_ref())).unwrap();
1065        assert_tokens(
1066            &txt.clone().compact(),
1067            &[
1068                Token::NewtypeStruct { name: "Txt" },
1069                Token::ByteBuf(b"\x03foo"),
1070            ],
1071        );
1072        assert_tokens(
1073            &txt.readable(),
1074            &[
1075                Token::NewtypeStruct { name: "Txt" },
1076                Token::Seq { len: None },
1077                Token::NewtypeStruct { name: "CharStr" },
1078                Token::BorrowedStr("foo"),
1079                Token::SeqEnd,
1080            ],
1081        );
1082
1083        let txt = Txt::from_octets(
1084            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}
1139