Address some CodeQL security concerns (#35572)

Although there is no real security problem
This commit is contained in:
wxiaoguang 2025-10-04 01:21:26 +08:00 committed by GitHub
parent c4532101a4
commit 71360a94cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 118 additions and 78 deletions

View File

@ -151,6 +151,7 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("generated random password is '%s'\n", password) fmt.Printf("generated random password is '%s'\n", password)
} else if userType == user_model.UserTypeIndividual { } else if userType == user_model.UserTypeIndividual {
return errors.New("must set either password or random-password flag") return errors.New("must set either password or random-password flag")

View File

@ -58,6 +58,7 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error {
return err return err
} }
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword) fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
return nil return nil
} }

View File

@ -91,6 +91,7 @@ func runGenerateSecretKey(_ context.Context, c *cli.Command) error {
return err return err
} }
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("%s", secretKey) fmt.Printf("%s", secretKey)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {

View File

@ -186,7 +186,7 @@ Gitea or set your environment appropriately.`, "")
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64) deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64) actionPerm, _ := strconv.Atoi(os.Getenv(repo_module.EnvActionPerm))
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
UserID: userID, UserID: userID,
@ -196,7 +196,7 @@ Gitea or set your environment appropriately.`, "")
GitPushOptions: pushOptions(), GitPushOptions: pushOptions(),
PullRequestID: prID, PullRequestID: prID,
DeployKeyID: deployKeyID, DeployKeyID: deployKeyID,
ActionPerm: int(actionPerm), ActionPerm: actionPerm,
} }
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)

View File

@ -605,7 +605,7 @@ func (repo *Repository) IsGenerated() bool {
// RepoPath returns repository path by given user and repository name. // RepoPath returns repository path by given user and repository name.
func RepoPath(userName, repoName string) string { //revive:disable-line:exported func RepoPath(userName, repoName string) string { //revive:disable-line:exported
return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git") return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)), filepath.Clean(strings.ToLower(repoName)+".git"))
} }
// RepoPath returns the repository path // RepoPath returns the repository path

View File

@ -980,7 +980,7 @@ func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, er
// UserPath returns the path absolute path of user repositories. // UserPath returns the path absolute path of user repositories.
func UserPath(userName string) string { //revive:disable-line:exported func UserPath(userName string) string { //revive:disable-line:exported
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName)) return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)))
} }
// GetUserByID returns the user object by given ID if exists. // GetUserByID returns the user object by given ID if exists.

View File

@ -61,17 +61,11 @@ func NewArgon2Hasher(config string) *Argon2Hasher {
return nil return nil
} }
parsed, err := parseUIntParam(vals[0], "time", "argon2", config, nil) var err error
hasher.time = uint32(parsed) hasher.time, err = parseUintParam[uint32](vals[0], "time", "argon2", config, nil)
hasher.memory, err = parseUintParam[uint32](vals[1], "memory", "argon2", config, err)
parsed, err = parseUIntParam(vals[1], "memory", "argon2", config, err) hasher.threads, err = parseUintParam[uint8](vals[2], "threads", "argon2", config, err)
hasher.memory = uint32(parsed) hasher.keyLen, err = parseUintParam[uint32](vals[3], "keyLen", "argon2", config, err)
parsed, err = parseUIntParam(vals[2], "threads", "argon2", config, err)
hasher.threads = uint8(parsed)
parsed, err = parseUIntParam(vals[3], "keyLen", "argon2", config, err)
hasher.keyLen = uint32(parsed)
if err != nil { if err != nil {
return nil return nil
} }

View File

@ -7,6 +7,7 @@ import (
"strconv" "strconv"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
) )
func parseIntParam(value, param, algorithmName, config string, previousErr error) (int, error) { func parseIntParam(value, param, algorithmName, config string, previousErr error) (int, error) {
@ -18,11 +19,12 @@ func parseIntParam(value, param, algorithmName, config string, previousErr error
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
} }
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) { //nolint:unparam // algorithmName is always argon2 func parseUintParam[T uint32 | uint8](value, param, algorithmName, config string, previousErr error) (ret T, _ error) {
parsed, err := strconv.ParseUint(value, 10, 64) _, isUint32 := any(ret).(uint32)
parsed, err := strconv.ParseUint(value, 10, util.Iif(isUint32, 32, 8))
if err != nil { if err != nil {
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config) log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
return 0, err return 0, err
} }
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed return T(parsed), previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
} }

