Use FullName in Emails to address the recipient if possible ()

Before we had just the plain mail address as recipient. But now we
provide additional Information for the Mail clients.

---
*Sponsored by Kithara Software GmbH*
This commit is contained in:
6543 2024-07-08 01:38:45 -07:00 committed by GitHub
parent d7c7a78994
commit 4696bcb3f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 13 deletions

@ -8,6 +8,8 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"mime"
"net/mail"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -413,6 +415,34 @@ func (u *User) DisplayName() string {
return u.Name return u.Name
} }
var emailToReplacer = strings.NewReplacer(
"\n", "",
"\r", "",
"<", "",
">", "",
",", "",
":", "",
";", "",
)
// EmailTo returns a string suitable to be put into a e-mail `To:` header.
func (u *User) EmailTo() string {
sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName())
// should be an edge case but nice to have
if sanitizedDisplayName == u.Email {
return u.Email
}
to := fmt.Sprintf("%s <%s>", sanitizedDisplayName, u.Email)
add, err := mail.ParseAddress(to)
if err != nil {
return u.Email
}
return fmt.Sprintf("%s <%s>", mime.QEncoding.Encode("utf-8", add.Name), add.Address)
}
// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, // GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set,
// returns username otherwise. // returns username otherwise.
func (u *User) GetDisplayName() string { func (u *User) GetDisplayName() string {

@ -529,6 +529,29 @@ func Test_NormalizeUserFromEmail(t *testing.T) {
} }
} }
func TestEmailTo(t *testing.T) {
testCases := []struct {
fullName string
mail string
result string
}{
{"Awareness Hub", "awareness@hub.net", "Awareness Hub <awareness@hub.net>"},
{"name@example.com", "name@example.com", "name@example.com"},
{"Hi Its <Mee>", "ee@mail.box", "Hi Its Mee <ee@mail.box>"},
{"Sinéad.O'Connor", "sinead.oconnor@gmail.com", "=?utf-8?q?Sin=C3=A9ad.O'Connor?= <sinead.oconnor@gmail.com>"},
{"Æsir", "aesir@gmx.de", "=?utf-8?q?=C3=86sir?= <aesir@gmx.de>"},
{"new😀user", "new.user@alo.com", "=?utf-8?q?new=F0=9F=98=80user?= <new.user@alo.com>"},
{`"quoted"`, "quoted@test.com", "quoted <quoted@test.com>"},
}
for _, testCase := range testCases {
t.Run(testCase.result, func(t *testing.T) {
testUser := &user_model.User{FullName: testCase.fullName, Email: testCase.mail}
assert.EqualValues(t, testCase.result, testUser.EmailTo())
})
}
}
func TestDisabledUserFeatures(t *testing.T) { func TestDisabledUserFeatures(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())

@ -82,7 +82,7 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s
return return
} }
msg := NewMessage(u.Email, subject, content.String()) msg := NewMessage(u.EmailTo(), subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info) msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info)
SendAsync(msg) SendAsync(msg)
@ -158,7 +158,7 @@ func SendRegisterNotifyMail(u *user_model.User) {
return return
} }
msg := NewMessage(u.Email, locale.TrString("mail.register_notify"), content.String()) msg := NewMessage(u.EmailTo(), locale.TrString("mail.register_notify"), content.String())
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID) msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID)
SendAsync(msg) SendAsync(msg)
@ -189,7 +189,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
return return
} }
msg := NewMessage(u.Email, subject, content.String()) msg := NewMessage(u.EmailTo(), subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID) msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)
SendAsync(msg) SendAsync(msg)

@ -40,10 +40,10 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) {
return return
} }
langMap := make(map[string][]string) langMap := make(map[string][]*user_model.User)
for _, user := range recipients { for _, user := range recipients {
if user.ID != rel.PublisherID { if user.ID != rel.PublisherID {
langMap[user.Language] = append(langMap[user.Language], user.Email) langMap[user.Language] = append(langMap[user.Language], user)
} }
} }
@ -52,7 +52,7 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) {
} }
} }
func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_model.Release) { func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, rel *repo_model.Release) {
locale := translation.NewLocale(lang) locale := translation.NewLocale(lang)
var err error var err error
@ -89,7 +89,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
publisherName := rel.Publisher.DisplayName() publisherName := rel.Publisher.DisplayName()
msgID := generateMessageIDForRelease(rel) msgID := generateMessageIDForRelease(rel)
for _, to := range tos { for _, to := range tos {
msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String()) msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String())
msg.Info = subject msg.Info = subject
msg.SetHeader("Message-ID", msgID) msg.SetHeader("Message-ID", msgID)
msgs = append(msgs, msg) msgs = append(msgs, msg)

@ -28,13 +28,13 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model.
return err return err
} }
langMap := make(map[string][]string) langMap := make(map[string][]*user_model.User)
for _, user := range users { for _, user := range users {
if !user.IsActive { if !user.IsActive {
// don't send emails to inactive users // don't send emails to inactive users
continue continue
} }
langMap[user.Language] = append(langMap[user.Language], user.Email) langMap[user.Language] = append(langMap[user.Language], user)
} }
for lang, tos := range langMap { for lang, tos := range langMap {
@ -46,11 +46,11 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model.
return nil return nil
} }
return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner.Email}, repo) return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []*user_model.User{newOwner}, repo)
} }
// sendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created for each language // sendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created for each language
func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.User, emails []string, repo *repo_model.Repository) error { func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.User, emailTos []*user_model.User, repo *repo_model.Repository) error {
var ( var (
locale = translation.NewLocale(lang) locale = translation.NewLocale(lang)
content bytes.Buffer content bytes.Buffer
@ -78,8 +78,8 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
return err return err
} }
for _, to := range emails { for _, to := range emailTos {
msg := NewMessage(to, subject, content.String()) msg := NewMessage(to.EmailTo(), subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID) msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID)
SendAsync(msg) SendAsync(msg)