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