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.ListIterator; 33 34import javax.annotation.Nullable; 35import javax.annotation.concurrent.NotThreadSafe; 36 37import com.google.common.collect.ImmutableMap; 38import com.google.common.collect.ImmutableSet; 39import com.google.common.collect.Lists; 40 41/** 42 * A sanitizer policy that applies element and attribute policies to tags. 43 */ 44@TCB 45@NotThreadSafe 46class ElementAndAttributePolicyBasedSanitizerPolicy 47 implements HtmlSanitizer.Policy { 48 final ImmutableMap<String, ElementAndAttributePolicies> elAndAttrPolicies; 49 final ImmutableSet<String> allowedTextContainers; 50 private final HtmlStreamEventReceiver out; 51 /** 52 * True to skip textual content. Used to ignore the content of embedded CDATA 53 * content that is not meant to be human-readable. 54 */ 55 transient boolean skipText = true; 56 /** 57 * Alternating input names and adjusted names of elements opened by the 58 * caller. 59 */ 60 private final List<String> openElementStack = Lists.newArrayList(); 61 62 ElementAndAttributePolicyBasedSanitizerPolicy( 63 HtmlStreamEventReceiver out, 64 ImmutableMap<String, ElementAndAttributePolicies> elAndAttrPolicies, 65 ImmutableSet<String> allowedTextContainers) { 66 this.out = out; 67 this.elAndAttrPolicies = elAndAttrPolicies; 68 this.allowedTextContainers = allowedTextContainers; 69 } 70 71 static final ImmutableSet<String> SKIPPABLE_ELEMENT_CONTENT 72 = ImmutableSet.of( 73 "script", "style", "noscript", "nostyle", "noembed", "noframes", 74 "iframe", "object", "frame", "frameset", "title"); 75 76 public void openDocument() { 77 skipText = false; 78 openElementStack.clear(); 79 out.openDocument(); 80 } 81 82 public void closeDocument() { 83 for (int i = openElementStack.size() - 1; i >= 0; i -= 2) { 84 String tagNameToClose = openElementStack.get(i); 85 if (tagNameToClose != null) { 86 out.closeTag(tagNameToClose); 87 } 88 } 89 openElementStack.clear(); 90 skipText = true; 91 out.closeDocument(); 92 } 93 94 public void text(String textChunk) { 95 if (!skipText) { 96 out.text(textChunk); 97 } 98 } 99 100 public void openTag(String elementName, List<String> attrs) { 101 // StylingPolicy repeats some of this code because it is more complicated 102 // to refactor it into multiple method bodies, so if you change this, 103 // check the override of it in that class. 104 ElementAndAttributePolicies policies = elAndAttrPolicies.get(elementName); 105 String adjustedElementName = applyPolicies(elementName, attrs, policies); 106 if (adjustedElementName != null 107 && !(attrs.isEmpty() && policies.skipIfEmpty)) { 108 writeOpenTag(policies, adjustedElementName, attrs); 109 return; 110 } 111 deferOpenTag(elementName); 112 } 113 114 static final @Nullable String applyPolicies( 115 String elementName, List<String> attrs, 116 ElementAndAttributePolicies policies) { 117 String adjustedElementName; 118 if (policies != null) { 119 for (ListIterator<String> attrsIt = attrs.listIterator(); 120 attrsIt.hasNext();) { 121 String name = attrsIt.next(); 122 AttributePolicy attrPolicy 123 = policies.attrPolicies.get(name); 124 if (attrPolicy == null) { 125 attrsIt.remove(); 126 attrsIt.next(); 127 attrsIt.remove(); 128 } else { 129 String value = attrsIt.next(); 130 String adjustedValue = attrPolicy.apply(elementName, name, value); 131 if (adjustedValue == null) { 132 attrsIt.remove(); 133 attrsIt.previous(); 134 attrsIt.remove(); 135 } else { 136 attrsIt.set(adjustedValue); 137 } 138 } 139 } 140 adjustedElementName = policies.elPolicy.apply(elementName, attrs); 141 } else { 142 adjustedElementName = null; 143 } 144 return adjustedElementName; 145 } 146 147 public void closeTag(String elementName) { 148 int n = openElementStack.size(); 149 for (int i = n; i > 0;) { 150 i -= 2; 151 String openElementName = openElementStack.get(i); 152 if (elementName.equals(openElementName)) { 153 for (int j = n - 1; j > i; j -= 2) { 154 String tagNameToClose = openElementStack.get(j); 155 if (tagNameToClose != null) { 156 out.closeTag(tagNameToClose); 157 } 158 } 159 openElementStack.subList(i, n).clear(); 160 break; 161 } 162 } 163 skipText = false; 164 for (int i = openElementStack.size() - 1; i >= 0; i -= 2) { 165 String adjustedName = openElementStack.get(i); 166 if (adjustedName != null) { 167 skipText = !(allowedTextContainers.contains(adjustedName)); 168 break; 169 } 170 } 171 } 172 173 void writeOpenTag( 174 ElementAndAttributePolicies policies, String adjustedElementName, 175 List<String> attrs) { 176 if (!policies.isVoid) { 177 openElementStack.add(policies.elementName); 178 openElementStack.add(adjustedElementName); 179 skipText = !allowedTextContainers.contains(adjustedElementName); 180 } 181 out.openTag(adjustedElementName, attrs); 182 } 183 184 void deferOpenTag(String elementName) { 185 if (HtmlTextEscapingMode.isVoidElement(elementName)) { 186 openElementStack.add(elementName); 187 openElementStack.add(null); 188 } 189 skipText = SKIPPABLE_ELEMENT_CONTENT.contains(elementName); 190 } 191} 192