Fix uidindex tests + body structure for text msg
This commit is contained in:
parent
05eb2e050b
commit
9b4fcf58df
2 changed files with 213 additions and 16 deletions
|
@ -4,16 +4,18 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Error, Result};
|
use anyhow::{anyhow, bail, Error, Result};
|
||||||
use boitalettres::proto::res::body::Data as Body;
|
use boitalettres::proto::res::body::Data as Body;
|
||||||
use chrono::{Offset, Utc, TimeZone};
|
use chrono::{Offset, TimeZone, Utc};
|
||||||
use futures::stream::{FuturesOrdered, StreamExt};
|
use futures::stream::{FuturesOrdered, StreamExt};
|
||||||
use imap_codec::types::address::Address;
|
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::core::{Atom, IString, NString, NonZeroBytes};
|
||||||
|
use imap_codec::types::datetime::MyDateTime;
|
||||||
use imap_codec::types::envelope::Envelope;
|
use imap_codec::types::envelope::Envelope;
|
||||||
use imap_codec::types::fetch_attributes::{FetchAttribute, MacroOrFetchAttributes};
|
use imap_codec::types::fetch_attributes::{FetchAttribute, MacroOrFetchAttributes};
|
||||||
use imap_codec::types::flag::Flag;
|
use imap_codec::types::flag::Flag;
|
||||||
use imap_codec::types::response::{Code, Data, MessageAttribute, Status};
|
use imap_codec::types::response::{Code, Data, MessageAttribute, Status};
|
||||||
use imap_codec::types::sequence::{self, SequenceSet};
|
use imap_codec::types::sequence::{self, SequenceSet};
|
||||||
|
use mail_parser::*;
|
||||||
|
|
||||||
use crate::mail::mailbox::Mailbox;
|
use crate::mail::mailbox::Mailbox;
|
||||||
use crate::mail::uidindex::UidIndex;
|
use crate::mail::uidindex::UidIndex;
|
||||||
|
@ -277,22 +279,106 @@ impl MailboxView {
|
||||||
attributes.push(MessageAttribute::Envelope(message_envelope(&parsed)))
|
attributes.push(MessageAttribute::Envelope(message_envelope(&parsed)))
|
||||||
}
|
}
|
||||||
FetchAttribute::Body => {
|
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 {
|
FetchAttribute::BodyExt {
|
||||||
section,
|
section,
|
||||||
partial,
|
partial,
|
||||||
peek,
|
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 => {
|
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 => {
|
FetchAttribute::InternalDate => {
|
||||||
attributes.push(MessageAttribute::InternalDate(
|
attributes.push(MessageAttribute::InternalDate(MyDateTime(
|
||||||
MyDateTime(Utc.fix().timestamp(i64::try_from(meta.internaldate / 1000)?, 0))
|
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())),
|
NString(host.map(|x| IString::try_from(x).unwrap())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_imap_email_struct<'a>(
|
||||||
|
msg: &Message<'a>,
|
||||||
|
node: &MessageStructure,
|
||||||
|
) -> Result<BodyStructure> {
|
||||||
|
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 <garagehq@deuxfleurs.fr>
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -279,15 +279,15 @@ mod tests {
|
||||||
// Early checks
|
// Early checks
|
||||||
assert_eq!(state.table.len(), 1);
|
assert_eq!(state.table.len(), 1);
|
||||||
let (uid, flags) = state.table.get(&m).unwrap();
|
let (uid, flags) = state.table.get(&m).unwrap();
|
||||||
assert_eq!(*uid, 1);
|
assert_eq!(*uid, NonZeroU32::new(1).unwrap());
|
||||||
assert_eq!(flags.len(), 2);
|
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);
|
assert_eq!(&m, ident);
|
||||||
let recent = state.idx_by_flag.0.get("\\Recent").unwrap();
|
let recent = state.idx_by_flag.0.get("\\Recent").unwrap();
|
||||||
assert_eq!(recent.len(), 1);
|
assert_eq!(recent.len(), 1);
|
||||||
assert_eq!(recent.iter().next().unwrap(), &1);
|
assert_eq!(recent.iter().next().unwrap(), &NonZeroU32::new(1).unwrap());
|
||||||
assert_eq!(state.uidnext, 2);
|
assert_eq!(state.uidnext, NonZeroU32::new(2).unwrap());
|
||||||
assert_eq!(state.uidvalidity, 1);
|
assert_eq!(state.uidvalidity, NonZeroU32::new(1).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add message 2
|
// Add message 2
|
||||||
|
@ -334,14 +334,14 @@ mod tests {
|
||||||
{
|
{
|
||||||
let m = UniqueIdent([0x03; 24]);
|
let m = UniqueIdent([0x03; 24]);
|
||||||
let f = vec!["\\Archive".to_string(), "\\Recent".to_string()];
|
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);
|
state = state.apply(&ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks
|
// Checks
|
||||||
{
|
{
|
||||||
assert_eq!(state.table.len(), 2);
|
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();
|
let (last_uid, ident) = state.idx_by_uid.get_max().unwrap();
|
||||||
assert_eq!(ident, &UniqueIdent([0x03; 24]));
|
assert_eq!(ident, &UniqueIdent([0x03; 24]));
|
||||||
|
@ -349,7 +349,7 @@ mod tests {
|
||||||
let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
|
let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
|
||||||
assert_eq!(archive.len(), 2);
|
assert_eq!(archive.len(), 2);
|
||||||
let mut iter = archive.iter();
|
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);
|
assert_eq!(iter.next().unwrap(), last_uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue