Some more FETCH things work
This commit is contained in:
parent
64a322b4cb
commit
24c6607304
8 changed files with 436 additions and 62 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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...");
|
||||||
|
|
|
@ -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
181
src/mail_parser_tests.rs
Normal 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();
|
||||||
|
}
|
|
@ -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
18
tests/inject_emails.sh
Executable 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)
|
Loading…
Reference in a new issue