Support inline rendering of CUSTOM_URL_SCHEMES (#8496)
* Support inline rendering of CUSTOM_URL_SCHEMES * Fix lint * Add tests * Fix lint
This commit is contained in:
parent
8ad2697611
commit
cea8ea5ae6
4 changed files with 68 additions and 13 deletions
|
@ -92,6 +92,32 @@ func getIssueFullPattern() *regexp.Regexp {
|
||||||
return issueFullPattern
|
return issueFullPattern
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
|
||||||
|
func CustomLinkURLSchemes(schemes []string) {
|
||||||
|
schemes = append(schemes, "http", "https")
|
||||||
|
withAuth := make([]string, 0, len(schemes))
|
||||||
|
validScheme := regexp.MustCompile(`^[a-z]+$`)
|
||||||
|
for _, s := range schemes {
|
||||||
|
if !validScheme.MatchString(s) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
without := false
|
||||||
|
for _, sna := range xurls.SchemesNoAuthority {
|
||||||
|
if s == sna {
|
||||||
|
without = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if without {
|
||||||
|
s += ":"
|
||||||
|
} else {
|
||||||
|
s += "://"
|
||||||
|
}
|
||||||
|
withAuth = append(withAuth, s)
|
||||||
|
}
|
||||||
|
linkRegex, _ = xurls.StrictMatchingScheme(strings.Join(withAuth, "|"))
|
||||||
|
}
|
||||||
|
|
||||||
// IsSameDomain checks if given url string has the same hostname as current Gitea instance
|
// IsSameDomain checks if given url string has the same hostname as current Gitea instance
|
||||||
func IsSameDomain(s string) bool {
|
func IsSameDomain(s string) bool {
|
||||||
if strings.HasPrefix(s, "/") {
|
if strings.HasPrefix(s, "/") {
|
||||||
|
|
|
@ -89,6 +89,11 @@ func TestRender_links(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Text that should be turned into URL
|
// Text that should be turned into URL
|
||||||
|
|
||||||
|
defaultCustom := setting.Markdown.CustomURLSchemes
|
||||||
|
setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"}
|
||||||
|
ReplaceSanitizer()
|
||||||
|
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"https://www.example.com",
|
"https://www.example.com",
|
||||||
`<p><a href="https://www.example.com" rel="nofollow">https://www.example.com</a></p>`)
|
`<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) {
|
||||||
test(
|
test(
|
||||||
"https://username:password@gitea.com",
|
"https://username:password@gitea.com",
|
||||||
`<p><a href="https://username:password@gitea.com" rel="nofollow">https://username:password@gitea.com</a></p>`)
|
`<p><a href="https://username:password@gitea.com" rel="nofollow">https://username:password@gitea.com</a></p>`)
|
||||||
|
test(
|
||||||
|
"ftp://gitea.com/file.txt",
|
||||||
|
`<p><a href="ftp://gitea.com/file.txt" rel="nofollow">ftp://gitea.com/file.txt</a></p>`)
|
||||||
|
test(
|
||||||
|
"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download",
|
||||||
|
`<p><a href="magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download</a></p>`)
|
||||||
|
|
||||||
// Test that should *not* be turned into URL
|
// Test that should *not* be turned into URL
|
||||||
test(
|
test(
|
||||||
|
@ -154,6 +165,14 @@ func TestRender_links(t *testing.T) {
|
||||||
test(
|
test(
|
||||||
"www",
|
"www",
|
||||||
`<p>www</p>`)
|
`<p>www</p>`)
|
||||||
|
test(
|
||||||
|
"ftps://gitea.com",
|
||||||
|
`<p>ftps://gitea.com</p>`)
|
||||||
|
|
||||||
|
// Restore previous settings
|
||||||
|
setting.Markdown.CustomURLSchemes = defaultCustom
|
||||||
|
ReplaceSanitizer()
|
||||||
|
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_email(t *testing.T) {
|
func TestRender_email(t *testing.T) {
|
||||||
|
|
|
@ -9,12 +9,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init initialize regexps for markdown parsing
|
// Init initialize regexps for markdown parsing
|
||||||
func Init() {
|
func Init() {
|
||||||
getIssueFullPattern()
|
getIssueFullPattern()
|
||||||
NewSanitizer()
|
NewSanitizer()
|
||||||
|
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
||||||
|
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
|
||||||
|
}
|
||||||
|
|
||||||
// since setting maybe changed extensions, this will reload all parser extensions mapping
|
// since setting maybe changed extensions, this will reload all parser extensions mapping
|
||||||
extParsers = make(map[string]Parser)
|
extParsers = make(map[string]Parser)
|
||||||
|
|
|
@ -28,22 +28,28 @@ var sanitizer = &Sanitizer{}
|
||||||
// entire application lifecycle.
|
// entire application lifecycle.
|
||||||
func NewSanitizer() {
|
func NewSanitizer() {
|
||||||
sanitizer.init.Do(func() {
|
sanitizer.init.Do(func() {
|
||||||
sanitizer.policy = bluemonday.UGCPolicy()
|
ReplaceSanitizer()
|
||||||
// We only want to allow HighlightJS specific classes for code blocks
|
|
||||||
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code")
|
|
||||||
|
|
||||||
// Checkboxes
|
|
||||||
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
|
||||||
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
|
|
||||||
|
|
||||||
// Custom URL-Schemes
|
|
||||||
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
|
||||||
|
|
||||||
// Allow keyword markup
|
|
||||||
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplaceSanitizer replaces the current sanitizer to account for changes in settings
|
||||||
|
func ReplaceSanitizer() {
|
||||||
|
sanitizer = &Sanitizer{}
|
||||||
|
sanitizer.policy = bluemonday.UGCPolicy()
|
||||||
|
// We only want to allow HighlightJS specific classes for code blocks
|
||||||
|
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code")
|
||||||
|
|
||||||
|
// Checkboxes
|
||||||
|
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||||
|
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
|
||||||
|
|
||||||
|
// Custom URL-Schemes
|
||||||
|
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||||
|
|
||||||
|
// Allow keyword markup
|
||||||
|
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span")
|
||||||
|
}
|
||||||
|
|
||||||
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
||||||
func Sanitize(s string) string {
|
func Sanitize(s string) string {
|
||||||
NewSanitizer()
|
NewSanitizer()
|
||||||
|
|
Reference in a new issue