fix: various bugs (#39661) (#39667)

Backport #39661

1. fix #39660: relax email validation
2. fix #39658: use "int64" instead of time.Duration (for JSON v2)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Giteabot
2026-10-07 10:11:58 -07:00
committed by GitHub
parent 4ebd5e319b
commit 27bd022b34
4 changed files with 36 additions and 19 deletions
+20 -8
View File
@@ -151,16 +151,28 @@ func TestListEmails(t *testing.T) {
func TestEmailAddressValidate(t *testing.T) { func TestEmailAddressValidate(t *testing.T) {
cases := map[string]bool{ cases := map[string]bool{
"": false, "": false,
"root@localhost": true, "@a": false,
"user@[192.168.1.2]": true,
"@a": false, // "_" shouldn't appear in domain but can appear in hostname, since we can't stop site admins from doing so, just accept it
"abc@gmail.com": true, "root@local_host": true,
"abc@gmail.com\n": false, "root@localhost": true,
"root@LOCALHOST": true,
"user@[192.168.1.2]": true,
"user@[IPv6:FFff::1]": true,
"abc@gmail.com": true,
"abc@gmail.com.": false,
"abc@gmail.com-": false,
"abc@gmail.com\n": false,
"abc@gmail..com": false,
"abc@gmail com": false,
"abc@gmail*com": false,
"Foo <foo@bar.com>": false, "Foo <foo@bar.com>": false,
"abc@gmail.com (x)": false, "abc@gmail.com (x)": false,
"jürgen@example.com": false, "jürgen@example.com": false, // utf8 address is not supported yet
"a@foo_bar.com": false,
} }
for tc, isValid := range cases { for tc, isValid := range cases {
t.Run(tc, func(t *testing.T) { t.Run(tc, func(t *testing.T) {
+2 -2
View File
@@ -38,14 +38,14 @@ func ReloadTemplates(ctx context.Context) ResponseExtra {
// FlushOptions represents the options for the flush call // FlushOptions represents the options for the flush call
type FlushOptions struct { type FlushOptions struct {
Timeout time.Duration Timeout int64
NonBlocking bool NonBlocking bool
} }
// FlushQueues calls the internal flush-queues function // FlushQueues calls the internal flush-queues function
func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) ResponseExtra { func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/flush-queues" reqURL := setting.LocalURL + "api/internal/manager/flush-queues"
req := newInternalRequestAPI(ctx, reqURL, "POST", FlushOptions{Timeout: timeout, NonBlocking: nonBlocking}) req := newInternalRequestAPI(ctx, reqURL, "POST", FlushOptions{Timeout: int64(timeout), NonBlocking: nonBlocking})
if timeout > 0 { if timeout > 0 {
req.SetReadWriteTimeout(timeout + 10*time.Second) req.SetReadWriteTimeout(timeout + 10*time.Second)
} }
+11 -7
View File
@@ -14,8 +14,6 @@ import (
"gitea.dev/modules/glob" "gitea.dev/modules/glob"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"golang.org/x/net/idna"
) )
type globalVarsStruct struct { type globalVarsStruct struct {
@@ -24,6 +22,8 @@ type globalVarsStruct struct {
invalidUsernamePattern *regexp.Regexp invalidUsernamePattern *regexp.Regexp
validBadgeSlugPattern *regexp.Regexp validBadgeSlugPattern *regexp.Regexp
invalidBadgeSlugPattern *regexp.Regexp invalidBadgeSlugPattern *regexp.Regexp
validEmailHostName *regexp.Regexp
validEmailHostIP *regexp.Regexp
} }
var globalVars = sync.OnceValue(func() *globalVarsStruct { var globalVars = sync.OnceValue(func() *globalVarsStruct {
@@ -33,6 +33,8 @@ var globalVars = sync.OnceValue(func() *globalVarsStruct {
invalidUsernamePattern: regexp.MustCompile(`[-._]{2,}|[-._]$`), // No consecutive or trailing non-alphanumeric chars invalidUsernamePattern: regexp.MustCompile(`[-._]{2,}|[-._]$`), // No consecutive or trailing non-alphanumeric chars
validBadgeSlugPattern: regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`), validBadgeSlugPattern: regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`),
invalidBadgeSlugPattern: regexp.MustCompile(`[-._]{2,}|[-._]$`), invalidBadgeSlugPattern: regexp.MustCompile(`[-._]{2,}|[-._]$`),
validEmailHostName: regexp.MustCompile(`^[a-zA-Z0-9][-.\w]*$`),
validEmailHostIP: regexp.MustCompile(`(?i)^\[([0-9.]+|ipv6:[0-9a-f:.]+)\]$`),
} }
}) })
@@ -124,10 +126,12 @@ func IsEmailAddressValid(email string) bool {
return false return false
} }
_, domain, _ := strings.Cut(email, "@") _, domain, _ := strings.Cut(email, "@")
if strings.HasPrefix(domain, "[") { if !globalVars().validEmailHostName.MatchString(domain) && !globalVars().validEmailHostIP.MatchString(domain) {
// address like "foo@[192.168.1.2]" return false
return true
} }
_, err = idna.Registration.ToASCII(domain) if strings.HasPrefix(domain, "-") || strings.HasSuffix(domain, "-") ||
return err == nil strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
return false
}
return true
} }
+3 -2
View File
@@ -5,6 +5,7 @@ package private
import ( import (
"net/http" "net/http"
"time"
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/graceful" "gitea.dev/modules/graceful"
@@ -34,7 +35,7 @@ func FlushQueues(ctx *context.PrivateContext) {
// Save the hammer ctx here - as a new one is created each time you call this. // Save the hammer ctx here - as a new one is created each time you call this.
baseCtx := graceful.GetManager().HammerContext() baseCtx := graceful.GetManager().HammerContext()
go func() { go func() {
err := queue.GetManager().FlushAll(baseCtx, opts.Timeout) err := queue.GetManager().FlushAll(baseCtx, time.Duration(opts.Timeout))
if err != nil { if err != nil {
log.Error("Flushing request timed-out with error: %v", err) log.Error("Flushing request timed-out with error: %v", err)
} }
@@ -44,7 +45,7 @@ func FlushQueues(ctx *context.PrivateContext) {
}) })
return return
} }
err := queue.GetManager().FlushAll(ctx, opts.Timeout) err := queue.GetManager().FlushAll(ctx, time.Duration(opts.Timeout))
if err != nil { if err != nil {
ctx.PrivateUserErrorf(http.StatusRequestTimeout, "%v", err) ctx.PrivateUserErrorf(http.StatusRequestTimeout, "%v", err)
return return