Skip to main content

mail_builder/encoders/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use 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}