Some more FETCH things work

This commit is contained in:
Alex 2022-07-15 16:15:48 +02:00
parent 64a322b4cb
commit 24c6607304
Signed by: lx
GPG key ID: 0E496D15096376BE
8 changed files with 436 additions and 62 deletions

46
Cargo.lock generated
View file

@ -11,6 +11,15 @@ dependencies = [
"nom 6.1.2", "nom 6.1.2",
] ]
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]] [[package]]
name = "adler" name = "adler"
version = "1.0.2" version = "1.0.2"
@ -24,6 +33,7 @@ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
"async-trait", "async-trait",
"backtrace",
"base64", "base64",
"boitalettres", "boitalettres",
"chrono", "chrono",
@ -343,6 +353,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -963,6 +988,12 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]] [[package]]
name = "gloo-timers" name = "gloo-timers"
version = "0.2.4" version = "0.2.4"
@ -1499,6 +1530,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "object"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.12.0" version = "1.12.0"
@ -1951,6 +1991,12 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"

View file

@ -10,6 +10,7 @@ description = "Encrypted mail storage over Garage"
anyhow = "1.0.28" anyhow = "1.0.28"
argon2 = "0.3" argon2 = "0.3"
async-trait = "0.1" async-trait = "0.1"
backtrace = "0.3"
base64 = "0.13" base64 = "0.13"
clap = { version = "3.1.18", features = ["derive", "env"] } clap = { version = "3.1.18", features = ["derive", "env"] }
duplexify = "1.1.0" duplexify = "1.1.0"

View file

