use std::fmt::{self, Write};
use super::MAX_LINE_LEN;
pub struct EmailWriter<'a> {
writer: &'a mut dyn Write,
line_len: usize,
spaces: usize,
can_go_to_new_line_now: bool,
}
impl<'a> EmailWriter<'a> {
pub fn new(
writer: &'a mut dyn Write,
line_len: usize,
spaces: usize,
can_go_to_new_line_now: bool,
) -> Self {
Self {
writer,
line_len,
spaces,
can_go_to_new_line_now,
}
}
pub fn new_line(&mut self) -> fmt::Result {
self.writer.write_str("\r\n")?;
self.line_len = 0;
self.can_go_to_new_line_now = false;
Ok(())
}
pub fn space(&mut self) {
self.spaces += 1;
}
pub(super) fn forget_spaces(&mut self) {
self.spaces = 0;
}
pub(super) fn has_spaces(&mut self) -> bool {
self.spaces >= 1
}
pub fn line_len(&self) -> usize {
self.line_len
}
pub fn projected_line_len(&self) -> usize {
self.line_len + self.spaces
}
pub fn folding<'b>(&'b mut self) -> FoldingEmailWriter<'a, 'b> {
FoldingEmailWriter { writer: self }
}
fn write_spaces(&mut self) -> fmt::Result {
while self.spaces > 0 {
self.writer.write_char(' ')?;
self.line_len += 1;
self.spaces -= 1;
}
Ok(())
}
}
impl<'a> Write for EmailWriter<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_spaces()?;
let s_after = s.trim_end_matches(' ');
self.spaces += s.len() - s_after.len();
if !s_after.is_empty() {
self.writer.write_str(s_after)?;
self.line_len += s_after.len();
self.can_go_to_new_line_now = true;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
if c == ' ' {
self.spaces += 1;
} else {
self.write_spaces()?;
self.can_go_to_new_line_now = true;
self.writer.write_char(c)?;
self.line_len += c.len_utf8();
}
Ok(())
}
}
impl<'a> Drop for EmailWriter<'a> {
fn drop(&mut self) {
let _ = self.write_spaces();
}
}
pub struct FoldingEmailWriter<'a, 'b> {
writer: &'b mut EmailWriter<'a>,
}
impl<'a, 'b> Write for FoldingEmailWriter<'a, 'b> {
fn write_str(&mut self, mut s: &str) -> fmt::Result {
while !s.is_empty() {
if s.starts_with(' ') {
self.writer.space();
s = &s[1..];
continue;
}
let (start, end) = s.find(' ').map_or((s, ""), |i| s.split_at(i));
if self.writer.can_go_to_new_line_now
&& self.writer.spaces >= 1
&& (self.writer.projected_line_len() + start.len()) > MAX_LINE_LEN
{
self.writer.new_line()?;
}
self.writer.write_str(start)?;
s = end;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
if c == ' ' {
self.writer.spaces += 1;
} else {
self.write_str(c.encode_utf8(&mut [0u8; 4]))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn wrap_immediate() {
let mut s =
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 0, true);
for _ in 0..16 {
w.folding().write_str("0123456789").unwrap();
}
}
assert_eq!(
s,
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
);
}
#[test]
fn wrap_keeping_final_whitespace() {
let mut s = "Subject: AAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, true);
w.folding().write_str("12345 ").unwrap();
w.new_line().unwrap();
w.folding().write_str("12345").unwrap();
}
assert_eq!(s, concat!("Subject: AAAAAAAAAAAAAA 12345\r\n", " 12345"));
}
#[test]
fn catch_space() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, true);
w.folding().write_str("BBB ").unwrap();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn catch_spaces() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, true);
w.folding().write_str("BBB ").unwrap();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn explicit_space() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, true);
w.folding().write_str("BBB").unwrap();
w.space();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn explicit_spaces() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, true);
w.folding().write_str("BBB").unwrap();
w.space();
w.write_char(' ').unwrap();
w.space();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn optional_breakpoint() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
.to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 0, true);
w.space();
w.folding().write_str("BBBBBBBBBB").unwrap();
w.space();
w.folding().write_str("CCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
" BBBBBBBBBB CCCCCCCCCC",
)
);
}
#[test]
fn double_spaces_issue_949() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ".to_string();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 0, true);
w.folding().write_str("BBBBBBBBBBBBB ").unwrap();
crate::headers::rfc2047::encode("sélection", &mut w).unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBB\r\n",
" =?utf-8?b?c8OpbGVjdGlvbg==?=",
)
);
}
#[test]
fn double_spaces_issue_949_no_space() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ".to_string();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 0, true);
w.folding().write_str("BBBBBBBBBBBBBBB").unwrap();
crate::headers::rfc2047::encode("sélection", &mut w).unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB=?utf-8?b?cw==?=\r\n",
" =?utf-8?b?w6lsZWN0aW9u?=",
)
);
}
}