Skip to content

Commit cea8ea5

Browse files
guillep2kzeripath
authored andcommitted
Support inline rendering of CUSTOM_URL_SCHEMES (#8496)
* Support inline rendering of CUSTOM_URL_SCHEMES * Fix lint * Add tests * Fix lint
1 parent 8ad2697 commit cea8ea5

File tree

4 files changed

+66
-11
lines changed

4 files changed

+66
-11
lines changed

modules/markup/html.go

+26
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,32 @@ func getIssueFullPattern() *regexp.Regexp {
9292
return issueFullPattern
9393
}
9494

95+
// CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
96+
func CustomLinkURLSchemes(schemes []string) {
97+
schemes = append(schemes, "http", "https")
98+
withAuth := make([]string, 0, len(schemes))
99+
validScheme := regexp.MustCompile(`^[a-z]+$`)
100+
for _, s := range schemes {
101+
if !validScheme.MatchString(s) {
102+
continue
103+
}
104+
without := false
105+
for _, sna := range xurls.SchemesNoAuthority {
106+
if s == sna {
107+
without = true
108+
break
109+
}
110+
}
111+
if without {
112+
s += ":"
113+
} else {
114+
s += "://"
115+
}
116+
withAuth = append(withAuth, s)
117+
}
118+
linkRegex, _ = xurls.StrictMatchingScheme(strings.Join(withAuth, "|"))
119+
}
120+
95121
// IsSameDomain checks if given url string has the same hostname as current Gitea instance
96122
func IsSameDomain(s string) bool {
97123
if strings.HasPrefix(s, "/") {

modules/markup/html_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ func TestRender_links(t *testing.T) {
8989
}
9090
// Text that should be turned into URL
9191

92+
defaultCustom := setting.Markdown.CustomURLSchemes
93+
setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"}
94+
ReplaceSanitizer()
95+
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
96+
9297
test(
9398
"https://www.example.com",
9499
`<p><a href="https://www.example.com" rel="nofollow">https://www.example.com</a></p>`)
@@ -131,6 +136,12 @@ func TestRender_links(t *testing.T) {
131136
test(
132137
"https://username:[email protected]",
133138
`<p><a href="https://username:[email protected]" rel="nofollow">https://username:[email protected]</a></p>`)
139+
test(
140+
"ftp://gitea.com/file.txt",
141+
`<p><a href="ftp://gitea.com/file.txt" rel="nofollow">ftp://gitea.com/file.txt</a></p>`)
142+
test(
143+
"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download",
144+
`<p><a href="magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download</a></p>`)
134145

135146
// Test that should *not* be turned into URL
136147
test(
@@ -154,6 +165,14 @@ func TestRender_links(t *testing.T) {
154165
test(
155166
"www",
156167
`<p>www</p>`)
168+
test(
169+
"ftps://gitea.com",
170+
`<p>ftps://gitea.com</p>`)
171+
172+
// Restore previous settings
173+
setting.Markdown.CustomURLSchemes = defaultCustom
174+
ReplaceSanitizer()
175+
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
157176
}
158177

159178
func TestRender_email(t *testing.T) {

modules/markup/markup.go

+4
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ import (
99
"strings"
1010

1111
"code.gitea.io/gitea/modules/log"
12+
"code.gitea.io/gitea/modules/setting"
1213
)
1314

1415
// Init initialize regexps for markdown parsing
1516
func Init() {
1617
getIssueFullPattern()
1718
NewSanitizer()
19+
if len(setting.Markdown.CustomURLSchemes) > 0 {
20+
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
21+
}
1822

1923
// since setting maybe changed extensions, this will reload all parser extensions mapping
2024
extParsers = make(map[string]Parser)

modules/markup/sanitizer.go

+17-11
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,26 @@ var sanitizer = &Sanitizer{}
2828
// entire application lifecycle.
2929
func NewSanitizer() {
3030
sanitizer.init.Do(func() {
31-
sanitizer.policy = bluemonday.UGCPolicy()
32-
// We only want to allow HighlightJS specific classes for code blocks
33-
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code")
31+
ReplaceSanitizer()
32+
})
33+
}
3434

35-
// Checkboxes
36-
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
37-
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
35+
// ReplaceSanitizer replaces the current sanitizer to account for changes in settings
36+
func ReplaceSanitizer() {
37+
sanitizer = &Sanitizer{}
38+
sanitizer.policy = bluemonday.UGCPolicy()
39+
// We only want to allow HighlightJS specific classes for code blocks
40+
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code")
3841

39-
// Custom URL-Schemes
40-
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
42+
// Checkboxes
43+
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
44+
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
4145

42-
// Allow keyword markup
43-
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span")
44-
})
46+
// Custom URL-Schemes
47+
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
48+
49+
// Allow keyword markup
50+
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span")
4551
}
4652

4753
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.

0 commit comments

Comments
 (0)