Skip to content

Commit dca05ee

Browse files
committed
Add warning for BIDI characters in page renders and in diffs
Fix go-gitea#17514 Signed-off-by: Andrew Thornton <[email protected]>
1 parent dbdaa71 commit dca05ee

File tree

16 files changed

+287
-10
lines changed

16 files changed

+287
-10
lines changed

modules/charset/charset.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,63 @@ import (
2323
// UTF8BOM is the utf-8 byte-order marker
2424
var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
2525

26+
// BIDIRunes are runes that are explicitly mentioned in CVE-2021-42574
27+
var BIDIRunes = "\u202A\u202B\u202C\u202D\u202E\u2066\u2067\u2068\u2069"
28+
29+
// ContainsBIDIRuneString checks some text for any bidi rune
30+
func ContainsBIDIRuneString(text string) bool {
31+
return strings.ContainsAny(text, BIDIRunes)
32+
}
33+
34+
// ContainsBIDIRuneBytes checks some text for any bidi rune
35+
func ContainsBIDIRuneBytes(text []byte) bool {
36+
return bytes.ContainsAny(text, BIDIRunes)
37+
}
38+
39+
// ContainsBIDIRuneReader checks some text for any bidi rune
40+
func ContainsBIDIRuneReader(text io.Reader) (bool, error) {
41+
buf := make([]byte, 4096)
42+
readStart := 0
43+
var err error
44+
var n int
45+
for err == nil {
46+
n, err = text.Read(buf[readStart:])
47+
bs := buf[:n]
48+
i := 0
49+
inner:
50+
for i < n {
51+
r, size := utf8.DecodeRune(bs[i:])
52+
if r == utf8.RuneError {
53+
// need to decide what to do here... runes can be at most 4 bytes - so... i123n
54+
if n-i > 3 {
55+
// this is a real broken rune
56+
return true, fmt.Errorf("text contains bad rune: %x", bs[i])
57+
}
58+
59+
break inner
60+
}
61+
if strings.ContainsRune(BIDIRunes, r) {
62+
return true, nil
63+
}
64+
i += size
65+
}
66+
if n > 0 {
67+
readStart = 0
68+
}
69+
if i < n {
70+
copy(buf, bs[i:n])
71+
readStart = n - i
72+
}
73+
}
74+
if readStart > 0 {
75+
return true, fmt.Errorf("text contains bad rune: %x", buf[0])
76+
}
77+
if err == io.EOF {
78+
return false, nil
79+
}
80+
return true, err
81+
}
82+
2683
// ToUTF8WithFallbackReader detects the encoding of content and coverts to UTF-8 reader if possible
2784
func ToUTF8WithFallbackReader(rd io.Reader) io.Reader {
2885
var buf = make([]byte, 2048)

modules/charset/charset_test.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
package charset
66

77
import (
8+
"bytes"
89
"strings"
910
"testing"
1011

1112
"code.gitea.io/gitea/modules/setting"
12-
1313
"github.com/stretchr/testify/assert"
1414
)
1515

@@ -272,3 +272,94 @@ func stringMustEndWith(t *testing.T, expected string, value string) {
272272
func bytesMustStartWith(t *testing.T, expected []byte, value []byte) {
273273
assert.Equal(t, expected, value[:len(expected)])
274274
}
275+
276+
var bidiTestCases = []struct {
277+
name string
278+
text string
279+
want bool
280+
}{
281+
{
282+
name: "Accented characters",
283+
text: string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}),
284+
want: false,
285+
},
286+
{
287+
name: "Program",
288+
text: "string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})",
289+
want: false,
290+
},
291+
{
292+
name: "CVE testcase",
293+
text: "if access_level != \"user\u202E \u2066// Check if admin\u2069 \u2066\" {",
294+
want: true,
295+
},
296+
}
297+
298+
func TestContainsBIDIRuneString(t *testing.T) {
299+
for _, tt := range bidiTestCases {
300+
t.Run(tt.name, func(t *testing.T) {
301+
if got := ContainsBIDIRuneString(tt.text); got != tt.want {
302+
t.Errorf("ContainsBIDIRuneString() = %v, want %v", got, tt.want)
303+
}
304+
})
305+
}
306+
}
307+
308+
func TestContainsBIDIRuneBytes(t *testing.T) {
309+
for _, tt := range bidiTestCases {
310+
t.Run(tt.name, func(t *testing.T) {
311+
if got := ContainsBIDIRuneBytes([]byte(tt.text)); got != tt.want {
312+
t.Errorf("ContainsBIDIRuneBytes() = %v, want %v", got, tt.want)
313+
}
314+
})
315+
}
316+
}
317+
318+
func TestContainsBIDIRuneReader(t *testing.T) {
319+
for _, tt := range bidiTestCases {
320+
t.Run(tt.name, func(t *testing.T) {
321+
got, err := ContainsBIDIRuneReader(strings.NewReader(tt.text))
322+
if err != nil {
323+
t.Errorf("ContainsBIDIRuneReader() error = %v", err)
324+
return
325+
}
326+
if got != tt.want {
327+
t.Errorf("ContainsBIDIRuneReader() = %v, want %v", got, tt.want)
328+
}
329+
})
330+
}
331+
332+
// now we need some specific tests with broken runes
333+
for _, tt := range bidiTestCases {
334+
t.Run(tt.name+"-terminal-xc2", func(t *testing.T) {
335+
bs := []byte(tt.text)
336+
bs = append(bs, 0xc2)
337+
got, err := ContainsBIDIRuneReader(bytes.NewReader(bs))
338+
if !tt.want {
339+
if err == nil {
340+
t.Errorf("ContainsBIDIRuneReader() should have errored")
341+
return
342+
}
343+
} else if !got {
344+
t.Errorf("ContainsBIDIRuneReader() should have returned true")
345+
return
346+
}
347+
})
348+
}
349+
350+
for _, tt := range bidiTestCases {
351+
t.Run(tt.name+"-prefix-xff", func(t *testing.T) {
352+
bs := append([]byte{0xff}, []byte(tt.text)...)
353+
got, err := ContainsBIDIRuneReader(bytes.NewReader(bs))
354+
if err == nil {
355+
t.Errorf("ContainsBIDIRuneReader() should have errored")
356+
return
357+
}
358+
if !got {
359+
t.Errorf("ContainsBIDIRuneReader() should have returned true")
360+
return
361+
}
362+
})
363+
}
364+
365+
}

options/locale/locale_en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,8 @@ file_view_rendered = View Rendered
977977
file_view_raw = View Raw
978978
file_permalink = Permalink
979979
file_too_large = The file is too large to be shown.
980+
bidi_header = `This file contains hidden Unicode characters!`
981+
bidi_description = `This file contains hidden Unicode characters that may be processed differently from what appears below.<br>If your use case is intentional and legitimate, you can safely ignore this warning.<br>Consult documentation of your favorite text editor for how to open this file using `DOS (CP 437)` encoding instead of Unicode, to reveal hidden characters.`
980982
file_copy_permalink = Copy Permalink
981983
video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag.
982984
audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag.
@@ -2057,6 +2059,7 @@ diff.protected = Protected
20572059
diff.image.side_by_side = Side by Side
20582060
diff.image.swipe = Swipe
20592061
diff.image.overlay = Overlay
2062+
diff.has_bidi = This line has hidden Unicode characters
20602063

20612064
releases.desc = Track project versions and downloads.
20622065
release.releases = Releases

routers/web/repo/lfs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ func LFSFileGet(ctx *context.Context) {
316316
output.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, index+1, index+1, line))
317317
}
318318
ctx.Data["FileContent"] = gotemplate.HTML(output.String())
319+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneString(output.String())
319320

