mail_builder/encoders/
encode.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 std::io::{self, Write};
8
9use super::{base64::base64_encode_mime, quoted_printable::inline_quoted_printable_encode};
10
11pub enum EncodingType {
12    Base64,
13    QuotedPrintable(bool),
14    None,
15}
16
17pub fn get_encoding_type(input: &[u8], is_inline: bool, is_body: bool) -> EncodingType {
18    let base64_len = (input.len() * 4 / 3 + 3) & !3;
19    let mut qp_len = if !is_inline { input.len() / 76 } else { 0 };
20    let mut is_ascii = true;
21    let mut needs_encoding = false;
22    let mut line_len = 0;
23    let mut prev_ch = 0;
24
25    for (pos, &ch) in input.iter().enumerate() {
26        line_len += 1;
27
28        if ch >= 127
29            || ((ch == b' ' || ch == b'\t')
30                && ((is_body
31                    && matches!(input.get(pos + 1..), Some([b'\n', ..] | [b'\r', b'\n', ..])))
32                    || pos == input.len() - 1))
33        {
34            qp_len += 3;
35            if !needs_encoding {
36                needs_encoding = true;
37            }
38            if is_ascii && ch >= 127 {
39                is_ascii = false;
40            }
41        } else if ch == b'='
42            || (!is_body && ch == b'\r')
43            || (is_inline && (ch == b'\t' || ch == b'\r' || ch == b'\n' || ch == b'?'))
44        {
45            qp_len += 3;
46        } else if ch == b'\n' {
47            if !needs_encoding && line_len > 77 {
48                needs_encoding = true;
49            }
50            if is_body {
51                if prev_ch != b'\r' {
52                    qp_len += 1;
53                }
54                qp_len += 1;
55            } else {
56                if !needs_encoding && prev_ch != b'\r' {
57                    needs_encoding = true;
58                }
59                qp_len += 3;
60            }
61            line_len = 0;
62        } else {
63            qp_len += 1;
64        }
65
66        prev_ch = ch;
67    }
68
69    if !needs_encoding && line_len > 77 {
70        needs_encoding = true;
71    }
72
73    if !needs_encoding {
74        EncodingType::None
75    } else if qp_len < base64_len {
76        EncodingType::QuotedPrintable(is_ascii)
77    } else {
78        EncodingType::Base64
79    }
80}
81
82pub fn rfc2047_encode(input: &str, mut output: impl Write) -> io::Result<usize> {
83    Ok(match get_encoding_type(input.as_bytes(), true, false) {
84        EncodingType::Base64 => {
85            output.write_all(b"\"=?utf-8?B?")?;
86            let bytes_written = base64_encode_mime(input.as_bytes(), &mut output, true)? + 14;
87            output.write_all(b"?=\"")?;
88            bytes_written
89        }
90        EncodingType::QuotedPrintable(is_ascii) => {
91            if !is_ascii {
92                output.write_all(b"\"=?utf-8?Q?")?;
93            } else {
94                output.write_all(b"\"=?us-ascii?Q?")?;
95            }
96            let bytes_written = inline_quoted_printable_encode(input.as_bytes(), &mut output)?
97                + if is_ascii { 19 } else { 14 };
98            output.write_all(b"?=\"")?;
99            bytes_written
100        }
101        EncodingType::None => {
102            let mut bytes_written = 2;
103            output.write_all(b"\"")?;
104            for &ch in input.as_bytes() {
105                if ch == b'\\' || ch == b'"' {
106                    output.write_all(b"\\")?;
107                    bytes_written += 1;
108                } else if ch == b'\r' || ch == b'\n' {
109                    continue;
110                }
111                output.write_all(&[ch])?;
112                bytes_written += 1;
113            }
114            output.write_all(b"\"")?;
115            bytes_written
116        }
117    })
118}