* Support inline rendering of CUSTOM_URL_SCHEMES * Fix lint * Add tests * Fix linttags/v1.21.12.1
| @@ -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,20 +28,26 @@ 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() | |||||
| // We only want to allow HighlightJS specific classes for code blocks | |||||
| sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code") | |||||
| ReplaceSanitizer() | |||||
| }) | |||||
| } | |||||
| // Checkboxes | |||||
| sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input") | |||||
| sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input") | |||||
| // 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") | |||||
| // Custom URL-Schemes | |||||
| sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...) | |||||
| // Checkboxes | |||||
| sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input") | |||||
| sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input") | |||||
| // Allow keyword markup | |||||
| sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span") | |||||
| }) | |||||
| // 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. | ||||