• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.List;
32 import java.util.ListIterator;
33 
34 import javax.annotation.Nullable;
35 import javax.annotation.concurrent.NotThreadSafe;
36 
37 import com.google.common.collect.ImmutableMap;
38 import com.google.common.collect.ImmutableSet;
39 import com.google.common.collect.Lists;
40 
41 /**
42  * A sanitizer policy that applies element and attribute policies to tags.
43  */
44 @TCB
45 @NotThreadSafe
46 class 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 
ElementAndAttributePolicyBasedSanitizerPolicy( HtmlStreamEventReceiver out, ImmutableMap<String, ElementAndAttributePolicies> elAndAttrPolicies, ImmutableSet<String> allowedTextContainers)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 
openDocument()76   public void openDocument() {
77     skipText = false;
78     openElementStack.clear();
79     out.openDocument();
80   }
81 
closeDocument()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 
text(String textChunk)94   public void text(String textChunk) {
95     if (!skipText) {
96       out.text(textChunk);
97     }
98   }
99 
openTag(String elementName, List<String> attrs)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 
applyPolicies( String elementName, List<String> attrs, ElementAndAttributePolicies policies)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 
closeTag(String elementName)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 
writeOpenTag( ElementAndAttributePolicies policies, String adjustedElementName, List<String> attrs)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 
deferOpenTag(String elementName)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