"context"
"encoding/hex"
"fmt"
+ "mime"
+ "net/mail"
"net/url"
"path/filepath"
"regexp"
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,
// returns username otherwise.
func (u *User) GetDisplayName() string {
}
}
+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) {
assert.NoError(t, unittest.PrepareTestDatabase())
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)
SendAsync(msg)
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)
SendAsync(msg)
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)
SendAsync(msg)
return
}
- langMap := make(map[string][]string)
+ langMap := make(map[string][]*user_model.User)
for _, user := range recipients {
if user.ID != rel.PublisherID {
- langMap[user.Language] = append(langMap[user.Language], user.Email)
+ langMap[user.Language] = append(langMap[user.Language], user)
}
}
}
}
-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)
var err error
publisherName := rel.Publisher.DisplayName()
msgID := generateMessageIDForRelease(rel)
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.SetHeader("Message-ID", msgID)
msgs = append(msgs, msg)
return err
}
- langMap := make(map[string][]string)
+ langMap := make(map[string][]*user_model.User)
for _, user := range users {
if !user.IsActive {
// don't send emails to inactive users
continue
}
- langMap[user.Language] = append(langMap[user.Language], user.Email)
+ langMap[user.Language] = append(langMap[user.Language], user)
}
for lang, tos := range langMap {
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
-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 (
locale = translation.NewLocale(lang)
content bytes.Buffer
return err
}
- for _, to := range emails {
- msg := NewMessage(to, subject, content.String())
+ for _, to := range emailTos {
+ msg := NewMessage(to.EmailTo(), subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID)
SendAsync(msg)