From 9b4fcf58df2032ae8cd4622cfd92b42e393db610 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 4 Jul 2022 12:07:48 +0200 Subject: [PATCH] Fix uidindex tests + body structure for text msg --- src/imap/mailbox_view.rs | 213 +++++++++++++++++++++++++++++++++++++-- src/mail/uidindex.rs | 16 +-- 2 files changed, 213 insertions(+), 16 deletions(-) diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index 7707aaf..b0ad95a 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -4,16 +4,18 @@ use std::sync::Arc; use anyhow::{anyhow, bail, Error, Result}; use boitalettres::proto::res::body::Data as Body; -use chrono::{Offset, Utc, TimeZone}; +use chrono::{Offset, TimeZone, Utc}; use futures::stream::{FuturesOrdered, StreamExt}; use imap_codec::types::address::Address; -use imap_codec::types::datetime::MyDateTime; +use imap_codec::types::body::{BasicFields, Body as FetchBody, BodyStructure, SpecificFields}; use imap_codec::types::core::{Atom, IString, NString, NonZeroBytes}; +use imap_codec::types::datetime::MyDateTime; use imap_codec::types::envelope::Envelope; use imap_codec::types::fetch_attributes::{FetchAttribute, MacroOrFetchAttributes}; use imap_codec::types::flag::Flag; use imap_codec::types::response::{Code, Data, MessageAttribute, Status}; use imap_codec::types::sequence::{self, SequenceSet}; +use mail_parser::*; use crate::mail::mailbox::Mailbox; use crate::mail::uidindex::UidIndex; @@ -277,22 +279,106 @@ impl MailboxView { attributes.push(MessageAttribute::Envelope(message_envelope(&parsed))) } FetchAttribute::Body => { - todo!() + /* + * CAPTURE: + b fetch 29878:29879 (BODY) + * 29878 FETCH (BODY (("text" "plain" ("charset" "utf-8") NIL NIL "quoted-printable" 3264 82)("text" "html" ("charset" "utf-8") NIL NIL "quoted-printable" 31834 643) "alternative")) + * 29879 FETCH (BODY ("text" "html" ("charset" "us-ascii") NIL NIL "7bit" 4107 131)) + ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^^^^^^ ^^^^ ^^^ + | | | | | | number of lines + | | | | | size + | | | | content transfer encoding + | | | description + | | id + | parameter list + b OK Fetch completed (0.001 + 0.000 secs). + * + */ + + /*match parsed.structure { + Part(part_id) => { + match parsed.parts.get(part_id)? { + Text( + let fb = FetchBody { + parameter_list: vec![], + id: NString(None), + descritpion: NString(None), + // Default value is 7bit" + // https://datatracker.ietf.org/doc/html/rfc2045#section-6.1 + content_transfer_encoding: IString::try_from("7bit").unwrap(), + }; + } + } + List(part_list) => todo!(), + MultiPart((first, rest)) => todo!(), + }*/ + + // @TODO This is a stub + let is = IString::try_from("test").unwrap(); + let b = BodyStructure::Single { + body: FetchBody { + basic: BasicFields { + parameter_list: vec![], + id: NString(Some(is.clone())), + description: NString(Some(is.clone())), + content_transfer_encoding: is.clone(), + size: 1, + }, + specific: SpecificFields::Text { + // @FIXME I do not understand yet how this part works + subtype: is, + number_of_lines: 1, + }, + }, + // Always None for Body, can be populated for BodyStructure + extension: None, + }; + + attributes.push(MessageAttribute::Body(b)); } FetchAttribute::BodyExt { section, partial, peek, } => { - todo!() + // @TODO This is a stub + let is = IString::try_from("test").unwrap(); + + attributes.push(MessageAttribute::BodyExt { + section: None, + origin: None, + data: NString(Some(is)), + }) } FetchAttribute::BodyStructure => { - todo!() + // @TODO This is a stub + let is = IString::try_from("test").unwrap(); + let b = BodyStructure::Single { + body: FetchBody { + basic: BasicFields { + parameter_list: vec![], + id: NString(Some(is.clone())), + description: NString(Some(is.clone())), + content_transfer_encoding: is.clone(), + size: 1, + }, + specific: SpecificFields::Text { + // @FIXME I do not understand yet how this part works + subtype: is, + number_of_lines: 1, + }, + }, + // Always None for Body, can be populated for BodyStructure + extension: None, + }; + + attributes.push(MessageAttribute::BodyStructure(b)); } FetchAttribute::InternalDate => { - attributes.push(MessageAttribute::InternalDate( - MyDateTime(Utc.fix().timestamp(i64::try_from(meta.internaldate / 1000)?, 0)) - )); + attributes.push(MessageAttribute::InternalDate(MyDateTime( + Utc.fix() + .timestamp(i64::try_from(meta.internaldate / 1000)?, 0), + ))); } } } @@ -477,3 +563,114 @@ fn convert_address(a: &mail_parser::Addr<'_>) -> Address { NString(host.map(|x| IString::try_from(x).unwrap())), ) } + +fn build_imap_email_struct<'a>( + msg: &Message<'a>, + node: &MessageStructure, +) -> Result { + match node { + MessageStructure::Part(id) => { + let part = msg.parts.get(*id).ok_or(anyhow!( + "Email part referenced in email structure is missing" + ))?; + match part { + MessagePart::Text(bp) => Ok(BodyStructure::Single { + body: FetchBody { + basic: BasicFields { + parameter_list: vec![], //@TODO + id: match bp.headers_rfc.get(&RfcHeader::ContentId) { + Some(HeaderValue::Text(v)) => { + NString(IString::try_from(v.clone().into_owned()).ok()) + } + _ => NString(None), + }, + description: NString(None), //@TODO + content_transfer_encoding: match bp + .headers_rfc + .get(&RfcHeader::ContentTransferEncoding) + { + Some(HeaderValue::Text(v)) => { + IString::try_from(v.clone().into_owned()) + .unwrap_or(unchecked_istring("7bit")) + } + _ => unchecked_istring("7bit"), + }, + size: u32::try_from(bp.len())?, + }, + specific: SpecificFields::Text { + subtype: match bp.headers_rfc.get(&RfcHeader::ContentType) { + Some(HeaderValue::ContentType(ContentType { + c_subtype: Some(st), + .. + })) => IString::try_from(st.clone().into_owned()) + .unwrap_or(unchecked_istring("plain")), + _ => unchecked_istring("plain"), + }, + number_of_lines: u32::try_from(bp.get_text_contents().lines().count())?, + }, + }, + extension: None, + }), + MessagePart::Multipart(_) => { + unreachable!("A multipart entry can not be found here.") + } + _ => todo!(), + } + } + MessageStructure::List(l) => todo!(), + /*BodyStructure::Multi { + bodies: l.map(|inner_node| build_email_struct(msg, inner_node)), + subtype: "", + extension_data: None, + },*/ + MessageStructure::MultiPart((id, l)) => { + todo!() + /*let part = msg.parts.get(id)?; + let mp = match part { + MessagePart::Multipart(mp) => mp, + _ => unreachable!("Only a MessagePart part entry is allowed here."); + } + + + BodyStructure::Multi { + bodies: l.map(|inner_node| build_email_struct(msg, inner_node)), + subtype: "", + extension_data: Some(MultipartExtensionData { + parameter_list: vec![], + disposition: None, + language: None, + location: None, + extension: vec![], + }) + } + */ + } + } +} + +/// s is set to static to ensure that only compile time values +/// checked by the 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") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rfc_to_imap() -> Result<()> { + let txt = br#"From: Garage team +Subject: Welcome to Aerogramme!! + +This is just a test email, feel free to ignore. +"#; + let message = Message::parse(txt).unwrap(); + + let bs = build_imap_email_struct(&message, &message.structure)?; + + print!("{:?}", bs); + + Ok(()) + } +} diff --git a/src/mail/uidindex.rs b/src/mail/uidindex.rs index 7166ea7..6da08c1 100644 --- a/src/mail/uidindex.rs +++ b/src/mail/uidindex.rs @@ -279,15 +279,15 @@ mod tests { // Early checks assert_eq!(state.table.len(), 1); let (uid, flags) = state.table.get(&m).unwrap(); - assert_eq!(*uid, 1); + assert_eq!(*uid, NonZeroU32::new(1).unwrap()); assert_eq!(flags.len(), 2); - let ident = state.idx_by_uid.get(&1).unwrap(); + let ident = state.idx_by_uid.get(&NonZeroU32::new(1).unwrap()).unwrap(); assert_eq!(&m, ident); let recent = state.idx_by_flag.0.get("\\Recent").unwrap(); assert_eq!(recent.len(), 1); - assert_eq!(recent.iter().next().unwrap(), &1); - assert_eq!(state.uidnext, 2); - assert_eq!(state.uidvalidity, 1); + assert_eq!(recent.iter().next().unwrap(), &NonZeroU32::new(1).unwrap()); + assert_eq!(state.uidnext, NonZeroU32::new(2).unwrap()); + assert_eq!(state.uidvalidity, NonZeroU32::new(1).unwrap()); } // Add message 2 @@ -334,14 +334,14 @@ mod tests { { let m = UniqueIdent([0x03; 24]); let f = vec!["\\Archive".to_string(), "\\Recent".to_string()]; - let ev = UidIndexOp::MailAdd(m, 1, f); + let ev = UidIndexOp::MailAdd(m, NonZeroU32::new(1).unwrap(), f); state = state.apply(&ev); } // Checks { assert_eq!(state.table.len(), 2); - assert!(state.uidvalidity > 1); + assert!(state.uidvalidity > NonZeroU32::new(1).unwrap()); let (last_uid, ident) = state.idx_by_uid.get_max().unwrap(); assert_eq!(ident, &UniqueIdent([0x03; 24])); @@ -349,7 +349,7 @@ mod tests { let archive = state.idx_by_flag.0.get("\\Archive").unwrap(); assert_eq!(archive.len(), 2); let mut iter = archive.iter(); - assert_eq!(iter.next().unwrap(), &1); + assert_eq!(iter.next().unwrap(), &NonZeroU32::new(1).unwrap()); assert_eq!(iter.next().unwrap(), last_uid); } }