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}