Skip to content

Commit bb88607

Browse files
committed
fix
1 parent 7304785 commit bb88607

File tree

15 files changed

+69
-123
lines changed

15 files changed

+69
-123
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,9 @@ LEVEL = Info
12121212
;; Max size of files to be displayed (default is 8MiB)
12131213
;MAX_DISPLAY_FILE_SIZE = 8388608
12141214
;;
1215+
;; Detect ambiguous unicode characters in file contents and show warnings on the UI
1216+
;AMBIGUOUS_UNICODE_DETECTION = true
1217+
;;
12151218
;; Whether the email of the user should be shown in the Explore Users page
12161219
;SHOW_USER_EMAIL = true
12171220
;;

docs/content/administration/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
220220
- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: All available themes. Allow users select personalized themes.
221221
regardless of the value of `DEFAULT_THEME`.
222222
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
223+
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
223224
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
224225
Values can be emoji alias (:smile:) or a unicode emoji.
225226
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png

modules/charset/escape.go

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,31 @@
88
package charset
99

1010
import (
11-
"bufio"
11+
"html/template"
1212
"io"
1313
"strings"
1414

1515
"code.gitea.io/gitea/modules/log"
16+
"code.gitea.io/gitea/modules/setting"
1617
"code.gitea.io/gitea/modules/translation"
1718
)
1819

1920
// RuneNBSP is the codepoint for NBSP
2021
const RuneNBSP = 0xa0
2122

2223
// EscapeControlHTML escapes the unicode control sequences in a provided html document
23-
func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
24+
func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) {
2425
sb := &strings.Builder{}
25-
outputStream := &HTMLStreamerWriter{Writer: sb}
26-
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
27-
28-
if err := StreamHTML(strings.NewReader(text), streamer); err != nil {
29-
streamer.escaped.HasError = true
30-
log.Error("Error whilst escaping: %v", err)
31-
}
32-
return streamer.escaped, sb.String()
26+
escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader
27+
return escaped, template.HTML(sb.String())
3328
}
3429

35-
// EscapeControlReaders escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
30+
// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus
3631
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
32+
if !setting.UI.AmbiguousUnicodeDetection {
33+
_, err = io.Copy(writer, reader)
34+
return &EscapeStatus{}, err
35+
}
3736
outputStream := &HTMLStreamerWriter{Writer: writer}
3837
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
3938

@@ -43,41 +42,3 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
4342
}
4443
return streamer.escaped, err
4544
}
46-
47-
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method.
48-
func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
49-
bufRd := bufio.NewReader(reader)
50-
outputStream := &HTMLStreamerWriter{Writer: writer}
51-
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
52-
53-
for {
54-
line, rdErr := bufRd.ReadString('\n')
55-
if len(line) > 0 {
56-
if err := streamer.Text(line); err != nil {
57-
streamer.escaped.HasError = true
58-
log.Error("Error whilst escaping: %v", err)
59-
return streamer.escaped, err
60-
}
61-
}
62-
if rdErr != nil {
63-
if rdErr != io.EOF {
64-
err = rdErr
65-
}
66-
break
67-
}
68-
}
69-
return streamer.escaped, err
70-
}
71-
72-
// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string
73-
func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
74-
sb := &strings.Builder{}
75-
outputStream := &HTMLStreamerWriter{Writer: sb}
76-
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
77-
78-
if err := streamer.Text(text); err != nil {
79-
streamer.escaped.HasError = true
80-
log.Error("Error whilst escaping: %v", err)
81-
}
82-
return streamer.escaped, sb.String()
83-
}

modules/charset/escape_stream.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (e *escapeStreamer) Text(data string) error {
6464
until, next = nextIdxs[0]+pos, nextIdxs[1]+pos
6565
}
6666

67-
// from pos until until we know that the runes are not \r\t\n or even ' '
67+
// from pos until we know that the runes are not \r\t\n or even ' '
6868
runes := make([]rune, 0, next-until)
6969
positions := make([]int, 0, next-until+1)
7070

modules/charset/escape_test.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,6 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
132132
},
133133
}
134134