320321
output.Reset()
321322
for i := 0; i < len(lines); i++ {

routers/web/repo/view.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,18 +332,21 @@ func renderDirectory(ctx *context.Context, treeLink string) {
332332
if err != nil {
333333
log.Error("Render failed: %v then fallback", err)
334334
bs, _ := io.ReadAll(rd)
335+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneBytes(bs)
335336
ctx.Data["FileContent"] = strings.ReplaceAll(
336337
gotemplate.HTMLEscapeString(string(bs)), "\n", `<br>`,
337338
)
338339
} else {
339340
ctx.Data["FileContent"] = result.String()
341+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneString(result.String())
340342
}
341343
} else {
342344
ctx.Data["IsRenderedHTML"] = true
343345
buf, err = io.ReadAll(rd)
344346
if err != nil {
345347
log.Error("ReadAll failed: %v", err)
346348
}
349+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneBytes(buf)
347350
ctx.Data["FileContent"] = strings.ReplaceAll(
348351
gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`,
349352
)
@@ -490,9 +493,11 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
490493
return
491494
}
492495
ctx.Data["FileContent"] = result.String()
496+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneString(result.String())
493497
} else if readmeExist {
494498
buf, _ := io.ReadAll(rd)
495499
ctx.Data["IsRenderedHTML"] = true
500+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneBytes(buf)
496501
ctx.Data["FileContent"] = strings.ReplaceAll(
497502
gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`,
498503
)
@@ -501,6 +506,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
501506
lineNums := linesBytesCount(buf)
502507
ctx.Data["NumLines"] = strconv.Itoa(lineNums)
503508
ctx.Data["NumLinesSet"] = true
509+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneBytes(buf)
504510
ctx.Data["FileContent"] = highlight.File(lineNums, blob.Name(), buf)
505511
}
506512
if !isLFSFile {
@@ -550,6 +556,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
550556
return
551557
}
552558
ctx.Data["FileContent"] = result.String()
559+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneString(result.String())
553560
}
554561
}
555562

