use super::{
base64::base64_encode_mime,
quoted_printable::{inline_quoted_printable_encode, phrase_quoted_printable_encode},
};
use std::io::{self, Write};
pub(crate) enum EncodingType {
Base64,
QuotedPrintable(bool),
None,
}
pub(crate) fn get_encoding_type(input: &[u8], is_inline: bool, is_body: bool) -> EncodingType {
let base64_len = (input.len() * 4 / 3 + 3) & !3;
let mut qp_len = if !is_inline { input.len() / 76 } else { 0 };
let mut is_ascii = true;
let mut needs_encoding = false;
let mut line_len = 0;
let mut prev_ch = 0;
for (pos, &ch) in input.iter().enumerate() {
line_len += 1;
if ch >= 127
|| ((ch == b' ' || ch == b'\t')
&& ((is_body
&& matches!(input.get(pos + 1..), Some([b'\n', ..] | [b'\r', b'\n', ..])))
|| pos == input.len() - 1))
{
qp_len += 3;
if !needs_encoding {
needs_encoding = true;
}
if is_ascii && ch >= 127 {
is_ascii = false;
}
} else if ch == b'='
|| (!is_body && ch == b'\r')
|| (is_inline && (ch == b'\t' || ch == b'\r' || ch == b'\n' || ch == b'?'))
{
qp_len += 3;
} else if ch == b'\n' {
if !needs_encoding && line_len > 77 {
needs_encoding = true;
}
if is_body {
if prev_ch != b'\r' {
qp_len += 1;
}
qp_len += 1;
} else {
if !needs_encoding && prev_ch != b'\r' {
needs_encoding = true;
}
qp_len += 3;
}
line_len = 0;
} else {
qp_len += 1;
}
prev_ch = ch;
}
if !needs_encoding && line_len > 77 {
needs_encoding = true;
}
if !needs_encoding {
EncodingType::None
} else if qp_len < base64_len {
EncodingType::QuotedPrintable(is_ascii)
} else {
EncodingType::Base64
}
}
fn quoted_string_encode(input: &str, mut output: impl Write) -> io::Result<usize> {
let mut bytes_written = 2;
output.write_all(b"\"")?;
for &ch in input.as_bytes() {
if ch == b'\\' || ch == b'"' {
output.write_all(b"\\")?;
bytes_written += 1;
} else if ch == b'\r' || ch == b'\n' {
continue;
}
output.write_all(&[ch])?;
bytes_written += 1;
}
output.write_all(b"\"")?;
Ok(bytes_written)
}
pub(crate) fn rfc2047_encode(input: &str, mut output: impl Write) -> io::Result<usize> {
Ok(match get_encoding_type(input.as_bytes(), true, false) {
EncodingType::Base64 => {
output.write_all(b"\"=?utf-8?B?")?;
let bytes_written = base64_encode_mime(input.as_bytes(), &mut output, true)? + 14;
output.write_all(b"?=\"")?;
bytes_written
}
EncodingType::QuotedPrintable(is_ascii) => {
let prefix: &[u8] = if is_ascii {
b"\"=?us-ascii?Q?"
} else {
b"\"=?utf-8?Q?"
};
output.write_all(prefix)?;
let bytes_written =
inline_quoted_printable_encode(input.as_bytes(), &mut output)? + prefix.len() + 3;
output.write_all(b"?=\"")?;
bytes_written
}
EncodingType::None => quoted_string_encode(input, &mut output)?,
})
}
pub(crate) fn rfc2047_encode_phrase(input: &str, mut output: impl Write) -> io::Result<usize> {
Ok(match get_encoding_type(input.as_bytes(), true, false) {
EncodingType::Base64 => {
output.write_all(b"=?utf-8?B?")?;
let bytes_written = base64_encode_mime(input.as_bytes(), &mut output, true)? + 12;
output.write_all(b"?=")?;
bytes_written
}
EncodingType::QuotedPrintable(is_ascii) => {
let prefix: &[u8] = if is_ascii {
b"=?us-ascii?Q?"
} else {
b"=?utf-8?Q?"
};
output.write_all(prefix)?;
let bytes_written =
phrase_quoted_printable_encode(input.as_bytes(), &mut output)? + prefix.len() + 2;
output.write_all(b"?=")?;
bytes_written
}
EncodingType::None => quoted_string_encode(input, &mut output)?,
})
}