Skip to main content

rsdns/names/
name.rs

1use crate::{
2    Error, Result,
3    bytes::{Cursor, Reader},
4    constants::DOMAIN_NAME_MAX_LENGTH,
5    names::InlineName,
6};
7use std::{
8    cmp::Ordering,
9    fmt::{self, Display, Formatter},
10    hash::{Hash, Hasher},
11    str::FromStr,
12};
13
14/// A domain name backed by [String].
15///
16/// This struct implements the domain name using the standard [String].
17/// Construction of [Name] involves dynamic memory allocation.
18///
19/// [Name] is used in resource record data, where usage of
20/// [InlineName] would make the size of the [ResourceRecord] structure too large.
21/// For example, the [Soa] record includes two domain names.
22/// This, together with the domain name in the record header, would make the size of
23/// [ResourceRecord] at least 765 bytes long, if [InlineName] was used in record data too.
24///
25/// [Name] stores the name in the canonical form `example.com.`.
26/// The trailing period denotes the root DNS zone.
27///
28/// Domain name max length, as defined in [RFC 1035], is 255 bytes.
29/// This includes all label length bytes, and the terminating zero length byte. Hence the effective
30/// max length of a domain name without the root zone is 253 bytes.
31///
32/// Domain name is case insensitive. Hence, when compared, both sides are converted to
33/// ASCII lowercase. Use [`Name::as_str`] when exact match is required.
34///
35/// Specifications:
36///
37/// - [RFC 1035 section 2.3.1](https://www.rfc-editor.org/rfc/rfc1035.html#section-2.3.1)
38/// - [RFC 1035 section 2.3.4](https://www.rfc-editor.org/rfc/rfc1035.html#section-2.3.4)
39/// - [RFC 1035 section 3.1](https://www.rfc-editor.org/rfc/rfc1035.html#section-3.1)
40/// - [RFC 1101 section 3.1](https://www.rfc-editor.org/rfc/rfc1101.html#section-3.1)
41/// - [RFC 2181 section 11](https://www.rfc-editor.org/rfc/rfc2181#section-11)
42///
43/// [RFC 1035]: https://www.rfc-editor.org/rfc/rfc1035.html#section-3.1
44/// [InlineName]: crate::names::InlineName
45/// [ResourceRecord]: crate::records::ResourceRecord
46/// [Soa]: crate::records::data::Soa
47#[derive(Debug, Default, Clone)]
48pub struct Name {
49    name: String,
50}
51
52impl Name {
53    /// Creates an empty domain name.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// # use rsdns::names::Name;
59    /// #
60    /// let dn = Name::new();
61    /// assert_eq!(dn.len(), 0);
62    /// assert!(dn.is_empty());
63    /// ```
64    #[inline(always)]
65    pub fn new() -> Self {
66        Self {
67            name: Default::default(),
68        }
69    }
70
71    /// Creates the root domain name.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// # use rsdns::names::Name;
77    /// #
78    /// let dn = Name::root();
79    /// assert_eq!(dn.len(), 1);
80    /// assert_eq!(dn.as_str(), ".");
81    /// ```
82    pub fn root() -> Self {
83        Self {
84            name: String::from("."),
85        }
86    }
87
88    fn from(s: &str) -> Result<Self> {
89        super::check_name(s)?;
90
91        let mut dn = Self {
92            // check_name verifies the length of the string,
93            // so the following unwrap will not panic.
94            name: String::from(s),
95        };
96
97        let bytes = s.as_bytes();
98
99        // check_name rejects an empty string, so it is sound to use unchecked access here
100        let last_byte = unsafe { *bytes.get_unchecked(bytes.len() - 1) };
101
102        if last_byte != b'.' {
103            // check_name verifies the length of the string and ensures that
104            // the root zone can be accommodated.
105            // Thus the following push is sound and will not panic.
106            dn.name.push('.');
107        }
108
109        Ok(dn)
110    }
111
112    /// Returns the domain name as a string slice.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// # use rsdns::names::Name;
118    /// # use std::str::FromStr;
119    /// #
120    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
121    /// #
122    /// let dn = Name::new();
123    /// assert_eq!(dn.as_str(), "");
124    ///
125    /// let dn = Name::from_str("example.com")?;
126    /// assert_eq!(dn.as_str(), "example.com.");
127    ///
128    /// let dn = Name::from_str(".")?;
129    /// assert_eq!(dn.as_str(), ".");
130    /// #
131    /// # Ok(())
132    /// # }
133    /// # foo().unwrap();
134    /// ```
135    #[inline(always)]
136    pub fn as_str(&self) -> &str {
137        &self.name
138    }
139
140    /// Returns the length of the domain name in bytes.
141    ///
142    /// Valid domain names are comprised of ASCII characters only.
143    /// Thus this value equals the number of characters in the domain name.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// # use rsdns::names::Name;
149    /// # use std::str::FromStr;
150    /// #
151    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
152    /// #
153    /// let dn = Name::new();
154    /// assert_eq!(dn.len(), 0);
155    ///
156    /// let dn = Name::from_str("example.com")?;
157    /// assert_eq!(dn.len(), 12); // includes the root zone
158    /// #
159    /// # Ok(())
160    /// # }
161    /// # foo().unwrap();
162    /// ```
163    #[inline(always)]
164    pub fn len(&self) -> usize {
165        self.name.len()
166    }
167
168    /// Checks if domain name is empty.
169    ///
170    /// **Note**: empty domain name is not valid.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// # use rsdns::names::Name;
176    /// # use std::str::FromStr;
177    /// #
178    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
179    /// #
180    /// let dn = Name::from_str("example.com")?;
181    /// assert_eq!(dn.is_empty(), false);
182    ///
183    /// let dn = Name::new();
184    /// assert_eq!(dn.is_empty(), true);
185    /// #
186    /// # Ok(())
187    /// # }
188    /// # foo().unwrap();
189    /// ```
190    #[inline(always)]
191    pub fn is_empty(&self) -> bool {
192        self.name.is_empty()
193    }
194
195    /// Make the domain name empty.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// # use rsdns::names::Name;
201    /// # use std::str::FromStr;
202    /// #
203    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
204    /// #
205    /// let mut dn = Name::from_str("example.com")?;
206    /// assert_eq!(dn.is_empty(), false);
207    /// assert_eq!(dn.len(), 12);
208    /// assert_eq!(dn.as_str(), "example.com.");
209    ///
210    /// dn.clear();
211    /// assert_eq!(dn.is_empty(), true);
212    /// assert_eq!(dn.len(), 0);
213    /// assert_eq!(dn.as_str(), "");
214    /// #
215    /// # Ok(())
216    /// # }
217    /// # foo().unwrap();
218    /// ```
219    #[inline(always)]
220    pub fn clear(&mut self) {
221        self.name.clear();
222    }
223
224    pub(crate) fn append_label_bytes(&mut self, label: &[u8]) -> Result<()> {
225        super::check_label_bytes(label)?;
226
227        // at this point the label is proven to be valid,
228        // which means it is sound to convert it unchecked as a valid label is ASCII
229        let label_as_str = unsafe { std::str::from_utf8_unchecked(label) };
230
231        let new_len = self.name.len() + label_as_str.len() + 1;
232        if new_len > DOMAIN_NAME_MAX_LENGTH {
233            return Err(Error::DomainNameTooLong(new_len));
234        }
235
236        self.name.push_str(label_as_str);
237        self.name.push('.');
238
239        Ok(())
240    }
241
242    pub(crate) fn append_label(&mut self, label: &str) -> Result<()> {
243        super::check_label(label)?;
244
245        let new_len = self.name.len() + label.len() + 1;
246        if new_len > DOMAIN_NAME_MAX_LENGTH {
247            return Err(Error::DomainNameTooLong(new_len));
248        }
249
250        self.name.push_str(label);
251        self.name.push('.');
252
253        Ok(())
254    }
255
256    /// Sets the domain name to denote the root DNS zone `.`.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// # use rsdns::names::Name;
262    /// # use std::str::FromStr;
263    /// #
264    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
265    /// #
266    /// let mut dn = Name::new();
267    /// assert!(dn.is_empty());
268    ///
269    /// dn.set_root();
270    /// assert_eq!(dn.as_str(), ".");
271    ///
272    /// dn = Name::from_str("example.com")?;
273    /// assert_eq!(dn.as_str(), "example.com.");
274    ///
275    /// dn.set_root();
276    /// assert_eq!(dn.as_str(), ".");
277    /// #
278    /// # Ok(())
279    /// # }
280    /// # foo().unwrap();
281    /// ```
282    pub fn set_root(&mut self) {
283        self.name.clear();
284        self.name.push('.');
285    }
286}
287
288impl TryFrom<&str> for Name {
289    type Error = Error;
290
291    fn try_from(value: &str) -> Result<Self> {
292        Self::from(value)
293    }
294}
295
296impl FromStr for Name {
297    type Err = Error;
298
299    fn from_str(s: &str) -> Result<Self> {
300        Self::from(s)
301    }
302}
303
304impl AsRef<str> for Name {
305    fn as_ref(&self) -> &str {
306        &self.name
307    }
308}
309
310impl PartialEq for Name {
311    fn eq(&self, other: &Self) -> bool {
312        self.name
313            .as_bytes()
314            .eq_ignore_ascii_case(other.name.as_bytes())
315    }
316}
317
318impl PartialOrd for Name {
319    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
320        Some(self.cmp(other))
321    }
322}
323
324impl Ord for Name {
325    fn cmp(&self, other: &Self) -> Ordering {
326        for i in 0..self.len().min(other.len()) {
327            let left = unsafe { self.name.as_bytes().get_unchecked(i) };
328            let right = unsafe { other.name.as_bytes().get_unchecked(i) };
329            let ord = left.to_ascii_lowercase().cmp(&right.to_ascii_lowercase());
330            if Ordering::Equal != ord {
331                return ord;
332            }
333        }
334        self.len().cmp(&other.len())
335    }
336}
337
338impl PartialEq<&str> for Name {
339    fn eq(&self, other: &&str) -> bool {
340        let l_is_root = self.name.as_bytes() == b".";
341        let r_is_root = *other == ".";
342
343        match (l_is_root, r_is_root) {
344            (true, true) => return true,
345            (false, false) => {}
346            _ => return false,
347        }
348
349        let mut bytes = self.name.as_bytes();
350        if !bytes.is_empty() && !other.ends_with('.') {
351            bytes = &bytes[..bytes.len() - 1];
352        }
353
354        bytes.eq_ignore_ascii_case(other.as_bytes())
355    }
356}
357
358impl Eq for Name {}
359
360impl Hash for Name {
361    fn hash<H: Hasher>(&self, state: &mut H) {
362        for b in self.name.as_bytes() {
363            state.write_u8(b.to_ascii_lowercase());
364        }
365    }
366}
367
368impl Display for Name {
369    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
370        f.pad(self.as_str())
371    }
372}
373
374impl From<Name> for String {
375    fn from(name: Name) -> Self {
376        name.name
377    }
378}
379
380impl From<InlineName> for Name {
381    fn from(name: InlineName) -> Self {
382        Self {
383            name: name.as_str().to_string(),
384        }
385    }
386}
387
388impl From<&InlineName> for Name {
389    fn from(name: &InlineName) -> Self {
390        Self {
391            name: name.as_str().to_string(),
392        }
393    }
394}
395
396impl super::private::DNameBase for Name {
397    #[inline(always)]
398    fn as_str(&self) -> &str {
399        self.as_str()
400    }
401
402    #[inline(always)]
403    fn len(&self) -> usize {
404        self.len()
405    }
406
407    #[inline(always)]
408    fn is_empty(&self) -> bool {
409        self.is_empty()
410    }
411
412    #[inline(always)]
413    fn clear(&mut self) {
414        self.clear()
415    }
416
417    #[inline(always)]
418    fn append_label_bytes(&mut self, label: &[u8]) -> Result<()> {
419        self.append_label_bytes(label)
420    }
421
422    #[inline(always)]
423    fn append_label(&mut self, label: &str) -> Result<()> {
424        self.append_label(label)
425    }
426
427    #[inline(always)]
428    fn set_root(&mut self) {
429        self.set_root()
430    }
431
432    #[inline(always)]
433    fn from_cursor(c: &mut Cursor<'_>) -> Result<Self> {
434        c.read()
435    }
436}
437
438impl super::DName for Name {}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443    use std::collections::HashSet;
444
445    #[test]
446    fn test_new() {
447        let dn = Name::new();
448
449        assert!(dn.is_empty());
450        assert_eq!(dn.len(), 0);
451    }
452
453    #[test]
454    fn test_default() {
455        let dn: Name = Default::default();
456
457        assert!(dn.is_empty());
458        assert_eq!(dn.len(), 0);
459    }
460
461    #[test]
462    fn test_from() {
463        let label_63 = "a".repeat(63);
464        let label_61 = "b".repeat(60);
465
466        let dn_253 = [
467            label_63.as_str(),
468            label_63.as_str(),
469            label_63.as_str(),
470            label_61.as_str(),
471        ]
472        .join(".");
473
474        let dn_254 = dn_253.clone() + ".";
475
476        let dn_255 = [
477            label_63.as_str(),
478            label_63.as_str(),
479            label_63.as_str(),
480            label_63.as_str(),
481        ]
482        .join(".");
483
484        let success_cases = &[
485            "3om",
486            "com",
487            "example.com",
488            "sub.example.com",
489            "3ub.example.com",
490            ".",
491            "example.com.",
492            "3xample.com.",
493            "EXAMPLE.com",
494            "EXAMPLE.COM",
495            "EXAMPLE.COM.",
496            dn_253.as_str(),
497            dn_254.as_str(),
498        ];
499
500        for sc in success_cases {
501            let dn = Name::from(sc).unwrap();
502            let expected = if sc.ends_with('.') {
503                sc.to_string()
504            } else {
505                format!("{sc}.")
506            };
507            assert_eq!(dn.as_str(), &expected);
508            assert_eq!(dn.len(), expected.len());
509        }
510
511        let failure_cases = &[
512            "",
513            "..",
514            "3c-",
515            "co-",
516            "example..com",
517            "sub..example.com",
518            "example-.com",
519            "-xample.com",
520            "examp|e.com",
521            "exa\u{203C}ple.com",
522            dn_255.as_str(),
523        ];
524
525        for fc in failure_cases {
526            assert!(Name::from(fc).is_err())
527        }
528    }
529
530    #[test]
531    fn test_len() {
532        let mut dn = Name::new();
533        assert_eq!(dn.len(), 0);
534
535        dn.append_label("example").unwrap();
536        assert_eq!(dn.len(), 8);
537
538        dn.append_label("com").unwrap();
539        assert_eq!(dn.len(), 12);
540    }
541
542    #[test]
543    fn test_append_label_too_long() {
544        let l_63 = "a".repeat(63);
545        let l_62 = "b".repeat(62);
546
547        let mut dn = Name::new();
548
549        dn.append_label(&l_63).unwrap();
550        assert_eq!(dn.len(), 64);
551
552        dn.append_label(&l_63).unwrap();
553        assert_eq!(dn.len(), 128);
554
555        dn.append_label(&l_63).unwrap();
556        assert_eq!(dn.len(), 192);
557
558        // test total size > 255
559        {
560            let mut dn = dn.clone();
561            dn.append_label("small").unwrap();
562
563            let res = dn.append_label(&l_63);
564            assert!(
565                matches!(res, Err(Error::DomainNameTooLong(s)) if s == dn.len() + l_63.len() + 1)
566            );
567        }
568
569        // test total size == 255
570        let res = dn.clone().append_label(&l_63);
571        assert!(matches!(res, Err(Error::DomainNameTooLong(s)) if s == dn.len() + l_63.len() + 1));
572
573        dn.append_label(&l_62).unwrap();
574        assert_eq!(dn.len(), 255);
575    }
576
577    #[test]
578    fn test_eq() {
579        let dn1 = Name::from("example.com").unwrap();
580        let dn2 = Name::from("EXAMPLE.COM").unwrap();
581        let dn3 = Name::from("eXaMpLe.cOm").unwrap();
582
583        assert_eq!(dn1, dn2);
584        assert_eq!(dn1, dn3);
585        assert_eq!(dn2, dn3);
586    }
587
588    #[test]
589    fn test_neq() {
590        let dn1 = Name::from("example.com").unwrap();
591        let dn2 = Name::from("sub.example.com").unwrap();
592        let dn3 = Name::from("Sub.examp1e.com").unwrap();
593
594        assert_ne!(dn1, dn2);
595        assert_ne!(dn1, dn3);
596        assert_ne!(dn2, dn3);
597    }
598
599    #[test]
600    fn test_eq_str() {
601        let dn1 = Name::from("example.com").unwrap();
602        let dn2 = Name::from("EXAMPLE.COM").unwrap();
603
604        assert_eq!(dn1, "EXAMPLE.COM.");
605        assert_eq!(dn1, "EXAMPLE.COM");
606
607        assert_eq!(dn1, "eXaMpLe.cOm.");
608        assert_eq!(dn2, "eXaMpLe.cOm");
609
610        assert_eq!(dn2, "eXaMpLe.cOm");
611        assert_eq!(dn2, "eXaMpLe.cOm.");
612
613        assert_eq!(Name::from("sub.example.com").unwrap(), "sub.example.com.");
614        assert_eq!(Name::from("sub.example.com.").unwrap(), "sub.example.com");
615
616        assert_eq!(Name::new(), "");
617        assert_eq!(Name::root(), ".");
618    }
619
620    #[test]
621    fn test_neq_str() {
622        let dn1 = Name::from("example.com").unwrap();
623        let dn2 = Name::from("sub.example.com").unwrap();
624
625        assert_ne!(dn1, "sub.example.com");
626        assert_ne!(dn1, "sub.example.com.");
627
628        assert_ne!(dn1, "Sub.examp1e.com");
629        assert_ne!(dn1, "Sub.examp1e.com.");
630
631        assert_ne!(dn2, "Sub.examp1e.com");
632        assert_ne!(dn2, "Sub.examp1e.com.");
633
634        assert_ne!(Name::new(), ".");
635        assert_ne!(Name::root(), "");
636    }
637
638    #[test]
639    fn test_hash() {
640        let dn = Name::from("example.com").unwrap();
641
642        let mut s = HashSet::new();
643        s.insert(dn);
644
645        assert!(s.contains(&Name::from("example.com.").unwrap()));
646        assert!(s.contains(&Name::from("eXaMpLe.COM").unwrap()));
647        assert!(s.contains(&Name::from("EXAMPLE.COM").unwrap()));
648
649        assert!(!s.contains(&Name::from("suB.Example.com.").unwrap()));
650    }
651
652    #[test]
653    fn test_ord() {
654        let dn1 = Name::from("example.com").unwrap();
655        let dn2 = Name::from("ExaMplE.com").unwrap();
656        let dn3 = Name::from("Sub.example.com").unwrap();
657
658        assert_eq!(Ordering::Equal, dn1.cmp(&dn2));
659        assert_eq!(Ordering::Less, dn1.cmp(&dn3));
660        assert_eq!(Ordering::Greater, dn3.cmp(&dn1));
661        assert_eq!(Ordering::Equal, Name::root().cmp(&Name::root()));
662        assert_eq!(Ordering::Equal, Name::new().cmp(&Name::new()));
663    }
664
665    #[test]
666    fn test_partial_ord() {
667        let dn1 = Name::from("example.com").unwrap();
668        let dn2 = Name::from("ExaMplE.com").unwrap();
669        let dn3 = Name::from("Sub.example.com").unwrap();
670
671        assert_eq!(Some(Ordering::Equal), dn1.partial_cmp(&dn2));
672        assert_eq!(Some(Ordering::Less), dn1.partial_cmp(&dn3));
673        assert_eq!(Some(Ordering::Greater), dn3.partial_cmp(&dn1));
674        assert_eq!(
675            Some(Ordering::Equal),
676            Name::root().partial_cmp(&Name::root())
677        );
678        assert_eq!(Some(Ordering::Equal), Name::new().partial_cmp(&Name::new()));
679    }
680}