Skip to content

Commit 58f5ab3

Browse files
wxiaoguangStelios Malathouras
authored and
Stelios Malathouras
committed
Fix a panic in NotifyCreateIssueComment (caused by string truncation) (go-gitea#17928)
* Fix a panic in NotifyCreateIssueComment (caused by string truncation) * more unit tests * refactor * fix some edge cases * use SplitStringAtByteN for comment content
1 parent a972cc9 commit 58f5ab3

File tree

4 files changed

+104
-17
lines changed

4 files changed

+104
-17
lines changed

models/repo_archiver.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package models
66

77
import (
88
"code.gitea.io/gitea/models/db"
9-
109
repo_model "code.gitea.io/gitea/models/repo"
1110
)
1211

modules/notification/action/action.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"code.gitea.io/gitea/modules/log"
1616
"code.gitea.io/gitea/modules/notification/base"
1717
"code.gitea.io/gitea/modules/repository"
18+
"code.gitea.io/gitea/modules/util"
1819
)
1920

2021
type actionNotifier struct {
@@ -100,14 +101,15 @@ func (a *actionNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *m
100101
IsPrivate: issue.Repo.IsPrivate,
101102
}
102103

103-
content := ""
104-
105-
if len(comment.Content) > 200 {
106-
content = comment.Content[:strings.LastIndex(comment.Content[0:200], " ")] + "…"
107-
} else {
108-
content = comment.Content
104+
truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200)
105+
if truncatedRight != "" {
106+
// in case the content is in a Latin family language, we remove the last broken word.
107+
lastSpaceIdx := strings.LastIndex(truncatedContent, " ")
108+
if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) {
109+
truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
110+
}
109111
}
110-
act.Content = fmt.Sprintf("%d|%s", issue.Index, content)
112+
act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
111113

112114
if issue.IsPull {
113115
act.OpType = models.ActionCommentPull

modules/util/truncate.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@ package util
66

77
import "unicode/utf8"
88

9+
// in UTF8 "…" is 3 bytes so doesn't really gain us anything...
10+
const utf8Ellipsis = "…"
11+
const asciiEllipsis = "..."
12+
913
// SplitStringAtByteN splits a string at byte n accounting for rune boundaries. (Combining characters are not accounted for.)
1014
func SplitStringAtByteN(input string, n int) (left, right string) {
1115
if len(input) <= n {
12-
left = input
13-
return
16+
return input, ""
1417
}
1518

1619
if !utf8.ValidString(input) {
17-
left = input[:n-3] + "..."
18-
right = "..." + input[n-3:]
19-
return
20+
if n-3 < 0 {
21+
return input, ""
22+
}
23+
return input[:n-3] + asciiEllipsis, asciiEllipsis + input[n-3:]
2024
}
2125

22-
// in UTF8 "…" is 3 bytes so doesn't really gain us anything...
2326
end := 0
2427
for end <= n-3 {
2528
_, size := utf8.DecodeRuneInString(input[end:])
@@ -29,7 +32,29 @@ func SplitStringAtByteN(input string, n int) (left, right string) {
2932
end += size
3033
}
3134

32-
left = input[:end] + "…"
33-
right = "…" + input[end:]
34-
return
35+
return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
36+
}
37+
38+
// SplitStringAtRuneN splits a string at rune n accounting for rune boundaries. (Combining characters are not accounted for.)
39+
func SplitStringAtRuneN(input string, n int) (left, right string) {
40+
if !utf8.ValidString(input) {
41+
if len(input) <= n || n-3 < 0 {
42+
return input, ""
43+
}
44+
return input[:n-3] + asciiEllipsis, asciiEllipsis + input[n-3:]
45+
}
46+
47+
if utf8.RuneCountInString(input) <= n {
48+
return input, ""
49+
}
50+
51+
count := 0
52+
end := 0
53+
for count < n-1 {
54+
_, size := utf8.DecodeRuneInString(input[end:])
55+
end += size
56+
count++
57+
}
58+
59+
return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
3560
}

modules/util/truncate_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package util
6+
7+
import (
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestSplitString(t *testing.T) {
14+
type testCase struct {
15+
input string
16+
n int
17+
leftSub string
18+
ellipsis string
19+
}
20+
21+
test := func(tc []*testCase, f func(input string, n int) (left, right string)) {
22+
for _, c := range tc {
23+
l, r := f(c.input, c.n)
24+
if c.ellipsis != "" {
25+
assert.Equal(t, c.leftSub+c.ellipsis, l, "test split %q at %d, expected leftSub: %q", c.input, c.n, c.leftSub)
26+
assert.Equal(t, c.ellipsis+c.input[len(c.leftSub):], r, "test split %s at %d, expected rightSub: %q", c.input, c.n, c.input[len(c.leftSub):])
27+
} else {
28+
assert.Equal(t, c.leftSub, l, "test split %q at %d, expected leftSub: %q", c.input, c.n, c.leftSub)
29+
assert.Equal(t, "", r, "test split %q at %d, expected rightSub: %q", c.input, c.n, "")
30+
}
31+
}
32+
}
33+
34+
tc := []*testCase{
35+
{"abc123xyz", 0, "", utf8Ellipsis},
36+
{"abc123xyz", 1, "", utf8Ellipsis},
37+
{"abc123xyz", 4, "a", utf8Ellipsis},
38+
{"啊bc123xyz", 4, "", utf8Ellipsis},
39+
{"啊bc123xyz", 6, "啊", utf8Ellipsis},
40+
{"啊bc", 5, "啊bc", ""},
41+
{"啊bc", 6, "啊bc", ""},
42+
{"abc\xef\x03\xfe", 3, "", asciiEllipsis},
43+
{"abc\xef\x03\xfe", 4, "a", asciiEllipsis},
44+
{"\xef\x03", 1, "\xef\x03", ""},
45+
}
46+
test(tc, SplitStringAtByteN)
47+
48+
tc = []*testCase{
49+
{"abc123xyz", 0, "", utf8Ellipsis},
50+
{"abc123xyz", 1, "", utf8Ellipsis},
51+
{"abc123xyz", 4, "abc", utf8Ellipsis},
52+
{"啊bc123xyz", 4, "啊bc", utf8Ellipsis},
53+
{"啊bc123xyz", 6, "啊bc12", utf8Ellipsis},
54+
{"啊bc", 3, "啊bc", ""},
55+
{"啊bc", 4, "啊bc", ""},
56+
{"abc\xef\x03\xfe", 3, "", asciiEllipsis},
57+
{"abc\xef\x03\xfe", 4, "a", asciiEllipsis},
58+
{"\xef\x03", 1, "\xef\x03", ""},
59+
}
60+
test(tc, SplitStringAtRuneN)
61+
}

0 commit comments

Comments
 (0)