From 1beac75e37e2cc2245a7c431cb0cd2958347f293 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 20 Apr 2023 00:20:18 +0300 Subject: [PATCH] core: notify submitter if they are already subscribed --- core/src/db/posts.rs | 87 +++++++++++++++++++++++++++++--- core/src/db/queue.rs | 25 ++++++++- core/src/mail/message_filters.rs | 13 +++-- core/src/templates.rs | 10 +++- core/tests/smtp.rs | 23 +++++++-- 5 files changed, 140 insertions(+), 18 deletions(-) diff --git a/core/src/db/posts.rs b/core/src/db/posts.rs index da5732c..6b0fabf 100644 --- a/core/src/db/posts.rs +++ b/core/src/db/posts.rs @@ -206,21 +206,65 @@ impl Connection { } } PostAction::Reject { reason } => { - /* FIXME - Notify submitter */ - trace!("PostAction::Reject {{ reason: {} }}", reason); - //futures::executor::block_on(conn.mail_transaction(&post.bytes, b)).unwrap(); + log::info!("PostAction::Reject {{ reason: {} }}", reason); + for f in env.from() { + /* send error notice to e-mail sender */ + self.send_reply_with_list_template( + TemplateRenderContext { + template: Template::GENERIC_FAILURE, + default_fn: Some(Template::default_generic_failure), + list: &list, + context: minijinja::context! { + list => &list, + subject => format!("Your post to {} was rejected.", list.id), + details => &reason, + }, + queue: Queue::Out, + comment: format!("PostAction::Reject {{ reason: {} }}", reason), + }, + std::iter::once(Cow::Borrowed(f)), + )?; + } return Err(PostRejected(reason).into()); } PostAction::Defer { reason } => { trace!("PostAction::Defer {{ reason: {} }}", reason); - /* - * - FIXME Notify submitter - * - FIXME Save in database */ + for f in env.from() { + /* send error notice to e-mail sender */ + self.send_reply_with_list_template( + TemplateRenderContext { + template: Template::GENERIC_FAILURE, + default_fn: Some(Template::default_generic_failure), + list: &list, + context: minijinja::context! { + list => &list, + subject => format!("Your post to {} was deferred.", list.id), + details => &reason, + }, + queue: Queue::Out, + comment: format!("PostAction::Defer {{ reason: {} }}", reason), + }, + std::iter::once(Cow::Borrowed(f)), + )?; + } + self.insert_to_queue(QueueEntry::new( + Queue::Deferred, + Some(list.pk), + Some(Cow::Borrowed(&post_env)), + &bytes, + Some(format!("PostAction::Defer {{ reason: {} }}", reason)), + )?)?; return Err(PostRejected(reason).into()); } PostAction::Hold => { trace!("PostAction::Hold"); - /* FIXME - Save in database */ + self.insert_to_queue(QueueEntry::new( + Queue::Hold, + Some(list.pk), + Some(Cow::Borrowed(&post_env)), + &bytes, + Some("PostAction::Hold".to_string()), + )?)?; return Err(PostRejected("Hold".into()).into()); } } @@ -245,12 +289,35 @@ impl Connection { env.from(), list ); - let approval_needed = post_policy .as_ref() .map(|p| p.approval_needed) .unwrap_or(false); for f in env.from() { + let email_from = f.get_email(); + if self + .list_subscription_by_address(list.pk, &email_from) + .is_ok() + { + /* send error notice to e-mail sender */ + self.send_reply_with_list_template( + TemplateRenderContext { + template: Template::GENERIC_FAILURE, + default_fn: Some(Template::default_generic_failure), + list, + context: minijinja::context! { + list => &list, + subject => format!("You are already subscribed to {}.", list.id), + details => "No action has been taken since you are already subscribed to the list.", + }, + queue: Queue::Out, + comment: format!("Address {} is already subscribed to list {}", f, list.id), + }, + std::iter::once(Cow::Borrowed(f)), + )?; + continue; + } + let subscription = ListSubscription { pk: 0, list: list.pk, @@ -368,6 +435,10 @@ impl Connection { list_owners.iter().map(|owner| Cow::Owned(owner.address())), )?; } else { + log::trace!( + "Added subscription to list {list:?} for address {f:?}, sending \ + confirmation." + ); self.send_reply_with_list_template( TemplateRenderContext { template: Template::SUBSCRIPTION_CONFIRMATION, diff --git a/core/src/db/queue.rs b/core/src/db/queue.rs index 5695764..334516b 100644 --- a/core/src/db/queue.rs +++ b/core/src/db/queue.rs @@ -61,7 +61,7 @@ impl Queue { } /// A queue entry. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueueEntry { /// Database primary key. pub pk: i64, @@ -93,6 +93,28 @@ impl std::fmt::Display for QueueEntry { } } +impl std::fmt::Debug for QueueEntry { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct(stringify!(QueueEntry)) + .field("pk", &self.pk) + .field("queue", &self.queue) + .field("list", &self.list) + .field("comment", &self.comment) + .field("to_addresses", &self.to_addresses) + .field("from_address", &self.from_address) + .field("subject", &self.subject) + .field("message_id", &self.message_id) + .field("message length", &self.message.len()) + .field( + "message", + &format!("{:.15}", String::from_utf8_lossy(&self.message)), + ) + .field("timestamp", &self.timestamp) + .field("datetime", &self.datetime) + .finish() + } +} + impl QueueEntry { /// Create new entry. pub fn new( @@ -125,6 +147,7 @@ impl QueueEntry { impl Connection { /// Insert a received email into a queue. pub fn insert_to_queue(&self, mut entry: QueueEntry) -> Result> { + log::trace!("Inserting to queue: {entry}"); let mut stmt = self.connection.prepare( "INSERT INTO queue(which, list, comment, to_addresses, from_address, subject, \ message_id, message, timestamp, datetime) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) \ diff --git a/core/src/mail/message_filters.rs b/core/src/mail/message_filters.rs index e3d1c92..333cd21 100644 --- a/core/src/mail/message_filters.rs +++ b/core/src/mail/message_filters.rs @@ -144,6 +144,16 @@ impl PostFilter for AddListHeaders { let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap(); let sender = format!("<{}>", ctx.list.address); headers.push((&b"Sender"[..], sender.as_bytes())); + let mut subject = format!("[{}] ", ctx.list.id).into_bytes(); + if let Some((_, subj_val)) = headers + .iter_mut() + .find(|(k, _)| k.eq_ignore_ascii_case(b"Subject")) + { + subject.extend(subj_val.iter().cloned()); + *subj_val = subject.as_slice(); + } else { + headers.push((&b"Subject"[..], subject.as_slice())); + } let list_id = Some(ctx.list.id_header()); let list_help = ctx.list.help_header(); @@ -233,9 +243,6 @@ impl PostFilter for FinalizeRecipients { trace!("Subscription gets copy"); recipients.push(subscription.address()); } - // TODO: - // - check for duplicates (To,Cc,Bcc) - // - send confirmation to submitter } ctx.scheduled_jobs.push(MailJob::Send { recipients }); if !digests.is_empty() { diff --git a/core/src/templates.rs b/core/src/templates.rs index 0641a95..35a6627 100644 --- a/core/src/templates.rs +++ b/core/src/templates.rs @@ -86,7 +86,10 @@ impl Template { pk: -1, name: Self::GENERIC_FAILURE.to_string(), list: None, - subject: Some("Your e-mail was not processed successfully.".to_string()), + subject: Some( + "{{ subject if subject else \"Your e-mail was not processed successfully.\" }}" + .to_string(), + ), headers_json: None, body: "{{ details|safe if details else \"The list owners and administrators have been \ notified.\" }}" @@ -100,7 +103,10 @@ impl Template { pk: -1, name: Self::GENERIC_SUCCESS.to_string(), list: None, - subject: Some("Your e-mail was processed successfully.".to_string()), + subject: Some( + "{{ subject if subject else \"Your e-mail was processed successfully.\" }}" + .to_string(), + ), headers_json: None, body: "{{ details|safe if details else \"\" }}".to_string(), } diff --git a/core/tests/smtp.rs b/core/tests/smtp.rs index 286c0db..e86c7d9 100644 --- a/core/tests/smtp.rs +++ b/core/tests/smtp.rs @@ -158,7 +158,7 @@ impl Handler for MyHandler { self.stored.lock().unwrap().push((to.clone(), env)); } Err(err) => { - eprintln!("envelope parse error {}", err); + panic!("envelope parse error {}", err); } } } @@ -168,8 +168,7 @@ impl Handler for MyHandler { .push(((ip, domain), Message::Helo)); return OK; } - log::error!("last self.mails item was not Message::Data: {last:?}"); - INTERNAL_ERROR + panic!("last self.mails item was not Message::Data: {last:?}"); //INTERNAL_ERROR } } @@ -313,7 +312,23 @@ fn test_smtp() { .unwrap(); } })); - assert_eq!(handler.stored.lock().unwrap().len(), 2); + let stored = handler.stored.lock().unwrap(); + assert_eq!(stored.len(), 3); + assert_eq!(&stored[0].0, "japoeunp@example.com"); + assert_eq!( + &stored[0].1.subject(), + "Your post to foo-chat was rejected." + ); + assert_eq!( + &stored[1].1.subject(), + "[foo-chat] thankful that I had the chance to written report, that I could learn and let \ + alone the chance $4454.32" + ); + assert_eq!( + &stored[2].1.subject(), + "[foo-chat] thankful that I had the chance to written report, that I could learn and let \ + alone the chance $4454.32" + ); } #[test]