From d643d0cd356d0a373d65a0a24f3a3704e9178ff6 Mon Sep 17 00:00:00 2001 From: nim4 Date: Fri, 15 Jan 2021 13:17:33 +0100 Subject: [PATCH] Closed #107 --- policy.go | 43 ++++++++++++++++++++++++++++--------------- policy_test.go | 48 ++++++++++++++++++++++++------------------------ sanitize.go | 20 ++++++++++++++++++++ sanitize_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 39 deletions(-) diff --git a/policy.go b/policy.go index 739d302..9c7e662 100644 --- a/policy.go +++ b/policy.go @@ -69,6 +69,9 @@ type Policy struct { // Will skip for href="/foo" or href="foo" requireNoReferrerFullyQualifiedLinks bool + // When true, add crossorigin="anonymous" to HTML audio, img, link, script, and video tags + requireCrossOriginAnonymous bool + // When true add target="_blank" to fully qualified links // Will add for href="http://foo" // Will skip for href="/foo" or href="foo" @@ -433,24 +436,24 @@ func (spb *stylePolicyBuilder) OnElements(elements ...string) *Policy { // and return the updated policy func (spb *stylePolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy { - for _, attr := range spb.propertyNames { + for _, attr := range spb.propertyNames { - if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok { - spb.p.elsMatchingAndStyles[regex] = make(map[string]stylePolicy) - } + if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok { + spb.p.elsMatchingAndStyles[regex] = make(map[string]stylePolicy) + } - sp := stylePolicy{} - if spb.handler != nil { - sp.handler = spb.handler - } else if len(spb.enum) > 0 { - sp.enum = spb.enum - } else if spb.regexp != nil { - sp.regexp = spb.regexp - } else { - sp.handler = getDefaultHandler(attr) - } - spb.p.elsMatchingAndStyles[regex][attr] = sp + sp := stylePolicy{} + if spb.handler != nil { + sp.handler = spb.handler + } else if len(spb.enum) > 0 { + sp.enum = spb.enum + } else if spb.regexp != nil { + sp.regexp = spb.regexp + } else { + sp.handler = getDefaultHandler(attr) } + spb.p.elsMatchingAndStyles[regex][attr] = sp + } return spb.p } @@ -558,6 +561,16 @@ func (p *Policy) RequireNoReferrerOnFullyQualifiedLinks(require bool) *Policy { return p } +// RequireCrossOriginAnonymous will result in all audio, img, link, script, and +// video tags having a crossorigin="anonymous" added to them if one does not +// already exist +func (p *Policy) RequireCrossOriginAnonymous(require bool) *Policy { + + p.requireCrossOriginAnonymous = require + + return p +} + // AddTargetBlankToFullyQualifiedLinks will result in all a, area and link tags // that point to a non-local destination (i.e. starts with a protocol and has a // host) having a target="_blank" added to them if one does not already exist diff --git a/policy_test.go b/policy_test.go index b352c30..da38734 100644 --- a/policy_test.go +++ b/policy_test.go @@ -72,7 +72,7 @@ func TestAllowElementsMatching(t *testing.T) { policyFn: func(policy *Policy) { policy.AllowElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -86,7 +86,7 @@ func TestAllowElementsMatching(t *testing.T) { policyFn: func(policy *Policy) { policy.AllowElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -96,12 +96,12 @@ func TestAllowElementsMatching(t *testing.T) {
`, - },"Self closing tags with regex prefix and custom attr should strip any that do not match": { + }, "Self closing tags with regex prefix and custom attr should strip any that do not match": { policyFn: func(policy *Policy) { policy.AllowElementsMatching(regexp.MustCompile(`^my-element-`)) policy.AllowElements("not-my-element-demo-one") }, - in: `
+ in: `
@@ -117,7 +117,7 @@ func TestAllowElementsMatching(t *testing.T) { for name, test := range tests { policy := NewPolicy().AllowElements("div") policy.AllowDataAttributes() - if test.policyFn != nil{ + if test.policyFn != nil { test.policyFn(policy) } out := policy.Sanitize(test.in) @@ -133,7 +133,7 @@ func TestAllowElementsMatching(t *testing.T) { } } -func TestAttrOnElementMatching(t *testing.T){ +func TestAttrOnElementMatching(t *testing.T) { tests := map[string]struct { policyFn func(policy *Policy) in string @@ -143,7 +143,7 @@ func TestAttrOnElementMatching(t *testing.T){ policyFn: func(policy *Policy) { policy.AllowAttrs("my-attr").OnElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -157,7 +157,7 @@ func TestAttrOnElementMatching(t *testing.T){ policyFn: func(policy *Policy) { policy.AllowAttrs("my-attr").OnElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -167,14 +167,14 @@ func TestAttrOnElementMatching(t *testing.T){
`, - },"Specific element rule defined should override matching rules": { + }, "Specific element rule defined should override matching rules": { policyFn: func(policy *Policy) { // specific element rule policy.AllowAttrs("my-other-attr").OnElements("my-element-demo-one") // matched rule takes lower precedence policy.AllowAttrs("my-attr").OnElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -190,7 +190,7 @@ func TestAttrOnElementMatching(t *testing.T){ for name, test := range tests { policy := NewPolicy().AllowElements("div") policy.AllowDataAttributes() - if test.policyFn != nil{ + if test.policyFn != nil { test.policyFn(policy) } out := policy.Sanitize(test.in) @@ -206,7 +206,7 @@ func TestAttrOnElementMatching(t *testing.T){ } } -func TestStyleOnElementMatching(t *testing.T){ +func TestStyleOnElementMatching(t *testing.T) { tests := map[string]struct { policyFn func(policy *Policy) in string @@ -216,12 +216,12 @@ func TestStyleOnElementMatching(t *testing.T){ policyFn: func(policy *Policy) { policy.AllowAttrs("style"). OnElementsMatching(regexp.MustCompile(`^my-element-`)) - policy.AllowStyles("color","mystyle"). + policy.AllowStyles("color", "mystyle"). MatchingHandler(func(s string) bool { - return true - }).OnElementsMatching(regexp.MustCompile(`^my-element-`)) + return true + }).OnElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -235,12 +235,12 @@ func TestStyleOnElementMatching(t *testing.T){ policyFn: func(policy *Policy) { policy.AllowAttrs("style"). OnElementsMatching(regexp.MustCompile(`^my-element-`)) - policy.AllowStyles("color","mystyle"). + policy.AllowStyles("color", "mystyle"). MatchingHandler(func(s string) bool { return true }).OnElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -250,23 +250,23 @@ func TestStyleOnElementMatching(t *testing.T){
`, - },"Specific element rule defined should override matching rules": { + }, "Specific element rule defined should override matching rules": { policyFn: func(policy *Policy) { policy.AllowAttrs("style"). OnElements("my-element-demo-one") - policy.AllowStyles("color","mystyle"). + policy.AllowStyles("color", "mystyle"). MatchingHandler(func(s string) bool { return true }).OnElements("my-element-demo-one") policy.AllowAttrs("style"). OnElementsMatching(regexp.MustCompile(`^my-element-`)) - policy.AllowStyles("color","customstyle"). + policy.AllowStyles("color", "customstyle"). MatchingHandler(func(s string) bool { return true }).OnElementsMatching(regexp.MustCompile(`^my-element-`)) }, - in: `
+ in: `
@@ -282,7 +282,7 @@ func TestStyleOnElementMatching(t *testing.T){ for name, test := range tests { policy := NewPolicy().AllowElements("div") policy.AllowDataAttributes() - if test.policyFn != nil{ + if test.policyFn != nil { test.policyFn(policy) } out := policy.Sanitize(test.in) @@ -296,4 +296,4 @@ func TestStyleOnElementMatching(t *testing.T){ ) } } -} \ No newline at end of file +} diff --git a/sanitize.go b/sanitize.go index 103f39f..b490353 100644 --- a/sanitize.go +++ b/sanitize.go @@ -664,6 +664,26 @@ func (p *Policy) sanitizeAttrs( } } + if p.requireCrossOriginAnonymous && len(cleanAttrs) > 0 { + switch elementName { + case "audio", "img", "link", "script", "video": + var crossOriginFound bool + for _, htmlAttr := range cleanAttrs { + if htmlAttr.Key == "crossorigin" { + crossOriginFound = true + htmlAttr.Val = "anonymous" + } + } + + if !crossOriginFound { + crossOrigin := html.Attribute{} + crossOrigin.Key = "crossorigin" + crossOrigin.Val = "anonymous" + cleanAttrs = append(cleanAttrs, crossOrigin) + } + } + } + return cleanAttrs } diff --git a/sanitize_test.go b/sanitize_test.go index 5f3c049..95a6475 100644 --- a/sanitize_test.go +++ b/sanitize_test.go @@ -1678,3 +1678,44 @@ func TestIssue85NoReferrer(t *testing.T) { } wg.Wait() } + +func TestIssue107(t *testing.T) { + p := UGCPolicy() + p.RequireCrossOriginAnonymous(true) + + tests := []test{ + { + in: ``, + expected: ``, + }, + { + in: ``, + expected: ``, + }, + { + in: ``, + expected: ``, + }, + } + + // These tests are run concurrently to enable the race detector to pick up + // potential issues + wg := sync.WaitGroup{} + wg.Add(len(tests)) + for ii, tt := range tests { + go func(ii int, tt test) { + out := p.Sanitize(tt.in) + if out != tt.expected { + t.Errorf( + "test %d failed;\ninput : %s\noutput : %s\nexpected: %s", + ii, + tt.in, + out, + tt.expected, + ) + } + wg.Done() + }(ii, tt) + } + wg.Wait() +}