email_encoding/headers/
writer.rs

1//! Utilities for writing email headers to a [`Write`]r.
2//!
3//! [`Write`]: core::fmt::Write
4
5use core::fmt::{self, Write};
6
7use super::MAX_LINE_LEN;
8
9/// Wrapper around [`Write`] that remembers the length of the
10/// last line written to it.
11///
12/// [`Write`]: core::fmt::Write
13pub struct EmailWriter<'a> {
14    writer: &'a mut dyn Write,
15    line_len: usize,
16    spaces: usize,
17    can_go_to_new_line_now: bool,
18}
19
20impl<'a> EmailWriter<'a> {
21    /// Construct a new `EmailWriter`.
22    ///
23    /// * `line_len` is the length of the last line in `writer`.
24    /// * `spaces` the number of spaces that must be written before
25    ///   the next write.
26    /// * `can_go_to_new_line_now` is whether the current line can
27    ///   be wrapped now or not.
28    pub fn new(
29        writer: &'a mut dyn Write,
30        line_len: usize,
31        spaces: usize,
32        can_go_to_new_line_now: bool,
33    ) -> Self {
34        Self {
35            writer,
36            line_len,
37            spaces,
38            can_go_to_new_line_now,
39        }
40    }
41
42    /// Go to a new line and reset the `line_len` to `0`.
43    pub fn new_line(&mut self) -> fmt::Result {
44        self.writer.write_str("\r\n")?;
45        self.line_len = 0;
46        self.can_go_to_new_line_now = false;
47
48        Ok(())
49    }
50
51    /// Write a space which _might_ get wrapped to a new line on the next write.
52    pub fn space(&mut self) {
53        self.spaces += 1;
54    }
55
56    /// Forget all buffered spaces
57    pub(super) fn forget_spaces(&mut self) {
58        self.spaces = 0;
59    }
60
61    pub(super) fn has_spaces(&mut self) -> bool {
62        self.spaces >= 1
63    }
64
65    /// Get the length in bytes of the last line written to the inner writer.
66    pub fn line_len(&self) -> usize {
67        self.line_len
68    }
69
70    /// Get the length in bytes of the last line written to the inner writer
71    /// plus the spaces which might be written to in on the next write call.
72    pub fn projected_line_len(&self) -> usize {
73        self.line_len + self.spaces
74    }
75
76    /// Get a [`Write`]r which automatically line folds text written to it.
77    ///
78    /// [`Write`]: core::fmt::Write
79    pub fn folding<'b>(&'b mut self) -> FoldingEmailWriter<'a, 'b> {
80        FoldingEmailWriter { writer: self }
81    }
82
83    fn write_spaces(&mut self) -> fmt::Result {
84        while self.spaces > 0 {
85            self.writer.write_char(' ')?;
86            self.line_len += 1;
87            self.spaces -= 1;
88        }
89
90        Ok(())
91    }
92}
93
94impl Write for EmailWriter<'_> {
95    fn write_str(&mut self, s: &str) -> fmt::Result {
96        self.write_spaces()?;
97
98        let s_after = s.trim_end_matches(' ');
99        self.spaces += s.len() - s_after.len();
100
101        if !s_after.is_empty() {
102            self.writer.write_str(s_after)?;
103            self.line_len += s_after.len();
104            self.can_go_to_new_line_now = true;
105        }
106
107        Ok(())
108    }
109
110    fn write_char(&mut self, c: char) -> fmt::Result {
111        if c == ' ' {
112            self.spaces += 1;
113        } else {
114            self.write_spaces()?;
115            self.can_go_to_new_line_now = true;
116
117            self.writer.write_char(c)?;
118            self.line_len += c.len_utf8();
119        }
120
121        Ok(())
122    }
123}
124
125impl Drop for EmailWriter<'_> {
126    fn drop(&mut self) {
127        let _ = self.write_spaces();
128    }
129}
130
131/// Wrapper around [`Write`] that remembers the length of the
132/// last line and automatically line folds text written to it.
133///
134/// [`Write`]: core::fmt::Write
135pub struct FoldingEmailWriter<'a, 'b> {
136    writer: &'b mut EmailWriter<'a>,
137}
138
139impl Write for FoldingEmailWriter<'_, '_> {
140    fn write_str(&mut self, mut s: &str) -> fmt::Result {
141        while !s.is_empty() {
142            if s.starts_with(' ') {
143                self.writer.space();
144                s = &s[1..];
145                continue;
146            }
147
148            let (start, end) = s.find(' ').map_or((s, ""), |i| s.split_at(i));
149
150            if self.writer.can_go_to_new_line_now
151                && self.writer.spaces >= 1
152                && (self.writer.projected_line_len() + start.len()) > MAX_LINE_LEN
153            {
154                self.writer.new_line()?;
155            }
156
157            self.writer.write_str(start)?;
158            s = end;
159        }
160
161        Ok(())
162    }
163
164    fn write_char(&mut self, c: char) -> fmt::Result {
165        if c == ' ' {
166            self.writer.spaces += 1;
167        } else {
168            self.write_str(c.encode_utf8(&mut [0u8; 4]))?;
169        }
170
171        Ok(())
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use alloc::borrow::ToOwned;
178
179    use pretty_assertions::assert_eq;
180
181    use super::*;
182
183    #[test]
184    fn wrap_immediate() {
185        let mut s =
186            "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
187        let line_len = s.len();
188
189        {
190            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
191            for _ in 0..16 {
192                w.folding().write_str("0123456789").unwrap();
193            }
194        }
195
196        assert_eq!(
197            s,
198            "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
199        );
200    }
201
202    #[test]
203    fn wrap_keeping_final_whitespace() {
204        let mut s = "Subject: AAAAAAAAAAAAAA".to_owned();
205        let line_len = s.len();
206
207        {
208            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
209            w.folding().write_str("12345 ").unwrap();
210            w.new_line().unwrap();
211            w.folding().write_str("12345").unwrap();
212        }
213
214        assert_eq!(s, concat!("Subject: AAAAAAAAAAAAAA 12345\r\n", " 12345"));
215    }
216
217    #[test]
218    fn catch_space() {
219        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
220        let line_len = s.len();
221
222        {
223            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
224            w.folding().write_str("BBB ").unwrap();
225            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
226        }
227
228        assert_eq!(
229            s,
230            concat!(
231                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
232                " CCCCCCCCCCCCC"
233            )
234        );
235    }
236
237    #[test]
238    fn catch_spaces() {
239        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
240        let line_len = s.len();
241
242        {
243            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
244            w.folding().write_str("BBB   ").unwrap();
245            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
246        }
247
248        assert_eq!(
249            s,
250            concat!(
251                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
252                "   CCCCCCCCCCCCC"
253            )
254        );
255    }
256
257    #[test]
258    fn explicit_space() {
259        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
260        let line_len = s.len();
261
262        {
263            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
264            w.folding().write_str("BBB").unwrap();
265            w.space();
266            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
267        }
268
269        assert_eq!(
270            s,
271            concat!(
272                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
273                " CCCCCCCCCCCCC"
274            )
275        );
276    }
277
278    #[test]
279    fn explicit_spaces() {
280        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
281        let line_len = s.len();
282
283        {
284            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
285            w.folding().write_str("BBB").unwrap();
286            w.space();
287            w.write_char(' ').unwrap();
288            w.space();
289            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
290        }
291
292        assert_eq!(
293            s,
294            concat!(
295                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
296                "   CCCCCCCCCCCCC"
297            )
298        );
299    }
300
301    #[test]
302    fn optional_breakpoint() {
303        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
304            .to_owned();
305        let line_len = s.len();
306
307        {
308            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
309            w.space();
310            w.folding().write_str("BBBBBBBBBB").unwrap();
311            w.space();
312            w.folding().write_str("CCCCCCCCCC").unwrap();
313        }
314
315        assert_eq!(
316            s,
317            concat!(
318                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
319                " BBBBBBBBBB CCCCCCCCCC",
320            )
321        );
322    }
323
324    #[test]
325    fn double_spaces_issue_949() {
326        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ".to_owned();
327        let line_len = s.len();
328
329        {
330            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
331            w.folding().write_str("BBBBBBBBBBBBB ").unwrap();
332            crate::headers::rfc2047::encode("sélection", &mut w).unwrap();
333        }
334
335        assert_eq!(
336            s,
337            concat!(
338                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBB\r\n",
339                " =?utf-8?b?c8OpbGVjdGlvbg==?=",
340            )
341        );
342    }
343
344    #[test]
345    fn double_spaces_issue_949_no_space() {
346        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ".to_owned();
347        let line_len = s.len();
348
349        {
350            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
351            w.folding().write_str("BBBBBBBBBBBBBBB").unwrap();
352            crate::headers::rfc2047::encode("sélection", &mut w).unwrap();
353        }
354
355        assert_eq!(
356            s,
357            concat!(
358                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB=?utf-8?b?cw==?=\r\n",
359                " =?utf-8?b?w6lsZWN0aW9u?=",
360            )
361        );
362    }
363}