mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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:
		| @@ -92,6 +92,32 @@ func getIssueFullPattern() *regexp.Regexp { | ||||
| 	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 | ||||
| func IsSameDomain(s string) bool { | ||||
| 	if strings.HasPrefix(s, "/") { | ||||
|   | ||||
| @@ -89,6 +89,11 @@ func TestRender_links(t *testing.T) { | ||||
| 	} | ||||
| 	// Text that should be turned into URL | ||||
|  | ||||
| 	defaultCustom := setting.Markdown.CustomURLSchemes | ||||
| 	setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"} | ||||
| 	ReplaceSanitizer() | ||||
| 	CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) | ||||
|  | ||||
| 	test( | ||||
| 		"https://www.example.com", | ||||
| 		`<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( | ||||
| 		"https://username:password@gitea.com", | ||||
| 		`<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( | ||||
| @@ -154,6 +165,14 @@ func TestRender_links(t *testing.T) { | ||||
| 	test( | ||||
| 		"www", | ||||
| 		`<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) { | ||||
|   | ||||
| @@ -9,12 +9,16 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| ) | ||||
|  | ||||
| // Init initialize regexps for markdown parsing | ||||
| func Init() { | ||||
| 	getIssueFullPattern() | ||||
| 	NewSanitizer() | ||||
| 	if len(setting.Markdown.CustomURLSchemes) > 0 { | ||||
| 		CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) | ||||
| 	} | ||||
|  | ||||
| 	// since setting maybe changed extensions, this will reload all parser extensions mapping | ||||
| 	extParsers = make(map[string]Parser) | ||||
|   | ||||
| @@ -28,22 +28,28 @@ var sanitizer = &Sanitizer{} | ||||
| // entire application lifecycle. | ||||
| func NewSanitizer() { | ||||
| 	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") | ||||
|  | ||||
| 		// 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() | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| func Sanitize(s string) string { | ||||
| 	NewSanitizer() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user