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 
29 package org.owasp.html;
30 
31 import java.util.Map;
32 import com.google.common.collect.ImmutableMap;
33 import com.google.common.collect.Maps;
34 import 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
42 final 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 
ElementAndAttributePolicies( String elementName, ElementPolicy elPolicy, Map<? extends String, ? extends AttributePolicy> attrPolicies, boolean skipIfEmpty)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 
and(ElementAndAttributePolicies p)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 
andGlobals( Map<String, AttributePolicy> globalAttrPolicies)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