1use std::{mem, ops::Deref};
2
3use crate::message::header::ContentTransferEncoding;
4
5#[derive(Debug, Clone)]
7pub struct Body {
8 buf: Vec<u8>,
9 encoding: ContentTransferEncoding,
10}
11
12#[derive(Debug, Clone)]
17pub enum MaybeString {
18 Binary(Vec<u8>),
20 String(String),
22}
23
24impl Body {
25 pub fn new<B: Into<MaybeString>>(buf: B) -> Self {
38 let mut buf: MaybeString = buf.into();
39
40 let encoding = buf.encoding(false);
41 buf.encode_crlf();
42 Self::new_impl(buf.into(), encoding)
43 }
44
45 pub fn new_with_encoding<B: Into<MaybeString>>(
55 buf: B,
56 encoding: ContentTransferEncoding,
57 ) -> Result<Self, Vec<u8>> {
58 let mut buf: MaybeString = buf.into();
59
60 let best_encoding = buf.encoding(true);
61 let ok = match (encoding, best_encoding) {
62 (ContentTransferEncoding::SevenBit, ContentTransferEncoding::SevenBit) => true,
63 (
64 ContentTransferEncoding::EightBit,
65 ContentTransferEncoding::SevenBit | ContentTransferEncoding::EightBit,
66 ) => true,
67 (ContentTransferEncoding::SevenBit | ContentTransferEncoding::EightBit, _) => false,
68 (
69 ContentTransferEncoding::QuotedPrintable
70 | ContentTransferEncoding::Base64
71 | ContentTransferEncoding::Binary,
72 _,
73 ) => true,
74 };
75 if !ok {
76 return Err(buf.into());
77 }
78
79 buf.encode_crlf();
80 Ok(Self::new_impl(buf.into(), encoding))
81 }
82
83 #[inline]
89 pub fn dangerous_pre_encoded(buf: Vec<u8>, encoding: ContentTransferEncoding) -> Self {
90 Self { buf, encoding }
91 }
92
93 fn new_impl(buf: Vec<u8>, encoding: ContentTransferEncoding) -> Self {
95 match encoding {
96 ContentTransferEncoding::SevenBit
97 | ContentTransferEncoding::EightBit
98 | ContentTransferEncoding::Binary => Self { buf, encoding },
99 ContentTransferEncoding::QuotedPrintable => {
100 let encoded = quoted_printable::encode(buf);
101
102 Self::dangerous_pre_encoded(encoded, ContentTransferEncoding::QuotedPrintable)
103 }
104 ContentTransferEncoding::Base64 => {
105 let len = email_encoding::body::base64::encoded_len(buf.len());
106
107 let mut out = String::with_capacity(len);
108 email_encoding::body::base64::encode(&buf, &mut out)
109 .expect("encode body as base64");
110
111 Self::dangerous_pre_encoded(out.into_bytes(), ContentTransferEncoding::Base64)
112 }
113 }
114 }
115
116 #[inline]
118 pub fn len(&self) -> usize {
119 self.buf.len()
120 }
121
122 #[inline]
124 pub fn is_empty(&self) -> bool {
125 self.buf.is_empty()
126 }
127
128 #[inline]
130 pub fn encoding(&self) -> ContentTransferEncoding {
131 self.encoding
132 }
133
134 #[inline]
136 pub fn into_vec(self) -> Vec<u8> {
137 self.buf
138 }
139}
140
141impl MaybeString {
142 fn encoding(&self, supports_utf8: bool) -> ContentTransferEncoding {
146 use email_encoding::body::Encoding;
147
148 let output = match self {
149 Self::String(s) => Encoding::choose(s.as_str(), supports_utf8),
150 Self::Binary(b) => Encoding::choose(b.as_slice(), supports_utf8),
151 };
152
153 match output {
154 Encoding::SevenBit => ContentTransferEncoding::SevenBit,
155 Encoding::EightBit => ContentTransferEncoding::EightBit,
156 Encoding::QuotedPrintable => ContentTransferEncoding::QuotedPrintable,
157 Encoding::Base64 => ContentTransferEncoding::Base64,
158 }
159 }
160
161 fn encode_crlf(&mut self) {
163 match self {
164 Self::String(string) => in_place_crlf_line_endings(string),
165 Self::Binary(_) => {}
166 }
167 }
168}
169
170pub trait IntoBody {
184 fn into_body(self, encoding: Option<ContentTransferEncoding>) -> Body;
186}
187
188impl<T> IntoBody for T
189where
190 T: Into<MaybeString>,
191{
192 fn into_body(self, encoding: Option<ContentTransferEncoding>) -> Body {
193 match encoding {
194 Some(encoding) => Body::new_with_encoding(self, encoding).expect("invalid encoding"),
195 None => Body::new(self),
196 }
197 }
198}
199
200impl IntoBody for Body {
201 fn into_body(self, encoding: Option<ContentTransferEncoding>) -> Body {
202 let _ = encoding;
203
204 self
205 }
206}
207
208impl AsRef<[u8]> for Body {
209 #[inline]
210 fn as_ref(&self) -> &[u8] {
211 self.buf.as_ref()
212 }
213}
214
215impl From<Vec<u8>> for MaybeString {
216 #[inline]
217 fn from(b: Vec<u8>) -> Self {
218 Self::Binary(b)
219 }
220}
221
222impl From<String> for MaybeString {
223 #[inline]
224 fn from(s: String) -> Self {
225 Self::String(s)
226 }
227}
228
229impl From<MaybeString> for Vec<u8> {
230 #[inline]
231 fn from(s: MaybeString) -> Self {
232 match s {
233 MaybeString::Binary(b) => b,
234 MaybeString::String(s) => s.into(),
235 }
236 }
237}
238
239impl Deref for MaybeString {
240 type Target = [u8];
241
242 #[inline]
243 fn deref(&self) -> &Self::Target {
244 match self {
245 Self::Binary(b) => b.as_ref(),
246 Self::String(s) => s.as_ref(),
247 }
248 }
249}
250
251fn in_place_crlf_line_endings(string: &mut String) {
253 let indices = find_all_lf_char_indices(string);
254
255 for i in indices {
256 string.insert(i, '\r');
258 }
259}
260
261fn find_all_lf_char_indices(s: &str) -> Vec<usize> {
266 let mut indices = Vec::new();
267
268 let mut found_lf = false;
269 for (i, c) in s.char_indices().rev() {
270 if mem::take(&mut found_lf) && c != '\r' {
271 indices.push(i + c.len_utf8());
273 }
274
275 found_lf = c == '\n';
276 }
277
278 if found_lf {
279 indices.push(0);
281 }
282
283 indices
284}
285
286#[cfg(test)]
287mod test {
288 use pretty_assertions::assert_eq;
289
290 use super::{in_place_crlf_line_endings, Body, ContentTransferEncoding};
291
292 #[test]
293 fn seven_bit_detect() {
294 let encoded = Body::new(String::from("Hello, world!"));
295
296 assert_eq!(encoded.encoding(), ContentTransferEncoding::SevenBit);
297 assert_eq!(encoded.as_ref(), b"Hello, world!");
298 }
299
300 #[test]
301 fn seven_bit_encode() {
302 let encoded = Body::new_with_encoding(
303 String::from("Hello, world!"),
304 ContentTransferEncoding::SevenBit,
305 )
306 .unwrap();
307
308 assert_eq!(encoded.encoding(), ContentTransferEncoding::SevenBit);
309 assert_eq!(encoded.as_ref(), b"Hello, world!");
310 }
311
312 #[test]
313 fn seven_bit_too_long_detect() {
314 let encoded = Body::new("Hello, world!".repeat(100));
315
316 assert_eq!(encoded.encoding(), ContentTransferEncoding::QuotedPrintable);
317 assert_eq!(
318 encoded.as_ref(),
319 concat!(
320 "Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, wor=\r\n",
321 "ld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, =\r\n",
322 "world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hell=\r\n",
323 "o, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!H=\r\n",
324 "ello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, worl=\r\n",
325 "d!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, w=\r\n",
326 "orld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello=\r\n",
327 ", world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!He=\r\n",
328 "llo, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world=\r\n",
329 "!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, wo=\r\n",
330 "rld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello,=\r\n",
331 " world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hel=\r\n",
332 "lo, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!=\r\n",
333 "Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, wor=\r\n",
334 "ld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, =\r\n",
335 "world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hell=\r\n",
336 "o, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!H=\r\n",
337 "ello, world!Hello, world!"
338 )
339 .as_bytes()
340 );
341 }
342
343 #[test]
344 fn seven_bit_too_long_fail() {
345 let result = Body::new_with_encoding(
346 "Hello, world!".repeat(100),
347 ContentTransferEncoding::SevenBit,
348 );
349
350 assert!(result.is_err());
351 }
352
353 #[test]
354 fn seven_bit_too_long_encode_quotedprintable() {
355 let encoded = Body::new_with_encoding(
356 "Hello, world!".repeat(100),
357 ContentTransferEncoding::QuotedPrintable,
358 )
359 .unwrap();
360
361 assert_eq!(encoded.encoding(), ContentTransferEncoding::QuotedPrintable);
362 assert_eq!(
363 encoded.as_ref(),
364 concat!(
365 "Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, wor=\r\n",
366 "ld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, =\r\n",
367 "world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hell=\r\n",
368 "o, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!H=\r\n",
369 "ello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, worl=\r\n",
370 "d!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, w=\r\n",
371 "orld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello=\r\n",
372 ", world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!He=\r\n",
373 "llo, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world=\r\n",
374 "!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, wo=\r\n",
375 "rld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello,=\r\n",
376 " world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hel=\r\n",
377 "lo, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!=\r\n",
378 "Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, wor=\r\n",
379 "ld!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, =\r\n",
380 "world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hell=\r\n",
381 "o, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!H=\r\n",
382 "ello, world!Hello, world!"
383 )
384 .as_bytes()
385 );
386 }
387
388 #[test]
389 fn seven_bit_invalid() {
390 let result = Body::new_with_encoding(
391 String::from("Привет, мир!"),
392 ContentTransferEncoding::SevenBit,
393 );
394
395 assert!(result.is_err());
396 }
397
398 #[test]
399 fn eight_bit_encode() {
400 let encoded = Body::new_with_encoding(
401 String::from("Привет, мир!"),
402 ContentTransferEncoding::EightBit,
403 )
404 .unwrap();
405
406 assert_eq!(encoded.encoding(), ContentTransferEncoding::EightBit);
407 assert_eq!(encoded.as_ref(), "Привет, мир!".as_bytes());
408 }
409
410 #[test]
411 fn eight_bit_too_long_fail() {
412 let result = Body::new_with_encoding(
413 "Привет, мир!".repeat(200),
414 ContentTransferEncoding::EightBit,
415 );
416
417 assert!(result.is_err());
418 }
419
420 #[test]
421 fn quoted_printable_detect() {
422 let encoded = Body::new(String::from("Questo messaggio è corto"));
423
424 assert_eq!(encoded.encoding(), ContentTransferEncoding::QuotedPrintable);
425 assert_eq!(encoded.as_ref(), b"Questo messaggio =C3=A8 corto");
426 }
427
428 #[test]
429 fn quoted_printable_encode_ascii() {
430 let encoded = Body::new_with_encoding(
431 String::from("Hello, world!"),
432 ContentTransferEncoding::QuotedPrintable,
433 )
434 .unwrap();
435
436 assert_eq!(encoded.encoding(), ContentTransferEncoding::QuotedPrintable);
437 assert_eq!(encoded.as_ref(), b"Hello, world!");
438 }
439
440 #[test]
441 fn quoted_printable_encode_utf8() {
442 let encoded = Body::new_with_encoding(
443 String::from("Привет, мир!"),
444 ContentTransferEncoding::QuotedPrintable,
445 )
446 .unwrap();
447
448 assert_eq!(encoded.encoding(), ContentTransferEncoding::QuotedPrintable);
449 assert_eq!(
450 encoded.as_ref(),
451 b"=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!".as_ref()
452 );
453 }
454
455 #[test]
456 fn quoted_printable_encode_line_wrap() {
457 let encoded = Body::new(String::from(
458 "Se lo standard 📬 fosse stato più semplice avremmo finito molto prima.",
459 ));
460
461 assert_eq!(encoded.encoding(), ContentTransferEncoding::QuotedPrintable);
462 println!("{}", std::str::from_utf8(encoded.as_ref()).unwrap());
463 assert_eq!(
464 encoded.as_ref(),
465 concat!(
466 "Se lo standard =F0=9F=93=AC fosse stato pi=C3=B9 semplice avremmo finito mo=\r\n",
467 "lto prima."
468 )
469 .as_bytes()
470 );
471 }
472
473 #[test]
474 fn base64_detect() {
475 let input = Body::new(vec![0; 80]);
476 let encoding = input.encoding();
477 assert_eq!(encoding, ContentTransferEncoding::Base64);
478 }
479
480 #[test]
481 fn base64_encode_bytes() {
482 let encoded =
483 Body::new_with_encoding(vec![0; 80], ContentTransferEncoding::Base64).unwrap();
484
485 assert_eq!(encoded.encoding(), ContentTransferEncoding::Base64);
486 assert_eq!(
487 encoded.as_ref(),
488 concat!(
489 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
490 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
491 )
492 .as_bytes()
493 );
494 }
495
496 #[test]
497 fn base64_encode_bytes_wrapping() {
498 let encoded = Body::new_with_encoding(
499 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].repeat(20),
500 ContentTransferEncoding::Base64,
501 )
502 .unwrap();
503
504 assert_eq!(encoded.encoding(), ContentTransferEncoding::Base64);
505 assert_eq!(
506 encoded.as_ref(),
507 concat!(
508 "AAECAwQFBgcICQABAgMEBQYHCAkAAQIDBAUGBwgJAAECAwQFBgcICQABAgMEBQYHCAkAAQIDBAUG\r\n",
509 "BwgJAAECAwQFBgcICQABAgMEBQYHCAkAAQIDBAUGBwgJAAECAwQFBgcICQABAgMEBQYHCAkAAQID\r\n",
510 "BAUGBwgJAAECAwQFBgcICQABAgMEBQYHCAkAAQIDBAUGBwgJAAECAwQFBgcICQABAgMEBQYHCAkA\r\n",
511 "AQIDBAUGBwgJAAECAwQFBgcICQABAgMEBQYHCAk="
512 )
513 .as_bytes()
514 );
515 }
516
517 #[test]
518 fn base64_encode_ascii() {
519 let encoded = Body::new_with_encoding(
520 String::from("Hello World!"),
521 ContentTransferEncoding::Base64,
522 )
523 .unwrap();
524
525 assert_eq!(encoded.encoding(), ContentTransferEncoding::Base64);
526 assert_eq!(encoded.as_ref(), b"SGVsbG8gV29ybGQh");
527 }
528
529 #[test]
530 fn base64_encode_ascii_wrapping() {
531 let encoded =
532 Body::new_with_encoding("Hello World!".repeat(20), ContentTransferEncoding::Base64)
533 .unwrap();
534
535 assert_eq!(encoded.encoding(), ContentTransferEncoding::Base64);
536 assert_eq!(
537 encoded.as_ref(),
538 concat!(
539 "SGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29y\r\n",
540 "bGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8g\r\n",
541 "V29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVs\r\n",
542 "bG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQhSGVsbG8gV29ybGQh\r\n",
543 "SGVsbG8gV29ybGQh"
544 )
545 .as_bytes()
546 );
547 }
548
549 #[test]
550 fn crlf() {
551 let mut string = String::from("Send me a ✉️\nwith\nlettre!\n😀");
552
553 in_place_crlf_line_endings(&mut string);
554 assert_eq!(string, "Send me a ✉️\r\nwith\r\nlettre!\r\n😀");
555 }
556
557 #[test]
558 fn harsh_crlf() {
559 let mut string = String::from("\n\nSend me a ✉️\r\n\nwith\n\nlettre!\n\r\n😀");
560
561 in_place_crlf_line_endings(&mut string);
562 assert_eq!(
563 string,
564 "\r\n\r\nSend me a ✉️\r\n\r\nwith\r\n\r\nlettre!\r\n\r\n😀"
565 );
566 }
567
568 #[test]
569 fn crlf_noop() {
570 let mut string = String::from("\r\nSend me a ✉️\r\nwith\r\nlettre!\r\n😀");
571
572 in_place_crlf_line_endings(&mut string);
573 assert_eq!(string, "\r\nSend me a ✉️\r\nwith\r\nlettre!\r\n😀");
574 }
575}