From 568d83f029e46d61e4f0d4df687db9712abbedcc Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sat, 3 Feb 2024 11:10:51 -0800 Subject: [PATCH] linkify URLs in plaintext emails. --- Cargo.lock | 10 ++++++++++ server/Cargo.toml | 1 + server/src/graphql.rs | 9 ++++++--- server/src/lib.rs | 19 +++++++++++++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bfffdb..f3b5381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1747,6 +1747,15 @@ version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +[[package]] +name = "linkify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +dependencies = [ + "memchr", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -3228,6 +3237,7 @@ dependencies = [ "async-graphql-rocket", "css-inline", "glog", + "linkify", "log 0.4.20", "lol_html", "mailparse", diff --git a/server/Cargo.toml b/server/Cargo.toml index da88b79..01afb28 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -27,6 +27,7 @@ lol_html = "1.2.0" css-inline = "0.13.0" anyhow = "1.0.79" maplit = "1.0.2" +linkify = "0.10.0" [dependencies.rocket_contrib] version = "0.4.11" diff --git a/server/src/graphql.rs b/server/src/graphql.rs index 55a4e14..c5a7af9 100644 --- a/server/src/graphql.rs +++ b/server/src/graphql.rs @@ -16,7 +16,7 @@ use memmap::MmapOptions; use notmuch::Notmuch; use rocket::time::Instant; -use crate::sanitize_html; +use crate::{linkify_html, sanitize_html}; pub struct QueryRoot; @@ -347,8 +347,11 @@ impl QueryRoot { .get_first_value("date") .and_then(|d| mailparse::dateparse(&d).ok()); let body = match extract_body(&m)? { - Body::PlainText(PlainText { text, content_tree }) => Body::PlainText(PlainText { - text, + Body::PlainText(PlainText { text, content_tree }) => Body::Html(Html { + html: format!( + r#"

{}

"#, + linkify_html(&text) + ), content_tree: if debug_content_tree { render_content_type_tree(&m) } else { diff --git a/server/src/lib.rs b/server/src/lib.rs index 9ec4903..139bef7 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -3,6 +3,7 @@ pub mod graphql; pub mod nm; use css_inline::{CSSInliner, InlineError, InlineOptions}; +use linkify::{LinkFinder, LinkKind}; use log::error; use lol_html::{element, errors::RewritingError, rewrite_str, RewriteStrSettings}; use maplit::{hashmap, hashset}; @@ -16,6 +17,22 @@ pub enum SanitizeError { InlineError(#[from] InlineError), } +pub fn linkify_html(text: &str) -> String { + let finder = LinkFinder::new(); + let mut parts = Vec::new(); + for span in finder.spans(text) { + // TODO(wathiede): use Cow? + match span.kind() { + // Text as-is + None => parts.push(span.as_str().to_string()), + // Wrap in anchor tag + Some(LinkKind::Url) => parts.push(format!(r#"{0}"#, span.as_str())), + _ => todo!("unhandled kind: {:?}", span.kind().unwrap()), + } + } + parts.join("") +} + pub fn sanitize_html(html: &str) -> Result { let element_content_handlers = vec![ // Open links in new tab @@ -204,8 +221,6 @@ pub fn sanitize_html(html: &str) -> Result { //let clean_html = inlined_html; Ok(rewrite_str( - // TODO(wathiede): replace ammonia with more lol-html rules. - // &ammonia::clean(&html), &clean_html, RewriteStrSettings { element_content_handlers,