Skip to main content

mail_builder/encoders/
encode.rs

1/*
2 * Copyright Stalwart Labs Ltd. See the COPYING
3 * file at the top-level directory of this distribution.
4 *
5 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
8 * option. This file may not be copied, modified, or distributed
9 * except according to those terms.
10 */
11
12use std::io::{self, Write};
13
14use super::{base64::base64_encode_mime, quoted_printable::quoted_printable_encode};
15
16pub enum EncodingType {
17    Base64,
18    QuotedPrintable(bool),
19    None,
20}
21
22pub fn get_encoding_type(input: &[u8], is_inline: bool, is_body: bool) -> EncodingType {
23    let base64_len = (input.len() * 4 / 3 + 3) & !3;
24    let mut qp_len = if !is_inline { input.len() / 76 } else { 0 };
25    let mut is_ascii = true;
26    let mut needs_encoding = false;
27    let mut line_len = 0;
28    let mut prev_ch = 0;
29
30    for (pos, &ch) in input.iter().enumerate() {
31        line_len += 1;
32
33        if ch >= 127
34            || ((ch == b' ' || ch == b'\t')
35                && ((is_body && matches!(input.get(pos + 1..), Some([b'\n', ..] | [b'\r', b'\n', ..])))
36                    || pos == input.len() - 1))
37        {
38            qp_len += 3;
39            if !needs_encoding {
40                needs_encoding = true;
41            }
42            if is_ascii && ch >= 127 {
43                is_ascii = false;
44            }
45        } else if ch == b'='
46            || (!is_body && ch == b'\r')
47            || (is_inline && (ch == b'\t' || ch == b'\r' || ch == b'\n' || ch == b'?'))
48        {
49            qp_len += 3;
50        } else if ch == b'\n' {
51            if !needs_encoding && line_len > 997 {
52                needs_encoding = true;
53            }
54            if is_body {
55                if prev_ch != b'\r' {
56                    qp_len += 1;
57                }
58                qp_len += 1;
59            } else {
60                if !needs_encoding && prev_ch != b'\r' {
61                    needs_encoding = true;
62                }
63                qp_len += 3;
64            }
65            line_len = 0;
66        } else {
67            qp_len += 1;
68        }
69
70        prev_ch = ch;
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 =
97                quoted_printable_encode(input.as_bytes(), &mut output, true, false)? + 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}