mail_builder/encoders/
quoted_printable.rs1use std::io::{self, Write};
13
14pub fn quoted_printable_encode(
15 input: &[u8],
16 mut output: impl Write,
17 is_inline: bool,
18 is_body: bool,
19) -> io::Result<usize> {
20 let mut bytes_written = 0;
21 if !is_inline {
22 if is_body {
23 let mut prev_ch = 0;
24 for (pos, &ch) in input.iter().enumerate() {
25 if ch == b'='
26 || ch >= 127
27 || ((ch == b' ' || ch == b'\t')
28 && (matches!(input.get(pos + 1..), Some([b'\n', ..] | [b'\r', b'\n', ..]))
29 || (pos == input.len() - 1)))
30 {
31 if bytes_written + 3 > 76 {
32 output.write_all(b"=\r\n")?;
33 bytes_written = 0;
34 }
35 output.write_all(format!("={:02X}", ch).as_bytes())?;
36 bytes_written += 3;
37 } else if ch == b'\n' {
38 if prev_ch != b'\r' {
39 output.write_all(b"\r\n")?;
40 } else {
41 output.write_all(b"\n")?;
42 }
43 bytes_written = 0;
44 } else {
45 prev_ch = ch;
46 if bytes_written + 1 > 76 {
47 output.write_all(b"=\r\n")?;
48 bytes_written = 0;
49 }
50 output.write_all(&[ch])?;
51 bytes_written += 1;
52 }
53 }
54 } else {
55 for (pos, &ch) in input.iter().enumerate() {
56 if ch == b'='
57 || ch >= 127
58 || (ch == b'\r' || ch == b'\n')
59 || ((ch == b' ' || ch == b'\t') && (pos == input.len() - 1))
60 {
61 if bytes_written + 3 > 76 {
62 output.write_all(b"=\r\n")?;
63 bytes_written = 0;
64 }
65 output.write_all(format!("={:02X}", ch).as_bytes())?;
66 bytes_written += 3;
67 } else {
68 if bytes_written + 1 > 76 {
69 output.write_all(b"=\r\n")?;
70 bytes_written = 0;
71 }
72 output.write_all(&[ch])?;
73 bytes_written += 1;
74 }
75 }
76 }
77 } else {
78 for &ch in input.iter() {
79 if ch == b'=' || ch == b'?' || ch == b'\t' || ch == b'\r' || ch == b'\n' || ch >= 127 {
80 output.write_all(format!("={:02X}", ch).as_bytes())?;
81 bytes_written += 3;
82 } else if ch == b' ' {
83 output.write_all(b"_")?;
84 bytes_written += 1;
85 } else {
86 output.write_all(&[ch])?;
87 bytes_written += 1;
88 }
89 }
90 }
91
92 Ok(bytes_written)
93}
94
95#[cfg(test)]
96mod tests {
97
98 #[test]
99 fn encode_quoted_printable() {
100 for (input, expected_result_body, expected_result_attachment, expected_result_inline) in [
101 ("hello world".to_string(), "hello world", "hello world", "hello_world"),
102 (
103 "hello ? world ?".to_string(),
104 "hello ? world ?",
105 "hello ? world ?",
106 "hello_=3F_world_=3F",
107 ),
108 (
109 "hello = world =".to_string(),
110 "hello =3D world =3D",
111 "hello =3D world =3D",
112 "hello_=3D_world_=3D",
113 ),
114 (
115 "hello\nworld\n".to_string(),
116 "hello\r\nworld\r\n",
117 "hello=0Aworld=0A",
118 "hello=0Aworld=0A",
119 ),
120 (
121 "hello \nworld \r\n ".to_string(),
122 "hello =20\r\nworld =20\r\n =20",
123 "hello =0Aworld =0D=0A =20",
124 "hello___=0Aworld___=0D=0A___",
125 ),
126 (
127 "hello \nworld \n".to_string(),
128 "hello =20\r\nworld =20\r\n",
129 "hello =0Aworld =0A",
130 "hello___=0Aworld___=0A",
131 ),
132 (
133 "áéíóú".to_string(),
134 "=C3=A1=C3=A9=C3=AD=C3=B3=C3=BA",
135 "=C3=A1=C3=A9=C3=AD=C3=B3=C3=BA",
136 "=C3=A1=C3=A9=C3=AD=C3=B3=C3=BA",
137 ),
138 (
139 "안녕하세요 세계".to_string(),
140 "=EC=95=88=EB=85=95=ED=95=98=EC=84=B8=EC=9A=94 =EC=84=B8=EA=B3=84",
141 "=EC=95=88=EB=85=95=ED=95=98=EC=84=B8=EC=9A=94 =EC=84=B8=EA=B3=84",
142 "=EC=95=88=EB=85=95=ED=95=98=EC=84=B8=EC=9A=94_=EC=84=B8=EA=B3=84",
143 ),
144 (
145 " ".repeat(100),
146 concat!(
147 " ",
148 " =\r\n ",
149 " =20"
150 ),
151 concat!(
152 " ",
153 " =\r\n ",
154 " =20"
155 ),
156 concat!(
157 "_________________________________________",
158 "_____________________________________________",
159 "______________"
160 ),
161 ),
162 ] {
163 let mut output = Vec::new();
164 super::quoted_printable_encode(input.as_bytes(), &mut output, false, true).unwrap();
165 assert_eq!(std::str::from_utf8(&output).unwrap(), expected_result_body, "body");
166
167 let mut output = Vec::new();
168 super::quoted_printable_encode(input.as_bytes(), &mut output, false, false).unwrap();
169 assert_eq!(std::str::from_utf8(&output).unwrap(), expected_result_attachment, "attachment");
170
171 let mut output = Vec::new();
172 super::quoted_printable_encode(input.as_bytes(), &mut output, true, false).unwrap();
173 assert_eq!(std::str::from_utf8(&output).unwrap(), expected_result_inline, "inline");
174 }
175 }
176}