routers/web/repo/wiki.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"code.gitea.io/gitea/models"
1818
"code.gitea.io/gitea/modules/base"
19+
"code.gitea.io/gitea/modules/charset"
1920
"code.gitea.io/gitea/modules/context"
2021
"code.gitea.io/gitea/modules/git"
2122
"code.gitea.io/gitea/modules/log"
@@ -232,6 +233,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
232233
return nil, nil
233234
}
234235
ctx.Data["content"] = buf.String()
236+
ctx.Data["HasBIDI"] = charset.ContainsBIDIRuneString(buf.String())
235237

236238
buf.Reset()
237239
if err := markdown.Render(rctx, bytes.NewReader(sidebarContent), &buf); err != nil {
@@ -243,6 +245,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
243245
}
244246
ctx.Data["sidebarPresent"] = sidebarContent != nil
245247
ctx.Data["sidebarContent"] = buf.String()
248+
ctx.Data["sidebarHasBIDI"] = charset.ContainsBIDIRuneString(buf.String())
246249

247250
buf.Reset()
248251
if err := markdown.Render(rctx, bytes.NewReader(footerContent), &buf); err != nil {
@@ -254,6 +257,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
254257
}
255258
ctx.Data["footerPresent"] = footerContent != nil
256259
ctx.Data["footerContent"] = buf.String()
260+
ctx.Data["footerHasBIDI"] = charset.ContainsBIDIRuneString(buf.String())
257261

258262
// get commit count - wiki revisions
259263
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)

services/gitdiff/gitdiff.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,27 @@ func (d *DiffLine) GetCommentSide() string {
117117
return d.Comments[0].DiffSide()
118118
}
119119

