async_smtp/
types.rs

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/// Email address
18#[derive(PartialEq, Eq, Clone, Debug)]
19pub struct EmailAddress(String);
20
21impl EmailAddress {
22    /// Creates new email address, checking that it does not contain invalid characters.
23    pub fn new(address: String) -> Result<EmailAddress> {
24        // Do basic checks to avoid injection of control characters into SMTP protocol.  Actual
25        // email validation should be done by the server.
26        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/// Simple email envelope representation
63///
64/// We only accept mailboxes, and do not support source routes (as per RFC).
65#[derive(PartialEq, Eq, Clone, Debug)]
66pub struct Envelope {
67    /// The envelope recipients' addresses
68    ///
69    /// This can not be empty.
70    forward_path: Vec<EmailAddress>,
71    /// The envelope sender address
72    reverse_path: Option<EmailAddress>,
73}
74
75impl Envelope {
76    /// Creates a new envelope, which may fail if `to` is empty.
77    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    /// Destination addresses of the envelope
88    pub fn to(&self) -> &[EmailAddress] {
89        self.forward_path.as_slice()
90    }
91
92    /// Source address of the envelope
93    pub fn from(&self) -> Option<&EmailAddress> {
94        self.reverse_path.as_ref()
95    }
96}
97
98/// Message buffer for sending.
99#[pin_project(project = MessageProj)]
100#[allow(missing_debug_implementations)]
101pub enum Message {
102    /// Message constructed from a reader.
103    Reader(#[pin] Box<dyn Read + Send + Sync>),
104    /// Message constructed from a byte vector.
105    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                // Probably safe..
119                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                // Probably safe..
141                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/// Sendable email structure
153#[allow(missing_debug_implementations)]
154pub struct SendableEmail {
155    /// Email envelope.
156    envelope: Envelope,
157    message: Message,
158}
159
160impl SendableEmail {
161    /// Creates new email out of an envelope and a byte slice.
162    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    /// Creates new email out of an envelope and a byte reader.
171    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    /// Returns email envelope.
182    pub fn envelope(&self) -> &Envelope {
183        &self.envelope
184    }
185
186    /// Returns email message.
187    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}