crabmail

Static HTML email archive viewer in Rust
git clone git://git.alexwennerberg.com/crabmail
Log | Files | Refs | README | LICENSE

commit 5b617781dd26f53b34580a76a80d2fe41de3799e
parent 1b5d1a3c50bc093753f361ed71530b2489c1ccef
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Thu,  6 Jan 2022 15:29:37 -0800

Add html text conversion

Diffstat:
MCargo.lock | 7+++++++
MCargo.toml | 1+
Msrc/main.rs | 45+++++++++++++++++++++++----------------------
Msrc/style.css | 4++++
4 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -48,6 +48,7 @@ dependencies = [ "linkify", "maildir", "mailparse", + "nanohtml2text", "once_cell", "sha3", "urlencoding", @@ -157,6 +158,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] +name = "nanohtml2text" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafa9a9139377b117cf9613054344fac0af3bc4234f978177f79119e5e128183" + +[[package]] name = "once_cell" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -19,3 +19,4 @@ maildir = "0.5.0" once_cell = "1.9" sha3 = "0.10" urlencoding = "2.1" +nanohtml2text = "0.1.0" diff --git a/src/main.rs b/src/main.rs @@ -319,6 +319,11 @@ impl<'a> MailThread<'a> { a(title="permalink", href=format!("#{}", &message.id)) { : " 🔗" } + @ if &message.mime == "text/html" { + span(class="light italic") { + : " (converted from html)"; + } + } br; br; div(class="email-body") { : Raw(utils::email_body(&message.body)) @@ -392,26 +397,15 @@ impl Email { } } -#[cfg(feature = "html")] -fn parse_html_body(email: &ParsedMail) -> String { - use std::collections::HashSet; - use std::iter::FromIterator; - // TODO dont initialize each time - // TODO sanitize id, classes, etc. - let tags = HashSet::from_iter(vec!["a", "b", "i", "br", "p", "span", "u"]); - let a = ammonia::Builder::new() - .tags(tags) - .clean(&email.get_body().unwrap_or("".to_string())) - .to_string(); - a -} - fn local_parse_email(parsed_mail: &ParsedMail) -> Result<Email> { let mut body: String = "[Message has no body]".to_owned(); let mut mime: String = "".to_owned(); let nobody = "[No body found]"; // nested lookup let mut queue = vec![parsed_mail]; + let text_found = false; + let mut text_body = None; + let mut html_body = None; while queue.len() > 0 { let top = queue.pop().unwrap(); for sub in &top.subparts { @@ -422,20 +416,27 @@ fn local_parse_email(parsed_mail: &ParsedMail) -> Result<Email> { // attachment handler } else { if top.ctype.mimetype == "text/plain" { - body = top.get_body().unwrap_or(nobody.to_owned()); + let b = top.get_body().unwrap_or(nobody.to_owned()); if parsed_mail.ctype.params.get("format") == Some(&"flowed".to_owned()) { - body = utils::unformat_flowed(&body); + text_body = Some(utils::unformat_flowed(&b)); + } else { + text_body = Some(b); } - mime = top.ctype.mimetype.clone(); - break; } - #[cfg(feature = "html")] - if sub.ctype.mimetype == "text/html" { - mime = sub.ctype.mimetype.clone(); - break; + if top.ctype.mimetype == "text/html" { + html_body = Some(nanohtml2text::html2text( + &top.get_body().unwrap_or(nobody.to_owned()), + )); } } } + if let Some(b) = text_body { + body = b; + mime = "text/plain".to_owned(); + } else if let Some(b) = html_body { + body = b; + mime = "text/html".to_owned(); + } let headers = &parsed_mail.headers; let id = headers .get_first_value("message-id") diff --git a/src/style.css b/src/style.css @@ -44,6 +44,10 @@ table { border-spacing: 0.5em 0.1em; } font-weight: bold; } +.italic { + font-style: italic; +} + a { text-decoration: none; color: var(--link);