lettre/transport/smtp/client/
mod.rs1#[cfg(feature = "serde")]
26use std::fmt::Debug;
27
28#[cfg(any(feature = "tokio1", feature = "async-std1"))]
29pub use self::async_connection::AsyncSmtpConnection;
30#[cfg(any(feature = "tokio1", feature = "async-std1"))]
31#[allow(deprecated)]
32pub use self::async_net::AsyncNetworkStream;
33#[cfg(feature = "tokio1")]
34pub use self::async_net::AsyncTokioStream;
35use self::net::NetworkStream;
36#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
37pub(super) use self::tls::InnerTlsParameters;
38#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
39pub use self::tls::TlsVersion;
40pub use self::{
41 connection::SmtpConnection,
42 tls::{Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder},
43};
44
45#[cfg(any(feature = "tokio1", feature = "async-std1"))]
46mod async_connection;
47#[cfg(any(feature = "tokio1", feature = "async-std1"))]
48mod async_net;
49mod connection;
50mod net;
51mod tls;
52
53#[derive(Debug)]
55struct ClientCodec {
56 status: CodecStatus,
57}
58
59impl ClientCodec {
60 pub(crate) fn new() -> Self {
62 Self {
63 status: CodecStatus::StartOfNewLine,
64 }
65 }
66
67 fn encode(&mut self, frame: &[u8], buf: &mut Vec<u8>) {
69 for &b in frame {
70 buf.push(b);
71 match (b, self.status) {
72 (b'\r', _) => {
73 self.status = CodecStatus::StartingNewLine;
74 }
75 (b'\n', CodecStatus::StartingNewLine) => {
76 self.status = CodecStatus::StartOfNewLine;
77 }
78 (_, CodecStatus::StartingNewLine) => {
79 self.status = CodecStatus::MiddleOfLine;
80 }
81 (b'.', CodecStatus::StartOfNewLine) => {
82 self.status = CodecStatus::MiddleOfLine;
83 buf.push(b'.');
84 }
85 (_, CodecStatus::StartOfNewLine) => {
86 self.status = CodecStatus::MiddleOfLine;
87 }
88 _ => {}
89 }
90 }
91 }
92}
93
94#[derive(Debug, Copy, Clone)]
95#[allow(clippy::enum_variant_names)]
96enum CodecStatus {
97 MiddleOfLine,
99 StartingNewLine,
101 StartOfNewLine,
103}
104
105#[cfg(feature = "tracing")]
108pub(super) fn escape_crlf(string: &str) -> String {
109 string.replace("\r\n", "<CRLF>")
110}
111
112#[cfg(test)]
113mod test {
114 use super::*;
115
116 #[test]
117 fn test_codec() {
118 let mut buf = Vec::new();
119 let mut codec = ClientCodec::new();
120
121 codec.encode(b".\r\n", &mut buf);
122 codec.encode(b"test\r\n", &mut buf);
123 codec.encode(b"test\r\n\r\n", &mut buf);
124 codec.encode(b".\r\n", &mut buf);
125 codec.encode(b"\r\ntest", &mut buf);
126 codec.encode(b"te\r\n.\r\nst", &mut buf);
127 codec.encode(b"test", &mut buf);
128 codec.encode(b"test.", &mut buf);
129 codec.encode(b"test\n", &mut buf);
130 codec.encode(b".test\n", &mut buf);
131 codec.encode(b"test", &mut buf);
132 codec.encode(b"test", &mut buf);
133 codec.encode(b"test\r\n", &mut buf);
134 codec.encode(b".test\r\n", &mut buf);
135 codec.encode(b"test.\r\n", &mut buf);
136 assert_eq!(
137 String::from_utf8(buf).unwrap(),
138 "..\r\ntest\r\ntest\r\n\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntesttesttest\r\n..test\r\ntest.\r\n"
139 );
140 }
141
142 #[test]
143 #[cfg(feature = "tracing")]
144 fn test_escape_crlf() {
145 assert_eq!(escape_crlf("\r\n"), "<CRLF>");
146 assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CRLF>");
147 assert_eq!(
148 escape_crlf("EHLO my_name\r\nSIZE 42\r\n"),
149 "EHLO my_name<CRLF>SIZE 42<CRLF>"
150 );
151 }
152}