View File

@ -72,7 +72,7 @@ func newRequest(ctx context.Context, method, url string, body io.ReadCloser) (*h
// Adding padding will make requests more secure, however is also slower // Adding padding will make requests more secure, however is also slower
// because artificial responses will be added to the response // because artificial responses will be added to the response
// For more information, see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/ // For more information, see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
func (c *Client) CheckPassword(pw string, padding bool) (int, error) { func (c *Client) CheckPassword(pw string, padding bool) (int64, error) {
if pw == "" { if pw == "" {
return -1, ErrEmptyPassword return -1, ErrEmptyPassword
} }
@ -111,7 +111,7 @@ func (c *Client) CheckPassword(pw string, padding bool) (int, error) {
if err != nil { if err != nil {
return -1, err return -1, err
} }
return int(count), nil return count, nil
} }
} }
return 0, nil return 0, nil

View File

@ -37,25 +37,25 @@ func TestPassword(t *testing.T) {
count, err := client.CheckPassword("", false) count, err := client.CheckPassword("", false)
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword") assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
assert.Equal(t, -1, count) assert.EqualValues(t, -1, count)
count, err = client.CheckPassword("pwned", false) count, err = client.CheckPassword("pwned", false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, count) assert.EqualValues(t, 1, count)
count, err = client.CheckPassword("notpwned", false) count, err = client.CheckPassword("notpwned", false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, count) assert.EqualValues(t, 0, count)
count, err = client.CheckPassword("paddedpwned", true) count, err = client.CheckPassword("paddedpwned", true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, count) assert.EqualValues(t, 1, count)
count, err = client.CheckPassword("paddednotpwned", true) count, err = client.CheckPassword("paddednotpwned", true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, count) assert.EqualValues(t, 0, count)
count, err = client.CheckPassword("paddednotpwnedzero", true) count, err = client.CheckPassword("paddednotpwnedzero", true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, count) assert.EqualValues(t, 0, count)
} }

View File

