dominion_parser/body/
name.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::binutils::*;
6use crate::ParseError;
7
8use thiserror::Error;
9
10use std::borrow::Cow;
11use std::fmt;
12use std::iter::zip;
13use std::ops::Deref;
14use std::str;
15
16const INIT_NUM_LABELS: usize = 8;
17
18pub(crate) const MAX_JUMPS: u8 = 5;
19
20pub(crate) const MAX_LABEL_SIZE: usize = 63;
21pub(crate) const MAX_NAME_SIZE: usize = 255;
22
23/// An error was encountered when trying to work with a domain name
24#[derive(Error, Debug)]
25pub enum NameError {
26    /// Some label in the DNS packet it too long, overflowing the packet or not following the DNS specification.
27    #[error(
28        "Specified label length ({0}) is empty or is bigger than DNS specification (maximum {}).",
29        MAX_LABEL_SIZE
30    )]
31    LabelLength(usize),
32    /// Some label in one of the domain names is not valid because it contains characters that are not alphanumeric or `-`.
33    #[error("The provided label is not a valid domain name label")]
34    LabelContent,
35    /// One of the labels in the packet has a length that is bigger than the DNS specification.
36    #[error(
37        "Name length ({0}) is too long, is bigger than DNS specification (maximum {}).",
38        MAX_NAME_SIZE
39    )]
40    NameLength(usize),
41}
42
43/// A domain name represented as an inverted list of labels.
44#[derive(Clone)]
45pub struct Name<'a> {
46    /// Domain name labels
47    labels: Vec<Cow<'a, str>>,
48    /// Length of the domain name
49    len: u8,
50}
51
52impl fmt::Display for Name<'_> {
53    #[inline]
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        for l in self.iter_human() {
56            write!(f, "{}.", l)?;
57        }
58        Ok(())
59    }
60}
61
62impl fmt::Debug for Name<'_> {
63    #[inline]
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        for l in self.iter_human() {
66            write!(f, "{}.", l)?;
67        }
68        Ok(())
69    }
70}
71
72impl Default for Name<'_> {
73    #[inline]
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl From<Name<'_>> for Vec<u8> {
80    #[inline]
81    fn from(name: Name<'_>) -> Self {
82        let mut out = Vec::with_capacity(name.len as _);
83        name.serialize(&mut out);
84        out
85    }
86}
87
88impl<'a> TryFrom<&'a str> for Name<'a> {
89    type Error = NameError;
90
91    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
92        let mut name = Name::default();
93        for label in value.rsplit('.') {
94            name.push_label(label.into())?;
95        }
96        Ok(name)
97    }
98}
99
100impl<'a> Name<'a> {
101    /// Parse from the specified `buff`, starting at position `pos`.
102    ///
103    /// # Errors
104    ///
105    /// It will error if the buffer does not contain a valid domain name. If the domain name
106    /// has been compressed the buffer should include all previous bytes from the DNS packet
107    /// to be considered valid. Jump pointers should only point backwards inside the `buf`.
108    #[inline]
109    pub fn parse(buff: &'a [u8], pos: usize) -> Result<(Self, usize), ParseError> {
110        let mut name = Name::new();
111        let blen = buff.len();
112        let (mut pos, mut size, mut jumps) = (pos, 0, 0);
113        loop {
114            if jumps > MAX_JUMPS {
115                Err(ParseError::ExcesiveJumps(jumps))?;
116            }
117            match read_label_metadata(buff, pos)? {
118                LabelMeta::Pointer(ptr) if ptr >= pos => Err(ParseError::InvalidJump)?,
119                LabelMeta::Size(s) if s > MAX_LABEL_SIZE => Err(NameError::LabelLength(s))?,
120                LabelMeta::Size(s) if blen <= pos + s => Err(NameError::LabelLength(s))?,
121                LabelMeta::Size(s) if name.len as usize + s > MAX_NAME_SIZE => {
122                    Err(NameError::NameLength(name.len as usize + s))?
123                }
124                LabelMeta::Size(s) if jumps == 0 => {
125                    name.push_bytes(&buff[pos + 1..pos + s + 1])?;
126                    pos += s + 1;
127                    size += s + 1;
128                }
129                LabelMeta::Size(s) => {
130                    name.push_bytes(&buff[pos + 1..pos + s + 1])?;
131                    pos += s + 1;
132                }
133                LabelMeta::Pointer(ptr) if jumps == 0 => {
134                    (pos, size, jumps) = (ptr, size + 2, jumps + 1);
135                }
136                LabelMeta::Pointer(ptr) => (pos, jumps) = (ptr, jumps + 1),
137                LabelMeta::End if jumps == 0 => {
138                    name.labels.reverse();
139                    return Ok((name, size + 1));
140                }
141                LabelMeta::End => {
142                    name.labels.reverse();
143                    return Ok((name, size));
144                }
145            }
146        }
147    }
148
149    /// Safely push a slice of bytes as as a subdomain label.
150    fn push_bytes(&mut self, bytes: &'a [u8]) -> Result<(), NameError> {
151        if valid_label(bytes) {
152            // SAFETY: Because we have verified that the label is only ASCII alphanumeric + `-`
153            // we now the label is valid UTF8.
154            let label = unsafe { str::from_utf8_unchecked(bytes) };
155            self.labels.push(label.into());
156            // SAFETY: It wont overflow because valid labels have a length that fits in one byte.
157            self.len += bytes.len() as u8 + 1;
158            Ok(())
159        } else {
160            Err(NameError::LabelContent)
161        }
162    }
163
164    /// Serialize the [Name] and append it tho the end of the provided `packet`
165    #[inline]
166    pub fn serialize(&self, packet: &mut Vec<u8>) {
167        for label in self.iter_human() {
168            packet.push(label.len() as _);
169            packet.extend(label.as_bytes());
170        }
171        packet.push(0u8);
172    }
173
174    /// Create a new, empty, domain name.
175    ///
176    /// ```
177    /// # use dominion_parser::body::name::Name;
178    /// let name = Name::new();
179    /// assert_eq!(name.to_string(), "".to_string())
180    /// ```
181    #[inline]
182    pub fn new() -> Self {
183        Name {
184            labels: Vec::with_capacity(INIT_NUM_LABELS),
185            len: 0,
186        }
187    }
188
189    /// Obtain the top level domain (TLD) of the provided domain name.
190    ///
191    /// ```
192    /// # use dominion_parser::body::name::Name;
193    /// let mut name = Name::try_from("example.com").unwrap();
194    /// assert_eq!(name.tld(), Some("com"))
195    /// ```
196    #[inline]
197    pub fn tld(&self) -> Option<&'_ str> {
198        self.labels.first().map(|cow| cow.deref())
199    }
200
201    /// Push a new label to the end of the domain name, as a subdomain of the current one.
202    ///
203    /// # Error
204    ///
205    /// Will error if the label is not a valid DNS label, or if the resulting Domain name is too big.
206    ///
207    /// ```
208    /// # use dominion_parser::body::name::Name;
209    /// let mut name = Name::new();
210    /// name.push_label("com".into()).unwrap();
211    /// name.push_label("example".into()).unwrap();
212    /// assert_eq!(name.to_string(), "example.com.".to_string())
213    /// ```
214    #[inline]
215    pub fn push_label(&mut self, label: Cow<'a, str>) -> Result<(), NameError> {
216        let len = label.len();
217        if label.is_empty() || len > MAX_LABEL_SIZE {
218            Err(NameError::LabelLength(len))
219        } else if len + self.len as usize > MAX_NAME_SIZE {
220            Err(NameError::NameLength(len + self.len as usize))
221        } else if !valid_label(label.as_bytes()) {
222            Err(NameError::LabelContent)
223        } else {
224            // SAFETY: It wont overflow because we have checked that the domain name length is not bigger than 255.
225            self.len += len as u8;
226            self.labels.push(label);
227            Ok(())
228        }
229    }
230
231    /// Get the number of labels in the domain name.
232    ///
233    /// ```
234    /// # use dominion_parser::body::name::Name;
235    /// let mut name = Name::try_from("example.com").unwrap();
236    /// assert_eq!(2, name.label_count())
237    /// ```
238    #[inline]
239    pub fn label_count(&self) -> usize {
240        self.labels.len()
241    }
242
243    /// Check if `sub` is a subdomain of the current domain name.
244    ///
245    /// ```
246    /// # use dominion_parser::body::name::Name;
247    /// let mut name = Name::try_from("example.com").unwrap();
248    /// let mut sub = Name::try_from("subdomain.example.com").unwrap();
249    ///
250    /// assert!(name.is_subdomain(&sub))
251    /// ```
252    #[inline]
253    pub fn is_subdomain(&self, sub: &Name<'_>) -> bool {
254        if self.labels.len() >= sub.labels.len() {
255            false
256        } else {
257            zip(self.iter_hierarchy(), sub.iter_hierarchy()).fold(true, |acc, (x, y)| acc && x == y)
258        }
259    }
260
261    /// Return an iterator over the labels in human order.
262    ///
263    /// ```
264    /// # use dominion_parser::body::name::Name;
265    /// let mut name = Name::try_from("subdomain.example.com").unwrap();
266    /// let mut human = name.iter_human();
267    ///
268    /// assert_eq!(human.next(), Some("subdomain"));
269    /// assert_eq!(human.next(), Some("example"));
270    /// assert_eq!(human.next(), Some("com"));
271    /// ```
272    #[inline]
273    pub fn iter_human(&self) -> impl DoubleEndedIterator<Item = &'_ str> {
274        self.iter_hierarchy().rev()
275    }
276
277    /// Return an iterator over the labels in hierarchical order.
278    ///
279    /// ```
280    /// # use dominion_parser::body::name::Name;
281    /// let mut name = Name::try_from("subdomain.example.com").unwrap();
282    /// let mut hierarchy = name.iter_hierarchy();
283    ///
284    /// assert_eq!(hierarchy.next(), Some("com"));
285    /// assert_eq!(hierarchy.next(), Some("example"));
286    /// assert_eq!(hierarchy.next(), Some("subdomain"));
287    /// ```
288    #[inline]
289    pub fn iter_hierarchy(&self) -> impl DoubleEndedIterator<Item = &'_ str> {
290        self.labels.iter().map(|cow| cow.deref())
291    }
292}
293
294/// A label can only contain a `-` or alphanumeric characters, and must begin with a letter.
295fn valid_label(label: &[u8]) -> bool {
296    label
297        .iter()
298        .all(|&b| b.is_ascii_alphanumeric() || b == b'-')
299}
300
301enum LabelMeta {
302    End,
303    // Although it is really an u8 because it is used for indexing we give an usize
304    Size(usize),
305    // Although it is really an u16 because it is used for indexing we give an usize
306    Pointer(usize),
307}
308
309#[inline]
310fn read_label_metadata(buff: &[u8], pos: usize) -> Result<LabelMeta, ParseError> {
311    let b = safe_u8_read(buff, pos)?;
312    match b {
313        0 => Ok(LabelMeta::End),
314        1..=0b0011_1111 => Ok(LabelMeta::Size(b as _)),
315        0b1100_0000..=0xFF => Ok(LabelMeta::Pointer(
316            (safe_u16_read(buff, pos)? ^ 0b1100_0000_0000_0000) as _,
317        )),
318        _ => Err(ParseError::LabelPrefix(b))?,
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn valid_labels() {
328        let valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
329        let invalid = "hello.world";
330        assert!(valid_label(valid.as_bytes()));
331        assert!(!valid_label(invalid.as_bytes()));
332    }
333
334    #[test]
335    fn no_jumps() {
336        let buff = [
337            5, 104, 101, 108, 108, 111, // hello
338            5, 119, 111, 114, 108, 100, // world
339            3, 99, 111, 109, // com
340            0, 1, 1, 1, // <end>
341        ];
342        let (name, n) = Name::parse(&buff[..], 0).unwrap();
343        assert_eq!(n, 17);
344        assert_eq!(name.to_string(), "hello.world.com.".to_string())
345    }
346
347    #[test]
348    fn with_jumps() {
349        let buff = [
350            5, 119, 111, 114, 108, 100, // world
351            3, 99, 111, 109, // com
352            0, 1, 1, 1, // <end>
353            5, 104, 101, 108, 108, 111, // hello
354            192, 0, 1, 1, 1, 1, 1, 1, // <jump to 0>
355        ];
356        let (name, n) = Name::parse(&buff[..], 14).unwrap();
357        assert_eq!(n, 8);
358        assert_eq!(name.to_string(), "hello.world.com.".to_string())
359    }
360
361    #[test]
362    fn name_parse_with_jumps() {
363        let buff = [
364            5, 119, 111, 114, 108, 100, // world
365            3, 99, 111, 109, // com
366            0, 1, 1, 1, // <end>
367            5, 104, 101, 108, 108, 111, // hello
368            192, 0, 1, 1, 1, 1, 1, 1, // <jump to 0>
369        ];
370        let (name, n) = Name::parse(&buff[..], 14).unwrap();
371        assert_eq!(n, 8);
372        assert_eq!(name.to_string(), "hello.world.com.".to_string())
373    }
374
375    #[test]
376    fn serialize() {
377        let buff = [
378            5, 104, 101, 108, 108, 111, // hello
379            5, 119, 111, 114, 108, 100, // world
380            3, 99, 111, 109, // com
381            0, 1, 1, 1, // <end>
382        ];
383        let (name, _) = Name::parse(&buff[..], 0).unwrap();
384        assert_eq!(name.to_string(), "hello.world.com.".to_string());
385        let out: Vec<u8> = name.into();
386        assert_eq!(&buff[..17], &out[..17])
387    }
388
389    #[test]
390    fn get_tld() {
391        let mut name = Name::new();
392        name.push_label("com".into()).unwrap();
393        name.push_label("world".into()).unwrap();
394        name.push_label("hello".into()).unwrap();
395
396        let tld = name.tld();
397        assert_eq!(tld, Some("com"));
398    }
399
400    #[test]
401    fn add_str_subdomain() {
402        let buff = [5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1]; // world.com
403        let (mut name, _) = Name::parse(&buff[..], 0).unwrap();
404        name.push_label("hello".into()).unwrap();
405        assert_eq!(name.to_string(), "hello.world.com.".to_string())
406    }
407
408    #[test]
409    fn add_string_subdomain() {
410        let sub = String::from("hello");
411        let buff = [5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1]; // world.com
412        let (mut name, _) = Name::parse(&buff[..], 0).unwrap();
413        name.push_label(sub.into()).unwrap();
414        assert_eq!(name.to_string(), "hello.world.com.".to_string())
415    }
416
417    #[test]
418    fn iterate_human() {
419        let mut name = Name::new();
420        name.push_label("com".into()).unwrap();
421        name.push_label("world".into()).unwrap();
422        name.push_label("hello".into()).unwrap();
423
424        let mut human = name.iter_human();
425        assert_eq!(human.next(), Some("hello"));
426        assert_eq!(human.next(), Some("world"));
427        assert_eq!(human.next(), Some("com"));
428    }
429
430    #[test]
431    fn iterate_hierarchy() {
432        let mut name = Name::new();
433        name.push_label("com".into()).unwrap();
434        name.push_label("world".into()).unwrap();
435        name.push_label("hello".into()).unwrap();
436
437        let mut human = name.iter_hierarchy();
438        assert_eq!(human.next(), Some("com"));
439        assert_eq!(human.next(), Some("world"));
440        assert_eq!(human.next(), Some("hello"));
441    }
442
443    #[test]
444    fn check_subdomain() {
445        let mut parent = Name::new();
446        parent.push_label("com".into()).unwrap();
447        parent.push_label("world".into()).unwrap();
448
449        let mut sub = Name::new();
450        sub.push_label("com".into()).unwrap();
451        sub.push_label("world".into()).unwrap();
452        sub.push_label("hello".into()).unwrap();
453
454        assert!(parent.is_subdomain(&sub));
455        assert!(!sub.is_subdomain(&parent));
456    }
457
458    #[test]
459    fn root_subdomain() {
460        let root = Name::default();
461        let subd = Name::try_from("example.com").unwrap();
462
463        assert!(root.is_subdomain(&subd));
464        assert!(!subd.is_subdomain(&root));
465    }
466}