crabmail

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

commit 4d2c5c777a587721fb1f91e4e00bad1940cbeebb
parent fd94cdd1382767145420bf09874fa9a201278b61
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Sat, 22 May 2021 13:03:42 -0700

Clean up escape HTML interface (kinda)

Diffstat:
Msrc/main.rs | 29+++++++++++++++++------------
Msrc/utils.rs | 50++++++++++++++++++++++++++++++--------------------
2 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/src/main.rs b/src/main.rs @@ -3,6 +3,7 @@ use mailparse::{parse_mail, MailHeaderMap, ParsedMail}; use std::fs::{File, OpenOptions}; use std::io::prelude::*; use std::path::Path; +use utils::EscapedHTML; mod utils; @@ -58,23 +59,27 @@ fn parse_path(s: &std::ffi::OsStr) -> Result<std::path::PathBuf, &'static str> { } fn email_to_html(email: ParsedMail) -> String { - // TODO use format strings here i think - let escaped_header = |f: &str| { - let mut s = String::new(); - let field = &email.headers.get_first_value(f).unwrap_or("".to_string()); - utils::escape_html(&mut s, &field); - return s; - }; + // Probably if I was better at Rust I could rewrite these in a more efficient way, + // avoiding unnecessary allocs. Could use some of the lower-level features of + // the mailparse library + // + // could definitely improve the api here + let get_header_alloc = |f| email.headers.get_first_value(f).unwrap_or("".to_string()); return format!( r#" -<b>From<b>: {from}<br> -<b>Subject</b>: {subject} +<b>From</b>: {from}<br> +<b>Subject</b>: {subject}<br> +<b>Date</b>: {date}<br> +<b>Message-Id</b>: {message_id} <div id="body"> {body} </div> "#, - from = escaped_header("From"), - subject = escaped_header("Subject"), - body = &email.get_body().unwrap() + from = EscapedHTML(&get_header_alloc("from")), + subject = EscapedHTML(&get_header_alloc("subject")), + date = EscapedHTML(&get_header_alloc("date")), + message_id = EscapedHTML(&get_header_alloc("message-id")), + // TODO replace with get body raw to avoid unneeded alloc. same w/ headers + body = EscapedHTML(&email.get_body().unwrap_or("".to_string())) ); } diff --git a/src/utils.rs b/src/utils.rs @@ -1,7 +1,10 @@ +use std::fmt; use std::io; use std::io::Write; + // Derived from https://github.com/raphlinus/pulldown-cmark/blob/master/src/escape.rs // Don't use single quotes (') in any of my attributes +// Homebrewing my html templating to minimize dependencies // !!!WIP!!! -- still need to add tests, audit security, etc const fn create_html_escape_table() -> [u8; 256] { @@ -17,27 +20,34 @@ static HTML_ESCAPE_TABLE: [u8; 256] = create_html_escape_table(); static HTML_ESCAPES: [&str; 5] = ["", "&quot;", "&amp;", "&lt;", "&gt;"]; -pub fn escape_html(w: &mut String, s: &str) { - let bytes = s.as_bytes(); - let mut mark = 0; - let mut i = 0; - while i < s.len() { - match bytes[i..] - .iter() - .position(|&c| HTML_ESCAPE_TABLE[c as usize] != 0) - { - Some(pos) => { - i += pos; +pub struct EscapedHTML<'a>(pub &'a str); + +impl fmt::Display for EscapedHTML<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = self.0; + let bytes = s.as_bytes(); + let mut mark = 0; + let mut i = 0; + // does this work + while i < s.len() { + match bytes[i..] + .iter() + .position(|&c| HTML_ESCAPE_TABLE[c as usize] != 0) + { + Some(pos) => { + i += pos; + } + None => break, } - None => break, + let c = bytes[i]; + let escape = HTML_ESCAPE_TABLE[c as usize]; + let escape_seq = HTML_ESCAPES[escape as usize]; + f.write_str(&s[mark..i])?; + f.write_str(escape_seq)?; + i += 1; + mark = i; // all escaped characters are ASCII } - let c = bytes[i]; - let escape = HTML_ESCAPE_TABLE[c as usize]; - let escape_seq = HTML_ESCAPES[escape as usize]; - w.push_str(&s[mark..i]); - w.push_str(escape_seq); - i += 1; - mark = i; // all escaped characters are ASCII + f.write_str(&s[mark..])?; + Ok(()) } - w.push_str(&s[mark..]) }