#![recursion_limit = "1024"]
#[macro_use]
extern crate error_chain;
extern crate psl;
#[macro_use]
extern crate rental;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate idna;
mod parser;
pub mod errors;
use std::fmt;
use std::net::IpAddr;
use std::str::FromStr;
use std::cmp::PartialEq;
use psl::{Psl, List};
use regex::RegexSet;
use errors::ErrorKind;
use parser::{parse_domain, to_targetcase};
pub use errors::{Result, Error};
lazy_static! {
static ref LOCAL: RegexSet = {
let global = r#"[[:alnum:]!#$%&'*+/=?^_`{|}~-]"#;
let non_ascii = r#"[^\x00-\x7F]"#;
let quoted = r#"["(),\\:;<>@\[\]. ]"#;
let combined = format!(r#"({}*{}*)"#, global, non_ascii);
let exprs = vec![
format!(r#"^{}+$"#, combined),
format!(r#"^({0}+[.]?{0}+)+$"#, combined),
format!(r#"^"({}*{}*)*"$"#, combined, quoted),
];
RegexSet::new(exprs).unwrap()
};
}
rental! {
mod inner {
use psl::Domain as Root;
#[rental(debug)]
pub struct Domain {
full: String,
root: Root<'full>,
}
#[rental(debug)]
pub struct Dns {
full: String,
root: Root<'full>,
}
}
}
#[derive(Debug)]
pub struct DomainName {
inner: inner::Domain,
}
#[derive(Debug)]
pub struct DnsName {
inner: inner::Dns,
}
#[derive(Debug)]
pub enum Host {
Ip(IpAddr),
Domain(DomainName),
}
#[derive(Debug)]
pub struct Email {
name: String,
host: Host,
}
impl FromStr for DomainName {
type Err = Error;
fn from_str(input: &str) -> Result<Self> {
use inner::Domain;
match parse_domain(input) {
Ok(domain) => {
let inner = Domain::try_new_or_drop(domain, |full| {
match List.domain(&full) {
Some(root) => { Ok(root) }
None => { Err(Error::from(ErrorKind::InvalidDomain(input.into()))) }
}
})?;
Ok(DomainName { inner })
}
Err(_) => {
Err(ErrorKind::InvalidDomain(input.into()).into())
}
}
}
}
impl FromStr for DnsName {
type Err = Error;
fn from_str(input: &str) -> Result<Self> {
use inner::Dns;
let full = to_targetcase(input);
let inner = Dns::try_new_or_drop(full, |full| {
match List::new().domain(&full) {
Some(root) => {
if parse_domain(root.to_str()).is_err() {
return Err(Error::from(ErrorKind::InvalidDomain(input.into())));
}
Ok(root)
}
None => { Err(Error::from(ErrorKind::InvalidDomain(input.into()))) }
}
})?;
Ok(DnsName { inner })
}
}
impl DnsName {
pub fn as_str(&self) -> &str {
self.inner.head()
}
pub fn root<'a>(&'a self) -> psl::Domain<'a> {
let rental = unsafe { self.inner.all_erased() };
*rental.root
}
}
impl FromStr for Host {
type Err = Error;
fn from_str(mut host: &str) -> Result<Host> {
if let Ok(domain) = DomainName::from_str(host) {
return Ok(Host::Domain(domain));
}
if host.starts_with("[")
&& !host.starts_with("[[")
&& host.ends_with("]")
&& !host.ends_with("]]")
{
host = host
.trim_left_matches("[")
.trim_right_matches("]");
};
if let Ok(ip) = IpAddr::from_str(host) {
return Ok(Host::Ip(ip));
}
Err(ErrorKind::InvalidHost.into())
}
}
impl Host {
pub fn is_ip(&self) -> bool {
if let &Host::Ip(_) = self {
return true;
}
false
}
pub fn is_domain(&self) -> bool {
if let &Host::Domain(_) = self {
return true;
}
false
}
}
impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Host::Ip(ref ip) => write!(f, "{}", ip),
&Host::Domain(ref domain) => write!(f, "{}", domain),
}
}
}
impl DomainName {
pub fn as_str(&self) -> &str {
self.inner.head()
}
pub fn root<'a>(&'a self) -> psl::Domain<'a> {
let rental = unsafe { self.inner.all_erased() };
*rental.root
}
}
impl FromStr for Email {
type Err = Error;
fn from_str(address: &str) -> Result<Email> {
let mut parts = address.rsplitn(2, "@");
let host = match parts.next() {
Some(host) => host,
None => { return Err(ErrorKind::InvalidEmail.into()); }
};
let local = match parts.next() {
Some(local) => local,
None => { return Err(ErrorKind::InvalidEmail.into()); }
};
if local.chars().count() > 64
|| address.chars().count() > 254
|| (!local.starts_with('"') && local.contains(".."))
|| !LOCAL.is_match(local)
{
return Err(ErrorKind::InvalidEmail.into());
}
let host = Host::from_str(host)?;
let name = local.to_owned();
Ok(Email { name, host })
}
}
impl Email {
pub fn user(&self) -> &str {
&self.name
}
pub fn host(&self) -> &Host {
&self.host
}
}
impl fmt::Display for DomainName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl fmt::Display for DnsName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl fmt::Display for Email {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}@{}", self.name, self.host)
}
}
impl PartialEq for DomainName {
fn eq(&self, other: &DomainName) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq for DnsName {
fn eq(&self, other: &DnsName) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<str> for Host {
fn eq(&self, other: &str) -> bool {
self.to_string() == other
}
}