diff --git a/helpers.go b/helpers.go
index 776a4a6..4e31fb6 100644
--- a/helpers.go
+++ b/helpers.go
@@ -295,3 +295,9 @@ func (p *Policy) AllowTables() {
CellVerticalAlign,
).OnElements("tbody", "tfoot")
}
+
+func (p *Policy) AllowIFrames(vals ...SandboxValue) {
+ p.AllowAttrs("sandbox").OnElements("iframe")
+
+ p.RequireSandboxOnIFrame(vals...)
+}
diff --git a/policy.go b/policy.go
index 71f6b8d..8a08163 100644
--- a/policy.go
+++ b/policy.go
@@ -74,6 +74,9 @@ type Policy struct {
// When true, add crossorigin="anonymous" to HTML audio, img, link, script, and video tags
requireCrossOriginAnonymous bool
+ // When true, add and filter sandbox attribute on iframe tags
+ requireSandboxOnIFrame map[string]bool
+
// When true add target="_blank" to fully qualified links
// Will add for href="http://foo"
// Will skip for href="/foo" or href="foo"
@@ -189,6 +192,25 @@ type stylePolicyBuilder struct {
type urlPolicy func(url *url.URL) (allowUrl bool)
+type SandboxValue int64
+
+const (
+ SandboxAllowDownloads SandboxValue = iota
+ SandboxAllowDownloadsWithoutUserActivation
+ SandboxAllowForms
+ SandboxAllowModals
+ SandboxAllowOrientationLock
+ SandboxAllowPointerLock
+ SandboxAllowPopups
+ SandboxAllowPopupsToEscapeSandbox
+ SandboxAllowPresentation
+ SandboxAllowSameOrigin
+ SandboxAllowScripts
+ SandboxAllowStorageAccessByUserActivation
+ SandboxAllowTopNavigation
+ SandboxAllowTopNavigationByUserActivation
+)
+
// init initializes the maps if this has not been done already
func (p *Policy) init() {
if !p.initialized {
@@ -680,6 +702,58 @@ func (p *Policy) AllowURLSchemeWithCustomPolicy(
return p
}
+// RequireSandboxOnIFrame will result in all iframe tags having a sandbox="" tag
+// Any sandbox values not specified here will be filtered from the generated HTML
+func (p *Policy) RequireSandboxOnIFrame(vals ...SandboxValue) {
+ p.requireSandboxOnIFrame = make(map[string]bool)
+
+ for val := range vals {
+ switch SandboxValue(val) {
+ case SandboxAllowDownloads:
+ p.requireSandboxOnIFrame["allow-downloads"] = true
+
+ case SandboxAllowDownloadsWithoutUserActivation:
+ p.requireSandboxOnIFrame["allow-downloads-without-user-activation"] = true
+
+ case SandboxAllowForms:
+ p.requireSandboxOnIFrame["allow-forms"] = true
+
+ case SandboxAllowModals:
+ p.requireSandboxOnIFrame["allow-modals"] = true
+
+ case SandboxAllowOrientationLock:
+ p.requireSandboxOnIFrame["allow-orientation-lock"] = true
+
+ case SandboxAllowPointerLock:
+ p.requireSandboxOnIFrame["allow-pointer-lock"] = true
+
+ case SandboxAllowPopups:
+ p.requireSandboxOnIFrame["allow-popups"] = true
+
+ case SandboxAllowPopupsToEscapeSandbox:
+ p.requireSandboxOnIFrame["allow-popups-to-escape-sandbox"] = true
+
+ case SandboxAllowPresentation:
+ p.requireSandboxOnIFrame["allow-presentation"] = true
+
+ case SandboxAllowSameOrigin:
+ p.requireSandboxOnIFrame["allow-same-origin"] = true
+
+ case SandboxAllowScripts:
+ p.requireSandboxOnIFrame["allow-scripts"] = true
+
+ case SandboxAllowStorageAccessByUserActivation:
+ p.requireSandboxOnIFrame["allow-storage-access-by-user-activation"] = true
+
+ case SandboxAllowTopNavigation:
+ p.requireSandboxOnIFrame["allow-top-navigation"] = true
+
+ case SandboxAllowTopNavigationByUserActivation:
+ p.requireSandboxOnIFrame["allow-top-navigation-by-user-activation"] = true
+ }
+ }
+}
+
// AddSpaceWhenStrippingTag states whether to add a single space " " when
// removing tags that are not allowed by the policy.
//
diff --git a/sanitize.go b/sanitize.go
index 97628ce..9bd91ab 100644
--- a/sanitize.go
+++ b/sanitize.go
@@ -240,7 +240,7 @@ func (p *Policy) sanitize(r io.Reader, w io.Writer) error {
// rather than:
// p := bluemonday.NewPolicy()
// If this is the case, and if they haven't yet triggered an action that
- // would initiliaze the maps, then we need to do that.
+ // would initialize the maps, then we need to do that.
p.init()
buff, ok := w.(stringWriterWriter)
@@ -809,6 +809,33 @@ attrsLoop:
}
}
+ if p.requireSandboxOnIFrame != nil && elementName == "iframe" {
+ var sandboxFound bool
+ for i, htmlAttr := range cleanAttrs {
+ if htmlAttr.Key == "sandbox" {
+ sandboxFound = true
+ var cleanVals []string
+ cleanValsSet := make(map[string]bool)
+ for _, val := range strings.Fields(htmlAttr.Val) {
+ if p.requireSandboxOnIFrame[val] {
+ if !cleanValsSet[val] {
+ cleanVals = append(cleanVals, val)
+ cleanValsSet[val] = true
+ }
+ }
+ }
+ cleanAttrs[i].Val = strings.Join(cleanVals, " ")
+ }
+ }
+
+ if !sandboxFound {
+ sandbox := html.Attribute{}
+ sandbox.Key = "sandbox"
+ sandbox.Val = ""
+ cleanAttrs = append(cleanAttrs, sandbox)
+ }
+ }
+
return cleanAttrs
}
diff --git a/sanitize_test.go b/sanitize_test.go
index 0905bd8..fa9e641 100644
--- a/sanitize_test.go
+++ b/sanitize_test.go
@@ -1871,6 +1871,24 @@ func TestIssue107(t *testing.T) {
wg.Wait()
}
+func TestIFrameSandbox(t *testing.T) {
+ p := NewPolicy()
+ p.AllowAttrs("sandbox").OnElements("iframe")
+ p.RequireSandboxOnIFrame(SandboxAllowDownloads)
+
+ in := ``
+ expected := ``
+ out := p.Sanitize(in)
+ if out != expected {
+ t.Errorf(
+ "test failed;\ninput : %s\noutput : %s\nexpected: %s",
+ in,
+ out,
+ expected,
+ )
+ }
+}
+
func TestSanitizedURL(t *testing.T) {
tests := []test{
{