From 1d4bff4f65d5e4a3969871ef91d3612daf272b45 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 3 Jun 2024 20:42:52 +0200 Subject: [PATCH] Add option for mailer to override mail headers (#27860) Add option to override headers of mails, gitea send out --- *Sponsored by Kithara Software GmbH* (cherry picked from commit aace3bccc3290446637cac30b121b94b5d03075f) Conflicts: docs/content/administration/config-cheat-sheet.en-us.md does not exist in Forgejo services/mailer/mailer_test.go trivial context conflict --- custom/conf/app.example.ini | 10 +++++ modules/setting/mailer.go | 23 ++++++---- services/mailer/mailer.go | 10 ++++- services/mailer/mailer_test.go | 76 ++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 451742ac06..013a0e5bec 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1746,6 +1746,16 @@ LEVEL = Info ;; convert \r\n to \n for Sendmail ;SENDMAIL_CONVERT_CRLF = true +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[mailer.override_header] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This is empty by default, use it only if you know what you need it for. +;Reply-To = test@example.com, test2@example.com +;Content-Type = text/html; charset=utf-8 +;In-Reply-To = + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[email.incoming] diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index e9ce640c7f..cfedb4613f 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -18,14 +18,15 @@ import ( // Mailer represents mail service. type Mailer struct { // Mailer - Name string `ini:"NAME"` - From string `ini:"FROM"` - EnvelopeFrom string `ini:"ENVELOPE_FROM"` - OverrideEnvelopeFrom bool `ini:"-"` - FromName string `ini:"-"` - FromEmail string `ini:"-"` - SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` - SubjectPrefix string `ini:"SUBJECT_PREFIX"` + Name string `ini:"NAME"` + From string `ini:"FROM"` + EnvelopeFrom string `ini:"ENVELOPE_FROM"` + OverrideEnvelopeFrom bool `ini:"-"` + FromName string `ini:"-"` + FromEmail string `ini:"-"` + SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` + SubjectPrefix string `ini:"SUBJECT_PREFIX"` + OverrideHeader map[string][]string `ini:"-"` // SMTP sender Protocol string `ini:"PROTOCOL"` @@ -159,6 +160,12 @@ func loadMailerFrom(rootCfg ConfigProvider) { log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err) } + overrideHeader := rootCfg.Section("mailer.override_header").Keys() + MailService.OverrideHeader = make(map[string][]string) + for _, key := range overrideHeader { + MailService.OverrideHeader[key.Name()] = key.Strings(",") + } + // Infer SMTPPort if not set if MailService.SMTPPort == "" { switch MailService.Protocol { diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index a9316cca76..0a723f974a 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message { msg.SetHeader(header, m.Headers[header]...) } - if len(setting.MailService.SubjectPrefix) > 0 { + if setting.MailService.SubjectPrefix != "" { msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject) } else { msg.SetHeader("Subject", m.Subject) @@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message { if len(msg.GetHeader("Message-ID")) == 0 { msg.SetHeader("Message-ID", m.generateAutoMessageID()) } + + for k, v := range setting.MailService.OverrideHeader { + if len(msg.GetHeader(k)) != 0 { + log.Debug("Mailer override header '%s' as per config", k) + } + msg.SetHeader(k, v...) + } + return msg } diff --git a/services/mailer/mailer_test.go b/services/mailer/mailer_test.go index 253454e89c..2f7da08697 100644 --- a/services/mailer/mailer_test.go +++ b/services/mailer/mailer_test.go @@ -4,6 +4,7 @@ package mailer import ( + "strings" "testing" "time" @@ -51,3 +52,78 @@ func TestGenerateMessageIDForRelease(t *testing.T) { m := createMessageIDForRelease(&rel) assert.Equal(t, "", m) } + +func TestToMessage(t *testing.T) { + oldConf := *setting.MailService + defer func() { + setting.MailService = &oldConf + }() + setting.MailService.From = "test@gitea.com" + + m1 := Message{ + Info: "info", + FromAddress: "test@gitea.com", + FromDisplayName: "Test Gitea", + To: "a@b.com", + Subject: "Issue X Closed", + Body: "Some Issue got closed by Y-Man", + } + + buf := &strings.Builder{} + _, err := m1.ToMessage().WriteTo(buf) + assert.NoError(t, err) + header, _ := extractMailHeaderAndContent(t, buf.String()) + assert.EqualValues(t, map[string]string{ + "Content-Type": "multipart/alternative;", + "Date": "Mon, 01 Jan 0001 00:00:00 +0000", + "From": "\"Test Gitea\" ", + "Message-ID": "", + "Mime-Version": "1.0", + "Subject": "Issue X Closed", + "To": "a@b.com", + "X-Auto-Response-Suppress": "All", + }, header) + + setting.MailService.OverrideHeader = map[string][]string{ + "Message-ID": {""}, // delete message id + "Auto-Submitted": {"auto-generated"}, // suppress auto replay + } + + buf = &strings.Builder{} + _, err = m1.ToMessage().WriteTo(buf) + assert.NoError(t, err) + header, _ = extractMailHeaderAndContent(t, buf.String()) + assert.EqualValues(t, map[string]string{ + "Content-Type": "multipart/alternative;", + "Date": "Mon, 01 Jan 0001 00:00:00 +0000", + "From": "\"Test Gitea\" ", + "Message-ID": "", + "Mime-Version": "1.0", + "Subject": "Issue X Closed", + "To": "a@b.com", + "X-Auto-Response-Suppress": "All", + "Auto-Submitted": "auto-generated", + }, header) +} + +func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string, string) { + header := make(map[string]string) + + parts := strings.SplitN(mail, "boundary=", 2) + if !assert.Len(t, parts, 2) { + return nil, "" + } + content := strings.TrimSpace("boundary=" + parts[1]) + + hParts := strings.Split(parts[0], "\n") + + for _, hPart := range hParts { + parts := strings.SplitN(hPart, ":", 2) + hk := strings.TrimSpace(parts[0]) + if hk != "" { + header[hk] = strings.TrimSpace(parts[1]) + } + } + + return header, content +}