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&hellip;</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&hellip;</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&hellip;</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>&lt;base&gt;</code> and <code>&lt;link&gt;</code> than on
327   * <code>&lt;a&gt;</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="&lt;CSS&gt;"</code> to simple non-JS containing
492   * <code>&lt;font&gt;</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