@ -45,7 +45,7 @@ func GetHook(repoPath, name string) (*Hook, error) {
} }
h := &Hook{ h := &Hook{
name: name, name: name,
path: filepath.Join(repoPath, "hooks", name+".d", name), path: filepath.Join(repoPath, filepath.Join("hooks", name+".d", name)),
} }
isFile, err := util.IsFile(h.path) isFile, err := util.IsFile(h.path)
if err != nil { if err != nil {

View File

@ -18,6 +18,7 @@ func GetLevel() Level {
} }
func Log(skip int, level Level, format string, v ...any) { func Log(skip int, level Level, format string, v ...any) {
// codeql[disable-next-line=go/clear-text-logging]
GetLogger(DEFAULT).Log(skip+1, &Event{Level: level}, format, v...) GetLogger(DEFAULT).Log(skip+1, &Event{Level: level}, format, v...)
} }

View File

@ -20,6 +20,7 @@ func BaseLoggerToGeneralLogger(b BaseLogger) Logger {
var _ Logger = (*baseToLogger)(nil) var _ Logger = (*baseToLogger)(nil)
func (s *baseToLogger) Log(skip int, event *Event, format string, v ...any) { func (s *baseToLogger) Log(skip int, event *Event, format string, v ...any) {
// codeql[disable-next-line=go/clear-text-logging]
s.base.Log(skip+1, event, format, v...) s.base.Log(skip+1, event, format, v...)
} }

View File

@ -65,7 +65,7 @@ func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
decodedBytes := make([]byte, len(toDecode)/2) decodedBytes := make([]byte, len(toDecode)/2)
for i := 0; i < len(toDecode)/2; i++ { for i := 0; i < len(toDecode)/2; i++ {
// Can ignore error here as we know these should be hexadecimal from the regexp // Can ignore error here as we know these should be hexadecimal from the regexp
byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 0) byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 8)
decodedBytes[i] = byte(byteInt) decodedBytes[i] = byte(byteInt)
} }
if inKey { if inKey {

View File

@ -19,7 +19,7 @@ type TempDir struct {
} }
func (td *TempDir) JoinPath(elems ...string) string { func (td *TempDir) JoinPath(elems ...string) string {
return filepath.Join(append([]string{td.base, td.sub}, elems...)...) return filepath.Join(append([]string{td.base, td.sub}, filepath.Join(elems...))...)
} }
// MkdirAllSub works like os.MkdirAll, but the base directory must exist // MkdirAllSub works like os.MkdirAll, but the base directory must exist

View File

@ -62,6 +62,9 @@ sub = Changed Sub String
found := lang1.HasKey("no-such") found := lang1.HasKey("no-such")
assert.False(t, found) assert.False(t, found)
assert.NoError(t, ls.Close()) assert.NoError(t, ls.Close())
res := lang1.TrHTML("<no-such>")
assert.Equal(t, "&lt;no-such&gt;", string(res))
} }
func TestLocaleStoreMoreSource(t *testing.T) { func TestLocaleStoreMoreSource(t *testing.T) {

View File

@ -6,6 +6,7 @@ package i18n
import ( import (
"errors" "errors"
"fmt" "fmt"
"html"
"html/template" "html/template"
"slices" "slices"
@ -109,8 +110,7 @@ func (store *localeStore) Close() error {
} }
func (l *locale) TrString(trKey string, trArgs ...any) string { func (l *locale) TrString(trKey string, trArgs ...any) string {
format := trKey var format string
idx, ok := l.store.trKeyToIdxMap[trKey] idx, ok := l.store.trKeyToIdxMap[trKey]
if ok { if ok {
if msg, ok := l.idxToMsgMap[idx]; ok { if msg, ok := l.idxToMsgMap[idx]; ok {
@ -122,7 +122,9 @@ func (l *locale) TrString(trKey string, trArgs ...any) string {
} }
} }
} }
if format == "" {
format = html.EscapeString(trKey)
}
msg, err := Format(format, trArgs...) msg, err := Format(format, trArgs...)
if err != nil { if err != nil {
log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err) log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err)

View File

@ -26,13 +26,14 @@ func HexToRBGColor(colorString string) (float64, float64, float64) {
if len(hexString) == 8 { if len(hexString) == 8 {
hexString = hexString[0:6] hexString = hexString[0:6]
} }
color, err := strconv.ParseUint(hexString, 16, 64) color, err := strconv.ParseUint(hexString, 16, 32)
color32 := uint32(color)
if err != nil { if err != nil {
return 0, 0, 0 return 0, 0, 0
} }
r := float64(uint8(0xFF & (uint32(color) >> 16))) r := float64(uint8(0xFF & (color32 >> 16)))
g := float64(uint8(0xFF & (uint32(color) >> 8))) g := float64(uint8(0xFF & (color32 >> 8)))
b := float64(uint8(0xFF & uint32(color))) b := float64(uint8(0xFF & color32))
return r, g, b return r, g, b
} }

View File

@ -169,7 +169,7 @@ func MoveIssuePin(ctx *context.APIContext) {
return return
} }
err = issues_model.MovePin(ctx, issue, int(ctx.PathParamInt64("position"))) err = issues_model.MovePin(ctx, issue, ctx.PathParamInt("position"))
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return

View File

@ -35,7 +35,7 @@ type RepoSearchOptions struct {
// This function is also used to render the Admin Repository Management page. // This function is also used to render the Admin Repository Management page.
func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
// Sitemap index for sitemap paths // Sitemap index for sitemap paths
page := int(ctx.PathParamInt64("idx")) page := ctx.PathParamInt("idx")
isSitemap := ctx.PathParam("idx") != "" isSitemap := ctx.PathParam("idx") != ""
if page <= 1 { if page <= 1 {
page = ctx.FormInt("page") page = ctx.FormInt("page")

View File

@ -34,7 +34,7 @@ func isKeywordValid(keyword string) bool {
// RenderUserSearch render user search page // RenderUserSearch render user search page
func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, tplName templates.TplName) { func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, tplName templates.TplName) {
// Sitemap index for sitemap paths // Sitemap index for sitemap paths
opts.Page = int(ctx.PathParamInt64("idx")) opts.Page = ctx.PathParamInt("idx")
isSitemap := ctx.PathParam("idx") != "" isSitemap := ctx.PathParam("idx") != ""
if opts.Page <= 1 { if opts.Page <= 1 {
opts.Page = ctx.FormInt("page") opts.Page = ctx.FormInt("page")

View File

@ -25,33 +25,28 @@ func Activity(ctx *context.Context) {
ctx.Data["PageIsPulse"] = true ctx.Data["PageIsPulse"] = true
ctx.Data["Period"] = ctx.PathParam("period")
timeUntil := time.Now() timeUntil := time.Now()
var timeFrom time.Time period, timeFrom := "weekly", timeUntil.Add(-time.Hour*168)
switch ctx.PathParam("period") {
switch ctx.Data["Period"] {
case "daily": case "daily":
timeFrom = timeUntil.Add(-time.Hour * 24) period, timeFrom = "daily", timeUntil.Add(-time.Hour*24)
case "halfweekly": case "halfweekly":
timeFrom = timeUntil.Add(-time.Hour * 72) period, timeFrom = "halfweekly", timeUntil.Add(-time.Hour*72)
case "weekly": case "weekly":
timeFrom = timeUntil.Add(-time.Hour * 168) period, timeFrom = "weekly", timeUntil.Add(-time.Hour*168)
case "monthly": case "monthly":
timeFrom = timeUntil.AddDate(0, -1, 0) period, timeFrom = "monthly", timeUntil.AddDate(0, -1, 0)
case "quarterly": case "quarterly":
timeFrom = timeUntil.AddDate(0, -3, 0) period, timeFrom = "quarterly", timeUntil.AddDate(0, -3, 0)
case "semiyearly": case "semiyearly":
timeFrom = timeUntil.AddDate(0, -6, 0) period, timeFrom = "semiyearly", timeUntil.AddDate(0, -6, 0)
case "yearly": case "yearly":
timeFrom = timeUntil.AddDate(-1, 0, 0) period, timeFrom = "yearly", timeUntil.AddDate(-1, 0, 0)
default:
ctx.Data["Period"] = "weekly"
timeFrom = timeUntil.Add(-time.Hour * 168)
} }
ctx.Data["DateFrom"] = timeFrom ctx.Data["DateFrom"] = timeFrom
ctx.Data["DateUntil"] = timeUntil ctx.Data["DateUntil"] = timeUntil
ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string)) ctx.Data["Period"] = period
ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + period)
canReadCode := ctx.Repo.CanRead(unit.TypeCode) canReadCode := ctx.Repo.CanRead(unit.TypeCode)
if canReadCode { if canReadCode {

View File

@ -376,7 +376,7 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string
ctx.Resp.WriteHeader(http.StatusBadRequest) ctx.Resp.WriteHeader(http.StatusBadRequest)
return return
} }
reqFile := filepath.Join(h.getRepoDir(), file) reqFile := filepath.Join(h.getRepoDir(), filepath.Clean(file))
fi, err := os.Stat(reqFile) fi, err := os.Stat(reqFile)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -395,13 +395,12 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string
var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`) var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`)
func prepareGitCmdWithAllowedService(service string) (*gitcmd.Command, error) { func prepareGitCmdWithAllowedService(service string) (*gitcmd.Command, error) {
if service == "receive-pack" { if service == ServiceTypeReceivePack {
return gitcmd.NewCommand("receive-pack"), nil return gitcmd.NewCommand(ServiceTypeReceivePack), nil
} }
if service == "upload-pack" { if service == ServiceTypeUploadPack {
return gitcmd.NewCommand("upload-pack"), nil return gitcmd.NewCommand(ServiceTypeUploadPack), nil
} }
return nil, fmt.Errorf("service %q is not allowed", service) return nil, fmt.Errorf("service %q is not allowed", service)
} }
@ -464,11 +463,16 @@ func serviceRPC(ctx *context.Context, h *serviceHandler, service string) {
} }
} }
const (
ServiceTypeUploadPack = "upload-pack"
ServiceTypeReceivePack = "receive-pack"
)
// ServiceUploadPack implements Git Smart HTTP protocol // ServiceUploadPack implements Git Smart HTTP protocol
func ServiceUploadPack(ctx *context.Context) { func ServiceUploadPack(ctx *context.Context) {
h := httpBase(ctx) h := httpBase(ctx)
if h != nil { if h != nil {
serviceRPC(ctx, h, "upload-pack") serviceRPC(ctx, h, ServiceTypeUploadPack)
} }
} }
@ -476,16 +480,18 @@ func ServiceUploadPack(ctx *context.Context) {
func ServiceReceivePack(ctx *context.Context) { func ServiceReceivePack(ctx *context.Context) {
h := httpBase(ctx) h := httpBase(ctx)
if h != nil { if h != nil {
serviceRPC(ctx, h, "receive-pack") serviceRPC(ctx, h, ServiceTypeReceivePack)
} }
} }
func getServiceType(ctx *context.Context) string { func getServiceType(ctx *context.Context) string {
serviceType := ctx.Req.FormValue("service") switch ctx.Req.FormValue("service") {
if !strings.HasPrefix(serviceType, "git-") { case "git-" + ServiceTypeUploadPack:
return "" return ServiceTypeUploadPack
case "git-" + ServiceTypeReceivePack:
return ServiceTypeReceivePack
} }
return strings.TrimPrefix(serviceType, "git-") return ""
} }
func updateServerInfo(ctx gocontext.Context, dir string) []byte { func updateServerInfo(ctx gocontext.Context, dir string) []byte {

View File

@ -279,7 +279,7 @@ func handleRepoViewSubmodule(ctx *context.Context, commitSubmoduleFile *git.Comm
ctx.Data["NotFoundPrompt"] = redirectLink ctx.Data["NotFoundPrompt"] = redirectLink
ctx.NotFound(nil) ctx.NotFound(nil)
} else { } else {
ctx.Redirect(submoduleWebLink.CommitWebLink) ctx.RedirectToCurrentSite(redirectLink)
} }
} }

View File

@ -31,7 +31,7 @@ func AvatarByUsernameSize(ctx *context.Context) {
return return
} }
} }
cacheableRedirect(ctx, user.AvatarLinkWithSize(ctx, int(ctx.PathParamInt64("size")))) cacheableRedirect(ctx, user.AvatarLinkWithSize(ctx, ctx.PathParamInt("size")))
} }
// AvatarByEmailHash redirects the browser to the email avatar link // AvatarByEmailHash redirects the browser to the email avatar link

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"text/template" "text/template"
"time" "time"
"unicode"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -37,6 +38,16 @@ const keyOfRequestIDInTemplate = ".RequestID"
// So, we accept a Request ID with a maximum character length of 40 // So, we accept a Request ID with a maximum character length of 40
const maxRequestIDByteLength = 40 const maxRequestIDByteLength = 40
func isSafeRequestID(id string) bool {
for _, r := range id {
safe := unicode.IsPrint(r)
if !safe {
return false
}
}
return true
}
func parseRequestIDFromRequestHeader(req *http.Request) string { func parseRequestIDFromRequestHeader(req *http.Request) string {
requestID := "-" requestID := "-"
for _, key := range setting.Log.RequestIDHeaders { for _, key := range setting.Log.RequestIDHeaders {
@ -45,6 +56,9 @@ func parseRequestIDFromRequestHeader(req *http.Request) string {
break break
} }
} }
if !isSafeRequestID(requestID) {
return "-"
}
if len(requestID) > maxRequestIDByteLength { if len(requestID) > maxRequestIDByteLength {
requestID = requestID[:maxRequestIDByteLength] + "..." requestID = requestID[:maxRequestIDByteLength] + "..."
} }

View File

@ -69,3 +69,8 @@ func TestAccessLogger(t *testing.T) {
recorder.record(time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), &testAccessLoggerResponseWriterMock{}, req) recorder.record(time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), &testAccessLoggerResponseWriterMock{}, req)
assert.Equal(t, []string{`remote-addr - - [02/Jan/2000:03:04:05 +0000] "GET /path https" 200 123123 "referer" "user-agent"`}, mockLogger.logs) assert.Equal(t, []string{`remote-addr - - [02/Jan/2000:03:04:05 +0000] "GET /path https" 200 123123 "referer" "user-agent"`}, mockLogger.logs)
} }
func TestAccessLoggerRequestID(t *testing.T) {
assert.False(t, isSafeRequestID("\x00"))
assert.True(t, isSafeRequestID("a b-c"))
}

