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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Name type and helper types
//!
use std::fmt;
use std::ascii::AsciiExt;
use std::str::FromStr;
use std::num::ParseIntError;
use std::sync::Arc;

quick_error! {
    /// Error parsing Name from string
    #[derive(Debug)]
    pub enum Error wraps ErrorEnum {
        DotError {
            description("name can't start with dot and \
                can't have subsequent dots")
        }
        InvalidChar {
            description("only ascii numbers and letters, \
                dash `-`, underscore `_` and dot `.` are supported in names")
        }
        InvalidPrefixSuffix {
            description("any part of name can't start or end with dash")
        }
        InvalidPort(err: ParseIntError) {
             description("error parsing default port number")
             display("default port number is invalid: {}", err)
             from()
        }
    }
}

/// A name is a barely ``Arc<String>`` but also checks that name is valid
///
/// Note: this is designed to be static, because it's often used inside
/// the futures which can't contain non-static content.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)]
pub struct Name(Arc<str>);

impl AsRef<str> for Name {
    fn as_ref(&self) -> &str {
        self.0.as_ref()
    }
}

impl Name {
    /// Create a name from an Arc.
    ///
    /// This allows to keep Arc shared with
    /// other components of your application
    pub fn from_arc(arc: &Arc<str>) -> Result<Name, Error> {
        namecheck(arc)?;
        Ok(Name(arc.clone()))
    }
    /// Return a clone of the inner Arc
    ///
    /// This allows to keep Arc shared with
    /// other components of your application
    pub fn inner(&self) -> Arc<str> {
        self.0.clone()
    }
}

impl FromStr for Name {
    type Err = Error;
    fn from_str(value: &str) -> Result<Name, Error> {
        namecheck(value)?;
        Ok(Name(value.into()))
    }
}

fn namecheck(mut name: &str) -> Result<(), Error> {
    // The dot at the end is allowed (means don't add search domain)
    if name.ends_with('.') {
        name = &name[..name.len()-1];
    }
    let pieces = name.split('.');
    for piece in pieces {
        if piece.len() == 0 {
            return Err(ErrorEnum::DotError.into());
        }
        if !piece.chars()
            .all(|c| c.is_ascii() &&
                (c.is_lowercase() || c.is_numeric() || c == '-' || c == '_'))
        {
            return Err(ErrorEnum::InvalidChar.into());
        }
        if piece.starts_with("-") || piece.ends_with("-") {
            return Err(ErrorEnum::InvalidPrefixSuffix.into());
        }
    }
    Ok(())
}

impl fmt::Display for Name {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&self.0)
    }
}


#[cfg(test)]
mod test {
    use std::str::FromStr;
    use super::Name;

    fn name_str(src: &str) -> Name {
        Name::from_str(src).unwrap()
    }
    fn name_err(src: &str) -> String {
        Name::from_str(src).unwrap_err().to_string()
    }
    fn bare(name: &str) -> Name {
        Name(name.into())
    }

    #[test]
    fn bare_name() {
        assert_eq!(name_str("localhost"), bare("localhost"));
        assert_eq!(name_str("host.name.org"), bare("host.name.org"));
        assert_eq!(name_str("name.root."), bare("name.root."));
    }

    #[test]
    fn name_with_numbers() {
        assert_eq!(name_str("x1"), bare("x1"));
        assert_eq!(name_str("x1.y1"), bare("x1.y1"));
        assert_eq!(name_str("1.2.x"), bare("1.2.x"));
    }

    #[test]
    fn name_with_dashes() {
        assert_eq!(name_str("x-a"), bare("x-a"));
        assert_eq!(name_str("x-a.y-b"), bare("x-a.y-b"));
    }

    #[test]
    fn display() {
        assert_eq!(bare("localhost").to_string(), "localhost");
        assert_eq!(bare("name.example.org.").to_string(), "name.example.org.");
        assert_eq!(bare("name.example.org").to_string(), "name.example.org");
    }

    #[test]
    fn dash() {
        assert_eq!(name_err("-name"),
            "any part of name can\'t start or end with dash");
        assert_eq!(name_err("name-"),
            "any part of name can\'t start or end with dash");
        assert_eq!(name_err("x.-y"),
            "any part of name can\'t start or end with dash");
        assert_eq!(name_err("x-.y"),
            "any part of name can\'t start or end with dash");
        assert_eq!(name_err("x-.-y"),
            "any part of name can\'t start or end with dash");
        assert_eq!(name_err("-xx.yy-"),
            "any part of name can\'t start or end with dash");
    }

    #[test]
    fn two_dots() {
        assert_eq!(name_err("name..name"),
            "name can\'t start with dot and can\'t have subsequent dots");
        assert_eq!(name_err(".name.org"),
            "name can\'t start with dot and can\'t have subsequent dots");
        assert_eq!(name_err("..name.org"),
            "name can\'t start with dot and can\'t have subsequent dots");
        assert_eq!(name_err("name.org.."),
            "name can\'t start with dot and can\'t have subsequent dots");
    }
}