@@ -3,26 +3,31 @@ const MAX_DOMAIN_LENGTH: u32 = 255;
3
3
const CHARACTERS : & str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890" ;
4
4
const SYMBOLS : & str = "!#$%&'*+-/=?^_`{|}~" ;
5
5
6
- /// Returns true if email address is valid, otherwise returns false
7
- ///
8
- /// A valid email address is made up of two parts, the local-part, and
9
- /// the domain.
10
- ///
11
- /// The local-part may contain the following:
12
- /// - uppercase & lowercase latin letters
13
- /// - digits 0-9
14
- /// - characters !#$%&'*+-/=?^_`{|}~
15
- /// - non consecutive dots ('.') not located at start or end of local-part
16
- ///
17
- /// The domain may contain the following:
18
- /// - uppercase & lowercase latin letters
19
- /// - digits 0-9
20
- /// - dash ('-') as long as not at start or end of domain
21
- ///
22
- /// The local-part and domain must be separated by an '@'
23
- /// The local-part must not contain more than 64 characters
24
- /// The domain must not contain more than 255 characters
25
- pub fn is_valid_email_address ( email_address : & str ) -> bool {
6
+ fn clear_quotes ( input : & str ) -> String {
7
+ let mut quote_count = input. starts_with ( '"' ) as u32 ;
8
+ let mut new_local_part: String = String :: from ( input. chars ( ) . next ( ) . unwrap ( ) ) ;
9
+ for i in 1 ..input. len ( ) {
10
+ if input. chars ( ) . nth ( i) . unwrap ( ) == '"' && input. chars ( ) . nth ( i - 1 ) . unwrap ( ) != '\\' {
11
+ quote_count += 1 ;
12
+
13
+ if !new_local_part. starts_with ( '"' ) && quote_count != 1 {
14
+ new_local_part. push ( '"' ) ;
15
+ }
16
+ }
17
+
18
+ if quote_count % 2 == 0 {
19
+ new_local_part. push ( input. chars ( ) . nth ( i) . unwrap ( ) ) ;
20
+ }
21
+ }
22
+
23
+ new_local_part
24
+ }
25
+
26
+ /// Follows email address rules as listed:
27
+ /// https://en.wikipedia.org/wiki/Email_address#Examples
28
+ pub fn is_valid_email_address ( input_email_address : & str ) -> bool {
29
+ let email_address = clear_quotes ( input_email_address) ;
30
+
26
31
let parts: Vec < String > = email_address
27
32
. split ( '@' )
28
33
. map ( |x| x. to_string ( ) )
@@ -42,31 +47,43 @@ pub fn is_valid_email_address(email_address: &str) -> bool {
42
47
return false ;
43
48
}
44
49
45
- // (3) validate the dots in the local part
50
+ // (3) check quote dots
51
+ for i in 1 ..local_part. len ( ) {
52
+ if local_part. chars ( ) . nth ( i - 1 ) . unwrap ( ) == '"'
53
+ && local_part. chars ( ) . nth ( i) . unwrap ( ) == '"'
54
+ {
55
+ let proceeding_quote = local_part. chars ( ) . nth ( i + 1 ) ;
56
+ if proceeding_quote. is_some ( ) && proceeding_quote. unwrap ( ) != '.' {
57
+ return false ;
58
+ }
59
+ }
60
+ }
61
+
62
+ // (4) validate the dots in the local part
46
63
if local_part. starts_with ( '.' ) || local_part. ends_with ( '.' ) || local_part. contains ( ".." ) {
47
64
return false ;
48
65
}
49
66
50
- // (4 ) check that the characters in the local part are valid
67
+ // (5 ) check that the characters in the local part are valid
51
68
for item in local_part. chars ( ) {
52
- if !CHARACTERS . contains ( item) && !SYMBOLS . contains ( item) && item != '.' {
69
+ if !CHARACTERS . contains ( item) && !SYMBOLS . contains ( item) && item != '.' && item != '"' {
53
70
return false ;
54
71
}
55
72
}
56
73
57
- // (5 ) check that the domain contains valid characters
74
+ // (6 ) check that the domain contains valid characters
58
75
for item in domain. chars ( ) {
59
- if !CHARACTERS . contains ( item) && item != '-' && item != '.' {
76
+ if !CHARACTERS . contains ( item) && item != '-' && item != '.' && ! "[]:" . contains ( item ) {
60
77
return false ;
61
78
}
62
79
}
63
80
64
- // (6 ) validate the dots in the domain
81
+ // (7 ) validate the dots in the domain
65
82
if domain. starts_with ( '.' ) || domain. ends_with ( '.' ) || domain. contains ( ".." ) {
66
83
return false ;
67
84
}
68
85
69
- // (7 ) validate the dashes in the domain
86
+ // (8 ) validate the dashes in the domain
70
87
if domain. starts_with ( '-' ) || domain. ends_with ( '-' ) {
71
88
return false ;
72
89
}
@@ -91,22 +108,32 @@ mod tests {
91
108
}
92
109
93
110
test_is_valid_email_address ! {
94
- normal_email
: ( "[email protected] " , true ) ,
95
- email_with_dots
: ( "[email protected] " , true ) ,
96
- dots_with_symbols
: ( "[email protected] " , true ) ,
97
- dash_test
: ( "[email protected] " , true ) ,
98
- short_local_part
: ( "[email protected] " , true ) ,
99
- multiple_dash
: ( "[email protected] " , true ) ,
100
- extreme_valid: ( "longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglon@example.com" , true ) ,
101
- extreme_invalid: ( "longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglongl@example.com" , false ) ,
102
- invalid_at: ( "where.is.at" , false ) ,
103
- multiple_ats
: ( "too@[email protected] " , false ) ,
104
- consecutive_dots
: ( "[email protected] " , false ) ,
105
- invalid_underscore: ( "wrong@no_underscore_here" , false ) ,
106
- dash_start
: ( "[email protected] " , false ) ,
107
- dash_end: ( "also-dont-put@here-" , false ) ,
108
- dot_start: ( ".not_at_start@dont" , false ) ,
109
- dot_end: ( "or-at-end@here." , false ) ,
110
- dot_start_end
: ( "[email protected] ." , false ) ,
111
+ basic
: ( "[email protected] " , true ) ,
112
+ basic_2
: ( "[email protected] " , true ) ,
113
+ cases
: ( "[email protected] " , true ) ,
114
+ one_letter_local
: ( "[email protected] " , true ) ,
115
+ long_email_subdomains
: ( "[email protected] " , true ) ,
116
+ tags
: ( "[email protected] " , true ) ,
117
+ slashes
: ( "name/[email protected] " , true ) ,
118
+ no_tld: ( "admin@example" , true ) ,
119
+
120
+ quotes_with_space: ( "\" \" @example.org" , true ) ,
121
+ quoted_double_dot: ( "\" john..doe\" @example.org" , true ) ,
122
+ host_route
: ( "[email protected] " , true ) ,
123
+ quoted_non_letters: ( r#""very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com"# , true ) ,
124
+ percent_symbol
: ( "user%[email protected] " , true ) ,
125
+ local_end_symbol
: ( "[email protected] " , true ) ,
126
+ ip_address: ( "postmaster@[123.123.123.123]" , true ) ,
127
+ other_ip: ( "postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]" , true ) ,
128
+ begin_with_underscore: ( "_test@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]" , true ) ,
129
+
130
+ no_at: ( "abc.example.com" , false ) ,
131
+ multiple_ats
: ( "a@b@[email protected] " , false ) ,
132
+ bad_local_characters
: ( "a\" b(c)d,e:f;g<h>i[j\\ k][email protected] " , false ) ,
133
+ bad_local_string
: ( "just\" not\" [email protected] " , false ) ,
134
+ bad_backslash
: ( "this is\" not\\ [email protected] " , false ) ,
135
+ escaped_backslash
: ( "this\\ still\\ \" not\\ \\ [email protected] " , false ) ,
136
+ long_local_part: ( "1234567890123456789012345678901234567890123456789012345678901234+x@example.com" , false ) ,
137
+ domain_underscore: ( "i.like.underscores@but_they_are_not_allowed_in_this_part" , false ) ,
111
138
}
112
139
}
0 commit comments