}, repo.Description)
if err != nil {
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
- return template.HTML(markup.Sanitize(repo.Description))
+ return template.HTML(markup.SanitizeDescription(repo.Description))
}
- return template.HTML(markup.Sanitize(desc))
+ return template.HTML(markup.SanitizeDescription(desc))
}
// CloneLink represents different types of clone URLs of repository.
// Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
// any modification to the underlying policies once it's been created.
type Sanitizer struct {
- defaultPolicy *bluemonday.Policy
- rendererPolicies map[string]*bluemonday.Policy
- init sync.Once
+ defaultPolicy *bluemonday.Policy
+ descriptionPolicy *bluemonday.Policy
+ rendererPolicies map[string]*bluemonday.Policy
+ init sync.Once
}
var (
func InitializeSanitizer() {
sanitizer.rendererPolicies = map[string]*bluemonday.Policy{}
sanitizer.defaultPolicy = createDefaultPolicy()
+ sanitizer.descriptionPolicy = createRepoDescriptionPolicy()
for name, renderer := range renderers {
sanitizerRules := renderer.SanitizerRules()
return policy
}
+// createRepoDescriptionPolicy returns a minimal more strict policy that is used for
+// repository descriptions.
+func createRepoDescriptionPolicy() *bluemonday.Policy {
+ policy := bluemonday.NewPolicy()
+
+ // Allow italics and bold.
+ policy.AllowElements("i", "b", "em", "strong")
+
+ // Allow code.
+ policy.AllowElements("code")
+
+ // Allow links
+ policy.AllowAttrs("href", "target", "rel").OnElements("a")
+
+ // Allow classes for emojis
+ policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img", "span")
+ policy.AllowAttrs("aria-label").OnElements("span")
+
+ return policy
+}
+
func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) {
for _, rule := range rules {
if rule.AllowDataURIImages {
}
}
+// SanitizeDescription sanitizes the HTML generated for a repository description.
+func SanitizeDescription(s string) string {
+ NewSanitizer()
+ return sanitizer.descriptionPolicy.Sanitize(s)
+}
+
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
func Sanitize(s string) string {
NewSanitizer()
}
}
+func TestDescriptionSanitizer(t *testing.T) {
+ NewSanitizer()
+
+ testCases := []string{
+ `<h1>Title</h1>`, `Title`,
+ `<img src='img.png' alt='image'>`, ``,
+ `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`,
+ `<span style="color: red">Hello World</span>`, `<span>Hello World</span>`,
+ `<br>`, ``,
+ `<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`, `<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`,
+ `<mark>Important!</mark>`, `Important!`,
+ `<details>Click me! <summary>Nothing to see here.</summary></details>`, `Click me! Nothing to see here.`,
+ `<input type="hidden">`, ``,
+ `<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`, `<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`,
+ `Provides alternative <code>wg(8)</code> tool`, `Provides alternative <code>wg(8)</code> tool`,
+ }
+
+ for i := 0; i < len(testCases); i += 2 {
+ assert.Equal(t, testCases[i+1], SanitizeDescription(testCases[i]))
+ }
+}
+
func TestSanitizeNonEscape(t *testing.T) {
descStr := "<scrİpt><script>alert(document.domain)</script></scrİpt>"