mail_builder/encoders/
mod.rs1use crate::encoders::{base64::base64_encode_mime, quoted_printable::quoted_printable_encode};
8use std::io::{self, Write};
9
10pub mod base64;
11pub mod encode;
12pub mod quoted_printable;
13
14#[repr(transparent)]
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub struct Base64Encoder {
17 wrap_lines: bool,
18}
19
20#[repr(transparent)]
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub struct QuotedPrintableEncoder {
23 preserve_line_breaks: bool,
24}
25
26impl Base64Encoder {
27 #[inline(always)]
28 pub fn new() -> Self {
29 Self { wrap_lines: false }
30 }
31
32 #[inline(always)]
33 pub fn wrap_lines(mut self) -> Self {
34 self.wrap_lines = true;
35 self
36 }
37
38 #[inline(always)]
39 pub fn encode(&self, input: &[u8]) -> io::Result<Vec<u8>> {
40 let mut buf = Vec::with_capacity(4 * (input.len() / 3));
41 base64_encode_mime(input, &mut buf, !self.wrap_lines)?;
42 Ok(buf)
43 }
44
45 #[inline(always)]
46 pub fn encode_to_writer(&self, input: &[u8], output: &mut impl Write) -> io::Result<usize> {
47 base64_encode_mime(input, output, !self.wrap_lines)
48 }
49}
50
51impl QuotedPrintableEncoder {
52 #[inline(always)]
53 pub fn new() -> Self {
54 Self {
55 preserve_line_breaks: false,
56 }
57 }
58
59 #[inline(always)]
60 pub fn preserve_line_breaks(mut self) -> Self {
61 self.preserve_line_breaks = true;
62 self
63 }
64
65 #[inline(always)]
66 pub fn encode(&self, input: &[u8]) -> io::Result<Vec<u8>> {
67 let mut buf = Vec::with_capacity(input.len() * 2);
68 quoted_printable_encode(input, &mut buf, self.preserve_line_breaks)?;
69 Ok(buf)
70 }
71
72 #[inline(always)]
73 pub fn encode_to_writer(&self, input: &[u8], output: &mut impl Write) -> io::Result<usize> {
74 quoted_printable_encode(input, output, self.preserve_line_breaks)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn base64_encoder_does_not_wrap_by_default() {
84 let encoded = Base64Encoder::new().encode(&[b'x'; 300]).unwrap();
85 assert!(!encoded.contains(&b'\n'), "unexpected line break");
86 }
87
88 #[test]
89 fn base64_encoder_wraps_lines_when_requested() {
90 let encoded = Base64Encoder::new()
91 .wrap_lines()
92 .encode(&[b'x'; 300])
93 .unwrap();
94 assert!(encoded.windows(2).any(|w| w == b"\r\n"), "no line break");
95 for line in encoded.split(|&ch| ch == b'\n') {
96 assert!(line.len() <= 77, "line too long: {}", line.len());
97 }
98 }
99
100 #[test]
101 fn quoted_printable_encoder_escapes_line_breaks_by_default() {
102 let encoded = QuotedPrintableEncoder::new().encode(b"a\r\nb").unwrap();
103 assert_eq!(encoded, b"a=0D=0Ab");
104 }
105
106 #[test]
107 fn quoted_printable_encoder_preserves_line_breaks_when_requested() {
108 let encoded = QuotedPrintableEncoder::new()
109 .preserve_line_breaks()
110 .encode(b"a\r\nb")
111 .unwrap();
112 assert_eq!(encoded, b"a\r\nb");
113 }
114}