1use 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#[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 pub const fn as_str(&self) -> &'a str {
43 self.full
44 }
45
46 pub const fn host(&self) -> Host<'a> {
48 self.host
49 }
50
51 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#[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#[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 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 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}