Skip to content

Commit

Permalink
Merge pull request #108 from nim4/master
Browse files Browse the repository at this point in the history
Closed #107
  • Loading branch information
David Kitchen authored Apr 5, 2021
2 parents 49300a2 + 0909ed7 commit 9db8330
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 39 deletions.
43 changes: 28 additions & 15 deletions policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
48 changes: 24 additions & 24 deletions policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestAllowElementsMatching(t *testing.T) {
policyFn: func(policy *Policy) {
policy.AllowElementsMatching(regexp.MustCompile(`^my-element-`))
},
in: `<div>
in: `<div>
<my-element-demo-one data-test="test" my-attr="test"/>
<my-element-demo-two data-test="test"/>
<not-my-element-demo-one data-test="test"/>
Expand All @@ -86,7 +86,7 @@ func TestAllowElementsMatching(t *testing.T) {
policyFn: func(policy *Policy) {
policy.AllowElementsMatching(regexp.MustCompile(`^my-element-`))
},
in: `<div>
in: `<div>
<my-element-demo-one data-test="test"></my-element-demo-one>
<my-element-demo-two data-test="test"></my-element-demo-two>
<not-my-element-demo-one data-test="test"></not-my-element-demo-one>
Expand All @@ -96,12 +96,12 @@ func TestAllowElementsMatching(t *testing.T) {
<my-element-demo-two data-test="test"></my-element-demo-two>
</div>`,
},"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: `<div>
in: `<div>
<my-element-demo-one data-test="test" my-attr="test"/>
<my-element-demo-two data-test="test"/>
<not-my-element-demo-one data-test="test"/>
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -143,7 +143,7 @@ func TestAttrOnElementMatching(t *testing.T){
policyFn: func(policy *Policy) {
policy.AllowAttrs("my-attr").OnElementsMatching(regexp.MustCompile(`^my-element-`))
},
in: `<div>
in: `<div>
<my-element-demo-one data-test="test" my-attr="test"/>
<my-element-demo-two data-test="test" other-attr="test"/>
<not-my-element-demo-one data-test="test"/>
Expand All @@ -157,7 +157,7 @@ func TestAttrOnElementMatching(t *testing.T){
policyFn: func(policy *Policy) {
policy.AllowAttrs("my-attr").OnElementsMatching(regexp.MustCompile(`^my-element-`))
},
in: `<div>
in: `<div>
<my-element-demo-one data-test="test" my-attr="test" other-attr="test"></my-element-demo-one>
<my-element-demo-two data-test="test" other-attr="test"></my-element-demo-two>
<not-my-element-demo-one data-test="test" other-attr="test"></not-my-element-demo-one>
Expand All @@ -167,14 +167,14 @@ func TestAttrOnElementMatching(t *testing.T){
<my-element-demo-two data-test="test"></my-element-demo-two>
</div>`,
},"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: `<div>
in: `<div>
<my-element-demo-one data-test="test" my-attr="test" my-other-attr="test"/>
<my-element-demo-two data-test="test" my-attr="test" my-other-attr="test"/>
<not-my-element-demo-one data-test="test"/>
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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: `<div>
in: `<div>
<my-element-demo-one data-test="test" style="color:#ffffff;mystyle:test;other:value"/>
<my-element-demo-two data-test="test" other-attr="test" style="other:value"/>
<not-my-element-demo-one data-test="test"/>
Expand All @@ -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: `<div>
in: `<div>
<my-element-demo-one data-test="test" style="color:#ffffff;mystyle:test;other:value"></my-element-demo-one>
<my-element-demo-two data-test="test" other-attr="test" style="other:value"></my-element-demo-two>
<not-my-element-demo-one data-test="test" other-attr="test"></not-my-element-demo-one>
Expand All @@ -250,23 +250,23 @@ func TestStyleOnElementMatching(t *testing.T){
<my-element-demo-two data-test="test"></my-element-demo-two>
</div>`,
},"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: `<div>
in: `<div>
<my-element-demo-one data-test="test" style="color:#ffffff;mystyle:test;other:value"/>
<my-element-demo-two data-test="test" style="color:#ffffff;mystyle:test;customstyle:value"/>
<not-my-element-demo-one data-test="test"/>
Expand All @@ -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)
Expand All @@ -296,4 +296,4 @@ func TestStyleOnElementMatching(t *testing.T){
)
}
}
}
}
20 changes: 20 additions & 0 deletions sanitize.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,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
}

Expand Down
41 changes: 41 additions & 0 deletions sanitize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,47 @@ func TestIssue85NoReferrer(t *testing.T) {
wg.Wait()
}

func TestIssue107(t *testing.T) {
p := UGCPolicy()
p.RequireCrossOriginAnonymous(true)

tests := []test{
{
in: `<img src="/path" />`,
expected: `<img src="/path" crossorigin="anonymous"/>`,
},
{
in: `<img src="/path" crossorigin="use-credentials"/>`,
expected: `<img src="/path" crossorigin="anonymous"/>`,
},
{
in: `<img src="/path" crossorigin=""/>`,
expected: `<img src="/path" crossorigin="anonymous"/>`,
},
}

// 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()
}

func TestSanitizedURL(t *testing.T) {
tests := []test{
{
Expand Down

0 comments on commit 9db8330

Please sign in to comment.