135-
func TestEscapeControlString(t *testing.T) {
136-
for _, tt := range escapeControlTests {
137-
t.Run(tt.name, func(t *testing.T) {
138-
status, result := EscapeControlString(tt.text, &translation.MockLocale{})
139-
if !reflect.DeepEqual(*status, tt.status) {
140-
t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
141-
}
142-
if result != tt.result {
143-
t.Errorf("EscapeControlString()\nresult= %v,\nwanted= %v", result, tt.result)
144-
}
145-
})
146-
}
147-
}
148-
149135
func TestEscapeControlReader(t *testing.T) {
150136
// lets add some control characters to the tests
151137
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
@@ -186,12 +172,3 @@ func TestEscapeControlReader(t *testing.T) {
186172
})
187173
}
188174
}
189-
190-
func TestEscapeControlReader_panic(t *testing.T) {
191-
bs := make([]byte, 0, 20479)
192-
bs = append(bs, 'A')
193-
for i := 0; i < 6826; i++ {
194-
bs = append(bs, []byte("—")...)
195-
}
196-
_, _ = EscapeControlString(string(bs), &translation.MockLocale{})
197-
}

modules/highlight/highlight.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"bytes"
1010
"fmt"
1111
gohtml "html"
12+
"html/template"
1213
"io"
1314
"path/filepath"
1415
"strings"
@@ -55,7 +56,7 @@ func NewContext() {
5556
}
5657

5758
// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name
58-
func Code(fileName, language, code string) (string, string) {
59+
func Code(fileName, language, code string) (output template.HTML, lexerName string) {
5960
NewContext()
6061

6162
// diff view newline will be passed as empty, change to literal '\n' so it can be copied
@@ -65,7 +66,7 @@ func Code(fileName, language, code string) (string, string) {
6566
}
6667

6768
if len(code) > sizeLimit {
68-
return code, ""
69+
return template.HTML(template.HTMLEscapeString(code)), ""
6970
}
7071

7172
var lexer chroma.Lexer
@@ -102,13 +103,11 @@ func Code(fileName, language, code string) (string, string) {
102103
cache.Add(fileName, lexer)
103104
}
104105

105-
lexerName := formatLexerName(lexer.Config().Name)
106-
107-
return CodeFromLexer(lexer, code), lexerName
106+
return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name)
108107
}
109108

