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.Map;
32import com.google.common.collect.ImmutableMap;
33import com.google.common.collect.Maps;
34import javax.annotation.concurrent.Immutable;
35
36/**
37 * Encapsulates all the information needed by the
38 * {@link ElementAndAttributePolicyBasedSanitizerPolicy} to sanitize one kind
39 * of element.
40 */
41@Immutable
42final class ElementAndAttributePolicies {
43  final String elementName;
44  final boolean isVoid;
45  final ElementPolicy elPolicy;
46  final ImmutableMap<String, AttributePolicy> attrPolicies;
47  final boolean skipIfEmpty;
48
49  ElementAndAttributePolicies(
50      String elementName,
51      ElementPolicy elPolicy,
52      Map<? extends String, ? extends AttributePolicy>
53        attrPolicies,
54      boolean skipIfEmpty) {
55    this.elementName = elementName;
56    this.isVoid = HtmlTextEscapingMode.isVoidElement(elementName);
57    this.elPolicy = elPolicy;
58    this.attrPolicies = ImmutableMap.copyOf(attrPolicies);
59    this.skipIfEmpty = skipIfEmpty;
60  }
61
62  ElementAndAttributePolicies and(ElementAndAttributePolicies p) {
63    assert elementName.equals(p.elementName):
64      elementName + " != " + p.elementName;
65    ImmutableMap.Builder<String, AttributePolicy> joinedAttrPolicies
66        = ImmutableMap.builder();
67    for (Map.Entry<String, AttributePolicy> e : this.attrPolicies.entrySet()) {
68      String attrName = e.getKey();
69      AttributePolicy a = e.getValue();
70      AttributePolicy b = p.attrPolicies.get(attrName);
71      if (b != null) {
72        a = AttributePolicy.Util.join(a, b);
73      }
74      joinedAttrPolicies.put(attrName, a);
75    }
76    for (Map.Entry<String, AttributePolicy> e : p.attrPolicies.entrySet()) {
77      String attrName = e.getKey();
78      if (!this.attrPolicies.containsKey(attrName)) {
79        joinedAttrPolicies.put(attrName, e.getValue());
80      }
81    }
82
83    // HACK: this is attempting to recognize when skipIfEmpty has been
84    // explicitly set in HtmlPolicyBuilder and can only make a best effort at
85    // that and is also too tightly coupled with HtmlPolicyBuilder.
86    // Maybe go tri-state.
87    boolean combinedSkipIfEmpty;
88    if (HtmlPolicyBuilder.DEFAULT_SKIP_IF_EMPTY.contains(elementName)) {
89      // Either policy explicitly opted out of skip if empty.
90      combinedSkipIfEmpty = skipIfEmpty && p.skipIfEmpty;
91    } else {
92      // Either policy explicitly specified skip if empty.
93      combinedSkipIfEmpty = skipIfEmpty || p.skipIfEmpty;
94    }
95
96    return new ElementAndAttributePolicies(
97        elementName,
98        ElementPolicy.Util.join(elPolicy, p.elPolicy),
99        joinedAttrPolicies.build(),
100        combinedSkipIfEmpty);
101  }
102
103  ElementAndAttributePolicies andGlobals(
104      Map<String, AttributePolicy> globalAttrPolicies) {
105    if (globalAttrPolicies.isEmpty()) { return this; }
106    Map<String, AttributePolicy> anded = null;
107    for (Map.Entry<String, AttributePolicy> e : this.attrPolicies.entrySet()) {
108      String attrName = e.getKey();
109      AttributePolicy globalAttrPolicy = globalAttrPolicies.get(attrName);
110      if (globalAttrPolicy != null) {
111        AttributePolicy attrPolicy = e.getValue();
112        AttributePolicy joined = AttributePolicy.Util.join(
113            attrPolicy, globalAttrPolicy);
114        if (!joined.equals(attrPolicy)) {
115          if (anded == null) {
116            anded = Maps.newLinkedHashMap();
117            anded.putAll(this.attrPolicies);
118          }
119          anded.put(attrName, joined);
120        }
121      }
122    }
123    for (Map.Entry<String, AttributePolicy> e : globalAttrPolicies.entrySet()) {
124      String attrName = e.getKey();
125      if (!this.attrPolicies.containsKey(attrName)) {
126        if (anded == null) {
127          anded = Maps.newLinkedHashMap();
128          anded.putAll(this.attrPolicies);
129        }
130        anded.put(attrName, e.getValue());
131      }
132    }
133    if (anded == null) { return this; }
134    return new ElementAndAttributePolicies(
135        elementName, elPolicy, anded, skipIfEmpty);
136  }
137
138}
139