@ -274,30 +274,28 @@ impl MailboxView {
FetchAttribute::Rfc822Size => { FetchAttribute::Rfc822Size => {
attributes.push(MessageAttribute::Rfc822Size(meta.rfc822_size as u32)) attributes.push(MessageAttribute::Rfc822Size(meta.rfc822_size as u32))
} }
FetchAttribute::Rfc822Header => attributes.push( FetchAttribute::Rfc822Header => {
MessageAttribute::Rfc822Header(NString(Some(IString::Literal( attributes.push(MessageAttribute::Rfc822Header(NString(
meta.headers meta.headers.to_vec().try_into().ok().map(IString::Literal),
.clone() )))
.try_into() }
.or(Err(Error::msg("IString conversion error")))?,
)))),
),
FetchAttribute::Rfc822Text => { FetchAttribute::Rfc822Text => {
let r = parsed let r = parsed
.raw_message.get(parsed.offset_body..parsed.offset_end) .raw_message.get(parsed.offset_body..parsed.offset_end)
.ok_or(Error::msg("Unable to extract email body, cursors out of bound. This is a bug."))? .ok_or(Error::msg("Unable to extract email body, cursors out of bound. This is a bug."))?;
.try_into()
.or(Err(Error::msg("IString conversion error")))?;
attributes.push(MessageAttribute::Rfc822Text(NString(Some( attributes.push(MessageAttribute::Rfc822Text(NString(
IString::Literal(r), r.try_into().ok().map(IString::Literal),
)))) )));
}
FetchAttribute::Rfc822 => {
attributes.push(MessageAttribute::Rfc822(NString(Some(IString::Literal(
body.as_ref().unwrap().clone().try_into().unwrap(),
)))))
} }
FetchAttribute::Rfc822 => attributes.push(MessageAttribute::Rfc822(NString(
body.as_ref()
.unwrap()
.clone()
.try_into()
.ok()
.map(IString::Literal),
))),
FetchAttribute::Envelope => { FetchAttribute::Envelope => {
attributes.push(MessageAttribute::Envelope(message_envelope(&parsed))) attributes.push(MessageAttribute::Envelope(message_envelope(&parsed)))
} }
@ -313,21 +311,8 @@ impl MailboxView {
peek, peek,
} => { } => {
// @TODO Add missing section specifiers // @TODO Add missing section specifiers
let text = match section { match get_message_section(&parsed, section) {
Some(FetchSection::Text(None)) => { Ok(text) => {
parsed
.raw_message.get(parsed.offset_body..parsed.offset_end)
.ok_or(Error::msg("Unable to extract email body, cursors out of bound. This is a bug."))?
}
Some(FetchSection::Header(None)) => {
parsed
.raw_message.get(..parsed.offset_body)
.ok_or(Error::msg("Unable to extract email body, cursors out of bound. This is a bug."))?
}
None => &parsed.raw_message,
_ => bail!("Unimplemented: section {:?}", section),
};
let seen_flag = Flag::Seen.to_string(); let seen_flag = Flag::Seen.to_string();
if !peek && !flags.iter().any(|x| *x == seen_flag) { if !peek && !flags.iter().any(|x| *x == seen_flag) {
// Add \Seen flag // Add \Seen flag
@ -342,21 +327,33 @@ impl MailboxView {
(&text[*begin as usize..], Some(*begin)) (&text[*begin as usize..], Some(*begin))
} else { } else {
( (
&text[*begin as usize..(*begin + len.get()) as usize], &text[*begin as usize
..(*begin + len.get()) as usize],
Some(*begin), Some(*begin),
) )
} }
} }
None => (text, None), None => (&text[..], None),
}; };
let is = IString::try_from(std::str::from_utf8(text)?).unwrap(); let data =
NString(text.to_vec().try_into().ok().map(IString::Literal));
attributes.push(MessageAttribute::BodyExt { attributes.push(MessageAttribute::BodyExt {
section: section.clone(), section: section.clone(),
origin, origin,
data: NString(Some(is)), data,
}) })
} }
Err(e) => {
tracing::error!(
"Could not get section {:?} of message {}: {}",
section,
uuid,
e
);
}
}
}
FetchAttribute::InternalDate => { FetchAttribute::InternalDate => {
attributes.push(MessageAttribute::InternalDate(MyDateTime( attributes.push(MessageAttribute::InternalDate(MyDateTime(
Utc.fix() Utc.fix()
@ -972,6 +969,129 @@ fn headers_to_basic_fields<'a, T>(bp: &'a Part<T>) -> Result<(SpecialAttrs<'a>,
Ok((attrs, bf)) Ok((attrs, bf))
} }
fn get_message_section<'a>(
parsed: &'a Message<'a>,
section: &Option<FetchSection>,
) -> Result<Cow<'a, [u8]>> {
match section {
Some(FetchSection::Text(None)) => Ok(parsed
.raw_message
.get(parsed.offset_body..parsed.offset_end)
.ok_or(Error::msg(
"Unable to extract email body, cursors out of bound. This is a bug.",
))?
.into()),
Some(FetchSection::Text(Some(part))) => {
subpart_msg_fn(parsed, part.0.as_slice(), |part_msg| {
Ok(part_msg
.raw_message
.get(part_msg.offset_body..parsed.offset_end)
.ok_or(Error::msg(
"Unable to extract email body, cursors out of bound. This is a bug.",
))?
.to_vec()
.into())
})
}
Some(FetchSection::Header(part)) => subpart_msg_fn(
parsed,
part.as_ref().map(|p| p.0.as_slice()).unwrap_or(&[]),
|part_msg| {
Ok(part_msg
.raw_message
.get(..part_msg.offset_body)
.ok_or(Error::msg(
"Unable to extract email header, cursors out of bound. This is a bug.",
))?
.to_vec()
.into())
},
),
Some(FetchSection::Part(part)) => subpart_fn(parsed, part.0.as_slice(), |_msg, part| {
let bytes = match part {
MessagePart::Text(p) | MessagePart::Html(p) => p.body.as_bytes().to_vec(),
MessagePart::Binary(p) | MessagePart::InlineBinary(p) => p.body.to_vec(),
MessagePart::Message(Part {
body: MessageAttachment::Raw(r),
..
}) => r.to_vec(),
MessagePart::Message(Part {
body: MessageAttachment::Parsed(p),
..
}) => p.raw_message.to_vec(),
MessagePart::Multipart(_) => bail!("Multipart part has no body"),
};
Ok(bytes.into())
}),
Some(FetchSection::Mime(part)) => subpart_fn(parsed, part.0.as_slice(), |msg, part| {
let raw_headers = match part {
MessagePart::Text(p) | MessagePart::Html(p) => &p.headers_raw,
MessagePart::Binary(p) | MessagePart::InlineBinary(p) => &p.headers_raw,
MessagePart::Message(p) => &p.headers_raw,
MessagePart::Multipart(m) => &m.headers_raw,
};
let mut ret = vec![];
for (name, body) in raw_headers {
ret.extend(name.as_str().as_bytes());
ret.extend(b": ");
ret.extend(&msg.raw_message[body.start..body.end]);
}
ret.extend(b"\r\n");
Ok(ret.into())
}),
None => Ok(parsed.raw_message.clone()),
_ => bail!("Unimplemented: section {:?}", section),
}
}
fn subpart_msg_fn<'a, F, R>(msg: &Message<'a>, path: &[NonZeroU32], f: F) -> Result<R>
where
F: FnOnce(&Message<'_>) -> Result<R>,
{
if path.is_empty() {
f(msg)
} else {
let part = msg
.parts
.get(path[0].get() as usize - 1)
.ok_or(anyhow!("No such subpart: {}", path[0]))?;
if matches!(part, MessagePart::Message(_)) {
let part_msg = part
.parse_message()
.ok_or(anyhow!("Cannot parse subpart: {}", path[0]))?;
subpart_msg_fn(&part_msg, &path[1..], f)
} else {
bail!("Subpart is not a message: {}", path[0]);
}
}
}
fn subpart_fn<'a, F, R>(msg: &Message<'a>, path: &[NonZeroU32], f: F) -> Result<R>
where
F: FnOnce(&Message<'_>, &MessagePart<'_>) -> Result<R>,
{
if path.is_empty() {
bail!("Unexpected empty path");
} else {
let part = msg
.parts
.get(path[0].get() as usize - 1)
.ok_or(anyhow!("No such subpart: {}", path[0]))?;
if path.len() == 1 {
f(msg, part)
} else {
if matches!(part, MessagePart::Message(_)) {
let part_msg = part
.parse_message()
.ok_or(anyhow!("Cannot parse subpart: {}", path[0]))?;
subpart_fn(&part_msg, &path[1..], f)
} else {
bail!("Subpart is not a message: {}", path[0]);
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -392,7 +392,7 @@ async fn k2v_lock_loop_internal(
let res = futures::try_join!(watch_lock_loop, lock_notify_loop, take_lock_loop); let res = futures::try_join!(watch_lock_loop, lock_notify_loop, take_lock_loop);
info!("lock loop exited: {:?}, releasing", res); info!("lock loop exited, releasing");
if !held_tx.is_closed() { if !held_tx.is_closed() {
warn!("wierd..."); warn!("wierd...");

View file

@ -1,4 +1,5 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io::Write;
pub mod incoming; pub mod incoming;
pub mod mailbox; pub mod mailbox;
@ -17,6 +18,9 @@ impl<'a> TryFrom<&'a [u8]> for IMF<'a> {
type Error = (); type Error = ();
fn try_from(body: &'a [u8]) -> Result<IMF<'a>, ()> { fn try_from(body: &'a [u8]) -> Result<IMF<'a>, ()> {
eprintln!("---- BEGIN PARSED MESSAGE ----");
let _ = std::io::stderr().write_all(body);
eprintln!("---- END PARSED MESSAGE ----");
let parsed = mail_parser::Message::parse(body).ok_or(())?; let parsed = mail_parser::Message::parse(body).ok_or(())?;
Ok(Self { raw: body, parsed }) Ok(Self { raw: body, parsed })
} }

181
src/mail_parser_tests.rs Normal file
View file

@ -0,0 +1,181 @@
use mail_parser::Message;
#[test]
fn test1() {
let input = br#"Content-Type: multipart/mixed; boundary="1234567890123456789012345678901234567890123456789012345678901234567890123456789012"
--1234567890123456789012345678901234567890123456789012345678901234567890123456789012
Content-Type: multipart/mixed; boundary="123456789012345678901234567890123456789012345678901234567890123456789012345678901"
--123456789012345678901234567890123456789012345678901234567890123456789012345678901
Content-Type: multipart/mixed; boundary="12345678901234567890123456789012345678901234567890123456789012345678901234567890"
--12345678901234567890123456789012345678901234567890123456789012345678901234567890
Content-Type: text/plain
1
--1234567890123456789012345678901234567890123456789012345678901234567890123456789012
Content-Type: text/plain
22
--123456789012345678901234567890123456789012345678901234567890123456789012345678901
Content-Type: text/plain
333
--12345678901234567890123456789012345678901234567890123456789012345678901234567890
Content-Type: text/plain
4444
"#;
let message = Message::parse(input);
dbg!(message);
}
#[test]
fn test2() {
let input = br#"Message-ID: <39235E1C.1DC7EA90@example.com>
Date: Wed, 17 May 2000 23:06:04 -0400
From: Doug Sauder <dwsauder@example.com>
X-Mailer: Mozilla 4.7 [en] (WinNT; I)
X-Accept-Language: en
MIME-Version: 1.0
To: Joe Blow <blow@example.com>
Subject: Test message from Netscape Communicator 4.7
Content-Type: multipart/mixed;
boundary="------------A1FCDEE154E03D875E5D6779"
This is a multi-part message in MIME format.
--------------A1FCDEE154E03D875E5D6779
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable
Die Hasen und die Fr=F6sche
Die Hasen klagten einst =FCber ihre mi=DFliche Lage; "wir leben", sprach =
ein
Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der Hunde,
der Adler, ja fast aller Raubtiere! Unsere stete Angst ist =E4rger als de=
r
Tod selbst. Auf, la=DFt uns ein f=FCr allemal sterben."
In einem nahen Teich wollten sie sich nun ers=E4ufen; sie eilten ihm zu;
allein das au=DFerordentliche Get=F6se und ihre wunderbare Gestalt
erschreckte eine Menge Fr=F6sche, die am Ufer sa=DFen, so sehr, da=DF sie=
aufs
schnellste untertauchten.
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ers=E4ufen noch ei=
n
wenig aufschieben, denn auch uns f=FCrchten, wie ihr seht, einige Tiere,
welche also wohl noch ungl=FCcklicher sein m=FCssen als wir."
--------------A1FCDEE154E03D875E5D6779
Content-Type: image/png;
name="redball.png"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
filename="redball.png"
iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAABAAALAAAV
AAAaAAAXAAARAAAKAAADAAAcAAAyAABEAABNAABIAAA9AAAjAAAWAAAmAABhAAB7AACGAACH
AAB9AAB0AABgAAA5AAAUAAAGAAAnAABLAABvAACQAAClAAC7AAC/AACrAAChAACMAABzAABb
AAAuAAAIAABMAAB3AACZAAC0GRnKODjVPT3bKSndBQW4AACoAAB5AAAxAAAYAAAEAABFAACa
AAC7JCTRYWHfhITmf3/mVlbqHx/SAAC5AACjAABdAABCAAAoAAAJAABnAAC6Dw/QVFTek5Pl
rKzpmZntZWXvJSXXAADBAACxAACcAABtAABTAAA2AAAbAAAFAABKAACBAADLICDdZ2fonJzr
pqbtiorvUVHvFBTRAADDAAC2AAB4AABeAABAAAAiAABXAACSAADCAADaGxvoVVXseHjveHjv
V1fvJibhAADOAAC3AACnAACVAABHAAArAAAPAACdAADFAADhBQXrKCjvPDzvNTXvGxvjAADQ
AADJAAC1AACXAACEAABsAABPAAASAAACAABiAADpAADvAgLnAADYAADLAAC6AACwAABwAAAT
AAAkAABYAADIAADTAADNAACzAACDAABuAAAeAAB+AADAAACkAACNAAB/AABpAABQAAAwAACR
AACpAAC8AACqAACbAABlAABJAAAqAAAOAAA0AACsAACvAACtAACmAACJAAB6AABrAABaAAA+
AAApAABqAACCAACfAACeAACWAACPAAB8AAAZAAAHAABVAACOAACKAAA4AAAQAAA/AAByAACA
AABcAAA3AAAsAABmAABDAABWAAAgAAAzAAA8AAA6AAAfAAAMAAAdAAANAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8
LtlFAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAII
SURBVHicY2CAg/8QwIABmJhZWFnZ2Dk4MaU5uLh5eHn5+LkFBDlQJf8zC/EIi4iKiUtI8koJ
Scsgyf5nlpWTV1BUUlZRVVPX4NFk1UJIyghp6+jq6RsYGhmbKJgK85mZW8Dk/rNaSlhZ29ja
2Ts4Ojkr6Li4urFDNf53N/Ow8vTy9vH18w8IDAoWDQkNC4+ASP5ni4wKio6JjYtPSExKTnFW
SE1LF4A69n9GZlZ2Tm5efkFhUXFySWlZlEd5RSVY7j+TkGRVdU1tXX1DY1Ozcktpa1t7h2Yn
OAj+d7l1tyo79vT29SdNSJ44SbFVdHIo9xSIHNPUaWqTpifNSJrZnK00S0U1a/acUG5piNz/
uXLzVJ2qm6dXz584S2WB1cJFi5cshZr539xVftnyFKUVTi2TVjqvyhJLXb1m7TqoHPt6F/HW
0g0bN63crGqVtWXrtu07BJihcsw71+zanRW8Z89eq337RQ/Ip60xO3gIElX/LbikDm8T36Kw
bNmRo7O3zpHkPSZwHBqL//8flz1x2OOkyKJTi7aqbzutfUZI2gIuF8F2lr/D5dw2+fZdwpl8
YVOlI+CJ4/9/joOyYed5QzMvhGqnm2V0WiClm///D0lfXHtJ6vLlK9w7rx7vQk5SQJbFtSms
1y9evXid7QZacgOxmSxktNzdtSwwU+J/VICaCPFIYU3XAJhIOtjf5sfyAAAAJXRFWHRDb21t
ZW50AGNsaXAyZ2lmIHYuMC42IGJ5IFl2ZXMgUGlndWV0NnM7vAAAAABJRU5ErkJggg==
--------------A1FCDEE154E03D875E5D6779
Content-Type: image/png;
name="greenball.png"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
filename="greenball.png"
iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAAAEAAAGAAA
IQAACAAAMQAAQgAAUgAAWgAASgAIYwAIcwAIewAQjAAIawAAOQAAYwAQlAAQnAAhpQAQpQAh
rQBCvRhjxjFjxjlSxiEpzgAYvQAQrQAYrQAhvQCU1mOt1nuE1lJK3hgh1gAYxgAYtQAAKQBC
zhDO55Te563G55SU52NS5yEh3gAYzgBS3iGc52vW75y974yE71JC7xCt73ul3nNa7ykh5wAY
1gAx5wBS7yFr7zlK7xgp5wAp7wAx7wAIhAAQtQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp
1fnZAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAFt
SURBVHicddJtV8IgFAdwD2zIgMEE1+NcqdsoK+m5tCyz7/+ZiLmHsyzvq53zO/cy+N9ery1b
Ve9PWQA9z4MQ+H8Yoj7GASZ95IHfaBGmLOSchyIgyOu22mgQSjUcDuNYcoGjLiLK1cHh0fHJ
aTKKOcMItgYxT89OzsfjyTTLC8UF0c2ZNmKquJhczq6ub+YmSVUYRF59GeDastu7+9nD41Nm
kiJ2jc2J3kAWZ9Pr55fH18XSmRuKUTXUaqHy7O19tfr4NFle/w3YDrWRUIlZrL/W86XJkyJV
G9EaEjIx2XyZmZJGioeUaL+2AY8TY8omR6nkLKhu70zjUKVJXsp3quS2DVSJWNh3zzJKCyex
I0ZxBP3afE0ElyqOlZJyw8r3BE2SFiJCyxA434SCkg65RhdeQBljQtCg39LWrA90RDDG1EWr
YUO23hMANUKRRl61E529cR++D2G5LK002dr/qrcfu9u0V3bxn/XdhR/NYeeN0ggsLAAAACV0
RVh0Q29tbWVudABjbGlwMmdpZiB2LjAuNiBieSBZdmVzIFBpZ3VldDZzO7wAAAAASUVORK5C
YII=
--------------A1FCDEE154E03D875E5D6779
Content-Type: image/png;
name="blueball.png"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
filename="blueball.png"
iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAgAABAAABgA
AAAACCkAEEIAEEoACDEAEFIIIXMIKXsIKYQIIWsAGFoACDkIIWMQOZwYQqUYQq0YQrUQOaUQ
MZQAGFIQMYwpUrU5Y8Y5Y84pWs4YSs4YQs4YQr1Ca8Z7nNacvd6Mtd5jlOcxa94hUt4YStYY
QsYQMaUAACHO5+/n7++cxu9ShO8pWucQOa1Ke86tzt6lzu9ajO8QMZxahNat1ufO7++Mve9K
e+8YOaUYSsaMvee15++Uve8AAClajOdzpe9rnO8IKYwxY+8pWu8IIXsAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADB
Mg1VAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAGI
SURBVHicddJtV5swGAbgEk6AJhBSk4bMCUynBSLaqovbrG/bfPn/vyh70lbsscebL5xznTsh
5BmNhgQoRChwo50EOIohUYLDj4zHhKYQkrEoQdvock4ne0IKMVUpKZLQDeqSTIsv+18PyqqW
Uw2IBsRM7307PPp+fDJrWtnpLDJvewYxnewfnvanZ+fzpmwXijC8KbqEa3Fx2ff91Y95U9XC
UpaDeQwiMpHXP/v+1++bWVPWQoGFawtjury9vru/f/C1Vi7ezT0WWpQHf/7+u/G71aLThK/M
jRxmT6KdzZ9fGk9yatMsTgZLl3XVgFRAC6spj/13enssqJVtWVa3NdBSacL8+VZmYqKmdd1C
SYoOiMOSGwtzlqqlFFIuOqv0a1ZEZrUkWICLLFW266y1KvWE1zV/iDAH1EopnVLCiygZCIom
H3NCKX0lnI+B1iuuzCGTxwXjnDO4d7NpbX42YJJHkBwmAm2TxwAZg40J3+Xtbv1rgOAZwG0N
xW62p+lT+Yi747sD/wEUVMzYmWkOvwAAACV0RVh0Q29tbWVudABjbGlwMmdpZiB2LjAuNiBi
eSBZdmVzIFBpZ3VldDZzO7wAAAAASUVORK5CYII=
--------------A1FCDEE154E03D875E5D6779--
"#;
let message = Message::parse(input).unwrap();
//dbg!(&message);
let part = message.parts.get(0).unwrap();
//dbg!(&part);
let part_msg = part.parse_message().unwrap();
}

View file

@ -9,6 +9,9 @@ mod mail;
mod server; mod server;
mod time; mod time;
#[cfg(test)]
mod mail_parser_tests;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@ -121,7 +124,8 @@ async fn main() -> Result<()> {
// Abort on panic (same behavior as in Go) // Abort on panic (same behavior as in Go)
std::panic::set_hook(Box::new(|panic_info| { std::panic::set_hook(Box::new(|panic_info| {
tracing::error!("{}", panic_info.to_string()); eprintln!("{}", panic_info.to_string());
eprintln!("{:?}", backtrace::Backtrace::new());
std::process::abort(); std::process::abort();
})); }));

18
tests/inject_emails.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/sh
cd $(dirname $0)
function mail_lmtp_session (
echo -e "LHLO localhost\r"
for mail in $(find emails -name '*.eml'); do
echo -e "MAIL FROM: <alex@adnab.me>\r"
echo -e "RCPT TO: <lx@staging.deuxfleurs.org>\r"
echo -e "DATA\r"
cat $mail
echo -e "\r"
echo -e ".\r"
done
echo -e "QUIT\r"
)
mail_lmtp_session | tee >(nc localhost 12024)