use crate::vcard::Jscomp;
use super::parser::Timestamp;
use mail_builder::encoders::base64::*;
use mail_parser::DateTime;
use std::fmt::{Display, Write};
pub(crate) fn write_text(
out: &mut impl Write,
line_len: &mut usize,
value: &str,
escape_semicolon: bool,
escape_comma: bool,
) -> std::fmt::Result {
for ch in value.chars() {
let ch_len = ch.len_utf8();
if *line_len + ch_len > 75 {
write!(out, "\r\n ")?;
*line_len = 1;
}
match ch {
'\r' => {
write!(out, "\\r")?;
*line_len += 2;
continue;
}
'\n' => {
write!(out, "\\n")?;
*line_len += 2;
continue;
}
'\\' => {
write!(out, "\\")?;
*line_len += 2;
}
';' if escape_semicolon => {
write!(out, "\\")?;
*line_len += 2;
}
',' if escape_comma => {
write!(out, "\\")?;
*line_len += 2;
}
_ => {
*line_len += ch.len_utf8();
}
}
write!(out, "{ch}")?;
}
Ok(())
}
pub(crate) fn write_bytes(
out: &mut impl Write,
mut line_len: Option<&mut usize>,
value: &[u8],
) -> std::fmt::Result {
const CHARPAD: u8 = b'=';
let mut i = 0;
let mut t1;
let mut t2;
let mut t3;
if value.len() > 2 {
while i < value.len() - 2 {
t1 = value[i];
t2 = value[i + 1];
t3 = value[i + 2];
for ch in [
E0[t1 as usize],
E1[(((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)) as usize],
E1[(((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)) as usize],
E2[t3 as usize],
] {
if let Some(line_len) = line_len.as_deref_mut() {
if *line_len + 1 > 75 {
write!(out, "\r\n ")?;
*line_len = 1;
}
*line_len += 1;
}
write!(out, "{}", char::from(ch))?;
}
i += 3;
}
}
let remaining = value.len() - i;
if remaining > 0 {
t1 = value[i];
let chs = if remaining == 1 {
[
E0[t1 as usize],
E1[((t1 & 0x03) << 4) as usize],
CHARPAD,
CHARPAD,
]
} else {
t2 = value[i + 1];
[
E0[t1 as usize],
E1[(((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)) as usize],
E2[((t2 & 0x0F) << 2) as usize],
CHARPAD,
]
};
for ch in chs.iter() {
if let Some(line_len) = line_len.as_deref_mut() {
if *line_len + 1 > 75 {
write!(out, "\r\n ")?;
*line_len = 1;
}
*line_len += 1;
}
write!(out, "{}", char::from(*ch))?;
}
}
Ok(())
}
pub(crate) trait NeedsQuotes {
fn needs_quotes(&self) -> bool;
}
impl<T: AsRef<[u8]>> NeedsQuotes for T {
fn needs_quotes(&self) -> bool {
self.as_ref()
.iter()
.any(|&ch| matches!(ch, b',' | b':' | b'=' | b' ' | b';' | b'"'))
}
}
pub(crate) fn write_param_value(
out: &mut impl Write,
line_len: &mut usize,
value: &str,
) -> std::fmt::Result {
let needs_quotes = value.needs_quotes();
if needs_quotes {
write!(out, "\"")?;
*line_len += 1;
}
for ch in value.chars() {
match ch as u32 {
0x0A => {
write!(out, "\\n")?;
*line_len += 2;
}
0x0D => {
write!(out, "\\r")?;
*line_len += 2;
}
0x5C => {
write!(out, "\\\\")?;
*line_len += 2;
}
0x22 => {
write!(out, "\\\"")?;
*line_len += 2;
}
0x20 | 0x09 | 0x21 | 0x23..=0x7E | 0x80.. => {
let ch_len = ch.len_utf8();
if *line_len + ch_len > 75 {
write!(out, "\r\n ")?;
*line_len = 1;
}
write!(out, "{ch}")?;
*line_len += ch_len;
}
_ => {}
}
}
if needs_quotes {
write!(out, "\"")?;
*line_len += 1;
}
Ok(())
}
pub(crate) fn write_jscomps(
out: &mut impl Write,
line_len: &mut usize,
values: &[Jscomp],
) -> std::fmt::Result {
for (pos, item) in values.iter().enumerate() {
if pos > 0 {
out.write_char(';')?;
*line_len += 1;
}
match item {
Jscomp::Entry { position, value } => {
write!(out, "{position}")?;
if *position > 9 {
*line_len += 2;
} else {
*line_len += 1;
}
if *value > 0 {
write!(out, ",{value}")?;
if *value > 9 {
*line_len += 3;
} else {
*line_len += 2;
}
}
}
Jscomp::Separator(s) => {
if !s.is_empty() {
out.write_str("s,")?;
*line_len += 2;
for ch in s.chars() {
if matches!(ch, ',' | ':' | '=' | ';' | '"') {
out.write_char('\\')?;
*line_len += 1;
}
out.write_char(ch)?;
*line_len += 1;
}
}
}
}
}
Ok(())
}
impl Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let dt = DateTime::from_timestamp(self.0);
write!(
f,
"{:04}{:02}{:02}T{:02}{:02}{:02}Z",
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
)
}
}