From 0e7595d65a7f2d8badc324bd20bad65f891a0f15 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 5 Jan 2024 09:45:47 +0100 Subject: [PATCH] message structure msg --- src/imap/mime_view.rs | 156 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 16 deletions(-) diff --git a/src/imap/mime_view.rs b/src/imap/mime_view.rs index 74bb6ad..f13b989 100644 --- a/src/imap/mime_view.rs +++ b/src/imap/mime_view.rs @@ -5,15 +5,18 @@ use std::collections::HashSet; use anyhow::{anyhow, bail, Result}; use imap_codec::imap_types::body::{BasicFields, Body as FetchBody, BodyStructure, SpecificFields}; -use imap_codec::imap_types::core::{AString, IString, NonEmptyVec}; +use imap_codec::imap_types::core::{AString, IString, NString, NonEmptyVec}; use imap_codec::imap_types::fetch::{ Section as FetchSection, Part as FetchPart }; use eml_codec::{ - header, part::AnyPart, part::composite, part::discrete, + header, mime, + part::AnyPart, part::composite, part::discrete, }; +use crate::imap::imf_view::message_envelope; + pub enum BodySection<'a> { Full(Cow<'a, [u8]>), @@ -80,14 +83,13 @@ pub fn body_ext<'a>( /// b OK Fetch completed (0.001 + 0.000 secs). /// ``` pub fn bodystructure(part: &AnyPart) -> Result> { - unimplemented!(); + NodeMime(part).structure() } /// NodeMime -/// - - - +/// +/// Used for recursive logic on MIME. +/// See SelectedMime for inspection. struct NodeMime<'a>(&'a AnyPart<'a>); impl<'a> NodeMime<'a> { /// A MIME object is a tree of elements. @@ -119,6 +121,15 @@ impl<'a> NodeMime<'a> { } } } + + fn structure(&self) -> Result> { + match self.0 { + AnyPart::Txt(x) => NodeTxt(self, x).structure(), + AnyPart::Bin(x) => NodeBin(self, x).structure(), + AnyPart::Mult(x) => NodeMult(self, x).structure(), + AnyPart::Msg(x) => NodeMsg(self, x).structure(), + } + } } //---------------------------------------------------------- @@ -150,6 +161,9 @@ impl<'a> SubsettedSection<'a> { } } +/// Used for current MIME inspection +/// +/// See NodeMime for recursive logic struct SelectedMime<'a>(&'a AnyPart<'a>); impl<'a> SelectedMime<'a> { /// The subsetted fetch section basically tells us the @@ -253,18 +267,110 @@ impl<'a> SelectedMime<'a> { } // ------------ - - /// Returns the structure of the message - fn structure(&self) -> Result> { - unimplemented!(); - } + + /// Basic field of a MIME part that is + /// common to all parts + fn basic_fields(&self) -> Result> { + let sz = match self.0 { + AnyPart::Txt(x) => x.body.len(), + AnyPart::Bin(x) => x.body.len(), + AnyPart::Msg(x) => x.raw_part.len(), + AnyPart::Mult(x) => 0 + }; + let m = self.0.mime(); + let parameter_list = m + .ctype + .as_ref() + .map(|x| { + x.params + .iter() + .map(|p| { + ( + IString::try_from(String::from_utf8_lossy(p.name).to_string()), + IString::try_from(p.value.to_string()), + ) + }) + .filter(|(k, v)| k.is_ok() && v.is_ok()) + .map(|(k, v)| (k.unwrap(), v.unwrap())) + .collect() + }) + .unwrap_or(vec![]); + + Ok(BasicFields { + parameter_list, + id: NString( + m.id.as_ref() + .and_then(|ci| IString::try_from(ci.to_string()).ok()), + ), + description: NString( + m.description + .as_ref() + .and_then(|cd| IString::try_from(cd.to_string()).ok()), + ), + content_transfer_encoding: match m.transfer_encoding { + mime::mechanism::Mechanism::_8Bit => unchecked_istring("8bit"), + mime::mechanism::Mechanism::Binary => unchecked_istring("binary"), + mime::mechanism::Mechanism::QuotedPrintable => unchecked_istring("quoted-printable"), + mime::mechanism::Mechanism::Base64 => unchecked_istring("base64"), + _ => unchecked_istring("7bit"), + }, + // @FIXME we can't compute the size of the message currently... + size: u32::try_from(sz)?, + }) + } } // --------------------------- -struct SelectedMsg<'a>(&'a SelectedMime<'a>, &'a composite::Message<'a>); -struct SelectedMult<'a>(&'a SelectedMime<'a>, &'a composite::Multipart<'a>); -struct SelectedTxt<'a>(&'a SelectedMime<'a>, &'a discrete::Text<'a>); -struct SelectedBin<'a>(&'a SelectedMime<'a>, &'a discrete::Binary<'a>); +struct NodeMsg<'a>(&'a NodeMime<'a>, &'a composite::Message<'a>); +impl<'a> NodeMsg<'a> { + fn structure(&self) -> Result> { + let basic = SelectedMime(self.0.0).basic_fields()?; + + Ok(BodyStructure::Single { + body: FetchBody { + basic, + specific: SpecificFields::Message { + envelope: Box::new(message_envelope(&self.1.imf)), + body_structure: Box::new(NodeMime(&self.1.child).structure()?), + number_of_lines: nol(self.1.raw_part), + }, + }, + extension_data: None, + }) + } +} +struct NodeMult<'a>(&'a NodeMime<'a>, &'a composite::Multipart<'a>); +impl<'a> NodeMult<'a> { + fn structure(&self) -> Result> { + let itype = &self.1.mime.interpreted_type; + let subtype = IString::try_from(itype.subtype.to_string()) + .unwrap_or(unchecked_istring("alternative")); + + let inner_bodies = self.1 + .children + .iter() + .filter_map(|inner| NodeMime(&inner).structure().ok()) + .collect::>(); + + NonEmptyVec::validate(&inner_bodies)?; + let bodies = NonEmptyVec::unvalidated(inner_bodies); + + Ok(BodyStructure::Multi { + bodies, + subtype, + extension_data: None, + /*Some(MultipartExtensionData { + parameter_list: vec![], + disposition: None, + language: None, + location: None, + extension: vec![], + })*/ + }) + } +} +struct NodeTxt<'a>(&'a NodeMime<'a>, &'a discrete::Text<'a>); +struct NodeBin<'a>(&'a NodeMime<'a>, &'a discrete::Binary<'a>); // --------------------------- @@ -329,3 +435,21 @@ impl<'a> ExtractedFull<'a> { } } } + +/// ---- LEGACY + +/// s is set to static to ensure that only compile time values +/// 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") +} + +// Number Of Lines +fn nol(input: &[u8]) -> u32 { + input + .iter() + .filter(|x| **x == b'\n') + .count() + .try_into() + .unwrap_or(0) +}