View File

@ -37,6 +37,11 @@ func (b *Base) PathParamInt64(p string) int64 {
return v return v
} }
func (b *Base) PathParamInt(p string) int {
v, _ := strconv.Atoi(b.PathParam(p))
return v
}
// SetPathParam set request path params into routes // SetPathParam set request path params into routes
func (b *Base) SetPathParam(name, value string) { func (b *Base) SetPathParam(name, value string) {
if strings.HasPrefix(name, ":") { if strings.HasPrefix(name, ":") {

View File

@ -1144,8 +1144,8 @@ $.api.settings = {
}, },
regExp : { regExp : {
required : /\{\$*[A-z0-9]+\}/g, required : /\{\$*[_A-Za-z0-9]+\}/g, // GITEA-PATCH: use "_A-Za-z" instead of "A-z" for variable name matching
optional : /\{\/\$*[A-z0-9]+\}/g, optional : /\{\/\$*[_A-Za-z0-9]+\}/g, // GITEA-PATCH: use "_A-Za-z" instead of "A-z" for variable name matching
}, },
className: { className: {

View File

@ -66,7 +66,7 @@ $.fn.dropdown = function(parameters) {
moduleNamespace = 'module-' + namespace, moduleNamespace = 'module-' + namespace,
$module = $(this), $module = $(this),
$context = $(settings.context), $context = (typeof settings.context === 'string') ? $(document).find(settings.context) : $(settings.context), // GITEA-PATCH: use "jQuery.find(selector)" instead of "jQuery(selector)"
$text = $module.find(selector.text), $text = $module.find(selector.text),
$search = $module.find(selector.search), $search = $module.find(selector.search),
$sizer = $module.find(selector.sizer), $sizer = $module.find(selector.sizer),

View File

@ -64,7 +64,7 @@ $.fn.modal = function(parameters) {
moduleNamespace = 'module-' + namespace, moduleNamespace = 'module-' + namespace,
$module = $(this), $module = $(this),
$context = $(settings.context), $context = (typeof settings.context === 'string') ? $(document).find(settings.context) : $(settings.context), // GITEA-PATCH: use "jQuery.find(selector)" instead of "jQuery(selector)"
$close = $module.find(selector.close), $close = $module.find(selector.close),
$allModals, $allModals,

View File

@ -26,13 +26,13 @@ test('textareaSplitLines', () => {
test('markdownHandleIndention', () => { test('markdownHandleIndention', () => {
const testInput = (input: string, expected?: string) => { const testInput = (input: string, expected?: string) => {
const inputPos = input.indexOf('|'); const inputPos = input.indexOf('|');
input = input.replace('|', ''); input = input.replaceAll('|', '');
const ret = markdownHandleIndention({value: input, selStart: inputPos, selEnd: inputPos}); const ret = markdownHandleIndention({value: input, selStart: inputPos, selEnd: inputPos});
if (expected === null) { if (expected === null) {
expect(ret).toEqual({handled: false}); expect(ret).toEqual({handled: false});
} else { } else {
const expectedPos = expected.indexOf('|'); const expectedPos = expected.indexOf('|');
expected = expected.replace('|', ''); expected = expected.replaceAll('|', '');
expect(ret).toEqual({ expect(ret).toEqual({
handled: true, handled: true,
valueSelection: {value: expected, selStart: expectedPos, selEnd: expectedPos}, valueSelection: {value: expected, selStart: expectedPos, selEnd: expectedPos},

View File

@ -333,7 +333,7 @@ export function initRepoPullRequestReview() {
let ntr = tr.nextElementSibling; let ntr = tr.nextElementSibling;
if (!ntr?.classList.contains('add-comment')) { if (!ntr?.classList.contains('add-comment')) {
ntr = createElementFromHTML(` ntr = createElementFromHTML(`
<tr class="add-comment" data-line-type="${lineType}"> <tr class="add-comment" data-line-type="${htmlEscape(lineType)}">
${isSplit ? ` ${isSplit ? `
<td class="add-comment-left" colspan="4"></td> <td class="add-comment-left" colspan="4"></td>
<td class="add-comment-right" colspan="4"></td> <td class="add-comment-right" colspan="4"></td>

View File

@ -14,4 +14,7 @@ export function linkLabelAndInput(label: Element, input: Element) {
} }
} }
export const fomanticQuery = $; export function fomanticQuery(s: string | Element | NodeListOf<Element>): ReturnType<typeof $> {
// intentionally make it only work for query selector, it isn't used for creating HTML elements (for safety)
return typeof s === 'string' ? $(document).find(s) : $(s);
}

View File

@ -35,7 +35,12 @@ export function isDarkTheme(): boolean {
/** strip <tags> from a string */ /** strip <tags> from a string */
export function stripTags(text: string): string { export function stripTags(text: string): string {
return text.replace(/<[^>]*>?/g, ''); let prev = '';
while (prev !== text) {
prev = text;
text = text.replace(/<[^>]*>?/g, '');
}
return text;
} }
export function parseIssueHref(href: string): IssuePathInfo { export function parseIssueHref(href: string): IssuePathInfo {