110109
// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes
111-
func CodeFromLexer(lexer chroma.Lexer, code string) string {
110+
func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML {
112111
formatter := html.New(html.WithClasses(true),
113112
html.WithLineNumbers(false),
114113
html.PreventSurroundingPre(true),
@@ -120,23 +119,23 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string {
120119
iterator, err := lexer.Tokenise(nil, code)
121120
if err != nil {
122121
log.Error("Can't tokenize code: %v", err)
123-
return code
122+
return template.HTML(template.HTMLEscapeString(code))
124123
}
125124
// style not used for live site but need to pass something
126125
err = formatter.Format(htmlw, githubStyles, iterator)
127126
if err != nil {
128127
log.Error("Can't format code: %v", err)
129-
return code
128+
return template.HTML(template.HTMLEscapeString(code))
130129
}
131130

132131
_ = htmlw.Flush()
133132
// Chroma will add newlines for certain lexers in order to highlight them properly
134133
// Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output
135-
return strings.TrimSuffix(htmlbuf.String(), "\n")
134+
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n"))
136135
}
137136

138137
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
139-
func File(fileName, language string, code []byte) ([]string, string, error) {
138+
func File(fileName, language string, code []byte) ([]template.HTML, string, error) {
140139
NewContext()
141140

142141
if len(code) > sizeLimit {
@@ -183,24 +182,24 @@ func File(fileName, language string, code []byte) ([]string, string, error) {
183182
tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
184183
htmlBuf := &bytes.Buffer{}
185184

186-
lines := make([]string, 0, len(tokensLines))
185+
lines := make([]template.HTML, 0, len(tokensLines))
187186
for _, tokens := range tokensLines {
188187
iterator = chroma.Literator(tokens...)
189188
err = formatter.Format(htmlBuf, githubStyles, iterator)
190189
if err != nil {
191190
return nil, "", fmt.Errorf("can't format code: %w", err)
192191
}
193-
lines = append(lines, htmlBuf.String())
192+
lines = append(lines, template.HTML(htmlBuf.String()))
194193
htmlBuf.Reset()
195194
}
196195

197196
return lines, lexerName, nil
198197
}
199198

200199
// PlainText returns non-highlighted HTML for code
201-
func PlainText(code []byte) []string {
200+
func PlainText(code []byte) []template.HTML {
202201
r := bufio.NewReader(bytes.NewReader(code))
203-
m := make([]string, 0, bytes.Count(code, []byte{'\n'})+1)
202+
m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1)
204203
for {
205204
content, err := r.ReadString('\n')
206205
if err != nil && err != io.EOF {
@@ -210,7 +209,7 @@ func PlainText(code []byte) []string {
210209
if content == "" && err == io.EOF {
211210
break
212211
}
213-
s := gohtml.EscapeString(content)
212+
s := template.HTML(gohtml.EscapeString(content))
214213
m = append(m, s)
215214
}
216215
return m

modules/highlight/highlight_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,25 @@
44
package highlight
55

66
import (
7+
"html/template"
78
"strings"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
1112
)
1213

13-
func lines(s string) []string {
14-
return strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n")
14+
func lines(s string) (out []template.HTML) {
15+
for _, line := range strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n") {
16+
out = append(out, template.HTML(line))
17+
}
18+
return out
1519
}
1620

1721
func TestFile(t *testing.T) {
1822
tests := []struct {
1923
name string
2024
code string
21-
want []string
25+
want []template.HTML
2226
lexerName string
2327
}{
2428
{
@@ -99,10 +103,7 @@ c=2
99103
t.Run(tt.name, func(t *testing.T) {
100104
out, lexerName, err := File(tt.name, "", []byte(tt.code))
101105
assert.NoError(t, err)
102-
expected := strings.Join(tt.want, "\n")
103-
actual := strings.Join(out, "\n")
104-
assert.Equal(t, strings.Count(actual, "<span"), strings.Count(actual, "</span>"))
105-
assert.EqualValues(t, expected, actual)
106+
assert.EqualValues(t, tt.want, out)
106107
assert.Equal(t, tt.lexerName, lexerName)
107108
})
108109
}
@@ -112,7 +113,7 @@ func TestPlainText(t *testing.T) {
112113
tests := []struct {
113114
name string
114115
code string
115-
want []string
116+
want []template.HTML
116117
}{
117118
{
118119
name: "empty.py",
@@ -165,9 +166,7 @@ c=2`),
165166
for _, tt := range tests {
166167
t.Run(tt.name, func(t *testing.T) {
167168
out := PlainText([]byte(tt.code))
168-
expected := strings.Join(tt.want, "\n")
169-
actual := strings.Join(out, "\n")
170-
assert.EqualValues(t, expected, actual)
169+
assert.EqualValues(t, tt.want, out)
171170
})
172171
}
173172
}

modules/indexer/code/search.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package code
66
import (
77
"bytes"
88
"context"
9+
"html/template"
910
"strings"
1011

1112
"code.gitea.io/gitea/modules/highlight"
@@ -22,7 +23,7 @@ type Result struct {
2223
Language string
2324
Color string
2425
LineNumbers []int
25-
FormattedLines string
26+
FormattedLines template.HTML
2627
}
2728

2829
type SearchResultLanguages = internal.SearchResultLanguages

modules/markup/orgmode/orgmode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
8787
}
8888
lexer = chroma.Coalesce(lexer)
8989

90-
if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil {
90+
if _, err := w.WriteString(string(highlight.CodeFromLexer(lexer, source))); err != nil {
9191
return ""
9292
}
9393
}

modules/setting/ui.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ var UI = struct {
3535
OnlyShowRelevantRepos bool
3636
ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"`
3737

38+
AmbiguousUnicodeDetection bool
39+
3840
Notification struct {
3941
MinTimeout time.Duration
4042
TimeoutStep time.Duration
@@ -82,6 +84,9 @@ var UI = struct {
8284
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
8385
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
8486
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
87+
88+
AmbiguousUnicodeDetection: true,
89+
8590
Notification: struct {
8691
MinTimeout time.Duration
8792
TimeoutStep time.Duration

routers/web/repo/blame.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
310310
lexerName = lexerNameForLine
311311
}
312312

313-
br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale)
314-
br.Code = gotemplate.HTML(line)
313+
br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale)
315314
rows = append(rows, br)
316315
escapeStatus = escapeStatus.Or(br.EscapeStatus)
317316
}

0 commit comments

Comments
 (0)