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:
guillep2k 2019-10-14 22:31:09 -03:00 committed by zeripath
parent 8ad2697611
commit cea8ea5ae6
4 changed files with 68 additions and 13 deletions

View File

@ -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, "/") {

View File

@ -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&amp;dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;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) {

View File

@ -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)

View File

@ -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()