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
This commit is contained in:
6543 2024-06-03 20:42:52 +02:00 committed by Earl Warren
parent 237d18974e
commit 1d4bff4f65
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
4 changed files with 110 additions and 9 deletions

View file

@ -1746,6 +1746,16 @@ LEVEL = Info
;; convert \r\n to \n for Sendmail ;; convert \r\n to \n for Sendmail
;SENDMAIL_CONVERT_CRLF = true ;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] ;[email.incoming]

View file

@ -18,14 +18,15 @@ import (
// Mailer represents mail service. // Mailer represents mail service.
type Mailer struct { type Mailer struct {
// Mailer // Mailer
Name string `ini:"NAME"` Name string `ini:"NAME"`
From string `ini:"FROM"` From string `ini:"FROM"`
EnvelopeFrom string `ini:"ENVELOPE_FROM"` EnvelopeFrom string `ini:"ENVELOPE_FROM"`
OverrideEnvelopeFrom bool `ini:"-"` OverrideEnvelopeFrom bool `ini:"-"`
FromName string `ini:"-"` FromName string `ini:"-"`
FromEmail string `ini:"-"` FromEmail string `ini:"-"`
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
SubjectPrefix string `ini:"SUBJECT_PREFIX"` SubjectPrefix string `ini:"SUBJECT_PREFIX"`
OverrideHeader map[string][]string `ini:"-"`
// SMTP sender // SMTP sender
Protocol string `ini:"PROTOCOL"` 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) 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 // Infer SMTPPort if not set
if MailService.SMTPPort == "" { if MailService.SMTPPort == "" {
switch MailService.Protocol { switch MailService.Protocol {

View file

@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message {
msg.SetHeader(header, m.Headers[header]...) msg.SetHeader(header, m.Headers[header]...)
} }
if len(setting.MailService.SubjectPrefix) > 0 { if setting.MailService.SubjectPrefix != "" {
msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject) msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
} else { } else {
msg.SetHeader("Subject", m.Subject) msg.SetHeader("Subject", m.Subject)
@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message {
if len(msg.GetHeader("Message-ID")) == 0 { if len(msg.GetHeader("Message-ID")) == 0 {
msg.SetHeader("Message-ID", m.generateAutoMessageID()) 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 return msg
} }

View file

@ -4,6 +4,7 @@
package mailer package mailer
import ( import (
"strings"
"testing" "testing"
"time" "time"
@ -51,3 +52,78 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
m := createMessageIDForRelease(&rel) m := createMessageIDForRelease(&rel)
assert.Equal(t, "<test/tag-test/releases/42@localhost>", m) assert.Equal(t, "<test/tag-test/releases/42@localhost>", 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\" <test@gitea.com>",
"Message-ID": "<autogen--6795364578871-69c000786adc60dc@localhost>",
"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\" <test@gitea.com>",
"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
}