1use std::{
4    error::Error,
5    ffi::OsStr,
6    fmt::{Display, Formatter, Result as FmtResult},
7    net::IpAddr,
8    str::FromStr,
9};
10
11use email_address::EmailAddress;
12use idna::domain_to_ascii;
13
14#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
50pub struct Address {
51    serialized: String,
53    at_start: usize,
55}
56
57impl Address {
58    pub fn new<U: AsRef<str>, D: AsRef<str>>(user: U, domain: D) -> Result<Self, AddressError> {
74        (user, domain).try_into()
75    }
76
77    pub fn user(&self) -> &str {
92        &self.serialized[..self.at_start]
93    }
94
95    pub fn domain(&self) -> &str {
110        &self.serialized[self.at_start + 1..]
111    }
112
113    pub(super) fn check_user(user: &str) -> Result<(), AddressError> {
114        if EmailAddress::is_valid_local_part(user) {
115            Ok(())
116        } else {
117            Err(AddressError::InvalidUser)
118        }
119    }
120
121    pub(super) fn check_domain(domain: &str) -> Result<(), AddressError> {
122        Address::check_domain_ascii(domain).or_else(|_| {
123            domain_to_ascii(domain)
124                .map_err(|_| AddressError::InvalidDomain)
125                .and_then(|domain| Address::check_domain_ascii(&domain))
126        })
127    }
128
129    fn check_domain_ascii(domain: &str) -> Result<(), AddressError> {
130        if EmailAddress::is_valid_domain(domain) {
132            return Ok(());
133        }
134
135        let ip = domain
137            .strip_prefix('[')
138            .and_then(|ip| ip.strip_suffix(']'))
139            .unwrap_or(domain);
140
141        if ip.parse::<IpAddr>().is_ok() {
142            return Ok(());
143        }
144
145        Err(AddressError::InvalidDomain)
146    }
147
148    #[cfg(feature = "smtp-transport")]
149    pub(super) fn is_ascii(&self) -> bool {
151        self.serialized.is_ascii()
152    }
153}
154
155impl Display for Address {
156    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
157        f.write_str(&self.serialized)
158    }
159}
160
161impl FromStr for Address {
162    type Err = AddressError;
163
164    fn from_str(val: &str) -> Result<Self, AddressError> {
165        let at_start = check_address(val)?;
166        Ok(Address {
167            serialized: val.into(),
168            at_start,
169        })
170    }
171}
172
173impl<U, D> TryFrom<(U, D)> for Address
174where
175    U: AsRef<str>,
176    D: AsRef<str>,
177{
178    type Error = AddressError;
179
180    fn try_from((user, domain): (U, D)) -> Result<Self, Self::Error> {
181        let user = user.as_ref();
182        Address::check_user(user)?;
183
184        let domain = domain.as_ref();
185        Address::check_domain(domain)?;
186
187        let serialized = format!("{user}@{domain}");
188        Ok(Address {
189            serialized,
190            at_start: user.len(),
191        })
192    }
193}
194
195impl TryFrom<String> for Address {
196    type Error = AddressError;
197
198    fn try_from(serialized: String) -> Result<Self, AddressError> {
199        let at_start = check_address(&serialized)?;
200        Ok(Address {
201            serialized,
202            at_start,
203        })
204    }
205}
206
207impl AsRef<str> for Address {
208    fn as_ref(&self) -> &str {
209        &self.serialized
210    }
211}
212
213impl AsRef<OsStr> for Address {
214    fn as_ref(&self) -> &OsStr {
215        self.serialized.as_ref()
216    }
217}
218
219fn check_address(val: &str) -> Result<usize, AddressError> {
220    let mut parts = val.rsplitn(2, '@');
221    let domain = parts.next().ok_or(AddressError::MissingParts)?;
222    let user = parts.next().ok_or(AddressError::MissingParts)?;
223
224    Address::check_user(user)?;
225    Address::check_domain(domain)?;
226    Ok(user.len())
227}
228
229#[derive(Debug, PartialEq, Eq, Clone, Copy)]
230#[non_exhaustive]
231pub enum AddressError {
233    MissingParts,
235    Unbalanced,
237    InvalidUser,
239    InvalidDomain,
241    InvalidInput,
243}
244
245impl Error for AddressError {}
246
247impl Display for AddressError {
248    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
249        match self {
250            AddressError::MissingParts => f.write_str("Missing domain or user"),
251            AddressError::Unbalanced => f.write_str("Unbalanced angle bracket"),
252            AddressError::InvalidUser => f.write_str("Invalid email user"),
253            AddressError::InvalidDomain => f.write_str("Invalid email domain"),
254            AddressError::InvalidInput => f.write_str("Invalid input"),
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn ascii_address() {
265        let addr_str = "something@example.com";
266        let addr = Address::from_str(addr_str).unwrap();
267        let addr2 = Address::new("something", "example.com").unwrap();
268        assert_eq!(addr, addr2);
269        assert_eq!(addr.user(), "something");
270        assert_eq!(addr.domain(), "example.com");
271        assert_eq!(addr2.user(), "something");
272        assert_eq!(addr2.domain(), "example.com");
273    }
274
275    #[test]
276    fn ascii_address_ipv4() {
277        let addr_str = "something@1.1.1.1";
278        let addr = Address::from_str(addr_str).unwrap();
279        let addr2 = Address::new("something", "1.1.1.1").unwrap();
280        assert_eq!(addr, addr2);
281        assert_eq!(addr.user(), "something");
282        assert_eq!(addr.domain(), "1.1.1.1");
283        assert_eq!(addr2.user(), "something");
284        assert_eq!(addr2.domain(), "1.1.1.1");
285    }
286
287    #[test]
288    fn ascii_address_ipv6() {
289        let addr_str = "something@[2606:4700:4700::1111]";
290        let addr = Address::from_str(addr_str).unwrap();
291        let addr2 = Address::new("something", "[2606:4700:4700::1111]").unwrap();
292        assert_eq!(addr, addr2);
293        assert_eq!(addr.user(), "something");
294        assert_eq!(addr.domain(), "[2606:4700:4700::1111]");
295        assert_eq!(addr2.user(), "something");
296        assert_eq!(addr2.domain(), "[2606:4700:4700::1111]");
297    }
298
299    #[test]
300    fn check_parts() {
301        assert!(Address::check_user("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").is_err());
302        assert!(
303            Address::check_domain("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com").is_err()
304        );
305    }
306}