mail_builder/headers/
text.rs1use super::{
8 Header,
9 fold::{FoldWriter, write_b_words, write_q_words, write_unstructured},
10};
11use crate::{
12 encoders::encode::{EncodingType, get_encoding_type},
13 writer::Writer,
14};
15use std::borrow::Cow;
16
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct Text<'x> {
20 pub text: Cow<'x, str>,
21}
22
23impl<'x> Text<'x> {
24 pub fn new(text: impl Into<Cow<'x, str>>) -> Self {
26 Self { text: text.into() }
27 }
28}
29
30impl<'x, T> From<T> for Text<'x>
31where
32 T: Into<Cow<'x, str>>,
33{
34 fn from(value: T) -> Self {
35 Self::new(value)
36 }
37}
38
39impl Header for Text<'_> {
40 fn write_header(&self, output: &mut impl Writer, column: usize) {
41 let mut folder = FoldWriter::new(output, column);
42
43 match get_encoding_type(self.text.as_bytes(), true, false) {
44 EncodingType::Base64 => write_b_words(&mut folder, &self.text, b""),
45 EncodingType::QuotedPrintable(is_ascii) => {
46 write_q_words::<_, false>(&mut folder, &self.text, is_ascii, b"")
47 }
48 EncodingType::None => write_unstructured(&mut folder, self.text.as_bytes()),
49 }
50
51 folder.finish();
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use mail_parser::MessageParser;
59
60 #[test]
61 fn test_utf8_q_encoding_boundaries() {
62 let mut buf = b"Subject: ".to_vec();
63
64 let mut input = String::new();
65
66 for _ in 0..20000 {
67 input += "x";
68 }
69 for _ in 0..600 {
70 input += "δ";
71 }
72
73 input += "x";
74 for _ in 0..600 {
75 input += "δ";
76 }
77
78 let header = Text::new(input.clone());
79 header.write_header(&mut buf, "Subject: ".len());
80
81 let output = str::from_utf8(&buf).unwrap();
82
83 for line in output.lines() {
84 assert!(
85 line.trim().len() <= 78,
86 "Line exceeds 78 characters: {}",
87 line
88 );
89 }
90 let message = MessageParser::new()
91 .parse_headers(output.as_bytes())
92 .unwrap();
93 assert_eq!(message.subject().unwrap(), input);
94
95 assert!(output.starts_with("Subject: =?utf-8?Q?xxx"));
96
97 assert!(!output.contains("CE?="));
98 assert!(!output.contains("=?utf-8?Q?=B4"));
99 }
100
101 fn b_encoded_input() -> String {
102 let mut input = String::new();
103
104 for _ in 0..600 {
105 input += "δ";
106 }
107 input += "x";
108 for _ in 0..600 {
109 input += "δ";
110 }
111 input
112 }
113
114 #[test]
115 fn test_utf8_b_encoding_boundaries() {
116 let mut buf = b"Subject: ".to_vec();
117
118 let input = b_encoded_input();
119
120 let header = Text::new(input.clone());
121 header.write_header(&mut buf, "Subject: ".len());
122
123 let output = str::from_utf8(&buf).unwrap();
124 for line in output.lines() {
125 assert!(
126 line.trim().len() <= 78,
127 "Line exceeds 78 characters: {}",
128 line
129 );
130 }
131 let message = MessageParser::new()
132 .parse_headers(output.as_bytes())
133 .unwrap();
134 assert_eq!(message.subject().unwrap(), input);
135
136 assert!(output.starts_with("Subject: =?utf-8?B?zrTOtM60zrTOtM60"));
137
138 assert!(!output.contains("zg==?="));
139 assert!(!output.contains("?B?tM60zr"));
140
141 assert!(output.ends_with("\r\n"));
142 }
143
144 #[test]
145 fn test_utf8_b_encoding_large_bytes_written() {
146 let mut buf = Vec::new();
147
148 let input = b_encoded_input();
149
150 let header = Text::new(input);
151
152 let bytes_written = 500;
153 header.write_header(&mut buf, bytes_written);
154
155 let output = str::from_utf8(&buf).unwrap();
156
157 for line in output.lines() {
158 assert!(
159 line.trim().len() <= 78,
160 "Line exceeds 78 characters: {}",
161 line
162 );
163 }
164
165 assert!(
166 output.starts_with("\r\n =?utf-8?B?zrTOtM60zrTOtM60"),
167 "{output:?}"
168 );
169 }
170}