Skip to content

Commit 342fe9f

Browse files
committed
add is_valid_email_address
1 parent 28dda98 commit 342fe9f

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@
314314
* [Burrows Wheeler Transform](https://github.com/TheAlgorithms/Rust/blob/master/src/string/burrows_wheeler_transform.rs)
315315
* [Duval Algorithm](https://github.com/TheAlgorithms/Rust/blob/master/src/string/duval_algorithm.rs)
316316
* [Hamming Distance](https://github.com/TheAlgorithms/Rust/blob/master/src/string/hamming_distance.rs)
317+
* [Is Valid Email Address](https://github.com/TheAlgorithms/Rust/blob/master/src/string/is_valid_email_address.rs)
317318
* [Isomorphism](https://github.com/TheAlgorithms/Rust/blob/master/src/string/isomorphism.rs)
318319
* [Jaro Winkler Distance](https://github.com/TheAlgorithms/Rust/blob/master/src/string/jaro_winkler_distance.rs)
319320
* [Knuth Morris Pratt](https://github.com/TheAlgorithms/Rust/blob/master/src/string/knuth_morris_pratt.rs)

src/string/is_valid_email_address.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
const MAX_LOCAL_PART_LENGTH: u32 = 64;
2+
const MAX_DOMAIN_LENGTH: u32 = 255;
3+
const CHARACTERS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
4+
const SYMBOLS: &str = "!#$%&'*+-/=?^_`{|}~";
5+
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+
31+
let parts: Vec<String> = email_address
32+
.split('@')
33+
.map(|x| x.to_string())
34+
.collect::<Vec<String>>();
35+
36+
// (1) ensure there is only one '@' symbol in the address
37+
if parts.len() != 2 {
38+
return false;
39+
}
40+
41+
let (local_part, domain): (String, String) = (parts[0].clone(), parts[1].clone());
42+
43+
// (2) check that the length of the two parts are within the valid range
44+
if local_part.len() > MAX_LOCAL_PART_LENGTH as usize
45+
|| domain.len() > MAX_DOMAIN_LENGTH as usize
46+
{
47+
return false;
48+
}
49+
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
63+
if local_part.starts_with('.') || local_part.ends_with('.') || local_part.contains("..") {
64+
return false;
65+
}
66+
67+
// (5) check that the characters in the local part are valid
68+
for item in local_part.chars() {
69+
if !CHARACTERS.contains(item) && !SYMBOLS.contains(item) && item != '.' && item != '"' {
70+
return false;
71+
}
72+
}
73+
74+
// (6) check that the domain contains valid characters
75+
for item in domain.chars() {
76+
if !CHARACTERS.contains(item) && item != '-' && item != '.' && !"[]:".contains(item) {
77+
return false;
78+
}
79+
}
80+
81+
// (7) validate the dots in the domain
82+
if domain.starts_with('.') || domain.ends_with('.') || domain.contains("..") {
83+
return false;
84+
}
85+
86+
// (8) validate the dashes in the domain
87+
if domain.starts_with('-') || domain.ends_with('-') {
88+
return false;
89+
}
90+
91+
true
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use crate::string::is_valid_email_address::is_valid_email_address;
97+
98+
macro_rules! test_is_valid_email_address {
99+
($($name:ident: $inputs:expr,)*) => {
100+
$(
101+
#[test]
102+
fn $name() {
103+
let (s, expected) = $inputs;
104+
assert_eq!(is_valid_email_address(s), expected);
105+
}
106+
)*
107+
}
108+
}
109+
110+
test_is_valid_email_address! {
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+
tld: ("[email protected]", true),
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),
138+
}
139+
}

src/string/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod boyer_moore_search;
55
mod burrows_wheeler_transform;
66
mod duval_algorithm;
77
mod hamming_distance;
8+
mod is_valid_email_address;
89
mod isomorphism;
910
mod jaro_winkler_distance;
1011
mod knuth_morris_pratt;
@@ -31,6 +32,7 @@ pub use self::burrows_wheeler_transform::{
3132
};
3233
pub use self::duval_algorithm::duval_algorithm;
3334
pub use self::hamming_distance::hamming_distance;
35+
pub use self::is_valid_email_address::is_valid_email_address;
3436
pub use self::isomorphism::is_isomorphic;
3537
pub use self::jaro_winkler_distance::jaro_winkler_distance;
3638
pub use self::knuth_morris_pratt::knuth_morris_pratt;

0 commit comments

Comments
 (0)