Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for mailer to override mail headers #27860

Merged
merged 26 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,16 @@ LEVEL = Info
;; convert \r\n to \n for Sendmail
;SENDMAIL_CONVERT_CRLF = true

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[mailer.override_header]
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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]
Expand Down
19 changes: 18 additions & 1 deletion docs/content/administration/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,11 +724,13 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type

## Mailer (`mailer`)

⚠️ This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
:::warning
This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
please refer to
[Gitea 1.17 app.ini example](https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini)
and
[Gitea 1.17 configuration document](https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md)
:::

- `ENABLED`: **false**: Enable to use a mail service.
- `PROTOCOL`: **_empty_**: Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._
Expand Down Expand Up @@ -761,6 +763,21 @@ and
- `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]`
- `SEND_AS_PLAIN_TEXT`: **false**: Send mails only in plain text, without HTML alternative.

## Override Email Headers (`mailer.override_header`)

:::warning
This is empty by default, use it only if you know what you need it for.
:::

examples would be:

```ini
[mailer.override_header]
Reply-To = test@example.com, test2@example.com
Content-Type = text/html; charset=utf-8
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
In-Reply-To =
```

## Incoming Email (`email.incoming`)

- `ENABLED`: **false**: Enable handling of incoming emails.
Expand Down
23 changes: 15 additions & 8 deletions modules/setting/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -151,6 +152,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 {
Expand Down
10 changes: 9 additions & 1 deletion services/mailer/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}

Expand Down
76 changes: 76 additions & 0 deletions services/mailer/mailer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package mailer

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -36,3 +37,78 @@ func TestGenerateMessageID(t *testing.T) {
gm = m.ToMessage()
assert.Equal(t, "<msg-d@domain.com>", gm.GetHeader("Message-ID")[0])
}

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
}