120+
// HasBIDI returns true if there is a BIDI rune in this line
121+
func (d *DiffLine) HasBIDI() bool {
122+
return charset.ContainsBIDIRuneString(d.Content)
123+
}
124+
125+
// LeftHasBIDI returns true if there is a BIDI rune in this line
126+
func (d *DiffLine) LeftHasBIDI() bool {
127+
if d.LeftIdx > 0 {
128+
return charset.ContainsBIDIRuneString(d.Content)
129+
}
130+
return false
131+
}
132+
133+
// RightHasBIDI returns true if there is a BIDI rune in this line
134+
func (d *DiffLine) RightHasBIDI() bool {
135+
if d.RightIdx > 0 {
136+
return charset.ContainsBIDIRuneString(d.Content)
137+
}
138+
return false
139+
}
140+
120141
// GetLineTypeMarker returns the line type marker
121142
func (d *DiffLine) GetLineTypeMarker() string {
122143
if strings.IndexByte(" +-", d.Content[0]) > -1 {

templates/repo/diff/section_split.tmpl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@
2222
</a>
2323
{{end}}
2424
</td>
25-
<td colspan="5" class="lines-code lines-code-old "><code class="code-inner">{{$section.GetComputedInlineDiffFor $line}}</span></td>
25+
<td colspan="5" class="lines-code lines-code-old ">{{if $line.LeftHasBIDI}}<a class="ui button code-has-bidi" title="{{$.root.i18n.Tr "repo.diff.has_bidi"}}">&#xfe0f;&#x26a0;</a>{{end}}<code class="code-inner">{{$section.GetComputedInlineDiffFor $line}}</span></td>
2626
{{else if and (eq .GetType 3) $hasmatch}}{{/* DEL */}}
2727
{{$match := index $section.Lines $line.Match}}
2828
<td class="lines-num lines-num-old del-code" data-line-num="{{$line.LeftIdx}}"><span rel="diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}"></span></td>
2929
<td class="lines-type-marker lines-type-marker-old del-code"><span class="mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
30-
<td class="lines-code lines-code-old halfwidth del-code">{{if and $.root.SignedUserID $.root.PageIsPullFiles}}<a class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="left" data-idx="{{$line.LeftIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}<code class="code-inner">{{if $line.LeftIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}</code></td>
30+
<td class="lines-code lines-code-old halfwidth del-code">{{if and $.root.SignedUserID $.root.PageIsPullFiles}}<a class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="left" data-idx="{{$line.LeftIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}{{if $line.LeftHasBIDI}}<a class="ui button code-has-bidi" title="{{$.root.i18n.Tr "repo.diff.has_bidi"}}">&#xfe0f;&#x26a0;</a>{{end}}<code class="code-inner">{{if $line.LeftIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}</code></td>
3131
<td class="lines-num lines-num-new add-code" data-line-num="{{if $match.RightIdx}}{{$match.RightIdx}}{{end}}"><span rel="{{if $match.RightIdx}}diff-{{Sha1 $file.Name}}R{{$match.RightIdx}}{{end}}"></span></td>
3232
<td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td>
33-
<td class="lines-code lines-code-new halfwidth add-code">{{if and $.root.SignedUserID $.root.PageIsPullFiles}}<a class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="right" data-idx="{{$match.RightIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}<code class="code-inner">{{if $match.RightIdx}}{{$section.GetComputedInlineDiffFor $match}}{{end}}</code></td>
33+
<td class="lines-code lines-code-new halfwidth add-code">{{if and $.root.SignedUserID $.root.PageIsPullFiles}}<a class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="right" data-idx="{{$match.RightIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}{{if $match.RightHasBIDI}}<a class="ui button code-has-bidi" title="{{$.root.i18n.Tr "repo.diff.has_bidi"}}">&#xfe0f;&#x26a0;</a>{{end}}<code class="code-inner">{{if $match.RightIdx}}{{$section.GetComputedInlineDiffFor $match}}{{end}}</code></td>
3434
{{else}}
3535
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}"></span></td>
3636
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
37-
<td class="lines-code lines-code-old halfwidth">{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}<a class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="left" data-idx="{{$line.LeftIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}<code class="code-inner">{{if $line.LeftIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}</code></td>
37+
<td class="lines-code lines-code-old halfwidth">{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}<a class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="left" data-idx="{{$line.LeftIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}{{if $line.LeftHasBIDI}}<a class="ui button code-has-bidi" title="{{$.root.i18n.Tr "repo.diff.has_bidi"}}">&#xfe0f;&#x26a0;</a>{{end}}<code class="code-inner">{{if $line.LeftIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}</code></td>
3838
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}"></span></td>
3939
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
40-
<td class="lines-code lines-code-new halfwidth">{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}<a class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="right" data-idx="{{$line.RightIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}<code class="code-inner">{{if $line.RightIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}</code></td>
40+
<td class="lines-code lines-code-new halfwidth">{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}<a class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} invisible{{end}}" data-path="{{$file.Name}}" data-side="right" data-idx="{{$line.RightIdx}}" data-new-comment-url="{{$.root.Issue.HTMLURL}}/files/reviews/new_comment">{{svg "octicon-plus"}}</a>{{end}}{{if $line.RightHasBIDI}}<a class="ui button code-has-bidi" title="{{$.root.i18n.Tr "repo.diff.has_bidi"}}">&#xfe0f;&#x26a0;</a>{{end}}<code class="code-inner">{{if $line.RightIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}</code></td>
4141
{{end}}
4242
</tr>
4343
{{if and (eq .GetType 3) $hasmatch}}

templates/repo/settings/lfs_file.tmpl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
</div>
1313
</h4>
1414
<div class="ui attached table unstackable segment">
15+
{{if .HasBIDI}}
16+
<div class="ui warning message">
17+
<span class="close icon">{{svg "octicon-x" 16 "close inside"}}</span>
18+
<div class="header">
19+
{{.i18n.Tr "repo.bidi_header"}}
20+
</div>
21+
{{.i18n.Tr "repo.bidi_description" | Str2html}}
22+
</div>
23+
{{end}}
1524
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsRenderedHTML}} plain-text{{else if .IsTextFile}} code-view{{end}}">
1625
{{if .IsMarkup}}
1726
{{if .FileContent}}{{.FileContent | Safe}}{{end}}

templates/repo/view_file.tmpl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@
6464
{{end}}
6565
</h4>
6666
<div class="ui attached table unstackable segment">
67+
{{if .HasBIDI}}
68+
<div class="ui error message">
69+
<span class="close icon">{{svg "octicon-x" 16 "close inside"}}</span>
70+
<div class="header">
71+
{{.i18n.Tr "repo.bidi_header"}}
72+
</div>
73+
{{.i18n.Tr "repo.bidi_description" | Str2html}}
74+
</div>
75+
{{end}}
6776
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsRenderedHTML}} plain-text{{else if .IsTextSource}} code-view{{end}}">
6877
{{if .IsMarkup}}
6978
{{if .FileContent}}{{.FileContent | Safe}}{{end}}

0 commit comments

Comments
 (0)