1use std::ffi::OsStr;
2use std::fmt::{self, Display, Formatter};
3use std::pin::Pin;
4use std::str::FromStr;
5use std::task::{Context, Poll};
6
7use anyhow::{bail, Error, Result};
8#[cfg(feature = "runtime-async-std")]
9use async_std::io::{Cursor, Read};
10use futures::io;
11use pin_project::pin_project;
12#[cfg(feature = "runtime-tokio")]
13use std::io::Cursor;
14#[cfg(feature = "runtime-tokio")]
15use tokio::io::AsyncRead as Read;
16
17#[derive(PartialEq, Eq, Clone, Debug)]
19pub struct EmailAddress(String);
20
21impl EmailAddress {
22 pub fn new(address: String) -> Result<EmailAddress> {
24 if address.chars().any(|c| {
27 !c.is_ascii() || c.is_ascii_control() || c.is_ascii_whitespace() || c == '<' || c == '>'
28 }) {
29 bail!("invalid email address");
30 }
31
32 Ok(EmailAddress(address))
33 }
34}
35
36impl FromStr for EmailAddress {
37 type Err = Error;
38
39 fn from_str(s: &str) -> Result<Self, Self::Err> {
40 EmailAddress::new(s.to_string())
41 }
42}
43
44impl Display for EmailAddress {
45 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
46 f.write_str(&self.0)
47 }
48}
49
50impl AsRef<str> for EmailAddress {
51 fn as_ref(&self) -> &str {
52 &self.0
53 }
54}
55
56impl AsRef<OsStr> for EmailAddress {
57 fn as_ref(&self) -> &OsStr {
58 self.0.as_ref()
59 }
60}
61
62#[derive(PartialEq, Eq, Clone, Debug)]
66pub struct Envelope {
67 forward_path: Vec<EmailAddress>,
71 reverse_path: Option<EmailAddress>,
73}
74
75impl Envelope {
76 pub fn new(from: Option<EmailAddress>, to: Vec<EmailAddress>) -> Result<Envelope> {
78 if to.is_empty() {
79 bail!("missing destination address");
80 }
81 Ok(Envelope {
82 forward_path: to,
83 reverse_path: from,
84 })
85 }
86
87 pub fn to(&self) -> &[EmailAddress] {
89 self.forward_path.as_slice()
90 }
91
92 pub fn from(&self) -> Option<&EmailAddress> {
94 self.reverse_path.as_ref()
95 }
96}
97
98#[pin_project(project = MessageProj)]
100#[allow(missing_debug_implementations)]
101pub enum Message {
102 Reader(#[pin] Box<dyn Read + Send + Sync>),
104 Bytes(#[pin] Cursor<Vec<u8>>),
106}
107
108#[cfg(feature = "runtime-tokio")]
109impl Read for Message {
110 #[allow(unsafe_code)]
111 fn poll_read(
112 self: Pin<&mut Self>,
113 cx: &mut Context,
114 buf: &mut tokio::io::ReadBuf<'_>,
115 ) -> Poll<io::Result<()>> {
116 match self.project() {
117 MessageProj::Reader(mut rdr) => {
118 let r: Pin<&mut _> = unsafe { Pin::new_unchecked(&mut **rdr) };
120 r.poll_read(cx, buf)
121 }
122 MessageProj::Bytes(rdr) => {
123 let _: Pin<&mut _> = rdr;
124 rdr.poll_read(cx, buf)
125 }
126 }
127 }
128}
129
130#[cfg(feature = "runtime-async-std")]
131impl Read for Message {
132 #[allow(unsafe_code)]
133 fn poll_read(
134 self: Pin<&mut Self>,
135 cx: &mut Context,
136 buf: &mut [u8],
137 ) -> Poll<io::Result<usize>> {
138 match self.project() {
139 MessageProj::Reader(mut rdr) => {
140 let r: Pin<&mut _> = unsafe { Pin::new_unchecked(&mut **rdr) };
142 r.poll_read(cx, buf)
143 }
144 MessageProj::Bytes(rdr) => {
145 let _: Pin<&mut _> = rdr;
146 rdr.poll_read(cx, buf)
147 }
148 }
149 }
150}
151
152#[allow(missing_debug_implementations)]
154pub struct SendableEmail {
155 envelope: Envelope,
157 message: Message,
158}
159
160impl SendableEmail {
161 pub fn new(envelope: Envelope, message: impl Into<Vec<u8>>) -> SendableEmail {
163 let message: Vec<u8> = message.into();
164 SendableEmail {
165 envelope,
166 message: Message::Bytes(Cursor::new(message)),
167 }
168 }
169
170 pub fn new_with_reader(
172 envelope: Envelope,
173 message: Box<dyn Read + Send + Sync>,
174 ) -> SendableEmail {
175 SendableEmail {
176 envelope,
177 message: Message::Reader(message),
178 }
179 }
180
181 pub fn envelope(&self) -> &Envelope {
183 &self.envelope
184 }
185
186 pub fn message(self) -> Message {
188 self.message
189 }
190}
191
192#[cfg(test)]
193mod test {
194 use super::*;
195
196 #[test]
197 fn test_email_address() {
198 assert!(EmailAddress::new("foobar@example.org".to_string()).is_ok());
199 assert!(EmailAddress::new("foobar@localhost".to_string()).is_ok());
200 assert!(EmailAddress::new("foo\rbar@localhost".to_string()).is_err());
201 assert!(EmailAddress::new("foobar@localhost".to_string()).is_ok());
202 assert!(EmailAddress::new(
203 "617b5772c6d10feda41fc6e0e43b976c4cc9383d3729310d3dc9e1332f0d9acd@yggmail".to_string()
204 )
205 .is_ok());
206 assert!(EmailAddress::new(">foobar@example.org".to_string()).is_err());
207 assert!(EmailAddress::new("foo bar@example.org".to_string()).is_err());
208 assert!(EmailAddress::new("foobar@exa\r\nmple.org".to_string()).is_err());
209 }
210}