1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Use new mail package instead of an unmintained one (#32682)

Resolve #18664
This commit is contained in:
Lunny Xiao
2024-12-04 14:33:43 -08:00
committed by GitHub
parent 4142397b0b
commit 5ab7aa700f
11 changed files with 133 additions and 71 deletions

View File

@@ -93,20 +93,20 @@ func TestComposeIssueCommentMessage(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, msgs, 2)
gomailMsg := msgs[0].ToMessage()
replyTo := gomailMsg.GetHeader("Reply-To")[0]
subject := gomailMsg.GetHeader("Subject")[0]
replyTo := gomailMsg.GetGenHeader("Reply-To")[0]
subject := gomailMsg.GetGenHeader("Subject")[0]
assert.Len(t, gomailMsg.GetHeader("To"), 1, "exactly one recipient is expected in the To field")
assert.Len(t, gomailMsg.GetAddrHeader("To"), 1, "exactly one recipient is expected in the To field")
tokenRegex := regexp.MustCompile(`\Aincoming\+(.+)@localhost\z`)
assert.Regexp(t, tokenRegex, replyTo)
token := tokenRegex.FindAllStringSubmatch(replyTo, 1)[0][1]
assert.Equal(t, "Re: ", subject[:4], "Comment reply subject should contain Re:")
assert.Equal(t, "Re: [user2/repo1] @user2 #1 - issue1", subject)
assert.Equal(t, "<user2/repo1/issues/1@localhost>", gomailMsg.GetHeader("In-Reply-To")[0], "In-Reply-To header doesn't match")
assert.ElementsMatch(t, []string{"<user2/repo1/issues/1@localhost>", "<reply-" + token + "@localhost>"}, gomailMsg.GetHeader("References"), "References header doesn't match")
assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", gomailMsg.GetHeader("Message-ID")[0], "Message-ID header doesn't match")
assert.Equal(t, "<mailto:"+replyTo+">", gomailMsg.GetHeader("List-Post")[0])
assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 2) // url + mailto
assert.Equal(t, "<user2/repo1/issues/1@localhost>", gomailMsg.GetGenHeader("In-Reply-To")[0], "In-Reply-To header doesn't match")
assert.ElementsMatch(t, []string{"<user2/repo1/issues/1@localhost>", "<reply-" + token + "@localhost>"}, gomailMsg.GetGenHeader("References"), "References header doesn't match")
assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", gomailMsg.GetGenHeader("Message-ID")[0], "Message-ID header doesn't match")
assert.Equal(t, "<mailto:"+replyTo+">", gomailMsg.GetGenHeader("List-Post")[0])
assert.Len(t, gomailMsg.GetGenHeader("List-Unsubscribe"), 2) // url + mailto
var buf bytes.Buffer
gomailMsg.WriteTo(&buf)
@@ -139,19 +139,19 @@ func TestComposeIssueMessage(t *testing.T) {
assert.Len(t, msgs, 2)
gomailMsg := msgs[0].ToMessage()
mailto := gomailMsg.GetHeader("To")
subject := gomailMsg.GetHeader("Subject")
messageID := gomailMsg.GetHeader("Message-ID")
inReplyTo := gomailMsg.GetHeader("In-Reply-To")
references := gomailMsg.GetHeader("References")
mailto := gomailMsg.GetAddrHeader("To")
subject := gomailMsg.GetGenHeader("Subject")
messageID := gomailMsg.GetGenHeader("Message-ID")
inReplyTo := gomailMsg.GetGenHeader("In-Reply-To")
references := gomailMsg.GetGenHeader("References")
assert.Len(t, mailto, 1, "exactly one recipient is expected in the To field")
assert.Equal(t, "[user2/repo1] @user2 #1 - issue1", subject[0])
assert.Equal(t, "<user2/repo1/issues/1@localhost>", inReplyTo[0], "In-Reply-To header doesn't match")
assert.Equal(t, "<user2/repo1/issues/1@localhost>", references[0], "References header doesn't match")
assert.Equal(t, "<user2/repo1/issues/1@localhost>", messageID[0], "Message-ID header doesn't match")
assert.Empty(t, gomailMsg.GetHeader("List-Post")) // incoming mail feature disabled
assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 1) // url without mailto
assert.Empty(t, gomailMsg.GetGenHeader("List-Post")) // incoming mail feature disabled
assert.Len(t, gomailMsg.GetGenHeader("List-Unsubscribe"), 1) // url without mailto
}
func TestTemplateSelection(t *testing.T) {
@@ -169,7 +169,7 @@ func TestTemplateSelection(t *testing.T) {
template.Must(bodyTemplates.New("issue/close").Parse("issue/close/body"))
expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) {
subject := msg.ToMessage().GetHeader("Subject")
subject := msg.ToMessage().GetGenHeader("Subject")
msgbuf := new(bytes.Buffer)
_, _ = msg.ToMessage().WriteTo(msgbuf)
wholemsg := msgbuf.String()
@@ -225,7 +225,7 @@ func TestTemplateServices(t *testing.T) {
Content: "test body", Comment: comment,
}, recipients, fromMention, "TestTemplateServices")
subject := msg.ToMessage().GetHeader("Subject")
subject := msg.ToMessage().GetGenHeader("Subject")
msgbuf := new(bytes.Buffer)
_, _ = msg.ToMessage().WriteTo(msgbuf)
wholemsg := msgbuf.String()

View File

@@ -48,11 +48,11 @@ func NewContext(ctx context.Context) {
mailQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "mail", func(items ...*sender_service.Message) []*sender_service.Message {
for _, msg := range items {
gomailMsg := msg.ToMessage()
log.Trace("New e-mail sending request %s: %s", gomailMsg.GetHeader("To"), msg.Info)
log.Trace("New e-mail sending request %s: %s", gomailMsg.GetGenHeader("To"), msg.Info)
if err := sender_service.Send(sender, msg); err != nil {
log.Error("Failed to send emails %s: %s - %v", gomailMsg.GetHeader("To"), msg.Info, err)
log.Error("Failed to send emails %s: %s - %v", gomailMsg.GetGenHeader("To"), msg.Info, err)
} else {
log.Trace("E-mails sent %s: %s", gomailMsg.GetHeader("To"), msg.Info)
log.Trace("E-mails sent %s: %s", gomailMsg.GetGenHeader("To"), msg.Info)
}
}
return nil

View File

@@ -0,0 +1,14 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package sender
import (
"testing"
"code.gitea.io/gitea/models/unittest"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

View File

@@ -6,6 +6,7 @@ package sender
import (
"fmt"
"hash/fnv"
"net/mail"
"strings"
"time"
@@ -14,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/jaytaylor/html2text"
"gopkg.in/gomail.v2"
gomail "github.com/wneessen/go-mail"
)
// Message mail body and log info
@@ -31,45 +32,46 @@ type Message struct {
}
// ToMessage converts a Message to gomail.Message
func (m *Message) ToMessage() *gomail.Message {
msg := gomail.NewMessage()
msg.SetAddressHeader("From", m.FromAddress, m.FromDisplayName)
msg.SetHeader("To", m.To)
func (m *Message) ToMessage() *gomail.Msg {
msg := gomail.NewMsg()
addr := mail.Address{Name: m.FromDisplayName, Address: m.FromAddress}
_ = msg.SetAddrHeader("From", addr.String())
_ = msg.SetAddrHeader("To", m.To)
if m.ReplyTo != "" {
msg.SetHeader("Reply-To", m.ReplyTo)
msg.SetGenHeader("Reply-To", m.ReplyTo)
}
for header := range m.Headers {
msg.SetHeader(header, m.Headers[header]...)
msg.SetGenHeader(gomail.Header(header), m.Headers[header]...)
}
if setting.MailService.SubjectPrefix != "" {
msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
msg.SetGenHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
} else {
msg.SetHeader("Subject", m.Subject)
msg.SetGenHeader("Subject", m.Subject)
}
msg.SetDateHeader("Date", m.Date)
msg.SetHeader("X-Auto-Response-Suppress", "All")
msg.SetDateWithValue(m.Date)
msg.SetGenHeader("X-Auto-Response-Suppress", "All")
plainBody, err := html2text.FromString(m.Body)
if err != nil || setting.MailService.SendAsPlainText {
if strings.Contains(base.TruncateString(m.Body, 100), "<html>") {
log.Warn("Mail contains HTML but configured to send as plain text.")
}
msg.SetBody("text/plain", plainBody)
msg.SetBodyString("text/plain", plainBody)
} else {
msg.SetBody("text/plain", plainBody)
msg.AddAlternative("text/html", m.Body)
msg.SetBodyString("text/plain", plainBody)
msg.AddAlternativeString("text/html", m.Body)
}
if len(msg.GetHeader("Message-ID")) == 0 {
msg.SetHeader("Message-ID", m.generateAutoMessageID())
if len(msg.GetGenHeader("Message-ID")) == 0 {
msg.SetGenHeader("Message-ID", m.generateAutoMessageID())
}
for k, v := range setting.MailService.OverrideHeader {
if len(msg.GetHeader(k)) != 0 {
if len(msg.GetGenHeader(gomail.Header(k))) != 0 {
log.Debug("Mailer override header '%s' as per config", k)
}
msg.SetHeader(k, v...)
msg.SetGenHeader(gomail.Header(k), v...)
}
return msg

View File

@@ -25,25 +25,27 @@ func TestGenerateMessageID(t *testing.T) {
m := NewMessageFrom("", "display-name", "from-address", "subject", "body")
m.Date = date
gm := m.ToMessage()
assert.Equal(t, "<autogen-946782245000-41e8fc54a8ad3a3f@localhost>", gm.GetHeader("Message-ID")[0])
assert.Equal(t, "<autogen-946782245000-41e8fc54a8ad3a3f@localhost>", gm.GetGenHeader("Message-ID")[0])
m = NewMessageFrom("a@b.com", "display-name", "from-address", "subject", "body")
m.Date = date
gm = m.ToMessage()
assert.Equal(t, "<autogen-946782245000-cc88ce3cfe9bd04f@localhost>", gm.GetHeader("Message-ID")[0])
assert.Equal(t, "<autogen-946782245000-cc88ce3cfe9bd04f@localhost>", gm.GetGenHeader("Message-ID")[0])
m = NewMessageFrom("a@b.com", "display-name", "from-address", "subject", "body")
m.SetHeader("Message-ID", "<msg-d@domain.com>")
gm = m.ToMessage()
assert.Equal(t, "<msg-d@domain.com>", gm.GetHeader("Message-ID")[0])
assert.Equal(t, "<msg-d@domain.com>", gm.GetGenHeader("Message-ID")[0])
}
func TestToMessage(t *testing.T) {
oldConf := *setting.MailService
oldConf := setting.MailService
defer func() {
setting.MailService = &oldConf
setting.MailService = oldConf
}()
setting.MailService.From = "test@gitea.com"
setting.MailService = &setting.Mailer{
From: "test@gitea.com",
}
m1 := Message{
Info: "info",
@@ -54,18 +56,24 @@ func TestToMessage(t *testing.T) {
Body: "Some Issue got closed by Y-Man",
}
assertHeaders := func(t *testing.T, expected, header map[string]string) {
for k, v := range expected {
assert.Equal(t, v, header[k], "Header %s should be %s but got %s", k, v, header[k])
}
}
buf := &strings.Builder{}
_, err := m1.ToMessage().WriteTo(buf)
assert.NoError(t, err)
header, _ := extractMailHeaderAndContent(t, buf.String())
assert.EqualValues(t, map[string]string{
assertHeaders(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",
"MIME-Version": "1.0",
"Subject": "Issue X Closed",
"To": "a@b.com",
"To": "<a@b.com>",
"X-Auto-Response-Suppress": "All",
}, header)
@@ -78,14 +86,14 @@ func TestToMessage(t *testing.T) {
_, err = m1.ToMessage().WriteTo(buf)
assert.NoError(t, err)
header, _ = extractMailHeaderAndContent(t, buf.String())
assert.EqualValues(t, map[string]string{
assertHeaders(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",
"MIME-Version": "1.0",
"Subject": "Issue X Closed",
"To": "a@b.com",
"To": "<a@b.com>",
"X-Auto-Response-Suppress": "All",
"Auto-Submitted": "auto-generated",
}, header)

View File

@@ -4,13 +4,15 @@
package sender
import (
"io"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"gopkg.in/gomail.v2"
)
type Sender gomail.Sender
type Sender interface {
Send(from string, to []string, msg io.WriterTo) error
}
var Send = send
@@ -19,9 +21,18 @@ func send(sender Sender, msgs ...*Message) error {
log.Error("Mailer: Send is being invoked but mail service hasn't been initialized")
return nil
}
goMsgs := []*gomail.Message{}
for _, msg := range msgs {
goMsgs = append(goMsgs, msg.ToMessage())
m := msg.ToMessage()
froms := m.GetFrom()
to, err := m.GetRecipients()
if err != nil {
return err
}
// TODO: implement sending from multiple addresses
if err := sender.Send(froms[0].Address, to, m); err != nil {
return err
}
}
return gomail.Send(sender, goMsgs...)
return nil
}

View File

@@ -8,12 +8,13 @@ import (
"fmt"
"io"
"net"
"net/smtp"
"os"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/wneessen/go-mail/smtp"
)
// SMTPSender Sender SMTP mail sender
@@ -106,7 +107,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
if strings.Contains(options, "CRAM-MD5") {
auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
} else if strings.Contains(options, "PLAIN") {
auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
auth = smtp.PlainAuth("", opts.User, opts.Passwd, host, false)
} else if strings.Contains(options, "LOGIN") {
// Patch for AUTH LOGIN
auth = LoginAuth(opts.User, opts.Passwd)
@@ -146,5 +147,10 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
return fmt.Errorf("SMTP close failed: %w", err)
}
return client.Quit()
err = client.Quit()
if err != nil {
log.Error("Quit client failed: %v", err)
}
return nil
}

View File

@@ -5,9 +5,9 @@ package sender
import (
"fmt"
"net/smtp"
"github.com/Azure/go-ntlmssp"
"github.com/wneessen/go-mail/smtp"
)
type loginAuth struct {