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