policy.go (32399B)
1 // Copyright (c) 2014, David Kitchen <david@buro9.com> 2 // 3 // All rights reserved. 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are met: 7 // 8 // * Redistributions of source code must retain the above copyright notice, this 9 // list of conditions and the following disclaimer. 10 // 11 // * Redistributions in binary form must reproduce the above copyright notice, 12 // this list of conditions and the following disclaimer in the documentation 13 // and/or other materials provided with the distribution. 14 // 15 // * Neither the name of the organisation (Microcosm) nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 package bluemonday 31 32 //TODO sgutzwiller create map of styles to default handlers 33 //TODO sgutzwiller create handlers for various attributes 34 import ( 35 "net/url" 36 "regexp" 37 "strings" 38 39 "github.com/microcosm-cc/bluemonday/css" 40 ) 41 42 // Policy encapsulates the allowlist of HTML elements and attributes that will 43 // be applied to the sanitised HTML. 44 // 45 // You should use bluemonday.NewPolicy() to create a blank policy as the 46 // unexported fields contain maps that need to be initialized. 47 type Policy struct { 48 49 // Declares whether the maps have been initialized, used as a cheap check to 50 // ensure that those using Policy{} directly won't cause nil pointer 51 // exceptions 52 initialized bool 53 54 // If true then we add spaces when stripping tags, specifically the closing 55 // tag is replaced by a space character. 56 addSpaces bool 57 58 // When true, add rel="nofollow" to HTML a, area, and link tags 59 requireNoFollow bool 60 61 // When true, add rel="nofollow" to HTML a, area, and link tags 62 // Will add for href="http://foo" 63 // Will skip for href="/foo" or href="foo" 64 requireNoFollowFullyQualifiedLinks bool 65 66 // When true, add rel="noreferrer" to HTML a, area, and link tags 67 requireNoReferrer bool 68 69 // When true, add rel="noreferrer" to HTML a, area, and link tags 70 // Will add for href="http://foo" 71 // Will skip for href="/foo" or href="foo" 72 requireNoReferrerFullyQualifiedLinks bool 73 74 // When true, add crossorigin="anonymous" to HTML audio, img, link, script, and video tags 75 requireCrossOriginAnonymous bool 76 77 // When true, add and filter sandbox attribute on iframe tags 78 requireSandboxOnIFrame map[string]bool 79 80 // When true add target="_blank" to fully qualified links 81 // Will add for href="http://foo" 82 // Will skip for href="/foo" or href="foo" 83 addTargetBlankToFullyQualifiedLinks bool 84 85 // When true, URLs must be parseable by "net/url" url.Parse() 86 requireParseableURLs bool 87 88 // When true, u, _ := url.Parse("url"); !u.IsAbs() is permitted 89 allowRelativeURLs bool 90 91 // When true, allow data attributes. 92 allowDataAttributes bool 93 94 // When true, allow comments. 95 allowComments bool 96 97 // map[htmlElementName]map[htmlAttributeName][]attrPolicy 98 elsAndAttrs map[string]map[string][]attrPolicy 99 100 // elsMatchingAndAttrs stores regex based element matches along with attributes 101 elsMatchingAndAttrs map[*regexp.Regexp]map[string][]attrPolicy 102 103 // map[htmlAttributeName][]attrPolicy 104 globalAttrs map[string][]attrPolicy 105 106 // map[htmlElementName]map[cssPropertyName][]stylePolicy 107 elsAndStyles map[string]map[string][]stylePolicy 108 109 // map[regex]map[cssPropertyName][]stylePolicy 110 elsMatchingAndStyles map[*regexp.Regexp]map[string][]stylePolicy 111 112 // map[cssPropertyName][]stylePolicy 113 globalStyles map[string][]stylePolicy 114 115 // If urlPolicy is nil, all URLs with matching schema are allowed. 116 // Otherwise, only the URLs with matching schema and urlPolicy(url) 117 // returning true are allowed. 118 allowURLSchemes map[string][]urlPolicy 119 120 // These regexps are used to match allowed URL schemes, for example 121 // if one would want to allow all URL schemes, they would add `.+` 122 allowURLSchemeRegexps []*regexp.Regexp 123 124 // If an element has had all attributes removed as a result of a policy 125 // being applied, then the element would be removed from the output. 126 // 127 // However some elements are valid and have strong layout meaning without 128 // any attributes, i.e. <table>. To prevent those being removed we maintain 129 // a list of elements that are allowed to have no attributes and that will 130 // be maintained in the output HTML. 131 setOfElementsAllowedWithoutAttrs map[string]struct{} 132 133 // If an element has had all attributes removed as a result of a policy 134 // being applied, then the element would be removed from the output. 135 // 136 // However some elements are valid and have strong layout meaning without 137 // any attributes, i.e. <table>. 138 // 139 // In this case, any element matching a regular expression will be accepted without 140 // attributes added. 141 setOfElementsMatchingAllowedWithoutAttrs []*regexp.Regexp 142 143 setOfElementsToSkipContent map[string]struct{} 144 145 // Permits fundamentally unsafe elements. 146 // 147 // If false (default) then elements such as `style` and `script` will not be 148 // permitted even if declared in a policy. These elements when combined with 149 // untrusted input cannot be safely handled by bluemonday at this point in 150 // time. 151 // 152 // If true then `style` and `script` would be permitted by bluemonday if a 153 // policy declares them. However this is not recommended under any circumstance 154 // and can lead to XSS being rendered thus defeating the purpose of using a 155 // HTML sanitizer. 156 allowUnsafe bool 157 } 158 159 type attrPolicy struct { 160 161 // optional pattern to match, when not nil the regexp needs to match 162 // otherwise the attribute is removed 163 regexp *regexp.Regexp 164 } 165 166 type stylePolicy struct { 167 // handler to validate 168 handler func(string) bool 169 170 // optional pattern to match, when not nil the regexp needs to match 171 // otherwise the property is removed 172 regexp *regexp.Regexp 173 174 // optional list of allowed property values, for properties which 175 // have a defined list of allowed values; property will be removed 176 // if the value is not allowed 177 enum []string 178 } 179 180 type attrPolicyBuilder struct { 181 p *Policy 182 183 attrNames []string 184 regexp *regexp.Regexp 185 allowEmpty bool 186 } 187 188 type stylePolicyBuilder struct { 189 p *Policy 190 191 propertyNames []string 192 regexp *regexp.Regexp 193 enum []string 194 handler func(string) bool 195 } 196 197 type urlPolicy func(url *url.URL) (allowUrl bool) 198 199 type SandboxValue int64 200 201 const ( 202 SandboxAllowDownloads SandboxValue = iota 203 SandboxAllowDownloadsWithoutUserActivation 204 SandboxAllowForms 205 SandboxAllowModals 206 SandboxAllowOrientationLock 207 SandboxAllowPointerLock 208 SandboxAllowPopups 209 SandboxAllowPopupsToEscapeSandbox 210 SandboxAllowPresentation 211 SandboxAllowSameOrigin 212 SandboxAllowScripts 213 SandboxAllowStorageAccessByUserActivation 214 SandboxAllowTopNavigation 215 SandboxAllowTopNavigationByUserActivation 216 ) 217 218 // init initializes the maps if this has not been done already 219 func (p *Policy) init() { 220 if !p.initialized { 221 p.elsAndAttrs = make(map[string]map[string][]attrPolicy) 222 p.elsMatchingAndAttrs = make(map[*regexp.Regexp]map[string][]attrPolicy) 223 p.globalAttrs = make(map[string][]attrPolicy) 224 p.elsAndStyles = make(map[string]map[string][]stylePolicy) 225 p.elsMatchingAndStyles = make(map[*regexp.Regexp]map[string][]stylePolicy) 226 p.globalStyles = make(map[string][]stylePolicy) 227 p.allowURLSchemes = make(map[string][]urlPolicy) 228 p.allowURLSchemeRegexps = make([]*regexp.Regexp, 0) 229 p.setOfElementsAllowedWithoutAttrs = make(map[string]struct{}) 230 p.setOfElementsToSkipContent = make(map[string]struct{}) 231 p.initialized = true 232 } 233 } 234 235 // NewPolicy returns a blank policy with nothing allowed or permitted. This 236 // is the recommended way to start building a policy and you should now use 237 // AllowAttrs() and/or AllowElements() to construct the allowlist of HTML 238 // elements and attributes. 239 func NewPolicy() *Policy { 240 241 p := Policy{} 242 243 p.addDefaultElementsWithoutAttrs() 244 p.addDefaultSkipElementContent() 245 246 return &p 247 } 248 249 // AllowAttrs takes a range of HTML attribute names and returns an 250 // attribute policy builder that allows you to specify the pattern and scope of 251 // the allowed attribute. 252 // 253 // The attribute policy is only added to the core policy when either Globally() 254 // or OnElements(...) are called. 255 func (p *Policy) AllowAttrs(attrNames ...string) *attrPolicyBuilder { 256 257 p.init() 258 259 abp := attrPolicyBuilder{ 260 p: p, 261 allowEmpty: false, 262 } 263 264 for _, attrName := range attrNames { 265 abp.attrNames = append(abp.attrNames, strings.ToLower(attrName)) 266 } 267 268 return &abp 269 } 270 271 // AllowDataAttributes permits all data attributes. We can't specify the name 272 // of each attribute exactly as they are customized. 273 // 274 // NOTE: These values are not sanitized and applications that evaluate or process 275 // them without checking and verification of the input may be at risk if this option 276 // is enabled. This is a 'caveat emptor' option and the person enabling this option 277 // needs to fully understand the potential impact with regards to whatever application 278 // will be consuming the sanitized HTML afterwards, i.e. if you know you put a link in a 279 // data attribute and use that to automatically load some new window then you're giving 280 // the author of a HTML fragment the means to open a malicious destination automatically. 281 // Use with care! 282 func (p *Policy) AllowDataAttributes() { 283 p.allowDataAttributes = true 284 } 285 286 // AllowComments allows comments. 287 // 288 // Please note that only one type of comment will be allowed by this, this is the 289 // the standard HTML comment <!-- --> which includes the use of that to permit 290 // conditionals as per https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)?redirectedfrom=MSDN 291 // 292 // What is not permitted are CDATA XML comments, as the x/net/html package we depend 293 // on does not handle this fully and we are not choosing to take on that work: 294 // https://pkg.go.dev/golang.org/x/net/html#Tokenizer.AllowCDATA . If the x/net/html 295 // package changes this then these will be considered, otherwise if you AllowComments 296 // but provide a CDATA comment, then as per the documentation in x/net/html this will 297 // be treated as a plain HTML comment. 298 func (p *Policy) AllowComments() { 299 p.allowComments = true 300 } 301 302 // AllowNoAttrs says that attributes on element are optional. 303 // 304 // The attribute policy is only added to the core policy when OnElements(...) 305 // are called. 306 func (p *Policy) AllowNoAttrs() *attrPolicyBuilder { 307 308 p.init() 309 310 abp := attrPolicyBuilder{ 311 p: p, 312 allowEmpty: true, 313 } 314 return &abp 315 } 316 317 // AllowNoAttrs says that attributes on element are optional. 318 // 319 // The attribute policy is only added to the core policy when OnElements(...) 320 // are called. 321 func (abp *attrPolicyBuilder) AllowNoAttrs() *attrPolicyBuilder { 322 323 abp.allowEmpty = true 324 325 return abp 326 } 327 328 // Matching allows a regular expression to be applied to a nascent attribute 329 // policy, and returns the attribute policy. 330 func (abp *attrPolicyBuilder) Matching(regex *regexp.Regexp) *attrPolicyBuilder { 331 332 abp.regexp = regex 333 334 return abp 335 } 336 337 // OnElements will bind an attribute policy to a given range of HTML elements 338 // and return the updated policy 339 func (abp *attrPolicyBuilder) OnElements(elements ...string) *Policy { 340 341 for _, element := range elements { 342 element = strings.ToLower(element) 343 344 for _, attr := range abp.attrNames { 345 346 if _, ok := abp.p.elsAndAttrs[element]; !ok { 347 abp.p.elsAndAttrs[element] = make(map[string][]attrPolicy) 348 } 349 350 ap := attrPolicy{} 351 if abp.regexp != nil { 352 ap.regexp = abp.regexp 353 } 354 355 abp.p.elsAndAttrs[element][attr] = append(abp.p.elsAndAttrs[element][attr], ap) 356 } 357 358 if abp.allowEmpty { 359 abp.p.setOfElementsAllowedWithoutAttrs[element] = struct{}{} 360 361 if _, ok := abp.p.elsAndAttrs[element]; !ok { 362 abp.p.elsAndAttrs[element] = make(map[string][]attrPolicy) 363 } 364 } 365 } 366 367 return abp.p 368 } 369 370 // OnElementsMatching will bind an attribute policy to all elements matching a given regex 371 // and return the updated policy 372 func (abp *attrPolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy { 373 for _, attr := range abp.attrNames { 374 if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok { 375 abp.p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy) 376 } 377 ap := attrPolicy{} 378 if abp.regexp != nil { 379 ap.regexp = abp.regexp 380 } 381 abp.p.elsMatchingAndAttrs[regex][attr] = append(abp.p.elsMatchingAndAttrs[regex][attr], ap) 382 } 383 384 if abp.allowEmpty { 385 abp.p.setOfElementsMatchingAllowedWithoutAttrs = append(abp.p.setOfElementsMatchingAllowedWithoutAttrs, regex) 386 if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok { 387 abp.p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy) 388 } 389 } 390 391 return abp.p 392 } 393 394 // Globally will bind an attribute policy to all HTML elements and return the 395 // updated policy 396 func (abp *attrPolicyBuilder) Globally() *Policy { 397 398 for _, attr := range abp.attrNames { 399 if _, ok := abp.p.globalAttrs[attr]; !ok { 400 abp.p.globalAttrs[attr] = []attrPolicy{} 401 } 402 403 ap := attrPolicy{} 404 if abp.regexp != nil { 405 ap.regexp = abp.regexp 406 } 407 408 abp.p.globalAttrs[attr] = append(abp.p.globalAttrs[attr], ap) 409 } 410 411 return abp.p 412 } 413 414 // AllowStyles takes a range of CSS property names and returns a 415 // style policy builder that allows you to specify the pattern and scope of 416 // the allowed property. 417 // 418 // The style policy is only added to the core policy when either Globally() 419 // or OnElements(...) are called. 420 func (p *Policy) AllowStyles(propertyNames ...string) *stylePolicyBuilder { 421 422 p.init() 423 424 abp := stylePolicyBuilder{ 425 p: p, 426 } 427 428 for _, propertyName := range propertyNames { 429 abp.propertyNames = append(abp.propertyNames, strings.ToLower(propertyName)) 430 } 431 432 return &abp 433 } 434 435 // Matching allows a regular expression to be applied to a nascent style 436 // policy, and returns the style policy. 437 func (spb *stylePolicyBuilder) Matching(regex *regexp.Regexp) *stylePolicyBuilder { 438 439 spb.regexp = regex 440 441 return spb 442 } 443 444 // MatchingEnum allows a list of allowed values to be applied to a nascent style 445 // policy, and returns the style policy. 446 func (spb *stylePolicyBuilder) MatchingEnum(enum ...string) *stylePolicyBuilder { 447 448 spb.enum = enum 449 450 return spb 451 } 452 453 // MatchingHandler allows a handler to be applied to a nascent style 454 // policy, and returns the style policy. 455 func (spb *stylePolicyBuilder) MatchingHandler(handler func(string) bool) *stylePolicyBuilder { 456 457 spb.handler = handler 458 459 return spb 460 } 461 462 // OnElements will bind a style policy to a given range of HTML elements 463 // and return the updated policy 464 func (spb *stylePolicyBuilder) OnElements(elements ...string) *Policy { 465 466 for _, element := range elements { 467 element = strings.ToLower(element) 468 469 for _, attr := range spb.propertyNames { 470 471 if _, ok := spb.p.elsAndStyles[element]; !ok { 472 spb.p.elsAndStyles[element] = make(map[string][]stylePolicy) 473 } 474 475 sp := stylePolicy{} 476 if spb.handler != nil { 477 sp.handler = spb.handler 478 } else if len(spb.enum) > 0 { 479 sp.enum = spb.enum 480 } else if spb.regexp != nil { 481 sp.regexp = spb.regexp 482 } else { 483 sp.handler = css.GetDefaultHandler(attr) 484 } 485 spb.p.elsAndStyles[element][attr] = append(spb.p.elsAndStyles[element][attr], sp) 486 } 487 } 488 489 return spb.p 490 } 491 492 // OnElementsMatching will bind a style policy to any HTML elements matching the pattern 493 // and return the updated policy 494 func (spb *stylePolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy { 495 496 for _, attr := range spb.propertyNames { 497 498 if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok { 499 spb.p.elsMatchingAndStyles[regex] = make(map[string][]stylePolicy) 500 } 501 502 sp := stylePolicy{} 503 if spb.handler != nil { 504 sp.handler = spb.handler 505 } else if len(spb.enum) > 0 { 506 sp.enum = spb.enum 507 } else if spb.regexp != nil { 508 sp.regexp = spb.regexp 509 } else { 510 sp.handler = css.GetDefaultHandler(attr) 511 } 512 spb.p.elsMatchingAndStyles[regex][attr] = append(spb.p.elsMatchingAndStyles[regex][attr], sp) 513 } 514 515 return spb.p 516 } 517 518 // Globally will bind a style policy to all HTML elements and return the 519 // updated policy 520 func (spb *stylePolicyBuilder) Globally() *Policy { 521 522 for _, attr := range spb.propertyNames { 523 if _, ok := spb.p.globalStyles[attr]; !ok { 524 spb.p.globalStyles[attr] = []stylePolicy{} 525 } 526 527 // Use only one strategy for validating styles, fallback to default 528 sp := stylePolicy{} 529 if spb.handler != nil { 530 sp.handler = spb.handler 531 } else if len(spb.enum) > 0 { 532 sp.enum = spb.enum 533 } else if spb.regexp != nil { 534 sp.regexp = spb.regexp 535 } else { 536 sp.handler = css.GetDefaultHandler(attr) 537 } 538 spb.p.globalStyles[attr] = append(spb.p.globalStyles[attr], sp) 539 } 540 541 return spb.p 542 } 543 544 // AllowElements will append HTML elements to the allowlist without applying an 545 // attribute policy to those elements (the elements are permitted 546 // sans-attributes) 547 func (p *Policy) AllowElements(names ...string) *Policy { 548 p.init() 549 550 for _, element := range names { 551 element = strings.ToLower(element) 552 553 if _, ok := p.elsAndAttrs[element]; !ok { 554 p.elsAndAttrs[element] = make(map[string][]attrPolicy) 555 } 556 } 557 558 return p 559 } 560 561 // AllowElementsMatching will append HTML elements to the allowlist if they 562 // match a regexp. 563 func (p *Policy) AllowElementsMatching(regex *regexp.Regexp) *Policy { 564 p.init() 565 if _, ok := p.elsMatchingAndAttrs[regex]; !ok { 566 p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy) 567 } 568 return p 569 } 570 571 // AllowURLSchemesMatching will append URL schemes to the allowlist if they 572 // match a regexp. 573 func (p *Policy) AllowURLSchemesMatching(r *regexp.Regexp) *Policy { 574 p.allowURLSchemeRegexps = append(p.allowURLSchemeRegexps, r) 575 return p 576 } 577 578 // RequireNoFollowOnLinks will result in all a, area, link tags having a 579 // rel="nofollow"added to them if one does not already exist 580 // 581 // Note: This requires p.RequireParseableURLs(true) and will enable it. 582 func (p *Policy) RequireNoFollowOnLinks(require bool) *Policy { 583 584 p.requireNoFollow = require 585 p.requireParseableURLs = true 586 587 return p 588 } 589 590 // RequireNoFollowOnFullyQualifiedLinks will result in all a, area, and link 591 // tags that point to a non-local destination (i.e. starts with a protocol and 592 // has a host) having a rel="nofollow" added to them if one does not already 593 // exist 594 // 595 // Note: This requires p.RequireParseableURLs(true) and will enable it. 596 func (p *Policy) RequireNoFollowOnFullyQualifiedLinks(require bool) *Policy { 597 598 p.requireNoFollowFullyQualifiedLinks = require 599 p.requireParseableURLs = true 600 601 return p 602 } 603 604 // RequireNoReferrerOnLinks will result in all a, area, and link tags having a 605 // rel="noreferrrer" added to them if one does not already exist 606 // 607 // Note: This requires p.RequireParseableURLs(true) and will enable it. 608 func (p *Policy) RequireNoReferrerOnLinks(require bool) *Policy { 609 610 p.requireNoReferrer = require 611 p.requireParseableURLs = true 612 613 return p 614 } 615 616 // RequireNoReferrerOnFullyQualifiedLinks will result in all a, area, and link 617 // tags that point to a non-local destination (i.e. starts with a protocol and 618 // has a host) having a rel="noreferrer" added to them if one does not already 619 // exist 620 // 621 // Note: This requires p.RequireParseableURLs(true) and will enable it. 622 func (p *Policy) RequireNoReferrerOnFullyQualifiedLinks(require bool) *Policy { 623 624 p.requireNoReferrerFullyQualifiedLinks = require 625 p.requireParseableURLs = true 626 627 return p 628 } 629 630 // RequireCrossOriginAnonymous will result in all audio, img, link, script, and 631 // video tags having a crossorigin="anonymous" added to them if one does not 632 // already exist 633 func (p *Policy) RequireCrossOriginAnonymous(require bool) *Policy { 634 635 p.requireCrossOriginAnonymous = require 636 637 return p 638 } 639 640 // AddTargetBlankToFullyQualifiedLinks will result in all a, area and link tags 641 // that point to a non-local destination (i.e. starts with a protocol and has a 642 // host) having a target="_blank" added to them if one does not already exist 643 // 644 // Note: This requires p.RequireParseableURLs(true) and will enable it. 645 func (p *Policy) AddTargetBlankToFullyQualifiedLinks(require bool) *Policy { 646 647 p.addTargetBlankToFullyQualifiedLinks = require 648 p.requireParseableURLs = true 649 650 return p 651 } 652 653 // RequireParseableURLs will result in all URLs requiring that they be parseable 654 // by "net/url" url.Parse() 655 // This applies to: 656 // - a.href 657 // - area.href 658 // - blockquote.cite 659 // - img.src 660 // - link.href 661 // - script.src 662 func (p *Policy) RequireParseableURLs(require bool) *Policy { 663 664 p.requireParseableURLs = require 665 666 return p 667 } 668 669 // AllowRelativeURLs enables RequireParseableURLs and then permits URLs that 670 // are parseable, have no schema information and url.IsAbs() returns false 671 // This permits local URLs 672 func (p *Policy) AllowRelativeURLs(require bool) *Policy { 673 674 p.RequireParseableURLs(true) 675 p.allowRelativeURLs = require 676 677 return p 678 } 679 680 // AllowURLSchemes will append URL schemes to the allowlist 681 // Example: p.AllowURLSchemes("mailto", "http", "https") 682 func (p *Policy) AllowURLSchemes(schemes ...string) *Policy { 683 p.init() 684 685 p.RequireParseableURLs(true) 686 687 for _, scheme := range schemes { 688 scheme = strings.ToLower(scheme) 689 690 // Allow all URLs with matching scheme. 691 p.allowURLSchemes[scheme] = nil 692 } 693 694 return p 695 } 696 697 // AllowURLSchemeWithCustomPolicy will append URL schemes with 698 // a custom URL policy to the allowlist. 699 // Only the URLs with matching schema and urlPolicy(url) 700 // returning true will be allowed. 701 func (p *Policy) AllowURLSchemeWithCustomPolicy( 702 scheme string, 703 urlPolicy func(url *url.URL) (allowUrl bool), 704 ) *Policy { 705 706 p.init() 707 708 p.RequireParseableURLs(true) 709 710 scheme = strings.ToLower(scheme) 711 712 p.allowURLSchemes[scheme] = append(p.allowURLSchemes[scheme], urlPolicy) 713 714 return p 715 } 716 717 // RequireSandboxOnIFrame will result in all iframe tags having a sandbox="" tag 718 // Any sandbox values not specified here will be filtered from the generated HTML 719 func (p *Policy) RequireSandboxOnIFrame(vals ...SandboxValue) { 720 p.requireSandboxOnIFrame = make(map[string]bool) 721 722 for _, val := range vals { 723 switch SandboxValue(val) { 724 case SandboxAllowDownloads: 725 p.requireSandboxOnIFrame["allow-downloads"] = true 726 727 case SandboxAllowDownloadsWithoutUserActivation: 728 p.requireSandboxOnIFrame["allow-downloads-without-user-activation"] = true 729 730 case SandboxAllowForms: 731 p.requireSandboxOnIFrame["allow-forms"] = true 732 733 case SandboxAllowModals: 734 p.requireSandboxOnIFrame["allow-modals"] = true 735 736 case SandboxAllowOrientationLock: 737 p.requireSandboxOnIFrame["allow-orientation-lock"] = true 738 739 case SandboxAllowPointerLock: 740 p.requireSandboxOnIFrame["allow-pointer-lock"] = true 741 742 case SandboxAllowPopups: 743 p.requireSandboxOnIFrame["allow-popups"] = true 744 745 case SandboxAllowPopupsToEscapeSandbox: 746 p.requireSandboxOnIFrame["allow-popups-to-escape-sandbox"] = true 747 748 case SandboxAllowPresentation: 749 p.requireSandboxOnIFrame["allow-presentation"] = true 750 751 case SandboxAllowSameOrigin: 752 p.requireSandboxOnIFrame["allow-same-origin"] = true 753 754 case SandboxAllowScripts: 755 p.requireSandboxOnIFrame["allow-scripts"] = true 756 757 case SandboxAllowStorageAccessByUserActivation: 758 p.requireSandboxOnIFrame["allow-storage-access-by-user-activation"] = true 759 760 case SandboxAllowTopNavigation: 761 p.requireSandboxOnIFrame["allow-top-navigation"] = true 762 763 case SandboxAllowTopNavigationByUserActivation: 764 p.requireSandboxOnIFrame["allow-top-navigation-by-user-activation"] = true 765 } 766 } 767 } 768 769 // AddSpaceWhenStrippingTag states whether to add a single space " " when 770 // removing tags that are not allowed by the policy. 771 // 772 // This is useful if you expect to strip tags in dense markup and may lose the 773 // value of whitespace. 774 // 775 // For example: "<p>Hello</p><p>World</p>"" would be sanitized to "HelloWorld" 776 // with the default value of false, but you may wish to sanitize this to 777 // " Hello World " by setting AddSpaceWhenStrippingTag to true as this would 778 // retain the intent of the text. 779 func (p *Policy) AddSpaceWhenStrippingTag(allow bool) *Policy { 780 781 p.addSpaces = allow 782 783 return p 784 } 785 786 // SkipElementsContent adds the HTML elements whose tags is needed to be removed 787 // with its content. 788 func (p *Policy) SkipElementsContent(names ...string) *Policy { 789 790 p.init() 791 792 for _, element := range names { 793 element = strings.ToLower(element) 794 795 if _, ok := p.setOfElementsToSkipContent[element]; !ok { 796 p.setOfElementsToSkipContent[element] = struct{}{} 797 } 798 } 799 800 return p 801 } 802 803 // AllowElementsContent marks the HTML elements whose content should be 804 // retained after removing the tag. 805 func (p *Policy) AllowElementsContent(names ...string) *Policy { 806 807 p.init() 808 809 for _, element := range names { 810 delete(p.setOfElementsToSkipContent, strings.ToLower(element)) 811 } 812 813 return p 814 } 815 816 // AllowUnsafe permits fundamentally unsafe elements. 817 // 818 // If false (default) then elements such as `style` and `script` will not be 819 // permitted even if declared in a policy. These elements when combined with 820 // untrusted input cannot be safely handled by bluemonday at this point in 821 // time. 822 // 823 // If true then `style` and `script` would be permitted by bluemonday if a 824 // policy declares them. However this is not recommended under any circumstance 825 // and can lead to XSS being rendered thus defeating the purpose of using a 826 // HTML sanitizer. 827 func (p *Policy) AllowUnsafe(allowUnsafe bool) *Policy { 828 p.init() 829 p.allowUnsafe = allowUnsafe 830 return p 831 } 832 833 // addDefaultElementsWithoutAttrs adds the HTML elements that we know are valid 834 // without any attributes to an internal map. 835 // i.e. we know that <table> is valid, but <bdo> isn't valid as the "dir" attr 836 // is mandatory 837 func (p *Policy) addDefaultElementsWithoutAttrs() { 838 p.init() 839 840 p.setOfElementsAllowedWithoutAttrs["abbr"] = struct{}{} 841 p.setOfElementsAllowedWithoutAttrs["acronym"] = struct{}{} 842 p.setOfElementsAllowedWithoutAttrs["address"] = struct{}{} 843 p.setOfElementsAllowedWithoutAttrs["article"] = struct{}{} 844 p.setOfElementsAllowedWithoutAttrs["aside"] = struct{}{} 845 p.setOfElementsAllowedWithoutAttrs["audio"] = struct{}{} 846 p.setOfElementsAllowedWithoutAttrs["b"] = struct{}{} 847 p.setOfElementsAllowedWithoutAttrs["bdi"] = struct{}{} 848 p.setOfElementsAllowedWithoutAttrs["blockquote"] = struct{}{} 849 p.setOfElementsAllowedWithoutAttrs["body"] = struct{}{} 850 p.setOfElementsAllowedWithoutAttrs["br"] = struct{}{} 851 p.setOfElementsAllowedWithoutAttrs["button"] = struct{}{} 852 p.setOfElementsAllowedWithoutAttrs["canvas"] = struct{}{} 853 p.setOfElementsAllowedWithoutAttrs["caption"] = struct{}{} 854 p.setOfElementsAllowedWithoutAttrs["center"] = struct{}{} 855 p.setOfElementsAllowedWithoutAttrs["cite"] = struct{}{} 856 p.setOfElementsAllowedWithoutAttrs["code"] = struct{}{} 857 p.setOfElementsAllowedWithoutAttrs["col"] = struct{}{} 858 p.setOfElementsAllowedWithoutAttrs["colgroup"] = struct{}{} 859 p.setOfElementsAllowedWithoutAttrs["datalist"] = struct{}{} 860 p.setOfElementsAllowedWithoutAttrs["dd"] = struct{}{} 861 p.setOfElementsAllowedWithoutAttrs["del"] = struct{}{} 862 p.setOfElementsAllowedWithoutAttrs["details"] = struct{}{} 863 p.setOfElementsAllowedWithoutAttrs["dfn"] = struct{}{} 864 p.setOfElementsAllowedWithoutAttrs["div"] = struct{}{} 865 p.setOfElementsAllowedWithoutAttrs["dl"] = struct{}{} 866 p.setOfElementsAllowedWithoutAttrs["dt"] = struct{}{} 867 p.setOfElementsAllowedWithoutAttrs["em"] = struct{}{} 868 p.setOfElementsAllowedWithoutAttrs["fieldset"] = struct{}{} 869 p.setOfElementsAllowedWithoutAttrs["figcaption"] = struct{}{} 870 p.setOfElementsAllowedWithoutAttrs["figure"] = struct{}{} 871 p.setOfElementsAllowedWithoutAttrs["footer"] = struct{}{} 872 p.setOfElementsAllowedWithoutAttrs["h1"] = struct{}{} 873 p.setOfElementsAllowedWithoutAttrs["h2"] = struct{}{} 874 p.setOfElementsAllowedWithoutAttrs["h3"] = struct{}{} 875 p.setOfElementsAllowedWithoutAttrs["h4"] = struct{}{} 876 p.setOfElementsAllowedWithoutAttrs["h5"] = struct{}{} 877 p.setOfElementsAllowedWithoutAttrs["h6"] = struct{}{} 878 p.setOfElementsAllowedWithoutAttrs["head"] = struct{}{} 879 p.setOfElementsAllowedWithoutAttrs["header"] = struct{}{} 880 p.setOfElementsAllowedWithoutAttrs["hgroup"] = struct{}{} 881 p.setOfElementsAllowedWithoutAttrs["hr"] = struct{}{} 882 p.setOfElementsAllowedWithoutAttrs["html"] = struct{}{} 883 p.setOfElementsAllowedWithoutAttrs["i"] = struct{}{} 884 p.setOfElementsAllowedWithoutAttrs["ins"] = struct{}{} 885 p.setOfElementsAllowedWithoutAttrs["kbd"] = struct{}{} 886 p.setOfElementsAllowedWithoutAttrs["li"] = struct{}{} 887 p.setOfElementsAllowedWithoutAttrs["mark"] = struct{}{} 888 p.setOfElementsAllowedWithoutAttrs["marquee"] = struct{}{} 889 p.setOfElementsAllowedWithoutAttrs["nav"] = struct{}{} 890 p.setOfElementsAllowedWithoutAttrs["ol"] = struct{}{} 891 p.setOfElementsAllowedWithoutAttrs["optgroup"] = struct{}{} 892 p.setOfElementsAllowedWithoutAttrs["option"] = struct{}{} 893 p.setOfElementsAllowedWithoutAttrs["p"] = struct{}{} 894 p.setOfElementsAllowedWithoutAttrs["picture"] = struct{}{} 895 p.setOfElementsAllowedWithoutAttrs["pre"] = struct{}{} 896 p.setOfElementsAllowedWithoutAttrs["q"] = struct{}{} 897 p.setOfElementsAllowedWithoutAttrs["rp"] = struct{}{} 898 p.setOfElementsAllowedWithoutAttrs["rt"] = struct{}{} 899 p.setOfElementsAllowedWithoutAttrs["ruby"] = struct{}{} 900 p.setOfElementsAllowedWithoutAttrs["s"] = struct{}{} 901 p.setOfElementsAllowedWithoutAttrs["samp"] = struct{}{} 902 p.setOfElementsAllowedWithoutAttrs["script"] = struct{}{} 903 p.setOfElementsAllowedWithoutAttrs["section"] = struct{}{} 904 p.setOfElementsAllowedWithoutAttrs["select"] = struct{}{} 905 p.setOfElementsAllowedWithoutAttrs["small"] = struct{}{} 906 p.setOfElementsAllowedWithoutAttrs["span"] = struct{}{} 907 p.setOfElementsAllowedWithoutAttrs["strike"] = struct{}{} 908 p.setOfElementsAllowedWithoutAttrs["strong"] = struct{}{} 909 p.setOfElementsAllowedWithoutAttrs["style"] = struct{}{} 910 p.setOfElementsAllowedWithoutAttrs["sub"] = struct{}{} 911 p.setOfElementsAllowedWithoutAttrs["summary"] = struct{}{} 912 p.setOfElementsAllowedWithoutAttrs["sup"] = struct{}{} 913 p.setOfElementsAllowedWithoutAttrs["svg"] = struct{}{} 914 p.setOfElementsAllowedWithoutAttrs["table"] = struct{}{} 915 p.setOfElementsAllowedWithoutAttrs["tbody"] = struct{}{} 916 p.setOfElementsAllowedWithoutAttrs["td"] = struct{}{} 917 p.setOfElementsAllowedWithoutAttrs["textarea"] = struct{}{} 918 p.setOfElementsAllowedWithoutAttrs["tfoot"] = struct{}{} 919 p.setOfElementsAllowedWithoutAttrs["th"] = struct{}{} 920 p.setOfElementsAllowedWithoutAttrs["thead"] = struct{}{} 921 p.setOfElementsAllowedWithoutAttrs["title"] = struct{}{} 922 p.setOfElementsAllowedWithoutAttrs["time"] = struct{}{} 923 p.setOfElementsAllowedWithoutAttrs["tr"] = struct{}{} 924 p.setOfElementsAllowedWithoutAttrs["tt"] = struct{}{} 925 p.setOfElementsAllowedWithoutAttrs["u"] = struct{}{} 926 p.setOfElementsAllowedWithoutAttrs["ul"] = struct{}{} 927 p.setOfElementsAllowedWithoutAttrs["var"] = struct{}{} 928 p.setOfElementsAllowedWithoutAttrs["video"] = struct{}{} 929 p.setOfElementsAllowedWithoutAttrs["wbr"] = struct{}{} 930 931 } 932 933 // addDefaultSkipElementContent adds the HTML elements that we should skip 934 // rendering the character content of, if the element itself is not allowed. 935 // This is all character data that the end user would not normally see. 936 // i.e. if we exclude a <script> tag then we shouldn't render the JavaScript or 937 // anything else until we encounter the closing </script> tag. 938 func (p *Policy) addDefaultSkipElementContent() { 939 p.init() 940 941 p.setOfElementsToSkipContent["frame"] = struct{}{} 942 p.setOfElementsToSkipContent["frameset"] = struct{}{} 943 p.setOfElementsToSkipContent["iframe"] = struct{}{} 944 p.setOfElementsToSkipContent["noembed"] = struct{}{} 945 p.setOfElementsToSkipContent["noframes"] = struct{}{} 946 p.setOfElementsToSkipContent["noscript"] = struct{}{} 947 p.setOfElementsToSkipContent["nostyle"] = struct{}{} 948 p.setOfElementsToSkipContent["object"] = struct{}{} 949 p.setOfElementsToSkipContent["script"] = struct{}{} 950 p.setOfElementsToSkipContent["style"] = struct{}{} 951 p.setOfElementsToSkipContent["title"] = struct{}{} 952 }