aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2022-07-05 17:08:12 +0200
committerQuentin Dufour <quentin@deuxfleurs.fr>2022-07-05 17:08:12 +0200
commitcc86653ed80ff57d06db28cf9f6b079dc1a7d158 (patch)
tree06675fdebb601918de90a5959511c3d981f4fd99
parentde19924a60e0d8c60003a829eb2f7719ef32fc25 (diff)
downloadaerogramme-cc86653ed80ff57d06db28cf9f6b079dc1a7d158.tar.gz
aerogramme-cc86653ed80ff57d06db28cf9f6b079dc1a7d158.zip
Refactor BODY parsing to generalize it
-rw-r--r--src/imap/mailbox_view.rs170
1 files changed, 103 insertions, 67 deletions
diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs
index 091c8f6..918e331 100644
--- a/src/imap/mailbox_view.rs
+++ b/src/imap/mailbox_view.rs
@@ -1,4 +1,4 @@
-use std::borrow::Borrow;
+use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::sync::Arc;
@@ -516,78 +516,30 @@ fn build_imap_email_struct<'a>(
MessagePart::Multipart(_) => {
unreachable!("A multipart entry can not be found here.")
}
- MessagePart::Text(bp) => {
- // Extract subtype+attributes from Content-Type header
- let (st, ct_attrs) = match bp.headers_rfc.get(&RfcHeader::ContentType) {
- Some(HeaderValue::ContentType(c)) => {
- (c.c_subtype.as_ref(), c.attributes.as_ref())
- }
- _ => (None, None),
- };
-
- // Transform the Content-Type attributes into IMAP's parameter list
- // Also check if there is a charset defined.
- let (charset_found, mut parameter_list) = ct_attrs
- .map(|attr| {
- attr.iter().fold(
- (false, vec![]),
- |(charset_found, mut param_list), (k, v)| {
- let nk = k.to_lowercase();
- match (
- IString::try_from(k.as_ref()),
- IString::try_from(v.as_ref()),
- ) {
- (Ok(ik), Ok(iv)) => param_list.push((ik, iv)),
- _ => (),
- };
-
- (charset_found || nk == "charset", param_list)
- },
- )
- })
- .unwrap_or((false, vec![]));
+ MessagePart::Text(bp) | MessagePart::Html(bp) => {
+ let (attrs, mut basic) = headers_to_basic_fields(bp)?;
// If the charset is not defined, set it to "us-ascii"
- if !charset_found {
- parameter_list
+ if attrs.charset.is_none() {
+ basic
+ .parameter_list
.push((unchecked_istring("charset"), unchecked_istring("us-ascii")));
}
- // If the subtype is not defined, set it to "plain"
- let subtype = st
- .map(|st| IString::try_from(st.clone().into_owned()).ok())
+ // If the subtype is not defined, set it to "plain". MIME (RFC2045) says that subtype
+ // MUST be defined and hence has no default. But mail-parser does not make any
+ // difference between MIME and raw emails, hence raw emails have no subtypes.
+ let subtype = bp
+ .get_content_type()
+ .map(|h| h.c_subtype.as_ref())
+ .flatten()
+ .map(|st| IString::try_from(st.to_string()).ok())
.flatten()
.unwrap_or(unchecked_istring("plain"));
Ok(BodyStructure::Single {
body: FetchBody {
- basic: BasicFields {
- parameter_list,
- id: match bp.headers_rfc.get(&RfcHeader::ContentId) {
- Some(HeaderValue::Text(v)) => {
- NString(IString::try_from(v.to_string()).ok())
- }
- _ => NString(None),
- },
- description: match bp
- .headers_rfc
- .get(&RfcHeader::ContentDescription)
- {
- Some(HeaderValue::Text(v)) => {
- NString(IString::try_from(v.to_string()).ok())
- }
- _ => NString(None),
- },
- content_transfer_encoding: match bp
- .headers_rfc
- .get(&RfcHeader::ContentTransferEncoding)
- {
- Some(HeaderValue::Text(v)) => IString::try_from(v.to_string())
- .unwrap_or(unchecked_istring("7bit")),
- _ => unchecked_istring("7bit"),
- },
- size: u32::try_from(bp.len())?,
- },
+ basic,
specific: SpecificFields::Text {
subtype,
number_of_lines: u32::try_from(
@@ -598,11 +550,14 @@ fn build_imap_email_struct<'a>(
extension: None,
})
}
- MessagePart::Html(bp) => {
+ MessagePart::Binary(_) | MessagePart::InlineBinary(_) => {
+ /*
+ * Note also that a subtype specification is MANDATORY -- it may not be
+ * omitted from a Content-Type header field. As such, there are no
+ * default subtypes.
+ */
todo!()
}
- MessagePart::Binary(_) => todo!(),
- MessagePart::InlineBinary(_) => todo!(),
MessagePart::Message(_) => todo!(),
}
}
@@ -638,11 +593,92 @@ fn build_imap_email_struct<'a>(
}
/// s is set to static to ensure that only compile time values
-/// checked by the developpers are passed.
+/// checked by developpers are passed.
fn unchecked_istring(s: &'static str) -> IString {
IString::try_from(s).expect("this value is expected to be a valid imap-codec::IString")
}
+#[derive(Default)]
+struct SpecialAttrs<'a> {
+ charset: Option<&'a Cow<'a, str>>,
+ boundary: Option<&'a Cow<'a, str>>,
+}
+
+/// Takes mail-parser Content-Type attributes, build imap-codec BasicFields.parameter_list and
+/// identify some specific attributes (charset and boundary).
+fn attrs_to_params<'a>(bp: &impl MimeHeaders<'a>) -> (SpecialAttrs, Vec<(IString, IString)>) {
+ // Try to extract Content-Type attributes from headers
+ let attrs = match bp.get_content_type().map(|c| c.attributes.as_ref()).flatten() {
+ Some(v) => v,
+ _ => return (SpecialAttrs::default(), vec![])
+ };
+
+
+ // Transform the Content-Type attributes into IMAP's parameter list
+ // Also collect some special attributes that might be used elsewhere
+ attrs.iter().fold(
+ (SpecialAttrs::default(), vec![]),
+ |(mut sa, mut param_list), (k, v)| {
+ let nk = k.to_lowercase();
+ match (IString::try_from(k.as_ref()), IString::try_from(v.as_ref())) {
+ (Ok(ik), Ok(iv)) => param_list.push((ik, iv)),
+ _ => return (sa, param_list),
+ };
+
+ match nk.as_str() {
+ "charset" => {
+ sa.charset = Some(v);
+ }
+ "boundary" => {
+ sa.boundary = Some(v);
+ }
+ _ => (),
+ };
+
+ (sa, param_list)
+ },
+ )
+}
+
+/// Takes mail-parser headers and build imap-codec BasicFields
+/// Return some special informations too
+fn headers_to_basic_fields<'a>(
+ bp: &(impl BodyPart<'a> + MimeHeaders<'a>),
+) -> Result<(SpecialAttrs, BasicFields)> {
+ let (attrs, parameter_list) = attrs_to_params(bp);
+
+ let bf = BasicFields {
+ parameter_list,
+
+ id: NString(
+ bp.get_content_id()
+ .map(|ci| IString::try_from(ci.to_string()).ok())
+ .flatten(),
+ ),
+
+ description: NString(
+ bp.get_content_description()
+ .map(|cd| IString::try_from(cd.to_string()).ok())
+ .flatten(),
+ ),
+
+ /*
+ * RFC2045 - section 6.1
+ * "Content-Transfer-Encoding: 7BIT" is assumed if the
+ * Content-Transfer-Encoding header field is not present.
+ */
+ content_transfer_encoding: bp
+ .get_content_transfer_encoding()
+ .map(|h| IString::try_from(h.to_string()).ok())
+ .flatten()
+ .unwrap_or(unchecked_istring("7bit")),
+
+ size: u32::try_from(bp.len())?,
+ };
+
+ Ok((attrs, bf))
+}
+
#[cfg(test)]
mod tests {
use super::*;