use std::borrow::Borrow;
use std::convert::AsRef;
use std::error::Error;
use std::fmt::{self, Display};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::ops::Deref;
use std::str::FromStr;
use crate::ascii::{IgnoreAsciiCaseStr, IgnoreAsciiCaseString};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Capability(EsmtpKeyword);
impl Deref for Capability {
type Target = EsmtpKeyword;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Borrow<IgnoreAsciiCaseStr> for Capability {
fn borrow(&self) -> &IgnoreAsciiCaseStr {
(self.0).0.as_ref()
}
}
impl From<EsmtpKeyword> for Capability {
fn from(keyword: EsmtpKeyword) -> Self {
Capability(keyword)
}
}
impl Into<EsmtpKeyword> for Capability {
fn into(self) -> EsmtpKeyword {
self.0
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct EsmtpKeyword(IgnoreAsciiCaseString);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct EsmtpValue(String);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct EhloParam(String);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Domain(IgnoreAsciiCaseString);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct AddressLiteral(IgnoreAsciiCaseString);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ForwardPath(String);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ReversePath(String);
macro_rules! impl_str_wrapper {
($($name:ident),*) => ($(
impl $name {
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
pub fn from_unchecked<I>(data: I) -> Self
where I: Into<String>
{
let string = data.into();
$name(string.into())
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Into<String> for $name {
fn into(self) -> String {
self.0.into()
}
}
impl PartialEq<str> for $name {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl<'a> PartialEq<&'a str> for $name {
fn eq(&self, other: &&'a str) -> bool {
self.0 == *other
}
}
impl PartialEq<String> for $name {
fn eq(&self, other: &String) -> bool {
&self.0 == other
}
}
)*);
}
impl_str_wrapper!(
Domain,
EhloParam,
AddressLiteral,
EsmtpKeyword,
EsmtpValue,
ForwardPath,
ReversePath
);
impl ReversePath {
pub fn empty() -> Self {
ReversePath("".to_owned())
}
}
impl FromStr for EhloParam {
type Err = SyntaxError;
fn from_str(inp: &str) -> Result<Self, Self::Err> {
let valid = inp.bytes().all(|bch| 33 <= bch && bch <= 126);
if valid {
Ok(EhloParam(inp.into()))
} else {
Err(SyntaxError::EhloParam(inp.into()))
}
}
}
impl EsmtpKeyword {
pub fn new<I>(val: I) -> Result<Self, SyntaxError>
where
I: Into<String>,
{
let val = val.into();
let valid = {
let mut iter = val.chars();
iter.next()
.map(|ch| ch.is_ascii_alphanumeric())
.unwrap_or(false)
&& iter.all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
};
if valid {
let mut val = val;
val.make_ascii_uppercase();
Ok(EsmtpKeyword(val.into()))
} else {
Err(SyntaxError::EsmtpKeyword(val))
}
}
}
impl FromStr for EsmtpKeyword {
type Err = SyntaxError;
fn from_str(inp: &str) -> Result<Self, Self::Err> {
EsmtpKeyword::new(inp)
}
}
impl EsmtpValue {
pub fn new<I>(val: I) -> Result<Self, SyntaxError>
where
I: Into<String>,
{
let val = val.into();
let valid = val
.bytes()
.all(|bch| 33 <= bch && (bch <= 60 || (62 <= bch && bch <= 128)));
if valid {
Ok(EsmtpValue(val))
} else {
Err(SyntaxError::EsmtpValue(val))
}
}
}
impl FromStr for EsmtpValue {
type Err = SyntaxError;
fn from_str(inp: &str) -> Result<Self, Self::Err> {
EsmtpValue::new(inp)
}
}
impl FromStr for Capability {
type Err = SyntaxError;
fn from_str(inp: &str) -> Result<Self, Self::Err> {
EsmtpKeyword::from_str(inp).map(Capability)
}
}
impl Domain {
pub fn new_unchecked(domain: String) -> Self {
Domain(domain.into())
}
}
impl FromStr for Domain {
type Err = SyntaxError;
fn from_str(inp: &str) -> Result<Self, Self::Err> {
let valid = inp.split('.').all(validate_subdomain);
if valid {
Ok(Domain(inp.to_lowercase().into()))
} else {
Err(SyntaxError::Domain(inp.into()))
}
}
}
fn validate_subdomain(inp: &str) -> bool {
let len = inp.len();
let binp = inp.as_bytes();
len > 1
&& binp[0].is_ascii_alphanumeric()
&& binp[1..len - 1]
.iter()
.all(|bch| bch.is_ascii_alphanumeric() || *bch == b'-')
&& binp[len - 1].is_ascii_alphanumeric()
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum SyntaxError {
Domain(String),
EhloParam(String),
AddressLiteral {
tag: String,
value: String,
was_bad_tag: bool,
},
EsmtpValue(String),
EsmtpKeyword(String),
}
impl Display for SyntaxError {
fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
use self::SyntaxError::*;
match self {
Domain(bad_param) => write!(fter, "syntax error parsing Domain in {:?}", bad_param),
EhloParam(bad_param) => {
write!(fter, "syntax error parsing EhloParam in {:?}", bad_param)
}
EsmtpKeyword(bad_kw) => {
write!(fter, "syntax error parsing esmtp-keyword in {:?}", bad_kw)
}
EsmtpValue(bad_value) => {
write!(fter, "syntax error parsing esmtp-value in {:?}", bad_value)
}
AddressLiteral {
tag,
value,
was_bad_tag,
} => {
let place = if *was_bad_tag { "tag" } else { "value" };
write!(
fter,
"syntax error parsing address-literal (malformed {}) in {:?}:{:?}",
place, tag, value
)
}
}
}
}
impl Error for SyntaxError {}
impl AddressLiteral {
#[doc(hidden)]
pub fn custom_literal<AS1, AS2>(
standarized_tag: AS1,
custom_part: AS2,
) -> Result<Self, SyntaxError>
where
AS1: AsRef<str>,
AS2: AsRef<str>,
{
let tag = standarized_tag.as_ref();
let valid_tag = tag
.as_bytes()
.last()
.map(|bch| *bch != b'-')
.unwrap_or(false)
&& tag
.bytes()
.all(|bch| bch.is_ascii_alphanumeric() || bch == b'-');
if !valid_tag {
return Err(SyntaxError::AddressLiteral {
tag: tag.into(),
value: custom_part.as_ref().into(),
was_bad_tag: true,
});
}
let custom_part = custom_part.as_ref();
let valid = custom_part
.bytes()
.all(|bch| (33 <= bch && bch <= 90) || (94 <= bch && bch <= 126));
if valid {
Ok(AddressLiteral(format!("[{}:{}]", tag, custom_part).into()))
} else {
Err(SyntaxError::AddressLiteral {
tag: tag.into(),
value: custom_part.into(),
was_bad_tag: false,
})
}
}
}
impl From<IpAddr> for AddressLiteral {
fn from(addr: IpAddr) -> Self {
use self::IpAddr::*;
match addr {
V4(addr) => AddressLiteral::from(addr),
V6(addr) => AddressLiteral::from(addr),
}
}
}
impl From<Ipv4Addr> for AddressLiteral {
fn from(addr: Ipv4Addr) -> Self {
AddressLiteral::from(&addr)
}
}
impl From<Ipv6Addr> for AddressLiteral {
fn from(addr: Ipv6Addr) -> Self {
AddressLiteral::from(&addr)
}
}
impl<'a> From<&'a Ipv4Addr> for AddressLiteral {
fn from(addr: &'a Ipv4Addr) -> Self {
AddressLiteral(format!("[{}]", addr).into())
}
}
impl<'a> From<&'a Ipv6Addr> for AddressLiteral {
fn from(addr: &'a Ipv6Addr) -> Self {
AddressLiteral(format!("[IPv6:{}]", addr).into())
}
}
#[cfg(test)]
mod test {
#![allow(non_snake_case)]
mod EhloParams {
use super::super::EhloParam;
#[test]
fn case_sensitive() {
let a: EhloParam = "affen".parse().unwrap();
let b: EhloParam = "AFFEN".parse().unwrap();
assert_ne!(a, b);
assert_ne!(a, "aFFen")
}
#[test]
fn displayed_unchanged() {
let a: EhloParam = "afFen".parse().unwrap();
let s: String = a.into();
assert_eq!(s, "afFen")
}
}
mod EsmtpKeyword {
use super::super::EsmtpKeyword;
#[test]
fn case_insensitive() {
let a: EsmtpKeyword = "affen".parse().unwrap();
let b: EsmtpKeyword = "AFFEN".parse().unwrap();
let c: EsmtpKeyword = "AffEN".parse().unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(a, "aFFen");
}
#[test]
fn displayed_uppercase() {
let a: EsmtpKeyword = "afFen".parse().unwrap();
let s: String = a.into();
assert_eq!(s, "AFFEN")
}
}
mod EsmtpValue {
use super::super::EsmtpValue;
#[test]
fn case_sensitive() {
let a: EsmtpValue = "affen".parse().unwrap();
let b: EsmtpValue = "AFFEN".parse().unwrap();
assert_ne!(a, b);
assert_ne!(a, "aFFen");
}
#[test]
fn displayed_unchanged() {
let a: EsmtpValue = "afFen".parse().unwrap();
let s: String = a.into();
assert_eq!(s, "afFen")
}
}
mod Capability {
use super::super::Capability;
use crate::ascii::IgnoreAsciiCaseStr;
use std::collections::HashMap;
#[test]
fn has_to_work_with_hashmaps() {
let mut map = HashMap::new();
let cap: Capability = "smtputf8".parse().unwrap();
let cap2: Capability = "SmtpUtf8".parse().unwrap();
map.insert(cap, ());
assert!(map.contains_key(&cap2))
}
#[test]
fn has_to_work_with_hashmaps_and_str() {
let mut map = HashMap::new();
let cap: Capability = "smtputf8".parse().unwrap();
map.insert(cap, ());
let str_key = "smtPUTf8";
let wrapped = <&IgnoreAsciiCaseStr>::from(str_key);
assert!(map.contains_key(wrapped))
}
}
mod Domain {
use super::super::Domain;
#[test]
fn case_insensitive() {
let a: Domain = "affen".parse().unwrap();
let b: Domain = "AFFEN".parse().unwrap();
let c: Domain = "AffEN".parse().unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(a, "aFFen");
}
#[test]
fn displayed_lowercase() {
let a: Domain = "afFen".parse().unwrap();
let s: String = a.into();
assert_eq!(s, "affen")
}
#[test]
fn from_unchecked() {
let a = Domain::from_unchecked("hy");
assert_eq!(a, "hy");
}
}
}