From 0e7595d65a7f2d8badc324bd20bad65f891a0f15 Mon Sep 17 00:00:00 2001
From: Quentin Dufour <quentin@deuxfleurs.fr>
Date: Fri, 5 Jan 2024 09:45:47 +0100
Subject: message structure msg

---
 src/imap/mime_view.rs | 156 ++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 140 insertions(+), 16 deletions(-)

(limited to 'src')

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<BodyStructure<'static>> {
-    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<BodyStructure<'static>> {
+        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<BodyStructure<'static>> {
-        unimplemented!();
-    } 
+
+    /// Basic field of a MIME part that is
+    /// common to all parts
+    fn basic_fields(&self) -> Result<BasicFields<'static>> {
+        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<BodyStructure<'static>> {
+        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<BodyStructure<'static>> {
+        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::<Vec<_>>();
+
+        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)
+}
-- 
cgit v1.2.3