use crate::AsValidateStr;
use regex::Regex;
lazy_static! {
static ref EMAIL_USER_RE: Regex = Regex::new(r"^(?i)[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z").unwrap();
static ref EMAIL_DOMAIN_RE: Regex = Regex::new(
r"(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"
).unwrap();
static ref EMAIL_LITERAL_RE: Regex = Regex::new(r"(?i)\[([A-f0-9:\.]+)\]\z").unwrap();
}
#[derive(Debug, Clone)]
pub struct Email;
impl Email {
pub fn validate<'a, T>(&self, val: &'a T) -> bool
where
T: AsValidateStr<'a>,
{
if let Some(val) = val.as_validate_str().as_ref() {
if val.is_empty() || !val.contains('@') {
return false;
}
let parts: Vec<&str> = val.rsplitn(2, '@').collect();
let user_part = parts[1];
if !EMAIL_USER_RE.is_match(user_part) {
return false;
}
let domain_part = parts[0];
if !validate_domain_part(domain_part) {
return match idna::domain_to_ascii(domain_part) {
Ok(d) => validate_domain_part(&d),
Err(_) => false,
};
}
}
true
}
}
fn validate_domain_part(domain_part: &str) -> bool {
if EMAIL_DOMAIN_RE.is_match(domain_part) {
return true;
}
match EMAIL_LITERAL_RE.captures(domain_part) {
Some(caps) => match caps.get(1) {
Some(c) => crate::ip::Ip.validate(&c.as_str()),
None => false,
},
None => false,
}
}
#[cfg(feature = "proc")]
impl quote::ToTokens for Email {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::quote;
tokens.extend(quote! {
(::prove::email::Email)
});
}
}