Try to prevent autolinking of displaynames by email readers (#19169)

Unfortunately many email readers will (helpfully) detect url or url-like names and
automatically create links to them, even in HTML emails. This is not ideal when
usernames can have dots in them.

This PR tries to prevent this behaviour by sticking ZWJ characters between dots and
also set the meta tag to prevent format detection.

Not every email template has been changed in this way - just the activation emails but
it may be that we should be setting the above meta tag in all of our emails too.

Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
zeripath 2022-03-23 12:34:20 +00:00 committed by GitHub
parent 3f280f89e7
commit 5248232c44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 38 additions and 22 deletions

View file

@ -632,6 +632,11 @@ func JSEscape(raw string) string {
return template.JSEscapeString(raw) return template.JSEscapeString(raw)
} }
// DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
func DotEscape(raw string) string {
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
}
// Sha1 returns sha1 sum of string // Sha1 returns sha1 sum of string
func Sha1(str string) string { func Sha1(str string) string {
return base.EncodeSha1(str) return base.EncodeSha1(str)

View file

@ -80,6 +80,7 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
var content bytes.Buffer var content bytes.Buffer
@ -130,6 +131,7 @@ func SendActivateEmailMail(u *user_model.User, email *user_model.EmailAddress) {
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
var content bytes.Buffer var content bytes.Buffer
@ -160,6 +162,7 @@ func SendRegisterNotifyMail(u *user_model.User) {
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
var content bytes.Buffer var content bytes.Buffer
@ -193,6 +196,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
var content bytes.Buffer var content bytes.Buffer
@ -277,6 +281,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
var mailSubject bytes.Buffer var mailSubject bytes.Buffer

View file

@ -77,6 +77,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *models.
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
var mailBody bytes.Buffer var mailBody bytes.Buffer

View file

@ -75,6 +75,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
// helper // helper
"i18n": locale, "i18n": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape,
} }
if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil { if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil {

View file

@ -2,12 +2,13 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.i18n.Tr "mail.activate_account.title" .DisplayName}}</title> <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"/>
<title>{{.i18n.Tr "mail.activate_account.title" (.DisplayName|DotEscape)}}</title>
</head> </head>
{{ $activate_url := printf "%suser/activate?code=%s" AppUrl (QueryEscape .Code)}} {{ $activate_url := printf "%suser/activate?code=%s" AppUrl (QueryEscape .Code)}}
<body> <body>
<p>{{.i18n.Tr "mail.activate_account.text_1" .DisplayName AppName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.activate_account.text_1" (.DisplayName|DotEscape) AppName | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.activate_account.text_2" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> <p>{{.i18n.Tr "mail.activate_account.text_2" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br>
<p>{{.i18n.Tr "mail.link_not_working_do_paste"}}</p> <p>{{.i18n.Tr "mail.link_not_working_do_paste"}}</p>

View file

@ -2,12 +2,13 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.i18n.Tr "mail.activate_email.title" .DisplayName}}</title> <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"/>
<title>{{.i18n.Tr "mail.activate_email.title" (.DisplayName|DotEscape)}}</title>
</head> </head>
{{ $activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl (QueryEscape .Code) (QueryEscape .Email)}} {{ $activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl (QueryEscape .Code) (QueryEscape .Email)}}
<body> <body>
<p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.hi_user_x" (.DisplayName|DotEscape) | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.activate_email.text" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> <p>{{.i18n.Tr "mail.activate_email.text" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br>
<p>{{.i18n.Tr "mail.link_not_working_do_paste"}}</p> <p>{{.i18n.Tr "mail.link_not_working_do_paste"}}</p>

View file

@ -2,12 +2,13 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.i18n.Tr "mail.register_notify.title" .DisplayName AppName}}</title> <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"/>
<title>{{.i18n.Tr "mail.register_notify.title" (.DisplayName|DotEscape) AppName}}</title>
</head> </head>
{{$set_pwd_url := printf "%[1]suser/forgot_password" AppUrl}} {{$set_pwd_url := printf "%[1]suser/forgot_password" AppUrl}}
<body> <body>
<p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.hi_user_x" (.DisplayName|DotEscape) | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.register_notify.text_1" AppName}}</p><br> <p>{{.i18n.Tr "mail.register_notify.text_1" AppName}}</p><br>
<p>{{.i18n.Tr "mail.register_notify.text_2" .Username}}</p><p><a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a></p><br> <p>{{.i18n.Tr "mail.register_notify.text_2" .Username}}</p><p><a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a></p><br>
<p>{{.i18n.Tr "mail.register_notify.text_3" ($set_pwd_url | Escape) | Str2html}}</p><br> <p>{{.i18n.Tr "mail.register_notify.text_3" ($set_pwd_url | Escape) | Str2html}}</p><br>

View file

@ -2,12 +2,13 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.i18n.Tr "mail.reset_password.title" .DisplayName}}</title> <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"/>
<title>{{.i18n.Tr "mail.reset_password.title" (.DisplayName|DotEscape)}}</title>
</head> </head>
{{ $recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}} {{ $recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}}
<body> <body>
<p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.hi_user_x" (.DisplayName|DotEscape) | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.reset_password.text" .ResetPwdCodeLives | Str2html}}</p><p><a href="{{$recover_url}}">{{$recover_url}}</a></p><br> <p>{{.i18n.Tr "mail.reset_password.text" .ResetPwdCodeLives | Str2html}}</p><p><a href="{{$recover_url}}">{{$recover_url}}</a></p><br>
<p>{{.i18n.Tr "mail.link_not_working_do_paste"}}</p> <p>{{.i18n.Tr "mail.link_not_working_do_paste"}}</p>