use ascii_utils;
use errors::*;
const MAX_LOCAL_PART: usize = 64;
const MAX_DOMAIN_PART: usize = 255;
const MAX_LABEL: usize = 63;
pub fn is_valid_email(address: &str) -> bool {
parse_email(address).is_ok()
}
pub fn parse_email(address: &str) -> Result<(), ParseError> {
if address.starts_with('@') {
return Err(ParseError::NoLocalPart);
}
if address.ends_with('@') {
return Err(ParseError::NoDomainPart);
}
ascii_utils::check_ascii_printable(address)?;
let mut address_iter = address.split('@');
let local = address_iter.next().unwrap();
let domain = address_iter.next().ok_or(ParseError::NoSignAt)?;
if address_iter.next().is_some() {
return Err(ParseError::TooAt);
}
if local.len() > MAX_LOCAL_PART {
return Err(ParseError::LocalTooLong);
}
if local.starts_with('.') {
return Err(ParseError::LocalStartPeriod);
}
if local.ends_with('.') {
return Err(ParseError::LocalEndPeriod);
}
let mut last_period: bool = false;
for ch in local.chars() {
if ascii_utils::Check::is_letter(ch) || ascii_utils::Check::is_digit(ch) {
if last_period {
last_period = false;
}
continue;
}
match ch {
'!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' | '^' |
'_' | '`' | '{' | '|' | '}' | '~' => {
if last_period {
last_period = false;
}
}
'.' => {
if last_period {
return Err(ParseError::ConsecutivePeriod);
}
last_period = true;
}
_ => return Err(ParseError::WrongCharLocal(ch)),
}
}
if domain.len() > MAX_DOMAIN_PART {
return Err(ParseError::DomainTooLong);
}
if domain.starts_with('.') {
return Err(ParseError::DomainStartPeriod);
}
if domain.ends_with('.') {
return Err(ParseError::DomainEndPeriod);
}
let labels: Vec<&str> = domain.split('.').collect();
if labels.len() == 1 {
return Err(ParseError::NoPeriodDomain);
}
for label in labels {
if label.is_empty() {
return Err(ParseError::ConsecutivePeriod);
}
if label.len() > MAX_LABEL {
return Err(ParseError::LabelTooLong);
}
if let Some(ch) = label.chars().find(|&x| {
!ascii_utils::Check::is_letter(x) && !ascii_utils::Check::is_digit(x) && x != '-'
}) {
return Err(ParseError::WrongCharDomain(ch));
}
let label_bytes = label.as_bytes();
let first_char = label_bytes[0];
if !ascii_utils::Check::is_letter(first_char) && !ascii_utils::Check::is_digit(first_char) {
return Err(ParseError::WrongStartLabel(label_bytes[0] as char));
}
let last_char = label_bytes[label_bytes.len() - 1];
if !ascii_utils::Check::is_letter(last_char) && !ascii_utils::Check::is_digit(last_char) {
return Err(ParseError::WrongEndLabel(last_char as char));
}
}
Ok(())
}
#[test]
fn test_length() {
let local_part = "a".repeat(MAX_LOCAL_PART);
let label = format!("{}.", "x".repeat(MAX_LABEL));
let all_labels = format!("{}", label.repeat(3));
let last_label = "y".repeat(MAX_DOMAIN_PART - all_labels.len());
let input_ok = format!("{}@{}{}", local_part, all_labels, last_label);
assert_eq!(parse_email(&input_ok), Ok(()));
let mut input_err = format!("a{}@{}{}", local_part, all_labels, last_label);
assert_eq!(parse_email(&input_err), Err(ParseError::LocalTooLong));
input_err = format!("{}@{}{}z", local_part, all_labels, last_label);
assert_eq!(parse_email(&input_err), Err(ParseError::DomainTooLong));
input_err = format!("{}@{}x{}", local_part, label, last_label);
assert_eq!(parse_email(&input_err), Err(ParseError::LabelTooLong));
}