Skip to main content

addr/
email.rs

1//! Email address types
2
3use crate::domain::Name;
4use crate::error::{Kind, Result};
5use crate::matcher;
6#[cfg(feature = "net")]
7#[cfg(not(feature = "std"))]
8use crate::net::IpAddr;
9use core::fmt;
10#[cfg(not(any(feature = "net", feature = "std")))]
11use core::str::FromStr;
12use psl_types::List;
13#[cfg(feature = "std")]
14use std::net::IpAddr;
15
16/// Holds information about a particular email address
17#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
18pub struct Address<'a> {
19    full: &'a str,
20    at_sign: usize,
21    host: Host<'a>,
22}
23
24impl<'a> Address<'a> {
25    pub(crate) fn parse<T: List + ?Sized>(list: &T, address: &'a str) -> Result<Address<'a>> {
26        if address.chars().count() > 254 {
27            return Err(Kind::EmailTooLong);
28        }
29        let at_sign = address.rfind('@').ok_or(Kind::NoAtSign)?;
30        let local = address.get(..at_sign).ok_or(Kind::NoUserPart)?;
31        matcher::is_email_local(local)?;
32        let rest = address.get(at_sign + 1..).ok_or(Kind::NoHostPart)?;
33        let host = Host::parse(list, rest)?;
34        Ok(Self {
35            host,
36            at_sign,
37            full: address,
38        })
39    }
40
41    /// The full email address as a `str`
42    pub const fn as_str(&self) -> &'a str {
43        self.full
44    }
45
46    /// The host part of the email address
47    pub const fn host(&self) -> Host<'a> {
48        self.host
49    }
50
51    /// The user (local) part of the email address
52    pub fn user(&self) -> &'a str {
53        &self.full[..self.at_sign]
54    }
55}
56
57impl fmt::Display for Address<'_> {
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        write!(f, "{}", self.full)
60    }
61}
62
63impl PartialEq<&str> for Address<'_> {
64    fn eq(&self, other: &&str) -> bool {
65        self.full == *other
66    }
67}
68
69// A placeholder IP address that can never be constructed
70#[cfg(not(any(feature = "net", feature = "std")))]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
73#[doc(hidden)]
74pub enum IpAddr {}
75
76#[cfg(not(any(feature = "net", feature = "std")))]
77impl FromStr for IpAddr {
78    type Err = Kind;
79
80    fn from_str(_: &str) -> Result<Self> {
81        unreachable!()
82    }
83}
84
85/// Information about the host part of an email address
86#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
87pub enum Host<'a> {
88    Domain(Name<'a>),
89    IpAddr(IpAddr),
90}
91
92impl<'a> Host<'a> {
93    pub(crate) fn parse<T: List + ?Sized>(list: &T, host: &'a str) -> Result<Host<'a>> {
94        if host.starts_with('[') && host.ends_with(']') {
95            let host_len = host.len();
96            if host_len < 3 {
97                return Err(Kind::InvalidIpAddr);
98            }
99            if cfg!(not(any(feature = "net", feature = "std"))) {
100                return Err(Kind::NetDisabled);
101            }
102            let ip_addr = host
103                .get(1..host_len - 1)
104                .ok_or(Kind::InvalidIpAddr)?
105                .parse()?;
106            Ok(Host::IpAddr(ip_addr))
107        } else {
108            Ok(Host::Domain(Name::parse(list, host)?))
109        }
110    }
111}
112
113#[cfg(test)]
114mod test {
115    use super::Address;
116    use psl::List;
117
118    #[test]
119    fn parse() {
120        // Valid email addresses
121        Address::parse(&List, "johndoe@example.com").unwrap();
122        Address::parse(&List, "john.doe@example.com").unwrap();
123        Address::parse(&List, "john+doe@example.com").unwrap();
124        Address::parse(&List, r#""john doe"@example.com"#).unwrap();
125
126        // Invalid email addresses
127        Address::parse(&List, "@example.com").unwrap_err();
128        Address::parse(&List, r#""@example.com"#).unwrap_err();
129        Address::parse(&List, " @example.com").unwrap_err();
130    }
131
132    #[test]
133    fn user() {
134        let email = Address::parse(&List, "johndoe@localhost").unwrap();
135        assert_eq!(email.user(), "johndoe");
136    }
137}