mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-09 17:37:20 -06:00
fix: truncate strings on character boundaries (#40267)
Byte slicing like `t.Description[:255]` can split a multi-byte character and store invalid UTF-8. Team descriptions were also cut to 255 bytes although their `MaxSize(255)` validation allows 255 characters. Team descriptions now truncate by runes. Signup user agents, Actions log lines and access log request IDs use `util.TruncateStringBytes`, which now returns short strings untouched. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -837,10 +837,7 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o
|
||||
}
|
||||
|
||||
// trim user agent string to a reasonable length, if necessary
|
||||
userAgent := strings.TrimSpace(meta.InitialUserAgent)
|
||||
if len(userAgent) > 255 {
|
||||
userAgent = userAgent[:255]
|
||||
}
|
||||
userAgent := util.TruncateRunes(strings.TrimSpace(meta.InitialUserAgent), 255)
|
||||
if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"gitea.dev/models/dbfs"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/storage"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/zstd"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -213,10 +214,7 @@ func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeek
|
||||
func FormatLog(timestamp time.Time, content string) string {
|
||||
// Content shouldn't contain new line, it will break log indexes, other control chars are safe.
|
||||
content = strings.ReplaceAll(content, "\n", `\n`)
|
||||
if len(content) > MaxLineSize {
|
||||
content = content[:MaxLineSize]
|
||||
}
|
||||
return fmt.Sprintf("%s %s", timestamp.UTC().Format(timeFormat), content)
|
||||
return fmt.Sprintf("%s %s", timestamp.UTC().Format(timeFormat), util.TruncateStringBytes(content, MaxLineSize))
|
||||
}
|
||||
|
||||
func ParseLog(in string) (time.Time, string, error) {
|
||||
|
||||
@@ -125,22 +125,29 @@ func EllipsisTruncateRunes(str string, limit int) (left, right string) {
|
||||
// TruncateRunes returns a truncated string with given rune limit,
|
||||
// it returns input string if its rune length doesn't exceed the limit.
|
||||
func TruncateRunes(str string, limit int) string {
|
||||
if utf8.RuneCountInString(str) < limit {
|
||||
if len(str) <= limit {
|
||||
return str
|
||||
}
|
||||
return string([]rune(str)[:limit])
|
||||
for i := range str {
|
||||
limit--
|
||||
if limit < 0 {
|
||||
return str[:i]
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// TruncateStringBytes returns a truncated string with given byte limit,
|
||||
// it returns input string if its byte length doesn't exceed the limit.
|
||||
func TruncateStringBytes(str string, limit int) string {
|
||||
l := 0
|
||||
if len(str) <= limit {
|
||||
return str
|
||||
}
|
||||
for i, r := range str {
|
||||
rl := utf8.RuneLen(r)
|
||||
if l+rl > limit {
|
||||
limit -= utf8.RuneLen(r)
|
||||
if limit < 0 {
|
||||
return str[:i]
|
||||
}
|
||||
l += rl
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -129,3 +129,12 @@ func TestTruncateRunes(t *testing.T) {
|
||||
assert.Equal(t, "测试", TruncateRunes("测试", 2))
|
||||
assert.Equal(t, "测试", TruncateRunes("测试", 3))
|
||||
}
|
||||
|
||||
func TestTruncateStringBytes(t *testing.T) {
|
||||
assert.Equal(t, "a", TruncateStringBytes("ab", 1))
|
||||
assert.Equal(t, "ab", TruncateStringBytes("ab", 2))
|
||||
assert.Empty(t, TruncateStringBytes("测试", 2))
|
||||
assert.Equal(t, "测", TruncateStringBytes("测试", 5))
|
||||
assert.Equal(t, "测试", TruncateStringBytes("测试", 6))
|
||||
assert.Equal(t, "\xff\xff", TruncateStringBytes("\xff\xff", 2))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"gitea.dev/modules/httplib"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web/middleware"
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ func parseRequestIDFromRequestHeader(req *http.Request) string {
|
||||
return "-"
|
||||
}
|
||||
if len(requestID) > maxRequestIDByteLength {
|
||||
requestID = requestID[:maxRequestIDByteLength] + "..."
|
||||
requestID = util.TruncateStringBytes(requestID, maxRequestIDByteLength) + "..."
|
||||
}
|
||||
return requestID
|
||||
}
|
||||
|
||||
@@ -110,9 +110,7 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA
|
||||
return util.NewInvalidArgumentErrorf("empty team name")
|
||||
}
|
||||
|
||||
if len(t.Description) > 255 {
|
||||
t.Description = t.Description[:255]
|
||||
}
|
||||
t.Description = util.TruncateRunes(t.Description, 255)
|
||||
|
||||
if err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
t.LowerName = strings.ToLower(t.Name)
|
||||
|
||||
Reference in New Issue
Block a user