Skip to content

Commit 1cb6495

Browse files
6543lunny
andauthored
Restrict email address validation (#17688) (#19085)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
1 parent 99861e3 commit 1cb6495

File tree

9 files changed

+111
-14
lines changed

9 files changed

+111
-14
lines changed

models/user/email_address.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"errors"
1111
"fmt"
1212
"net/mail"
13+
"regexp"
1314
"strings"
1415

1516
"code.gitea.io/gitea/models/db"
@@ -21,10 +22,23 @@ import (
2122
"xorm.io/builder"
2223
)
2324

24-
var (
25-
// ErrEmailNotActivated e-mail address has not been activated error
26-
ErrEmailNotActivated = errors.New("E-mail address has not been activated")
27-
)
25+
// ErrEmailNotActivated e-mail address has not been activated error
26+
var ErrEmailNotActivated = errors.New("e-mail address has not been activated")
27+
28+
// ErrEmailCharIsNotSupported e-mail address contains unsupported character
29+
type ErrEmailCharIsNotSupported struct {
30+
Email string
31+
}
32+
33+
// IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
34+
func IsErrEmailCharIsNotSupported(err error) bool {
35+
_, ok := err.(ErrEmailCharIsNotSupported)
36+
return ok
37+
}
38+
39+
func (err ErrEmailCharIsNotSupported) Error() string {
40+
return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
41+
}
2842

2943
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
3044
type ErrEmailInvalid struct {
@@ -108,12 +122,24 @@ func (email *EmailAddress) BeforeInsert() {
108122
}
109123
}
110124

125+
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
126+
111127
// ValidateEmail check if email is a allowed address
112128
func ValidateEmail(email string) error {
113129
if len(email) == 0 {
114130
return nil
115131
}
116132

133+
if !emailRegexp.MatchString(email) {
134+
return ErrEmailCharIsNotSupported{email}
135+
}
136+
137+
if !(email[0] >= 'a' && email[0] <= 'z') &&
138+
!(email[0] >= 'A' && email[0] <= 'Z') &&
139+
!(email[0] >= '0' && email[0] <= '9') {
140+
return ErrEmailInvalid{email}
141+
}
142+
117143
if _, err := mail.ParseAddress(email); err != nil {
118144
return ErrEmailInvalid{email}
119145
}

models/user/email_address_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,58 @@ func TestListEmails(t *testing.T) {
252252
assert.Len(t, emails, 5)
253253
assert.Greater(t, count, int64(len(emails)))
254254
}
255+
256+
func TestEmailAddressValidate(t *testing.T) {
257+
kases := map[string]error{
258+
"abc@gmail.com": nil,
259+
"132@hotmail.com": nil,
260+
"1-3-2@test.org": nil,
261+
"1.3.2@test.org": nil,
262+
"a_123@test.org.cn": nil,
263+
`first.last@iana.org`: nil,
264+
`first!last@iana.org`: nil,
265+
`first#last@iana.org`: nil,
266+
`first$last@iana.org`: nil,
267+
`first%last@iana.org`: nil,
268+
`first&last@iana.org`: nil,
269+
`first'last@iana.org`: nil,
270+
`first*last@iana.org`: nil,
271+
`first+last@iana.org`: nil,
272+
`first/last@iana.org`: nil,
273+
`first=last@iana.org`: nil,
274+
`first?last@iana.org`: nil,
275+
`first^last@iana.org`: nil,
276+
"first`last@iana.org": nil,
277+
`first{last@iana.org`: nil,
278+
`first|last@iana.org`: nil,
279+
`first}last@iana.org`: nil,
280+
`first~last@iana.org`: nil,
281+
`first;last@iana.org`: ErrEmailCharIsNotSupported{`first;last@iana.org`},
282+
".233@qq.com": ErrEmailInvalid{".233@qq.com"},
283+
"!233@qq.com": ErrEmailInvalid{"!233@qq.com"},
284+
"#233@qq.com": ErrEmailInvalid{"#233@qq.com"},
285+
"$233@qq.com": ErrEmailInvalid{"$233@qq.com"},
286+
"%233@qq.com": ErrEmailInvalid{"%233@qq.com"},
287+
"&233@qq.com": ErrEmailInvalid{"&233@qq.com"},
288+
"'233@qq.com": ErrEmailInvalid{"'233@qq.com"},
289+
"*233@qq.com": ErrEmailInvalid{"*233@qq.com"},
290+
"+233@qq.com": ErrEmailInvalid{"+233@qq.com"},
291+
"/233@qq.com": ErrEmailInvalid{"/233@qq.com"},
292+
"=233@qq.com": ErrEmailInvalid{"=233@qq.com"},
293+
"?233@qq.com": ErrEmailInvalid{"?233@qq.com"},
294+
"^233@qq.com": ErrEmailInvalid{"^233@qq.com"},
295+
"`233@qq.com": ErrEmailInvalid{"`233@qq.com"},
296+
"{233@qq.com": ErrEmailInvalid{"{233@qq.com"},
297+
"|233@qq.com": ErrEmailInvalid{"|233@qq.com"},
298+
"}233@qq.com": ErrEmailInvalid{"}233@qq.com"},
299+
"~233@qq.com": ErrEmailInvalid{"~233@qq.com"},
300+
";233@qq.com": ErrEmailCharIsNotSupported{";233@qq.com"},
301+
"Foo <foo@bar.com>": ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
302+
string([]byte{0xE2, 0x84, 0xAA}): ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
303+
}
304+
for kase, err := range kases {
305+
t.Run(kase, func(t *testing.T) {
306+
assert.EqualValues(t, err, ValidateEmail(kase))
307+
})
308+
}
309+
}

models/user/user.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,15 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
644644
u.Visibility = overwriteDefault[0].Visibility
645645
}
646646

647+
// validate data
648+
if err := validateUser(u); err != nil {
649+
return err
650+
}
651+
652+
if err := ValidateEmail(u.Email); err != nil {
653+
return err
654+
}
655+
647656
ctx, committer, err := db.TxContext()
648657
if err != nil {
649658
return err
@@ -652,11 +661,6 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
652661

653662
sess := db.GetEngine(ctx)
654663

655-
// validate data
656-
if err := validateUser(u); err != nil {
657-
return err
658-
}
659-
660664
isExist, err := isUserExist(sess, 0, u.Name)
661665
if err != nil {
662666
return err

models/user/user_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
232232

233233
err := CreateUser(user)
234234
assert.Error(t, err)
235-
assert.True(t, IsErrEmailInvalid(err))
235+
assert.True(t, IsErrEmailCharIsNotSupported(err))
236236
}
237237

238238
func TestCreateUserEmailAlreadyUsed(t *testing.T) {

routers/api/v1/admin/user.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ func CreateUser(ctx *context.APIContext) {
119119
user_model.IsErrEmailAlreadyUsed(err) ||
120120
db.IsErrNameReserved(err) ||
121121
db.IsErrNameCharsNotAllowed(err) ||
122+
user_model.IsErrEmailCharIsNotSupported(err) ||
122123
user_model.IsErrEmailInvalid(err) ||
123124
db.IsErrNamePatternNotAllowed(err) {
124125
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -265,7 +266,9 @@ func EditUser(ctx *context.APIContext) {
265266
}
266267

267268
if err := user_model.UpdateUser(u, emailChanged); err != nil {
268-
if user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailInvalid(err) {
269+
if user_model.IsErrEmailAlreadyUsed(err) ||
270+
user_model.IsErrEmailCharIsNotSupported(err) ||
271+
user_model.IsErrEmailInvalid(err) {
269272
ctx.Error(http.StatusUnprocessableEntity, "", err)
270273
} else {
271274
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)

routers/api/v1/user/email.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ func AddEmail(ctx *context.APIContext) {
8080
if err := user_model.AddEmailAddresses(emails); err != nil {
8181
if user_model.IsErrEmailAlreadyUsed(err) {
8282
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
83-
} else if user_model.IsErrEmailInvalid(err) {
83+
} else if user_model.IsErrEmailCharIsNotSupported(err) ||
84+
user_model.IsErrEmailInvalid(err) {
8485
errMsg := fmt.Sprintf("Email address %s invalid", err.(user_model.ErrEmailInvalid).Email)
8586
ctx.Error(http.StatusUnprocessableEntity, "", errMsg)
8687
} else {

routers/web/admin/users.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ func NewUserPost(ctx *context.Context) {
171171
case user_model.IsErrEmailAlreadyUsed(err):
172172
ctx.Data["Err_Email"] = true
173173
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
174+
case user_model.IsErrEmailCharIsNotSupported(err):
175+
ctx.Data["Err_Email"] = true
176+
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
174177
case user_model.IsErrEmailInvalid(err):
175178
ctx.Data["Err_Email"] = true
176179
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
@@ -386,7 +389,8 @@ func EditUserPost(ctx *context.Context) {
386389
if user_model.IsErrEmailAlreadyUsed(err) {
387390
ctx.Data["Err_Email"] = true
388391
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
389-
} else if user_model.IsErrEmailInvalid(err) {
392+
} else if user_model.IsErrEmailCharIsNotSupported(err) ||
393+
user_model.IsErrEmailInvalid(err) {
390394
ctx.Data["Err_Email"] = true
391395
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
392396
} else {

routers/web/auth/auth.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,9 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
573573
case user_model.IsErrEmailAlreadyUsed(err):
574574
ctx.Data["Err_Email"] = true
575575
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
576+
case user_model.IsErrEmailCharIsNotSupported(err):
577+
ctx.Data["Err_Email"] = true
578+
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
576579
case user_model.IsErrEmailInvalid(err):
577580
ctx.Data["Err_Email"] = true
578581
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)

routers/web/user/setting/account.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ func EmailPost(ctx *context.Context) {
188188

189189
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
190190
return
191-
} else if user_model.IsErrEmailInvalid(err) {
191+
} else if user_model.IsErrEmailCharIsNotSupported(err) ||
192+
user_model.IsErrEmailInvalid(err) {
192193
loadAccountData(ctx)
193194

194195
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)

0 commit comments

Comments
 (0)