diff --git a/core/src/lib.rs b/core/src/lib.rs index 4f30b93..e56a80a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -221,6 +221,33 @@ impl StripCarets for &str { } } +/// Trait for stripping carets ('<','>') from Message IDs inplace. +pub trait StripCaretsInplace { + /// If `self` is surrounded by carets, strip them. + fn strip_carets_inplace(self) -> Self; +} + +impl StripCaretsInplace for &str { + fn strip_carets_inplace(self) -> Self { + let mut self_ref = self.trim(); + if self_ref.starts_with('<') && self_ref.ends_with('>') { + self_ref = &self_ref[1..self_ref.len().saturating_sub(1)]; + } + self_ref + } +} + +impl StripCaretsInplace for String { + fn strip_carets_inplace(mut self) -> Self { + if self.starts_with('<') && self.ends_with('>') { + self.drain(0..1); + let len = self.len(); + self.drain(len.saturating_sub(1)..len); + } + self + } +} + use percent_encoding::CONTROLS; pub use percent_encoding::{utf8_percent_encode, AsciiSet}; diff --git a/core/src/posts.rs b/core/src/posts.rs index 259afb8..d3525dd 100644 --- a/core/src/posts.rs +++ b/core/src/posts.rs @@ -658,7 +658,7 @@ impl Connection { ) -> Result>> { let mut stmt = self.connection.prepare( "SELECT *, strftime('%Y-%m', CAST(timestamp AS INTEGER), 'unixepoch') AS month_year \ - FROM post WHERE list = ? AND message_id = ?;", + FROM post WHERE list = ?1 AND (message_id = ?2 OR concat('<', ?2, '>') = message_id);", )?; let ret = stmt .query_row(rusqlite::params![&list_pk, &message_id], |row| { diff --git a/web/src/lists.rs b/web/src/lists.rs index 82b3bba..f9d130e 100644 --- a/web/src/lists.rs +++ b/web/src/lists.rs @@ -19,7 +19,7 @@ use chrono::TimeZone; use indexmap::IndexMap; -use mailpot::models::Post; +use mailpot::{models::Post, StripCarets, StripCaretsInplace}; use super::*; @@ -228,7 +228,7 @@ pub async fn list_post( list_obj.set_safety(list_owners.as_slice(), &state.conf.administrators); let context = minijinja::context! { - canonical_url => ListPostPath(ListPathIdentifier::from(list.id.clone()), msg_id.to_string()).to_crumb(), + canonical_url => ListPostPath(ListPathIdentifier::from(list.id.clone()), msg_id.to_string().strip_carets_inplace()).to_crumb(), page_title => subject_ref, description => &list.description, list => Value::from_object(list_obj), @@ -239,8 +239,8 @@ pub async fn list_post( to => &envelope.field_to_to_string(), subject => &envelope.subject(), trimmed_subject => subject_ref, - in_reply_to => &envelope.in_reply_to_display().map(|r| r.to_string().as_str().strip_carets().to_string()), - references => &envelope.references().into_iter().map(|m| m.to_string().as_str().strip_carets().to_string()).collect::>(), + in_reply_to => &envelope.in_reply_to_display().map(|r| r.to_string().strip_carets_inplace()), + references => &envelope.references().into_iter().map(|m| m.to_string().strip_carets_inplace()).collect::>(), message_id => msg_id, message => post.message, timestamp => post.timestamp, diff --git a/web/src/typed_paths.rs b/web/src/typed_paths.rs index c21656d..6e0b3de 100644 --- a/web/src/typed_paths.rs +++ b/web/src/typed_paths.rs @@ -177,13 +177,10 @@ macro_rules! list_post_impl { msg_id: Value, ) -> std::result::Result { urlize(state, { - let Some(msg_id) = msg_id.as_str().map(|s| { - if s.starts_with('<') && s.ends_with('>') { - s.to_string() - } else { - format!("<{s}>") - } - }) else { + let Some(msg_id) = msg_id + .as_str() + .map(|s| s.to_string().strip_carets_inplace()) + else { return Err(Error::new( minijinja::ErrorKind::UnknownMethod, "Second argument of list_post_path must be a string.",