1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! A fully qualified domain name representation
//!
//! Notice that a fully qualified domain name (FQDN) is case-insensitive.
//! So the implementation of traits like `Hash` or `PartialEq` do the same.
//!
//! # Crate features
//! These features control how the parsing of a String should be done.
//! Violation of one of these activated limitations raises an error (see [`Error`]).
//!
//! Some limitations are set by the Internet RFC but, by default, some of them are relaxed to fit
//! with more applicative contexts. The following features are available in order to activate or not these
//! limitations, depending on applicative purposes.
//!
//!
//! ### `domain-label-length-limited-to-63`
//! The internet standards specifies that each label of a FQDN is limited to 63 characters.
//! By default, this crate allows up to 256 characters but the 63 limitation could be set
//! through the activation of this feature.
//!
//! ### `domain-name-length-limited-to-255`
//! The internet standards specifies that the total length of a FQDN is limited to 255 characters.
//! By default, the only limit is the available memory but the 255 limitation could be set
//! through the activation of this feature.
//!
//! ### `domain-name-without-special-chars`
//! The internet standards specifies that a FQDN should only contains digits, letters and hyphens (`-`).
//! But, many network equipment accept also `_` (underscore) without any problem. If this crate is used to design
//! something like a firewall, it could be necessary to deal with this, so do this feature.
//! At the contrary, the activation of this feature refuses these special characters.
//!
//! ### `domain-label-should-start-with-letter`
//! The internet standards specifies that FQDN should always start with a letter (nor a digit, nor a hyphen).
//! By default, this crate accept any of theses characters event at the first position.
//! The activation of this feature forces the use of a letter at the beginning of FQDN.
//!
//! ### `domain-label-should-have-trailing-dot`
//! The internet standards specifies that the human readable representation of FQDN should always end with a dot.
//! If this feature is activated, then parsing or printing a FQDN strictly apply this rule. By default,
//! the parsing behavior is more lenient (i.e. the trailing dot could miss).
//!
//! # RFC 1035
//! The RFC 1035 has some restrictions that are not activated by default.
//! The feature `strict-rfc-1035` activates all of them:
//! * `domain-label-length-limited-to-63`
//! * `domain-name-length-limited-to-255`
//! * `domain-name-without-special-chars`
//! * `domain-label-should-start-with-letter`
//! * `domain-label-should-have-trailing-dot`
//!
//! See above for more details.
//!
mod fqdnref;
mod fqdn;
mod check;


/// Parses a list of strings and creates an new
/// FQDN by concatenating them.
///
/// If the trailing dot is missing, it is automatically added.
///
/// # Examples
/// ```
/// use fqdn::fqdn;
///
/// let fqdn = fqdn!("rust-lang", "github.io");
/// ```
/// # Panics
/// If one of the elements is not a valid symbol, the macro panics.
/// ```should_panic
/// use fqdn::fqdn;
///
/// let s = fqdn!("w@w","fr"); // panics !!
/// ```
#[macro_export]
macro_rules! fqdn {
    ($($args:expr),*) => {{
        #[allow(unused_mut)]
        let mut str = std::string::String::new();
        $( str += "."; str += $args; )*
        match str.as_str().as_bytes().last() {
            None => $crate::FQDN::default(),
            Some(b'.') => str[1..].parse::<$crate::FQDN>().unwrap(),
            _ => (str + ".")[1..].parse::<$crate::FQDN>().unwrap(),
        }
    }}
}

pub use crate::fqdn::FQDN;
pub use fqdnref::Fqdn;
pub use check::Error;

#[cfg(test)]
mod tests {
    use crate as fqdn;
    use fqdn::*;

    #[test]
    fn parsing_string()
    {
        assert!(FQDN::default().is_root());
        assert!("github.com.".parse::<FQDN>().is_ok());

        #[cfg(feature="domain-name-should-have-trailing-dot")]
        assert_eq!("crates.io".parse::<FQDN>(), Err(fqdn::Error::TrailingDotMissing));

        #[cfg(not(feature="domain-name-should-have-trailing-dot"))]
        assert_eq!("crates.io".parse::<FQDN>().map(|fqdn| fqdn.to_string()), Ok("crates.io.".to_string()));

        assert_eq!("github..com.".parse::<FQDN>(), Err(fqdn::Error::EmptyLabel));
        assert_eq!(".github.com.".parse::<FQDN>(), Err(fqdn::Error::EmptyLabel));
        assert_eq!("git@ub.com.".parse::<FQDN>(), Err(fqdn::Error::InvalidLabelChar));
    }

    #[test]
    fn parsing_bytes()
    {
        assert!(Fqdn::from_bytes(b"\x06github\x03com\x00").is_ok());

        assert_eq!(Fqdn::from_bytes(b"\x06github\x03com"), Err(fqdn::Error::TrailingNulCharMissing));
        assert_eq!(Fqdn::from_bytes(b"\x06g|thub\x03com\x00"), Err(fqdn::Error::InvalidLabelChar));

        #[cfg(feature="domain-label-should-start-with-letter")]
        assert_eq!(Fqdn::from_bytes(b"\x04yeah\x0512345\x03com\x00"), Err(fqdn::Error::LabelDoesNotStartWithLetter));
    }


    #[test]
    fn depth()
    {
        assert_eq!(".".parse::<FQDN>().map(|f| f.is_root()), Ok(true));
        assert_eq!(".".parse::<FQDN>().map(|f| f.depth()), Ok(0));
        assert_eq!("github.com.".parse::<FQDN>().map(|f| f.depth()), Ok(2));
        assert_eq!("rust-lang.github.com.".parse::<FQDN>().map(|f| f.depth()), Ok(3));
    }

    #[test]
    fn subdomains()
    {
        let a = "rust-lang.github.com.".parse::<FQDN>().unwrap();
        let b = "GitHub.com.".parse::<FQDN>().unwrap();

        assert!( a.is_subdomain_of(&a));
        assert!( a.is_subdomain_of(&b));
        assert!( !b.is_subdomain_of(&a));

        assert!( fqdn!("com").is_tld() );
        assert_eq!( a, fqdn!("rust-lang","github","com") );

    }
}