From d09faeca3e8a80219c6be2354148be38d45fb88d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 15:24:05 +0200 Subject: [PATCH 01/24] Use FullName in Emails to address the recipient if possible --- models/user/user.go | 31 +++++++++++++++++++++++++++++++ models/user/user_test.go | 23 +++++++++++++++++++++++ services/mailer/mail.go | 8 +++++--- services/mailer/mail_release.go | 8 ++++---- services/mailer/mail_repo.go | 4 ++-- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index 23637f4616288..dc1f01f0dfe83 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -8,9 +8,11 @@ import ( "context" "encoding/hex" "fmt" + "net/mail" "net/url" "path/filepath" "regexp" + "strconv" "strings" "time" "unicode" @@ -413,6 +415,35 @@ func (u *User) DisplayName() string { return u.Name } +// EmailTo returns full name and email. +func (u *User) EmailTo() string { + // we don't deal with utf8 to ascii conversion + if u.DisplayName() != strings.Trim(strconv.QuoteToASCII(u.DisplayName()), `"`) { + return u.Email + } + // ok just be sure we don't let the user break things somehow + sanitizedDisplayName := strings.ReplaceAll(u.DisplayName(), "\n", "") + sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, "\r", "") + sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, "<", "") + sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ">", "") + sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ",", "") + sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ":", "") + sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ";", "") + + // 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>", 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 { diff --git a/models/user/user_test.go b/models/user/user_test.go index c4e278caab756..0711d877f0018 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -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 "}, + {"name@example.com", "name@example.com", "name@example.com"}, + {"Hi Its ", "ee@mail.box", "Hi Its Mee ee@mail.box"}, + {"Sinéad.O'Connor", "sinead.oconnor@gmail.com", "sinead.oconnor@gmail.com"}, + {"Æsir", "aesir@gmx.de", "aesir@gmx.de"}, + {"new😀user", "new.user@alo.com", "new.user@alo.com"}, + {`"quoted"`, "quoted@test.com", "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()) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 000cc835c8b77..c16759407d561 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -82,7 +82,7 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s 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) @@ -130,6 +130,8 @@ func SendActivateEmailMail(u *user_model.User, email string) { return } + email = fmt.Sprintf("%s <%s>", u.DisplayName(), email) // TODO: do we need to sanitize the display name? + msg := NewMessage(email, locale.TrString("mail.activate_email"), content.String()) msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID) @@ -158,7 +160,7 @@ func SendRegisterNotifyMail(u *user_model.User) { 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) @@ -189,7 +191,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) 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) diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index b7a4da0db92b3..f1f2e558a776b 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -40,10 +40,10 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) { 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) } } @@ -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) var err error @@ -89,7 +89,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo 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) diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index e0d55bb120043..3befe55d94342 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -34,7 +34,7 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. // 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.EmailTo()) } for lang, tos := range langMap { @@ -46,7 +46,7 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. return nil } - return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner.Email}, repo) + return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner.EmailTo()}, repo) } // sendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created for each language From 6111bb60a0e6526fce1892dcca7771a33d129abc Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 15:27:23 +0200 Subject: [PATCH 02/24] not worth it --- services/mailer/mail.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index c16759407d561..11810a036d62d 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -130,8 +130,6 @@ func SendActivateEmailMail(u *user_model.User, email string) { return } - email = fmt.Sprintf("%s <%s>", u.DisplayName(), email) // TODO: do we need to sanitize the display name? - msg := NewMessage(email, locale.TrString("mail.activate_email"), content.String()) msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID) From 484894da1120f4ce033a790171d9af9e932f40df Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 15:28:17 +0200 Subject: [PATCH 03/24] jaja --- models/user/user_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/user_test.go b/models/user/user_test.go index 0711d877f0018..6aa72a3e1cefe 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -537,7 +537,7 @@ func TestEmailTo(t *testing.T) { }{ {"Awareness Hub", "awareness@hub.net", "Awareness Hub "}, {"name@example.com", "name@example.com", "name@example.com"}, - {"Hi Its ", "ee@mail.box", "Hi Its Mee ee@mail.box"}, + {"Hi Its ", "ee@mail.box", "Hi Its Mee "}, {"Sinéad.O'Connor", "sinead.oconnor@gmail.com", "sinead.oconnor@gmail.com"}, {"Æsir", "aesir@gmx.de", "aesir@gmx.de"}, {"new😀user", "new.user@alo.com", "new.user@alo.com"}, From d8bf307bc9deed6ffbda1a02027699ff54f4e97b Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 16:12:39 +0200 Subject: [PATCH 04/24] more copy-by-ref --- services/mailer/mail_repo.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 3befe55d94342..619223033259a 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -28,13 +28,13 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. 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.EmailTo()) + langMap[user.Language] = append(langMap[user.Language], user) } for lang, tos := range langMap { @@ -46,11 +46,11 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. return nil } - return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner.EmailTo()}, repo) + return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{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 @@ -78,8 +78,8 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U 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) From afc8257d0eb7414d598920b058f93e26ef1a4d86 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 16:41:49 +0200 Subject: [PATCH 05/24] fix --- services/mailer/mail_repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 619223033259a..28b9cef8a75e1 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -46,7 +46,7 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. return nil } - return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner}, 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 From 1ddcf84acd3835ee0425f78c94e501c1648768c7 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 16:42:19 +0200 Subject: [PATCH 06/24] start --- modules/setting/mailer.go | 14 ++++++++++++++ services/mailer/mail.go | 15 ++++++++++++++- services/mailer/mail_release.go | 2 +- services/mailer/mail_repo.go | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index 58bfd67bfb504..ec693796dd56d 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -8,6 +8,7 @@ import ( "net" "net/mail" "strings" + "text/template" "time" "code.gitea.io/gitea/modules/log" @@ -46,6 +47,10 @@ type Mailer struct { SendmailArgs []string `ini:"-"` SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"` SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"` + + // Customization + FromDisplayNameFormat string `ini:"FROM_DISPLAY_NAME_FORMAT"` + FromDisplayNameFormatTemplate *template.Template `ini:"-"` } // MailService the global mailer @@ -226,6 +231,15 @@ func loadMailerFrom(rootCfg ConfigProvider) { log.Error("no mailer.FROM provided, email system may not work.") } + if MailService.FromDisplayNameFormat == "" { + MailService.FromDisplayNameFormat = "{{ .DisplayName }}" + var err error + MailService.FromDisplayNameFormatTemplate, err = template.New("mailFrom").Parse(MailService.FromDisplayNameFormat) + if err != nil { + log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err) + } + } + switch MailService.EnvelopeFrom { case "": MailService.OverrideEnvelopeFrom = false diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 11810a036d62d..7d37f3cfbcfac 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -314,7 +314,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient for _, recipient := range recipients { msg := NewMessageFrom( recipient.Email, - ctx.Doer.GetCompleteName(), + fromDisplayName(ctx.Doer), setting.MailService.FromEmail, subject, mailBody.String(), @@ -536,3 +536,16 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act } return typeName, name, template } + +func fromDisplayName(u *user_model.User) string { + /* + var ctx bytes.Buffer + setting.Mailer.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ + "DisplayName": u.DisplayName(), + "AppName" ... + "AppHost" ... + }) + return ctx.String() + */ + return u.GetCompleteName() +} diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index f1f2e558a776b..01a8929e2d028 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -86,7 +86,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re } msgs := make([]*Message, 0, len(tos)) - publisherName := rel.Publisher.DisplayName() + publisherName := fromDisplayName(rel.Publisher) msgID := generateMessageIDForRelease(rel) for _, to := range tos { msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String()) diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 28b9cef8a75e1..ae15ac43e4233 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -79,7 +79,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U } for _, to := range emailTos { - msg := NewMessage(to.EmailTo(), subject, content.String()) + msg := NewMessageFrom(to.EmailTo(), fromDisplayName(doer), subject, content.String()) msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID) SendAsync(msg) From 8bac25595a636cae10510ad97c45bcfe63505a54 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 29 Jun 2024 16:49:03 +0200 Subject: [PATCH 07/24] only docu missing --- services/mailer/mail.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 7d37f3cfbcfac..1ccbd9d28ed1c 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -538,14 +538,14 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act } func fromDisplayName(u *user_model.User) string { - /* - var ctx bytes.Buffer - setting.Mailer.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ - "DisplayName": u.DisplayName(), - "AppName" ... - "AppHost" ... - }) - return ctx.String() - */ - return u.GetCompleteName() + var ctx bytes.Buffer + err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ + "DisplayName": u.DisplayName(), + "AppName": setting.AppName, + "Domain": setting.Domain, + }) + if err != nil { + return u.GetCompleteName() + } + return ctx.String() } From dc9ce7cde598ae800b8e941096ce23d3a8ef3011 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 30 Jun 2024 02:29:53 +0200 Subject: [PATCH 08/24] isLatin --- models/user/user.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index dc1f01f0dfe83..33203291f4201 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -12,7 +12,6 @@ import ( "net/url" "path/filepath" "regexp" - "strconv" "strings" "time" "unicode" @@ -415,20 +414,24 @@ func (u *User) DisplayName() string { return u.Name } +var emailToReplacer = strings.NewReplacer( + "\n", "", + "\r", "", + "<", "", + ">", "", + ",", "", + ":", "", + ";", "", +) + // EmailTo returns full name and email. func (u *User) EmailTo() string { - // we don't deal with utf8 to ascii conversion - if u.DisplayName() != strings.Trim(strconv.QuoteToASCII(u.DisplayName()), `"`) { + sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName()) + + // we don't deal with non ascii strings + if !isLatin(sanitizedDisplayName) { return u.Email } - // ok just be sure we don't let the user break things somehow - sanitizedDisplayName := strings.ReplaceAll(u.DisplayName(), "\n", "") - sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, "\r", "") - sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, "<", "") - sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ">", "") - sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ",", "") - sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ":", "") - sanitizedDisplayName = strings.ReplaceAll(sanitizedDisplayName, ";", "") // should be an edge case but nice to have if sanitizedDisplayName == u.Email { @@ -444,6 +447,15 @@ func (u *User) EmailTo() string { return fmt.Sprintf("%s <%s>", add.Name, add.Address) } +func isLatin(s string) bool { + for _, char := range s { + if !unicode.IsLetter(char) && !unicode.IsDigit(char) { + return false + } + } + return true +} + // GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, // returns username otherwise. func (u *User) GetDisplayName() string { From 72cb96b717e0072580e2a625b10f46230a5a9840 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 30 Jun 2024 03:28:45 +0200 Subject: [PATCH 09/24] regex --- models/user/user.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index 33203291f4201..f224eefd9c21c 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -424,12 +424,14 @@ var emailToReplacer = strings.NewReplacer( ";", "", ) +var isAZ09 = regexp.MustCompile(`^[a-zA-Z0-9 ]+$`) + // EmailTo returns full name and email. func (u *User) EmailTo() string { sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName()) // we don't deal with non ascii strings - if !isLatin(sanitizedDisplayName) { + if !isAZ09.Match([]byte(sanitizedDisplayName)) { return u.Email } @@ -447,15 +449,6 @@ func (u *User) EmailTo() string { return fmt.Sprintf("%s <%s>", add.Name, add.Address) } -func isLatin(s string) bool { - for _, char := range s { - if !unicode.IsLetter(char) && !unicode.IsDigit(char) { - return false - } - } - return true -} - // GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, // returns username otherwise. func (u *User) GetDisplayName() string { From 4a47d3255d9880d5ce874d4342e0257b345fc54d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 2 Jul 2024 06:42:52 +0200 Subject: [PATCH 10/24] Update models/user/user.go Co-authored-by: silverwind --- models/user/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/user.go b/models/user/user.go index f224eefd9c21c..f80e477a1e66b 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -426,7 +426,7 @@ var emailToReplacer = strings.NewReplacer( var isAZ09 = regexp.MustCompile(`^[a-zA-Z0-9 ]+$`) -// EmailTo returns full name and email. +// EmailTo returns a string suitable to be put into a e-mail `To:` header. func (u *User) EmailTo() string { sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName()) From 872d8da262285e12e5ef28ed785317648f26de5b Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 4 Jul 2024 16:51:04 +0200 Subject: [PATCH 11/24] use mime-encode --- models/user/user.go | 10 ++-------- models/user/user_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index f80e477a1e66b..5ac24f09778c3 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -8,6 +8,7 @@ import ( "context" "encoding/hex" "fmt" + "mime" "net/mail" "net/url" "path/filepath" @@ -424,17 +425,10 @@ var emailToReplacer = strings.NewReplacer( ";", "", ) -var isAZ09 = regexp.MustCompile(`^[a-zA-Z0-9 ]+$`) - // EmailTo returns a string suitable to be put into a e-mail `To:` header. func (u *User) EmailTo() string { sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName()) - // we don't deal with non ascii strings - if !isAZ09.Match([]byte(sanitizedDisplayName)) { - return u.Email - } - // should be an edge case but nice to have if sanitizedDisplayName == u.Email { return u.Email @@ -446,7 +440,7 @@ func (u *User) EmailTo() string { return u.Email } - return fmt.Sprintf("%s <%s>", add.Name, add.Address) + 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, diff --git a/models/user/user_test.go b/models/user/user_test.go index 6aa72a3e1cefe..26cda3f89e324 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -538,10 +538,10 @@ func TestEmailTo(t *testing.T) { {"Awareness Hub", "awareness@hub.net", "Awareness Hub "}, {"name@example.com", "name@example.com", "name@example.com"}, {"Hi Its ", "ee@mail.box", "Hi Its Mee "}, - {"Sinéad.O'Connor", "sinead.oconnor@gmail.com", "sinead.oconnor@gmail.com"}, - {"Æsir", "aesir@gmx.de", "aesir@gmx.de"}, - {"new😀user", "new.user@alo.com", "new.user@alo.com"}, - {`"quoted"`, "quoted@test.com", "quoted@test.com"}, + {"Sinéad.O'Connor", "sinead.oconnor@gmail.com", "=?utf-8?q?Sin=C3=A9ad.O'Connor?= "}, + {"Æsir", "aesir@gmx.de", "=?utf-8?q?=C3=86sir?= "}, + {"new😀user", "new.user@alo.com", "=?utf-8?q?new=F0=9F=98=80user?= "}, + {`"quoted"`, "quoted@test.com", "quoted "}, } for _, testCase := range testCases { From 60d2807d8bddf0cce0c08faf8931ec0f7c031993 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 4 Jul 2024 16:53:19 +0200 Subject: [PATCH 12/24] clean --- models/user/user.go | 30 ------------------------------ models/user/user_test.go | 23 ----------------------- services/mailer/mail.go | 6 +++--- services/mailer/mail_release.go | 8 ++++---- services/mailer/mail_repo.go | 12 ++++++------ 5 files changed, 13 insertions(+), 66 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index 5ac24f09778c3..23637f4616288 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -8,8 +8,6 @@ import ( "context" "encoding/hex" "fmt" - "mime" - "net/mail" "net/url" "path/filepath" "regexp" @@ -415,34 +413,6 @@ func (u *User) DisplayName() string { 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 { diff --git a/models/user/user_test.go b/models/user/user_test.go index 26cda3f89e324..c4e278caab756 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -529,29 +529,6 @@ 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 "}, - {"name@example.com", "name@example.com", "name@example.com"}, - {"Hi Its ", "ee@mail.box", "Hi Its Mee "}, - {"Sinéad.O'Connor", "sinead.oconnor@gmail.com", "=?utf-8?q?Sin=C3=A9ad.O'Connor?= "}, - {"Æsir", "aesir@gmx.de", "=?utf-8?q?=C3=86sir?= "}, - {"new😀user", "new.user@alo.com", "=?utf-8?q?new=F0=9F=98=80user?= "}, - {`"quoted"`, "quoted@test.com", "quoted "}, - } - - 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()) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 1ccbd9d28ed1c..39a64cb85d76e 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -82,7 +82,7 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s return } - msg := NewMessage(u.EmailTo(), subject, content.String()) + msg := NewMessage(u.Email, subject, content.String()) msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info) SendAsync(msg) @@ -158,7 +158,7 @@ func SendRegisterNotifyMail(u *user_model.User) { return } - msg := NewMessage(u.EmailTo(), locale.TrString("mail.register_notify"), content.String()) + msg := NewMessage(u.Email, locale.TrString("mail.register_notify"), content.String()) msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID) SendAsync(msg) @@ -189,7 +189,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) return } - msg := NewMessage(u.EmailTo(), subject, content.String()) + msg := NewMessage(u.Email, subject, content.String()) msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID) SendAsync(msg) diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 01a8929e2d028..581e1c73a636d 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -40,10 +40,10 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) { return } - langMap := make(map[string][]*user_model.User) + langMap := make(map[string][]string) for _, user := range recipients { if user.ID != rel.PublisherID { - langMap[user.Language] = append(langMap[user.Language], user) + langMap[user.Language] = append(langMap[user.Language], user.Email) } } @@ -52,7 +52,7 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) { } } -func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, rel *repo_model.Release) { +func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_model.Release) { locale := translation.NewLocale(lang) var err error @@ -89,7 +89,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re publisherName := fromDisplayName(rel.Publisher) msgID := generateMessageIDForRelease(rel) for _, to := range tos { - msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String()) + msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String()) msg.Info = subject msg.SetHeader("Message-ID", msgID) msgs = append(msgs, msg) diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index ae15ac43e4233..592bd1a5636ac 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -28,13 +28,13 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. return err } - langMap := make(map[string][]*user_model.User) + langMap := make(map[string][]string) for _, user := range users { if !user.IsActive { // don't send emails to inactive users continue } - langMap[user.Language] = append(langMap[user.Language], user) + langMap[user.Language] = append(langMap[user.Language], user.Email) } for lang, tos := range langMap { @@ -46,11 +46,11 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model. return nil } - return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []*user_model.User{newOwner}, repo) + return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner.Email}, 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, emailTos []*user_model.User, repo *repo_model.Repository) error { +func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.User, emails []string, repo *repo_model.Repository) error { var ( locale = translation.NewLocale(lang) content bytes.Buffer @@ -78,8 +78,8 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U return err } - for _, to := range emailTos { - msg := NewMessageFrom(to.EmailTo(), fromDisplayName(doer), subject, content.String()) + for _, to := range emails { + msg := NewMessageFrom(to, fromDisplayName(doer), setting.MailService.FromEmail, subject, content.String()) msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID) SendAsync(msg) From 1569bcd9285626cf5a7be105fd22bf5fa4f21d0f Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 4 Jul 2024 17:10:19 +0200 Subject: [PATCH 13/24] use mime encoding for from too --- services/mailer/mail.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 39a64cb85d76e..267693f9e3ce7 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -540,7 +540,7 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act func fromDisplayName(u *user_model.User) string { var ctx bytes.Buffer err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ - "DisplayName": u.DisplayName(), + "DisplayName": mime.QEncoding.Encode("utf-8", u.DisplayName()), "AppName": setting.AppName, "Domain": setting.Domain, }) From f2a857595044ec74f55565a85959859bb64532b6 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 4 Jul 2024 17:10:31 +0200 Subject: [PATCH 14/24] document option --- custom/conf/app.example.ini | 4 ++++ docs/content/administration/config-cheat-sheet.en-us.md | 1 + 2 files changed, 5 insertions(+) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 9196180d81e3c..e42359ebb31c9 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1672,6 +1672,10 @@ LEVEL = Info ;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. ;ENVELOPE_FROM = ;; +;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, +;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Beside **DisplayName** and **AppName** we have support for **Domain** too. +;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }} +;; ;; Mailer user name and password, if required by provider. ;USER = ;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 0c15a866b6e88..1874f479bc541 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -755,6 +755,7 @@ and - `HELO_HOSTNAME`: **(retrieved from system)**: HELO hostname. - `FROM`: **_empty_**: Mail from address, RFC 5322. This can be just an email address, or the "Name" \ format. - `ENVELOPE_FROM`: **_empty_**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address. +- `FROM_DISPLAY_NAME_FORMAT`: **{{ .DisplayName }}**: If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, set it to `{{ .DisplayName }} (by {{ .AppName }})`. Beside **DisplayName** and **AppName** we have support for **Domain** too. - `SUBJECT_PREFIX`: **_empty_**: Prefix to be placed before e-mail subject lines. - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be command or full path). - `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`) From a8aec2937a1227930c6ced2968728dc46e281dab Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 4 Jul 2024 17:14:23 +0200 Subject: [PATCH 15/24] harden func --- services/mailer/mail.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 267693f9e3ce7..49b6e335b468c 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -538,14 +538,16 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act } func fromDisplayName(u *user_model.User) string { - var ctx bytes.Buffer - err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ - "DisplayName": mime.QEncoding.Encode("utf-8", u.DisplayName()), - "AppName": setting.AppName, - "Domain": setting.Domain, - }) - if err != nil { - return u.GetCompleteName() + if setting.MailService.FromDisplayNameFormatTemplate != nil { + var ctx bytes.Buffer + err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ + "DisplayName": mime.QEncoding.Encode("utf-8", u.DisplayName()), + "AppName": setting.AppName, + "Domain": setting.Domain, + }) + if err == nil { + return ctx.String() + } } - return ctx.String() + return u.GetCompleteName() } From 964aa7ddbede22411aa666214187fdab734d1095 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 4 Jul 2024 17:15:14 +0200 Subject: [PATCH 16/24] dont drop err silent --- services/mailer/mail.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 49b6e335b468c..5a9e8bc5fa0c2 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -548,6 +548,7 @@ func fromDisplayName(u *user_model.User) string { if err == nil { return ctx.String() } + log.Error("fromDisplayName: %w", err) } return u.GetCompleteName() } From 8563b9d32495307c5b25905915a332ba76de0623 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 5 Jul 2024 00:39:23 +0200 Subject: [PATCH 17/24] tests --- services/mailer/mail_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 0739f4233fc89..8d56616610a6e 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -403,3 +403,35 @@ func TestGenerateMessageIDForRelease(t *testing.T) { }) assert.Equal(t, "", msgID) } + +func TestFromDisplayName(t *testing.T) { + template, err := texttmpl.New("mailFrom").Parse("{{ .DisplayName }}") + assert.NoError(t, err) + setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template} + defer func() { setting.MailService = nil }() + + tests := []struct { + userDisplayName string + fromDisplayName string + }{{ + userDisplayName: "test", + fromDisplayName: "test", + }, { + userDisplayName: "Hi Its ", + fromDisplayName: "Hi Its ", + }, { + userDisplayName: "Æsir", + fromDisplayName: "=?utf-8?q?=C3=86sir?=", + }, { + userDisplayName: "new😀user", + fromDisplayName: "=?utf-8?q?new=F0=9F=98=80user?=", + }} + + for _, tc := range tests { + t.Run(tc.userDisplayName, func(t *testing.T) { + user := &user_model.User{FullName: tc.userDisplayName, Name: "tmp"} + got := fromDisplayName(user) + assert.EqualValues(t, tc.fromDisplayName, got) + }) + } +} From 506b0d77e771465a950441a6bc38b2d46baf3558 Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Fri, 5 Jul 2024 15:37:30 +0200 Subject: [PATCH 18/24] encode ad the end --- services/mailer/mail.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 5a9e8bc5fa0c2..cc2dc05628256 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -541,12 +541,12 @@ func fromDisplayName(u *user_model.User) string { if setting.MailService.FromDisplayNameFormatTemplate != nil { var ctx bytes.Buffer err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ - "DisplayName": mime.QEncoding.Encode("utf-8", u.DisplayName()), + "DisplayName": u.DisplayName(), "AppName": setting.AppName, "Domain": setting.Domain, }) if err == nil { - return ctx.String() + return mime.QEncoding.Encode("utf-8", ctx.String()) } log.Error("fromDisplayName: %w", err) } From efbee4d4d762d7a09b4e3a4f1996fd9bcf2f5cb6 Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Fri, 5 Jul 2024 15:54:10 +0200 Subject: [PATCH 19/24] cover more via tests --- services/mailer/mail_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 8d56616610a6e..976baaad2181c 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -434,4 +434,21 @@ func TestFromDisplayName(t *testing.T) { assert.EqualValues(t, tc.fromDisplayName, got) }) } + + t.Run("template with all available vars", func(t *testing.T) { + template, err = texttmpl.New("mailFrom").Parse("{{ .DisplayName }} (by {{ .AppName }} on [{{ .Domain }}])") + assert.NoError(t, err) + setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template} + oldAppName := setting.AppName + setting.AppName = "Code IT" + oldDomain := setting.Domain + setting.Domain = "code.it" + defer func() { + setting.AppName = oldAppName + setting.Domain = oldDomain + }() + + assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) + }) + } From 46e5fdd7360426b43c92fd068ba9cd91c761be2f Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Fri, 5 Jul 2024 17:34:22 +0200 Subject: [PATCH 20/24] fix --- services/mailer/mail_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 976baaad2181c..40fd21dea58a0 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -450,5 +450,4 @@ func TestFromDisplayName(t *testing.T) { assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) }) - } From 8c2576b73d5f28248602a0f5481413f5f25eb1c8 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 10 Jul 2024 13:35:58 +0200 Subject: [PATCH 21/24] Update docs/content/administration/config-cheat-sheet.en-us.md Co-authored-by: silverwind --- docs/content/administration/config-cheat-sheet.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 1874f479bc541..f2d086b74322d 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -755,7 +755,7 @@ and - `HELO_HOSTNAME`: **(retrieved from system)**: HELO hostname. - `FROM`: **_empty_**: Mail from address, RFC 5322. This can be just an email address, or the "Name" \ format. - `ENVELOPE_FROM`: **_empty_**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address. -- `FROM_DISPLAY_NAME_FORMAT`: **{{ .DisplayName }}**: If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, set it to `{{ .DisplayName }} (by {{ .AppName }})`. Beside **DisplayName** and **AppName** we have support for **Domain** too. +- `FROM_DISPLAY_NAME_FORMAT`: **{{ .DisplayName }}**: If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`. - `SUBJECT_PREFIX`: **_empty_**: Prefix to be placed before e-mail subject lines. - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be command or full path). - `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`) From ba06cbb72f553f16a06de39cd9b828531d5b1db3 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 10 Jul 2024 13:47:37 +0200 Subject: [PATCH 22/24] moved to https://gitea.com/gitea/docs/pulls/23 --- docs/content/administration/config-cheat-sheet.en-us.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index ae2e1b9c6c003..0c15a866b6e88 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -517,18 +517,14 @@ And the following unique queues: - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. -- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`, `manage_mfa`, `manage_credentials` and more features can be added in future. +- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys` and more features can be added in future. - `deletion`: User cannot delete their own account. - `manage_ssh_keys`: User cannot configure ssh keys. - `manage_gpg_keys`: User cannot configure gpg keys. - - `manage_mfa`: a User cannot configure mfa devices. - - `manage_credentials`: a user cannot configure emails, passwords, or openid -- `EXTERNAL_USER_DISABLE_FEATURES`: **_empty_**: Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`, `manage_mfa`, `manage_credentials`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. +- `EXTERNAL_USER_DISABLE_FEATURES`: **_empty_**: Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. - `deletion`: User cannot delete their own account. - `manage_ssh_keys`: User cannot configure ssh keys. - `manage_gpg_keys`: User cannot configure gpg keys. - - `manage_mfa`: a User cannot configure mfa devices. - - `manage_credentials`: a user cannot configure emails, passwords, or openid ## Security (`security`) @@ -759,7 +755,6 @@ and - `HELO_HOSTNAME`: **(retrieved from system)**: HELO hostname. - `FROM`: **_empty_**: Mail from address, RFC 5322. This can be just an email address, or the "Name" \ format. - `ENVELOPE_FROM`: **_empty_**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address. -- `FROM_DISPLAY_NAME_FORMAT`: **{{ .DisplayName }}**: If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`. - `SUBJECT_PREFIX`: **_empty_**: Prefix to be placed before e-mail subject lines. - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be command or full path). - `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`) From 3dc65143286d0911c433da679786e05139cc8fc5 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 10 Jul 2024 19:46:10 +0200 Subject: [PATCH 23/24] sync --- custom/conf/app.example.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 90850d0db6320..c29d2e5be43ee 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1677,7 +1677,7 @@ LEVEL = Info ;ENVELOPE_FROM = ;; ;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, -;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Beside **DisplayName** and **AppName** we have support for **Domain** too. +;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`. ;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }} ;; ;; Mailer user name and password, if required by provider. From a3eb15f8e490d0403d677cdc56941141c6a6b220 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 12 Jul 2024 00:37:04 +0200 Subject: [PATCH 24/24] make default work right --- modules/setting/mailer.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index ec693796dd56d..d4db55dc7bc70 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -231,12 +231,13 @@ func loadMailerFrom(rootCfg ConfigProvider) { log.Error("no mailer.FROM provided, email system may not work.") } - if MailService.FromDisplayNameFormat == "" { - MailService.FromDisplayNameFormat = "{{ .DisplayName }}" - var err error - MailService.FromDisplayNameFormatTemplate, err = template.New("mailFrom").Parse(MailService.FromDisplayNameFormat) + MailService.FromDisplayNameFormatTemplate, _ = template.New("mailFrom").Parse("{{ .DisplayName }}") + if MailService.FromDisplayNameFormat != "" { + template, err := template.New("mailFrom").Parse(MailService.FromDisplayNameFormat) if err != nil { log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err) + } else { + MailService.FromDisplayNameFormatTemplate = template } }