HtmlPolicyBuilder.java revision 04fec67bccd1004fba68e662ba9709747aa65d30
1// Copyright (c) 2011, Mike Samuel 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions 6// are met: 7// 8// Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// Redistributions in binary form must reproduce the above copyright 11// notice, this list of conditions and the following disclaimer in the 12// documentation and/or other materials provided with the distribution. 13// Neither the name of the OWASP nor the names of its contributors may 14// be used to endorse or promote products derived from this software 15// without specific prior written permission. 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27// POSSIBILITY OF SUCH DAMAGE. 28 29package org.owasp.html; 30 31import java.util.List; 32import java.util.Map; 33import java.util.Set; 34import java.util.regex.Pattern; 35 36import javax.annotation.Nullable; 37import javax.annotation.concurrent.NotThreadSafe; 38 39import com.google.common.base.Function; 40import com.google.common.base.Predicate; 41import com.google.common.collect.ImmutableMap; 42import com.google.common.collect.ImmutableSet; 43import com.google.common.collect.Maps; 44import com.google.common.collect.Sets; 45 46 47/** 48 * Conveniences for configuring policies for the {@link HtmlSanitizer}. 49 * 50 * <h3>Usage</h3> 51 * <p> 52 * To create a policy, first construct an instance of this class; then call 53 * <code>allow…</code> methods to turn on tags, attributes, and other 54 * processing modes; and finally call <code>build()</code> or 55 * <code>toFactory()</code>. 56 * </p> 57 * <pre class="prettyprint lang-java"> 58 * // Define the policy. 59 * Function<HtmlStreamEventReceiver, HtmlSanitizer.Policy> policyDefinition 60 * = new HtmlPolicyBuilder() 61 * .allowElements("a", "p") 62 * .allowAttributesOnElement("a", "href") 63 * .toFactory(); 64 * 65 * // Sanitize your output. 66 * HtmlSanitizer.sanitize(myHtml. policyDefinition.apply(myHtmlStreamRenderer)); 67 * </pre> 68 * 69 * <h3>Embedded Content</h3> 70 * <p> 71 * Embedded URLs are filtered by 72 * {@link HtmlPolicyBuilder#allowUrlProtocols protocol}. 73 * There is a {@link HtmlPolicyBuilder#allowStandardUrlProtocols canned policy} 74 * so you can easily white-list widely used policies that don't violate the 75 * current pages origin. See "Customization" below for ways to do further 76 * filtering. If you allow links it might be worthwhile to 77 * {@link HtmlPolicyBuilder#requireRelNofollowOnLinks() require} 78 * {@code rel=nofollow}. 79 * </p> 80 * <p> 81 * This class simply throws out all embedded JS. 82 * Use a custom element or attribute policy to allow through 83 * signed or otherwise known-safe code. 84 * Check out the Caja project if you need a way to contain third-party JS. 85 * </p> 86 * <p> 87 * This class does not attempt to faithfully parse and sanitize CSS. 88 * It does provide {@link HtmlPolicyBuilder#allowStyling() one} styling option 89 * that allows through a few CSS properties that allow textual styling, but that 90 * disallow image loading, history stealing, layout breaking, code execution, 91 * etc. 92 * </p> 93 * 94 * <h3>Customization</h3> 95 * <p> 96 * You can easily do custom processing on tags and attributes by supplying your 97 * own {@link ElementPolicy element policy} or 98 * {@link AttributePolicy attribute policy} when calling 99 * <code>allow…</code>. 100 * E.g. to convert headers into {@code <div>}s, you could use an element policy 101 * </p> 102 * <pre class="prettyprint lang-java"> 103 * new HtmlPolicyBuilder 104 * .allowElement( 105 * new ElementPolicy() { 106 * public String apply(String elementName, List<String> attributes) { 107 * attributes.add("class"); 108 * attributes.add("header-" + elementName); 109 * return "div"; 110 * } 111 * }, 112 * "h1", "h2", "h3", "h4", "h5", "h6") 113 * .build(outputChannel) 114 * </pre> 115 * 116 * <h3>Rules of Thumb</h3> 117 * <p> 118 * Throughout this class, several rules hold: 119 * <ul> 120 * <li>Everything is denied by default. There are 121 * <code>disallow…</code> methods, but those reverse 122 * allows instead of rolling back overly permissive defaults. 123 * <li>The order of allows and disallows does not matter. 124 * Disallows trump allows whether they occur before or after them. 125 * The only method that needs to be called in a particular place is 126 * {@link HtmlPolicyBuilder#build}. 127 * Allows or disallows after {@code build} is called have no 128 * effect on the already built policy. 129 * <li>Element and attribute policies are applied in the following order: 130 * element specific attribute policy, global attribute policy, element 131 * policy. 132 * Element policies come last so they can observe all the post-processed 133 * attributes, and so they can add attributes that are exempt from 134 * attribute policies. 135 * Element specific policies go first, so they can normalize content to 136 * a form that might be acceptable to a more simplistic global policy. 137 * </ul> 138 * 139 * <h3>Thread safety and efficiency</h3> 140 * <p> 141 * This class is not thread-safe. The resulting policy will not violate its 142 * security guarantees as a result of race conditions, but is not thread safe 143 * because it maintains state to track whether text inside disallowed elements 144 * should be suppressed. 145 * <p> 146 * The resulting policy can be reused, but if you use the 147 * {@link HtmlPolicyBuilder#toFactory()} method instead of {@link #build}, then 148 * binding policies to output channels is cheap so there's no need. 149 * </p> 150 * 151 * @author Mike Samuel <mikesamuel@gmail.com> 152 */ 153@TCB 154@NotThreadSafe 155public class HtmlPolicyBuilder { 156 /** 157 * The default set of elements that are removed if they have no attributes. 158 * Since {@code <img>} is in this set, by default, a policy will remove 159 * {@code <img src=javascript:alert(1337)>} because its URL is not allowed 160 * and it has no other attributes that would warrant it appearing in the 161 * output. 162 */ 163 public static final ImmutableSet<String> DEFAULT_SKIP_IF_EMPTY 164 = ImmutableSet.of("a", "font", "img", "input", "span"); 165 166 private final Map<String, ElementPolicy> elPolicies = Maps.newLinkedHashMap(); 167 private final Map<String, Map<String, AttributePolicy>> attrPolicies 168 = Maps.newLinkedHashMap(); 169 private final Map<String, AttributePolicy> globalAttrPolicies 170 = Maps.newLinkedHashMap(); 171 private final Set<String> allowedProtocols = Sets.newLinkedHashSet(); 172 private final Set<String> skipIfEmpty = Sets.newLinkedHashSet( 173 DEFAULT_SKIP_IF_EMPTY); 174 private boolean requireRelNofollowOnLinks, allowStyling; 175 176 /** 177 * Allows the named elements. 178 */ 179 public HtmlPolicyBuilder allowElements(String... elementName) { 180 return allowElements(ElementPolicy.IDENTITY_ELEMENT_POLICY, elementName); 181 } 182 183 /** 184 * Disallows the named elements. Elements are disallowed by default, so 185 * there is no need to disallow elements, unless you are making an exception 186 * based on an earlier allow. 187 */ 188 public HtmlPolicyBuilder disallowElements(String... elementName) { 189 return allowElements(ElementPolicy.REJECT_ALL_ELEMENT_POLICY, elementName); 190 } 191 192 /** 193 * Allow the given elements with the given policy. 194 * 195 * @param policy May remove or add attributes, change the element name, or 196 * deny the element. 197 */ 198 public HtmlPolicyBuilder allowElements( 199 ElementPolicy policy, String... elementNames) { 200 invalidateCompiledState(); 201 for (String elementName : elementNames) { 202 elementName = HtmlLexer.canonicalName(elementName); 203 ElementPolicy newPolicy = ElementPolicy.Util.join( 204 elPolicies.get(elementName), policy); 205 // Don't remove if newPolicy is the always reject policy since we want 206 // that to infect later allowElement calls for this particular element 207 // name. rejects should have higher priority than allows. 208 elPolicies.put(elementName, newPolicy); 209 } 210 return this; 211 } 212 213 /** 214 * A canned policy that allows a number of common formatting elements. 215 */ 216 public HtmlPolicyBuilder allowCommonInlineFormattingElements() { 217 return allowElements( 218 "b", "i", "font", "s", "u", "o", "sup", "sub", "ins", "del", "strong", 219 "strike", "tt", "code", "big", "small", "br", "span"); 220 } 221 222 /** 223 * A canned policy that allows a number of common block elements. 224 */ 225 public HtmlPolicyBuilder allowCommonBlockElements() { 226 return allowElements( 227 "p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", 228 "blockquote"); 229 } 230 231 /** 232 * Assuming the given elements are allowed, allows them to appear without 233 * attributes. 234 * 235 * @see #DEFAULT_SKIP_IF_EMPTY 236 * @see #disallowWithoutAttributes 237 */ 238 public HtmlPolicyBuilder allowWithoutAttributes(String... elementNames) { 239 invalidateCompiledState(); 240 for (String elementName : elementNames) { 241 elementName = HtmlLexer.canonicalName(elementName); 242 skipIfEmpty.remove(elementName); 243 } 244 return this; 245 } 246 247 /** 248 * Disallows the given elements from appearing without attributes. 249 * 250 * @see #DEFAULT_SKIP_IF_EMPTY 251 * @see #allowWithoutAttributes 252 */ 253 public HtmlPolicyBuilder disallowWithoutAttributes(String... elementNames) { 254 invalidateCompiledState(); 255 for (String elementName : elementNames) { 256 elementName = HtmlLexer.canonicalName(elementName); 257 skipIfEmpty.add(elementName); 258 } 259 return this; 260 } 261 262 /** 263 * Allows the given attributes on any elements. 264 * Be careful of using this with attributes like <code>type</code> which have 265 * different meanings on different attributes. 266 */ 267 public HtmlPolicyBuilder allowAttributesGlobally(String... attributeNames) { 268 return allowAttributesGlobally( 269 AttributePolicy.IDENTITY_ATTRIBUTE_POLICY, attributeNames); 270 } 271 272 /** 273 * Disallows the given attributes on any elements. 274 * Attributes are disallowed unless explicitly allowed, so there is no need 275 * to call this except to reverse an earlier 276 * {@link #allowAttributesGlobally allow}. 277 * Disallowing an attribute globally also disallows it on specific elements. 278 */ 279 public HtmlPolicyBuilder disallowAttributesGlobally( 280 String... attributeNames) { 281 return allowAttributesGlobally( 282 AttributePolicy.REJECT_ALL_ATTRIBUTE_POLICY, attributeNames); 283 } 284 285 /** 286 * Allows the given attributes on any elements as long as the value matches 287 * the pattern. 288 * 289 * @param p A pattern that the attribute value must match. 290 */ 291 public HtmlPolicyBuilder allowAttributesGlobally( 292 final Pattern p, String... attributeNames) { 293 return allowAttributesGlobally( 294 new AttributePolicy() { 295 public @Nullable String apply( 296 String elementName, String attributeName, String value) { 297 return p.matcher(value).matches() ? value : null; 298 } 299 }, attributeNames); 300 } 301 302 /** 303 * Allows the given attributes on any elements as long as the value matches 304 * the predicate. 305 * 306 * @param p A predicate that the attribute value must match. 307 */ 308 public HtmlPolicyBuilder allowAttributesGlobally( 309 final Predicate<? super String> p, String... attributeNames) { 310 return allowAttributesGlobally( 311 new AttributePolicy() { 312 public @Nullable String apply( 313 String elementName, String attributeName, String value) { 314 return p.apply(value) ? value : null; 315 } 316 }, attributeNames); 317 } 318 319 /** 320 * Allows the given attributes on any elements. 321 * Global attribute policies are applied after element specific policies. 322 * Be careful of using this with attributes like <code>type</code> which have 323 * different meanings on different attributes. 324 * Also be careful of allowing globally attributes like <code>href</code> 325 * which can have more far-reaching effects on tags like 326 * <code><base></code> and <code><link></code> than on 327 * <code><a></code> because in the former, they have an effect without 328 * user interaction and can change the behavior of the current page. 329 * 330 * @param policy Can allow, specify a different value for, or deny the 331 * attribute. 332 */ 333 public HtmlPolicyBuilder allowAttributesGlobally( 334 AttributePolicy policy, String... attributeNames) { 335 invalidateCompiledState(); 336 for (String attributeName : attributeNames) { 337 attributeName = HtmlLexer.canonicalName(attributeName); 338 // We reinterpret the identity policy later via policy joining since its 339 // the default passed from the policy-less method, but we don't do 340 // anything here since we don't know until build() is called whether the 341 // policy author wants to allow certain URL protocols or wants to deal 342 // with styles. 343 AttributePolicy oldPolicy = globalAttrPolicies.get(attributeName); 344 globalAttrPolicies.put( 345 attributeName, AttributePolicy.Util.join(oldPolicy, policy)); 346 } 347 return this; 348 } 349 350 /** 351 * Allows the named attributes on the given element. 352 */ 353 public HtmlPolicyBuilder allowAttributesOnElement( 354 String elementName, String... attributeNames) { 355 return allowAttributesOnElement( 356 AttributePolicy.IDENTITY_ATTRIBUTE_POLICY, elementName, attributeNames); 357 } 358 359 /** 360 * Allows the given attributes on any elements as long as the value matches 361 * the pattern. 362 * 363 * @param p A pattern that the attribute value must match. 364 */ 365 public HtmlPolicyBuilder allowAttributesOnElement( 366 final Pattern p, String elementName, String... attributeNames) { 367 return allowAttributesOnElement( 368 new AttributePolicy() { 369 public @Nullable String apply( 370 String elementName, String attributeName, String value) { 371 return p.matcher(value).matches() ? value : null; 372 } 373 }, elementName, attributeNames); 374 } 375 376 /** 377 * Allows the given attributes on the given element as long as the value 378 * matches the predicate. 379 * 380 * @param p A predicate that the attribute value must match. 381 */ 382 public HtmlPolicyBuilder allowAttributesOnElement( 383 final Predicate<? super String> p, String elementName, 384 String... attributeNames) { 385 return allowAttributesOnElement( 386 new AttributePolicy() { 387 public @Nullable String apply( 388 String elementName, String attributeName, String value) { 389 return p.apply(value) ? value : null; 390 } 391 }, elementName, attributeNames); 392 } 393 394 /** 395 * Allows the named attributes on the given element. 396 * 397 * @param policy Can allow, specify a different value for, or deny the 398 * attribute. 399 */ 400 public HtmlPolicyBuilder allowAttributesOnElement( 401 AttributePolicy policy, String elementName, String... attributeNames) { 402 invalidateCompiledState(); 403 elementName = HtmlLexer.canonicalName(elementName); 404 Map<String, AttributePolicy> policies = attrPolicies.get(elementName); 405 if (policies == null) { 406 policies = Maps.newLinkedHashMap(); 407 attrPolicies.put(elementName, policies); 408 } 409 for (String attributeName : attributeNames) { 410 attributeName = HtmlLexer.canonicalName(attributeName); 411 AttributePolicy oldPolicy = policies.get(attributeName); 412 policies.put( 413 attributeName, 414 AttributePolicy.Util.join(oldPolicy, policy)); 415 } 416 return this; 417 } 418 419 /** 420 * Reverse an earlier element-specific attribute 421 * {@link #allowAttributesOnElement allow}. 422 * <p> 423 * Attributes are disallowed by default, so there is no need to call this 424 * with a laundry list of attribute/element pairs. 425 */ 426 public HtmlPolicyBuilder disallowAttributesOnElement( 427 String elementName, String... attributeNames) { 428 return allowAttributesOnElement( 429 AttributePolicy.REJECT_ALL_ATTRIBUTE_POLICY, 430 elementName, attributeNames); 431 } 432 433 /** 434 * Adds <a href="http://en.wikipedia.org/wiki/Nofollow"><code>rel=nofollow</code></a> 435 * to links. 436 */ 437 public HtmlPolicyBuilder requireRelNofollowOnLinks() { 438 invalidateCompiledState(); 439 this.requireRelNofollowOnLinks = true; 440 return this; 441 } 442 443 /** 444 * Adds to the set of protocols that are allowed in URL attributes. 445 * For each URL attribute that is allowed, we further constrain it by 446 * only allowing the value through if it specifies no protocol, or if it 447 * specifies one in the allowedProtocols white-list. 448 * This is done regardless of whether any protocols have been allowed, so 449 * allowing the attribute "href" globally with the identity policy but 450 * not white-listing any protocols, effectively disallows the "href" 451 * attribute globally. 452 * <p> 453 * Do not allow any <code>*script</code> such as <code>javascript</code> 454 * protocols if you might use this policy with untrusted code. 455 */ 456 public HtmlPolicyBuilder allowUrlProtocols(String... protocols) { 457 invalidateCompiledState(); 458 // If there is at least one allowed protocol, then allow URLs and 459 // add a filter that checks href and src values. 460 461 // Do not allow href and srcs through otherwise, and only allow on images 462 // and links. 463 for (String protocol : protocols) { 464 protocol = Strings.toLowerCase(protocol); 465 allowedProtocols.add(protocol); 466 } 467 return this; 468 } 469 470 /** 471 * Reverses a decision made by {@link #allowUrlProtocols}. 472 */ 473 public HtmlPolicyBuilder disallowUrlProtocols(String... protocols) { 474 invalidateCompiledState(); 475 for (String protocol : protocols) { 476 protocol = Strings.toLowerCase(protocol); 477 allowedProtocols.remove(protocol); 478 } 479 return this; 480 } 481 482 /** 483 * A canned URL protocol policy that allows <code>http</code>, 484 * <code>https</code>, and <code>mailto</code>. 485 */ 486 public HtmlPolicyBuilder allowStandardUrlProtocols() { 487 return allowUrlProtocols("http", "https", "mailto"); 488 } 489 490 /** 491 * Convert <code>style="<CSS>"</code> to simple non-JS containing 492 * <code><font></code> tags to allow color, font-size, typeface, and 493 * other styling. 494 */ 495 public HtmlPolicyBuilder allowStyling() { 496 invalidateCompiledState(); 497 allowStyling = true; 498 return this; 499 } 500 501 /** 502 * Names of attributes from HTML 4 whose values are URLs. 503 * Other attributes, e.g. <code>style</code> may contain URLs even though 504 * there values are not URLs. 505 */ 506 private static final Set<String> URL_ATTRIBUTE_NAMES = ImmutableSet.of( 507 "action", "archive", "background", "cite", "classid", "codebase", "data", 508 "dsync", "href", "longdesc", "src", "usemap"); 509 510 /** 511 * Produces a policy based on the allow and disallow calls previously made. 512 * 513 * @param out receives calls to open only tags allowed by 514 * previous calls to this object. 515 * Typically a {@link HtmlStreamRenderer}. 516 */ 517 public HtmlSanitizer.Policy build(HtmlStreamEventReceiver out) { 518 return toFactory().apply(out); 519 } 520 521 /** 522 * Like {@link #build} but can be reused to create many different policies 523 * each backed by a different output channel. 524 */ 525 public Function<HtmlStreamEventReceiver, HtmlSanitizer.Policy> toFactory() { 526 return new Factory(compilePolicies(), allowStyling); 527 } 528 529 // Speed up subsequent builds by caching the compiled policies. 530 private transient ImmutableMap<String, ElementAndAttributePolicies> 531 compiledPolicies; 532 533 /** Called by mutators to signal that any compiled policy is out-of-date. */ 534 private void invalidateCompiledState() { 535 compiledPolicies = null; 536 } 537 538 private ImmutableMap<String, ElementAndAttributePolicies> compilePolicies() { 539 if (compiledPolicies != null) { return compiledPolicies; } 540 541 // Copy maps before normalizing in case builder is reused. 542 Map<String, ElementPolicy> elPolicies 543 = Maps.newLinkedHashMap(this.elPolicies); 544 Map<String, Map<String, AttributePolicy>> attrPolicies 545 = Maps.newLinkedHashMap(this.attrPolicies); 546 for (Map.Entry<String, Map<String, AttributePolicy>> e : 547 attrPolicies.entrySet()) { 548 e.setValue(Maps.newLinkedHashMap(e.getValue())); 549 } 550 Map<String, AttributePolicy> globalAttrPolicies 551 = Maps.newLinkedHashMap(this.globalAttrPolicies); 552 Set<String> allowedProtocols = ImmutableSet.copyOf(this.allowedProtocols); 553 554 // Implement requireRelNofollowOnLinks 555 if (requireRelNofollowOnLinks) { 556 elPolicies.put( 557 "a", 558 ElementPolicy.Util.join( 559 elPolicies.get("a"), 560 new ElementPolicy() { 561 public String apply(String elementName, List<String> attrs) { 562 for (int i = 0, n = attrs.size(); i < n; i += 2) { 563 if ("href".equals(attrs.get(i))) { 564 attrs.add("rel"); 565 attrs.add("nofollow"); 566 break; 567 } 568 } 569 return elementName; 570 } 571 })); 572 } 573 574 // Implement protocol policies. 575 // For each URL attribute that is allowed, we further constrain it by 576 // only allowing the value through if it specifies no protocol, or if it 577 // specifies one in the allowedProtocols white-list. 578 // This is done regardless of whether any protocols have been allowed, so 579 // allowing the attribute "href" globally with the identity policy but 580 // not white-listing any protocols, effectively disallows the "href" 581 // attribute globally. 582 { 583 AttributePolicy urlAttributePolicy; 584 if (allowedProtocols.size() == 3 585 && allowedProtocols.contains("mailto") 586 && allowedProtocols.contains("http") 587 && allowedProtocols.contains("https")) { 588 urlAttributePolicy = StandardUrlAttributePolicy.INSTANCE; 589 } else { 590 urlAttributePolicy = new FilterUrlByProtocolAttributePolicy( 591 allowedProtocols); 592 } 593 Set<String> toGuard = Sets.newLinkedHashSet(URL_ATTRIBUTE_NAMES); 594 for (String urlAttributeName : URL_ATTRIBUTE_NAMES) { 595 if (globalAttrPolicies.containsKey(urlAttributeName)) { 596 toGuard.remove(urlAttributeName); 597 globalAttrPolicies.put(urlAttributeName, AttributePolicy.Util.join( 598 urlAttributePolicy, globalAttrPolicies.get(urlAttributeName))); 599 } 600 } 601 // Implement guards not implemented on global policies in the per-element 602 // policy maps. 603 for (Map.Entry<String, Map<String, AttributePolicy>> e 604 : attrPolicies.entrySet()) { 605 Map<String, AttributePolicy> policies = e.getValue(); 606 for (String urlAttributeName : toGuard) { 607 if (policies.containsKey(urlAttributeName)) { 608 policies.put(urlAttributeName, AttributePolicy.Util.join( 609 urlAttributePolicy, policies.get(urlAttributeName))); 610 } 611 } 612 } 613 } 614 615 ImmutableMap.Builder<String, ElementAndAttributePolicies> policiesBuilder 616 = ImmutableMap.builder(); 617 for (Map.Entry<String, ElementPolicy> e : elPolicies.entrySet()) { 618 String elementName = e.getKey(); 619 ElementPolicy elPolicy = e.getValue(); 620 if (ElementPolicy.REJECT_ALL_ELEMENT_POLICY.equals(elPolicy)) { 621 continue; 622 } 623 624 Map<String, AttributePolicy> elAttrPolicies 625 = attrPolicies.get(elementName); 626 if (elAttrPolicies == null) { elAttrPolicies = ImmutableMap.of(); } 627 ImmutableMap.Builder<String, AttributePolicy> attrs 628 = ImmutableMap.builder(); 629 for (Map.Entry<String, AttributePolicy> ape : elAttrPolicies.entrySet()) { 630 String attributeName = ape.getKey(); 631 if (globalAttrPolicies.containsKey(attributeName)) { continue; } 632 AttributePolicy policy = ape.getValue(); 633 if (!AttributePolicy.REJECT_ALL_ATTRIBUTE_POLICY.equals(policy)) { 634 attrs.put(attributeName, policy); 635 } 636 } 637 for (Map.Entry<String, AttributePolicy> ape 638 : globalAttrPolicies.entrySet()) { 639 String attributeName = ape.getKey(); 640 AttributePolicy policy = AttributePolicy.Util.join( 641 elAttrPolicies.get(attributeName), ape.getValue()); 642 if (!AttributePolicy.REJECT_ALL_ATTRIBUTE_POLICY.equals(policy)) { 643 attrs.put(attributeName, policy); 644 } 645 } 646 647 policiesBuilder.put( 648 elementName, 649 new ElementAndAttributePolicies( 650 elementName, 651 elPolicy, attrs.build(), skipIfEmpty.contains(elementName))); 652 } 653 return compiledPolicies = policiesBuilder.build(); 654 } 655} 656 657final class Factory 658 implements Function<HtmlStreamEventReceiver, HtmlSanitizer.Policy> { 659 private final ImmutableMap<String, ElementAndAttributePolicies> policies; 660 private final boolean allowStyling; 661 662 Factory( 663 ImmutableMap<String, ElementAndAttributePolicies> policies, 664 boolean allowStyling) { 665 this.policies = policies; 666 this.allowStyling = allowStyling; 667 } 668 669 public HtmlSanitizer.Policy apply(HtmlStreamEventReceiver out) { 670 if (allowStyling) { 671 return new StylingPolicy(out, policies); 672 } else { 673 return new ElementAndAttributePolicyBasedSanitizerPolicy( 674 out, policies); 675 } 676 } 677} 678