1 use crate::{MailParseError, ParsedContentType};
2 use charset::{decode_ascii, Charset};
3
4 /// Represents the body of an email (or mail subpart)
5 pub enum Body<'a> {
6 /// A body with 'base64' Content-Transfer-Encoding.
7 Base64(EncodedBody<'a>),
8 /// A body with 'quoted-printable' Content-Transfer-Encoding.
9 QuotedPrintable(EncodedBody<'a>),
10 /// A body with '7bit' Content-Transfer-Encoding.
11 SevenBit(TextBody<'a>),
12 /// A body with '8bit' Content-Transfer-Encoding.
13 EightBit(TextBody<'a>),
14 /// A body with 'binary' Content-Transfer-Encoding.
15 Binary(BinaryBody<'a>),
16 }
17
18 impl<'a> Body<'a> {
new( body: &'a [u8], ctype: &'a ParsedContentType, transfer_encoding: &Option<String>, ) -> Body<'a>19 pub fn new(
20 body: &'a [u8],
21 ctype: &'a ParsedContentType,
22 transfer_encoding: &Option<String>,
23 ) -> Body<'a> {
24 transfer_encoding
25 .as_ref()
26 .map(|encoding| match encoding.as_ref() {
27 "base64" => Body::Base64(EncodedBody {
28 decoder: decode_base64,
29 body,
30 ctype,
31 }),
32 "quoted-printable" => Body::QuotedPrintable(EncodedBody {
33 decoder: decode_quoted_printable,
34 body,
35 ctype,
36 }),
37 "7bit" => Body::SevenBit(TextBody { body, ctype }),
38 "8bit" => Body::EightBit(TextBody { body, ctype }),
39 "binary" => Body::Binary(BinaryBody { body, ctype }),
40 _ => Body::get_default(body, ctype),
41 })
42 .unwrap_or_else(|| Body::get_default(body, ctype))
43 }
44
get_default(body: &'a [u8], ctype: &'a ParsedContentType) -> Body<'a>45 fn get_default(body: &'a [u8], ctype: &'a ParsedContentType) -> Body<'a> {
46 Body::SevenBit(TextBody { body, ctype })
47 }
48 }
49
50 /// Struct that holds the encoded body representation of the message (or message subpart).
51 pub struct EncodedBody<'a> {
52 decoder: fn(&[u8]) -> Result<Vec<u8>, MailParseError>,
53 ctype: &'a ParsedContentType,
54 body: &'a [u8],
55 }
56
57 impl<'a> EncodedBody<'a> {
58 /// Get the body Content-Type
get_content_type(&self) -> &'a ParsedContentType59 pub fn get_content_type(&self) -> &'a ParsedContentType {
60 self.ctype
61 }
62
63 /// Get the raw body of the message exactly as it is written in the message (or message subpart).
get_raw(&self) -> &'a [u8]64 pub fn get_raw(&self) -> &'a [u8] {
65 self.body
66 }
67
68 /// Get the decoded body of the message (or message subpart).
get_decoded(&self) -> Result<Vec<u8>, MailParseError>69 pub fn get_decoded(&self) -> Result<Vec<u8>, MailParseError> {
70 (self.decoder)(self.body)
71 }
72
73 /// Get the body of the message as a Rust string.
74 /// This function tries to decode the body and then converts
75 /// the result into a Rust UTF-8 string using the charset in the Content-Type
76 /// (or "us-ascii" if the charset was missing or not recognized).
77 /// This operation returns a valid result only if the decoded body
78 /// has a text format.
get_decoded_as_string(&self) -> Result<String, MailParseError>79 pub fn get_decoded_as_string(&self) -> Result<String, MailParseError> {
80 get_body_as_string(&self.get_decoded()?, &self.ctype)
81 }
82 }
83
84 /// Struct that holds the textual body representation of the message (or message subpart).
85 pub struct TextBody<'a> {
86 ctype: &'a ParsedContentType,
87 body: &'a [u8],
88 }
89
90 impl<'a> TextBody<'a> {
91 /// Get the body Content-Type
get_content_type(&self) -> &'a ParsedContentType92 pub fn get_content_type(&self) -> &'a ParsedContentType {
93 self.ctype
94 }
95
96 /// Get the raw body of the message exactly as it is written in the message (or message subpart).
get_raw(&self) -> &'a [u8]97 pub fn get_raw(&self) -> &'a [u8] {
98 self.body
99 }
100
101 /// Get the body of the message as a Rust string.
102 /// This function converts the body into a Rust UTF-8 string using the charset
103 /// in the Content-Type
104 /// (or "us-ascii" if the charset was missing or not recognized).
get_as_string(&self) -> Result<String, MailParseError>105 pub fn get_as_string(&self) -> Result<String, MailParseError> {
106 get_body_as_string(self.body, &self.ctype)
107 }
108 }
109
110 /// Struct that holds a binary body representation of the message (or message subpart).
111 pub struct BinaryBody<'a> {
112 ctype: &'a ParsedContentType,
113 body: &'a [u8],
114 }
115
116 impl<'a> BinaryBody<'a> {
117 /// Get the body Content-Type
get_content_type(&self) -> &'a ParsedContentType118 pub fn get_content_type(&self) -> &'a ParsedContentType {
119 self.ctype
120 }
121
122 /// Get the raw body of the message exactly as it is written in the message (or message subpart).
get_raw(&self) -> &'a [u8]123 pub fn get_raw(&self) -> &'a [u8] {
124 self.body
125 }
126
127 /// Get the body of the message as a Rust string. This function attempts
128 /// to convert the body into a Rust UTF-8 string using the charset in the
129 /// Content-Type header (or "us-ascii" as default). However, this may not
130 /// always work for "binary" data. The API is provided anyway for
131 /// convenient handling of real-world emails that may provide textual data
132 /// with a binary transfer encoding, but use this at your own risk!
get_as_string(&self) -> Result<String, MailParseError>133 pub fn get_as_string(&self) -> Result<String, MailParseError> {
134 get_body_as_string(self.body, &self.ctype)
135 }
136 }
137
decode_base64(body: &[u8]) -> Result<Vec<u8>, MailParseError>138 fn decode_base64(body: &[u8]) -> Result<Vec<u8>, MailParseError> {
139 let cleaned = body
140 .iter()
141 .filter(|c| !c.is_ascii_whitespace())
142 .cloned()
143 .collect::<Vec<u8>>();
144 Ok(base64::decode(&cleaned)?)
145 }
146
decode_quoted_printable(body: &[u8]) -> Result<Vec<u8>, MailParseError>147 fn decode_quoted_printable(body: &[u8]) -> Result<Vec<u8>, MailParseError> {
148 Ok(quoted_printable::decode(
149 body,
150 quoted_printable::ParseMode::Robust,
151 )?)
152 }
153
get_body_as_string(body: &[u8], ctype: &ParsedContentType) -> Result<String, MailParseError>154 fn get_body_as_string(body: &[u8], ctype: &ParsedContentType) -> Result<String, MailParseError> {
155 let cow = if let Some(charset) = Charset::for_label(ctype.charset.as_bytes()) {
156 let (cow, _, _) = charset.decode(body);
157 cow
158 } else {
159 decode_ascii(body)
160 };
161 Ok(cow.into_owned())
162 }
163