1 /// Check if input is a valid domain label
is_label(input: &str) -> bool2 pub fn is_label(input: &str) -> bool {
3     let mut chars = input.chars();
4 
5     // we need at least one char
6     let first = match chars.next() {
7         None => {
8             return false;
9         }
10         Some(c) => c,
11     };
12 
13     // it can start with an alphanumeric character
14     if !first.is_ascii_alphanumeric() {
15         return false;
16     }
17 
18     // then optionally be followed by any combination of
19     // alphanumeric characters and dashes
20     let last_index = input.len() - 2.min(input.len());
21     for (index, c) in chars.enumerate() {
22         // before finally ending with an alphanumeric character
23         if !c.is_ascii_alphanumeric() && (index == last_index || c != '-') {
24             return false;
25         }
26     }
27 
28     true
29 }
30 
31 /// Check the local part of an email address (before @)
is_email_local(input: &str) -> bool32 pub fn is_email_local(input: &str) -> bool {
33     let mut chars = input.chars();
34 
35     // we need at least one char
36     let first = match chars.next() {
37         None => {
38             return false;
39         }
40         Some(c) => c,
41     };
42 
43     let last_index = input.len() - 2.min(input.len());
44     if first == ' ' {
45         return false;
46     } else if first == '"' {
47         // quoted
48         if input.len() == 1 {
49             return false;
50         }
51         for (index, c) in chars.enumerate() {
52             if index == last_index {
53                 if c != '"' {
54                     return false;
55                 }
56             } else if !is_combined(c) && !is_quoted(c) {
57                 return false;
58             }
59         }
60     } else {
61         // not quoted
62         if first == '.' {
63             return false;
64         }
65         for (index, c) in chars.enumerate() {
66             if !is_combined(c) && (index == last_index || c != '.') {
67                 return false;
68             }
69         }
70     }
71 
72     true
73 }
74 
75 // these characters can be anywhere in the expresion
76 // [[:alnum:]!#$%&'*+/=?^_`{|}~-]
is_global(c: char) -> bool77 fn is_global(c: char) -> bool {
78     c.is_ascii_alphanumeric()
79         || c == '-'
80         || c == '!'
81         || c == '#'
82         || c == '$'
83         || c == '%'
84         || c == '&'
85         || c == '\''
86         || c == '*'
87         || c == '+'
88         || c == '/'
89         || c == '='
90         || c == '?'
91         || c == '^'
92         || c == '_'
93         || c == '`'
94         || c == '{'
95         || c == '|'
96         || c == '}'
97         || c == '~'
98 }
is_non_ascii(c: char) -> bool99 fn is_non_ascii(c: char) -> bool {
100     c as u32 > 0x7f // non-ascii characters (can also be unquoted)
101 }
is_quoted(c: char) -> bool102 fn is_quoted(c: char) -> bool {
103     // ["(),\\:;<>@\[\]. ]
104     c == '"'
105         || c == '.'
106         || c == ' '
107         || c == '('
108         || c == ')'
109         || c == ','
110         || c == '\\'
111         || c == ':'
112         || c == ';'
113         || c == '<'
114         || c == '>'
115         || c == '@'
116         || c == '['
117         || c == ']'
118 }
is_combined(c: char) -> bool119 fn is_combined(c: char) -> bool {
120     is_global(c) || is_non_ascii(c)
121 }
122 
123 #[cfg(test)]
124 mod test {
125     use super::*;
126 
127     #[test]
is_label_correct()128     fn is_label_correct() {
129         for l in &["a", "ab", "a-b", "a--b", "0Z"] {
130             assert!(is_label(l));
131         }
132     }
133 
134     #[test]
is_label_incorrect()135     fn is_label_incorrect() {
136         for l in &["", "-", "a-", "-b", "$"] {
137             assert!(!is_label(l));
138         }
139     }
140 
141     #[test]
is_email_local_correct()142     fn is_email_local_correct() {
143         for l in &[
144             "a",
145             "ab",
146             "a.b",
147             "a\u{0080}",
148             "$",
149             "\"\"\"",
150             "\"a b\"",
151             "\" \"",
152             "\"a<>@\"",
153         ] {
154             assert!(is_email_local(l));
155         }
156     }
157 
158     #[test]
is_email_local_incorrect()159     fn is_email_local_incorrect() {
160         for l in &["", " a", "a ", "a.", ".b", "a\x7f", "\"", "\"a", "a\""] {
161             assert!(!is_email_local(l));
162         }
163     }
164 }
165