lettre/address/
types.rs

1//! Representation of an email address
2
3use 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/// Represents an email address with a user and a domain name.
15///
16/// This type contains email in canonical form (_user@domain.tld_).
17///
18/// **NOTE**: Enable feature "serde" to be able to serialize/deserialize it using [serde](https://serde.rs/).
19///
20/// # Examples
21///
22/// You can create an `Address` from a user and a domain:
23///
24/// ```
25/// use lettre::Address;
26///
27/// # use std::error::Error;
28/// # fn main() -> Result<(), Box<dyn Error>> {
29/// let address = Address::new("user", "email.com")?;
30/// assert_eq!(address.user(), "user");
31/// assert_eq!(address.domain(), "email.com");
32/// # Ok(())
33/// # }
34/// ```
35///
36/// You can also create an `Address` from a string literal by parsing it:
37///
38/// ```
39/// use lettre::Address;
40///
41/// # use std::error::Error;
42/// # fn main() -> Result<(), Box<dyn Error>> {
43/// let address = "user@email.com".parse::<Address>()?;
44/// assert_eq!(address.user(), "user");
45/// assert_eq!(address.domain(), "email.com");
46/// # Ok(())
47/// # }
48/// ```
49#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
50pub struct Address {
51    /// Complete address
52    serialized: String,
53    /// Index into `serialized` before the '@'
54    at_start: usize,
55}
56
57impl Address {
58    /// Creates a new email address from a user and domain.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use lettre::Address;
64    ///
65    /// # use std::error::Error;
66    /// # fn main() -> Result<(), Box<dyn Error>> {
67    /// let address = Address::new("user", "email.com")?;
68    /// let expected = "user@email.com".parse::<Address>()?;
69    /// assert_eq!(expected, address);
70    /// # Ok(())
71    /// # }
72    /// ```
73    pub fn new<U: AsRef<str>, D: AsRef<str>>(user: U, domain: D) -> Result<Self, AddressError> {
74        (user, domain).try_into()
75    }
76
77    /// Gets the user portion of the `Address`.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use lettre::Address;
83    ///
84    /// # use std::error::Error;
85    /// # fn main() -> Result<(), Box<dyn Error>> {
86    /// let address = Address::new("user", "email.com")?;
87    /// assert_eq!(address.user(), "user");
88    /// # Ok(())
89    /// # }
90    /// ```
91    pub fn user(&self) -> &str {
92        &self.serialized[..self.at_start]
93    }
94
95    /// Gets the domain portion of the `Address`.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use lettre::Address;
101    ///
102    /// # use std::error::Error;
103    /// # fn main() -> Result<(), Box<dyn Error>> {
104    /// let address = Address::new("user", "email.com")?;
105    /// assert_eq!(address.domain(), "email.com");
106    /// # Ok(())
107    /// # }
108    /// ```
109    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        // Domain
131        if EmailAddress::is_valid_domain(domain) {
132            return Ok(());
133        }
134
135        // IP
136        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    /// Check if the address contains non-ascii chars
150    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]
231/// Errors in email addresses parsing
232pub enum AddressError {
233    /// Missing domain or user
234    MissingParts,
235    /// Unbalanced angle bracket
236    Unbalanced,
237    /// Invalid email user
238    InvalidUser,
239    /// Invalid email domain
240    InvalidDomain,
241    /// Invalid input found
242    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}