use charset::{decode_ascii, Charset};
use {MailParseError, ParsedContentType};
pub enum Body<'a> {
Base64(EncodedBody<'a>),
QuotedPrintable(EncodedBody<'a>),
SevenBit(TextBody<'a>),
EightBit(TextBody<'a>),
Binary(BinaryBody<'a>),
}
impl<'a> Body<'a> {
pub fn new(
body: &'a [u8],
ctype: &'a ParsedContentType,
transfer_encoding: &Option<String>,
) -> Body<'a> {
transfer_encoding
.as_ref()
.map(|encoding| match encoding.as_ref() {
"base64" => Body::Base64(EncodedBody {
decoder: decode_base64,
body,
ctype,
}),
"quoted-printable" => Body::QuotedPrintable(EncodedBody {
decoder: decode_quoted_printable,
body,
ctype,
}),
"7bit" => Body::SevenBit(TextBody { body, ctype }),
"8bit" => Body::EightBit(TextBody { body, ctype }),
"binary" => Body::Binary(BinaryBody { body, ctype }),
_ => Body::get_default(body, ctype),
})
.unwrap_or_else(|| Body::get_default(body, ctype))
}
fn get_default(body: &'a [u8], ctype: &'a ParsedContentType) -> Body<'a> {
Body::SevenBit(TextBody { body, ctype })
}
}
pub struct EncodedBody<'a> {
decoder: fn(&[u8]) -> Result<Vec<u8>, MailParseError>,
ctype: &'a ParsedContentType,
body: &'a [u8],
}
impl<'a> EncodedBody<'a> {
pub fn get_content_type(&self) -> &'a ParsedContentType {
self.ctype
}
pub fn get_raw(&self) -> &'a [u8] {
self.body
}
pub fn get_decoded(&self) -> Result<Vec<u8>, MailParseError> {
(self.decoder)(self.body)
}
pub fn get_decoded_as_string(&self) -> Result<String, MailParseError> {
get_body_as_string(&self.get_decoded()?, &self.ctype)
}
}
pub struct TextBody<'a> {
ctype: &'a ParsedContentType,
body: &'a [u8],
}
impl<'a> TextBody<'a> {
pub fn get_content_type(&self) -> &'a ParsedContentType {
self.ctype
}
pub fn get_raw(&self) -> &'a [u8] {
self.body
}
pub fn get_as_string(&self) -> Result<String, MailParseError> {
get_body_as_string(self.body, &self.ctype)
}
}
pub struct BinaryBody<'a> {
ctype: &'a ParsedContentType,
body: &'a [u8],
}
impl<'a> BinaryBody<'a> {
pub fn get_content_type(&self) -> &'a ParsedContentType {
self.ctype
}
pub fn get_raw(&self) -> &'a [u8] {
self.body
}
}
fn decode_base64(body: &[u8]) -> Result<Vec<u8>, MailParseError> {
let cleaned = body
.iter()
.filter(|c| !c.is_ascii_whitespace())
.cloned()
.collect::<Vec<u8>>();
Ok(base64::decode(&cleaned)?)
}
fn decode_quoted_printable(body: &[u8]) -> Result<Vec<u8>, MailParseError> {
Ok(quoted_printable::decode(
body,
quoted_printable::ParseMode::Robust,
)?)
}
fn get_body_as_string(body: &[u8], ctype: &ParsedContentType) -> Result<String, MailParseError> {
let cow = if let Some(charset) = Charset::for_label(ctype.charset.as_bytes()) {
let (cow, _, _) = charset.decode(body);
cow
} else {
decode_ascii(body)
};
Ok(cow.into_owned())
}