email_encoding/headers/
writer.rs

1//! Utilities for writing email headers to a [`Write`]r.
2//!
3//! [`Write`]: std::fmt::Write
4
5use std::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`]: std::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`]: std::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<'a> Write for EmailWriter<'a> {
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<'a> Drop for EmailWriter<'a> {
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`]: std::fmt::Write
135pub struct FoldingEmailWriter<'a, 'b> {
136    writer: &'b mut EmailWriter<'a>,
137}
138
139impl<'a, 'b> Write for FoldingEmailWriter<'a, 'b> {
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 pretty_assertions::assert_eq;
178
179    use super::*;
180
181    #[test]
182    fn wrap_immediate() {
183        let mut s =
184            "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
185        let line_len = s.len();
186
187        {
188            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
189            for _ in 0..16 {
190                w.folding().write_str("0123456789").unwrap();
191            }
192        }
193
194        assert_eq!(
195            s,
196            "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
197        );
198    }
199
200    #[test]
201    fn wrap_keeping_final_whitespace() {
202        let mut s = "Subject: AAAAAAAAAAAAAA".to_owned();
203        let line_len = s.len();
204
205        {
206            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
207            w.folding().write_str("12345 ").unwrap();
208            w.new_line().unwrap();
209            w.folding().write_str("12345").unwrap();
210        }
211
212        assert_eq!(s, concat!("Subject: AAAAAAAAAAAAAA 12345\r\n", " 12345"));
213    }
214
215    #[test]
216    fn catch_space() {
217        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
218        let line_len = s.len();
219
220        {
221            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
222            w.folding().write_str("BBB ").unwrap();
223            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
224        }
225
226        assert_eq!(
227            s,
228            concat!(
229                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
230                " CCCCCCCCCCCCC"
231            )
232        );
233    }
234
235    #[test]
236    fn catch_spaces() {
237        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
238        let line_len = s.len();
239
240        {
241            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
242            w.folding().write_str("BBB   ").unwrap();
243            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
244        }
245
246        assert_eq!(
247            s,
248            concat!(
249                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
250                "   CCCCCCCCCCCCC"
251            )
252        );
253    }
254
255    #[test]
256    fn explicit_space() {
257        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
258        let line_len = s.len();
259
260        {
261            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
262            w.folding().write_str("BBB").unwrap();
263            w.space();
264            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
265        }
266
267        assert_eq!(
268            s,
269            concat!(
270                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
271                " CCCCCCCCCCCCC"
272            )
273        );
274    }
275
276    #[test]
277    fn explicit_spaces() {
278        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
279        let line_len = s.len();
280
281        {
282            let mut w = EmailWriter::new(&mut s, line_len, 1, true);
283            w.folding().write_str("BBB").unwrap();
284            w.space();
285            w.write_char(' ').unwrap();
286            w.space();
287            w.folding().write_str("CCCCCCCCCCCCC").unwrap();
288        }
289
290        assert_eq!(
291            s,
292            concat!(
293                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
294                "   CCCCCCCCCCCCC"
295            )
296        );
297    }
298
299    #[test]
300    fn optional_breakpoint() {
301        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
302            .to_owned();
303        let line_len = s.len();
304
305        {
306            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
307            w.space();
308            w.folding().write_str("BBBBBBBBBB").unwrap();
309            w.space();
310            w.folding().write_str("CCCCCCCCCC").unwrap();
311        }
312
313        assert_eq!(
314            s,
315            concat!(
316                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
317                " BBBBBBBBBB CCCCCCCCCC",
318            )
319        );
320    }
321
322    #[test]
323    fn double_spaces_issue_949() {
324        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ".to_string();
325        let line_len = s.len();
326
327        {
328            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
329            w.folding().write_str("BBBBBBBBBBBBB ").unwrap();
330            crate::headers::rfc2047::encode("sélection", &mut w).unwrap();
331        }
332
333        assert_eq!(
334            s,
335            concat!(
336                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBB\r\n",
337                " =?utf-8?b?c8OpbGVjdGlvbg==?=",
338            )
339        );
340    }
341
342    #[test]
343    fn double_spaces_issue_949_no_space() {
344        let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ".to_string();
345        let line_len = s.len();
346
347        {
348            let mut w = EmailWriter::new(&mut s, line_len, 0, true);
349            w.folding().write_str("BBBBBBBBBBBBBBB").unwrap();
350            crate::headers::rfc2047::encode("sélection", &mut w).unwrap();
351        }
352
353        assert_eq!(
354            s,
355            concat!(
356                "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB=?utf-8?b?cw==?=\r\n",
357                " =?utf-8?b?w6lsZWN0aW9u?=",
358            )
359        );
360    }
361}