• 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.Map;
32 
33 import javax.annotation.Nonnull;
34 import javax.annotation.Nullable;
35 import javax.annotation.concurrent.Immutable;
36 import javax.annotation.concurrent.ThreadSafe;
37 
38 import com.google.common.base.Function;
39 import com.google.common.collect.ImmutableMap;
40 import com.google.common.collect.ImmutableSet;
41 
42 /**
43  * A factory that can be used to link a sanitizer to an output receiver and that
44  * provides a convenient <code>{@link PolicyFactory#sanitize sanitize}</code>
45  * method and a <code>{@link PolicyFactory#and and}</code> method to compose
46  * policies.
47  *
48  * @author Mike Samuel <mikesamuel@gmail.com>
49  */
50 @ThreadSafe
51 @Immutable
52 @TCB
53 public final class PolicyFactory
54     implements Function<HtmlStreamEventReceiver, HtmlSanitizer.Policy> {
55 
56   private final ImmutableMap<String, ElementAndAttributePolicies> policies;
57   private final ImmutableMap<String, AttributePolicy> globalAttrPolicies;
58   private final ImmutableSet<String> textContainers;
59 
PolicyFactory( ImmutableMap<String, ElementAndAttributePolicies> policies, ImmutableSet<String> textContainers, ImmutableMap<String, AttributePolicy> globalAttrPolicies)60   PolicyFactory(
61       ImmutableMap<String, ElementAndAttributePolicies> policies,
62       ImmutableSet<String> textContainers,
63       ImmutableMap<String, AttributePolicy> globalAttrPolicies) {
64     this.policies = policies;
65     this.textContainers = textContainers;
66     this.globalAttrPolicies = globalAttrPolicies;
67   }
68 
69   /** Produces a sanitizer that emits tokens to {@code out}. */
apply(@onnull HtmlStreamEventReceiver out)70   public HtmlSanitizer.Policy apply(@Nonnull HtmlStreamEventReceiver out) {
71     return new ElementAndAttributePolicyBasedSanitizerPolicy(
72         out, policies, textContainers);
73   }
74 
75   /**
76    * Produces a sanitizer that emits tokens to {@code out} and that notifies
77    * any {@code listener} of any dropped tags and attributes.
78    * @param out a renderer that receives approved tokens only.
79    * @param listener if non-null, receives notifications of tags and attributes
80    *     that were rejected by the policy.  This may tie into intrusion
81    *     detection systems.
82    * @param context if {@code (listener != null)} then the context value passed
83    *     with notifications.  This can be used to let the listener know from
84    *     which connection or request the questionable HTML was received.
85    */
apply( HtmlStreamEventReceiver out, @Nullable HtmlChangeListener<CTX> listener, @Nullable CTX context)86   public <CTX> HtmlSanitizer.Policy apply(
87       HtmlStreamEventReceiver out, @Nullable HtmlChangeListener<CTX> listener,
88       @Nullable CTX context) {
89     if (listener == null) {
90       return apply(out);
91     } else {
92       HtmlChangeReporter<CTX> r = new HtmlChangeReporter<CTX>(
93           out, listener, context);
94       r.setPolicy(apply(r.getWrappedRenderer()));
95       return r.getWrappedPolicy();
96     }
97   }
98 
99   /** A convenience function that sanitizes a string of HTML. */
sanitize(@ullable String html)100   public String sanitize(@Nullable String html) {
101     return sanitize(html, null, null);
102   }
103 
104   /**
105    * A convenience function that sanitizes a string of HTML and reports
106    * the names of rejected element and attributes to listener.
107    * @param html the string of HTML to sanitize.
108    * @param listener if non-null, receives notifications of tags and attributes
109    *     that were rejected by the policy.  This may tie into intrusion
110    *     detection systems.
111    * @param context if {@code (listener != null)} then the context value passed
112    *     with notifications.  This can be used to let the listener know from
113    *     which connection or request the questionable HTML was received.
114    * @return a string of HTML that complies with this factory's policy.
115    */
sanitize( @ullable String html, @Nullable HtmlChangeListener<CTX> listener, @Nullable CTX context)116   public <CTX> String sanitize(
117       @Nullable String html,
118       @Nullable HtmlChangeListener<CTX> listener, @Nullable CTX context) {
119     if (html == null) { return ""; }
120     StringBuilder out = new StringBuilder(html.length());
121     HtmlSanitizer.sanitize(
122         html,
123         apply(HtmlStreamRenderer.create(out, Handler.DO_NOTHING),
124               listener, context));
125     return out.toString();
126   }
127 
128   /**
129    * Produces a factory that allows the union of the grants, and intersects
130    * policies where they overlap on a particular granted attribute or element
131    * name.
132    */
and(PolicyFactory f)133   public PolicyFactory and(PolicyFactory f) {
134     ImmutableMap.Builder<String, ElementAndAttributePolicies> b
135         = ImmutableMap.builder();
136     // Merge this and f into a map of element names to attribute policies.
137     for (Map.Entry<String, ElementAndAttributePolicies> e
138         : policies.entrySet()) {
139       String elName = e.getKey();
140       ElementAndAttributePolicies p = e.getValue();
141       ElementAndAttributePolicies q = f.policies.get(elName);
142       if (q != null) {
143         p = p.and(q);
144       } else {
145         // Mix in any globals that are not already taken into account in this.
146         p = p.andGlobals(f.globalAttrPolicies);
147       }
148       b.put(elName, p);
149     }
150     // Handle keys that are in f but not in this.
151     for (Map.Entry<String, ElementAndAttributePolicies> e
152         : f.policies.entrySet()) {
153       String elName = e.getKey();
154       if (!policies.containsKey(elName)) {
155         ElementAndAttributePolicies p = e.getValue();
156         // Mix in any globals that are not already taken into account in this.
157         p = p.andGlobals(f.globalAttrPolicies);
158         b.put(elName, p);
159       }
160     }
161     ImmutableSet<String> textContainers;
162     if (this.textContainers.containsAll(f.textContainers)) {
163       textContainers = this.textContainers;
164     } else if (f.textContainers.containsAll(this.textContainers)) {
165       textContainers = f.textContainers;
166     } else {
167       textContainers = ImmutableSet.<String>builder()
168         .addAll(this.textContainers)
169         .addAll(f.textContainers)
170         .build();
171     }
172     ImmutableMap<String, AttributePolicy> allGlobalAttrPolicies;
173     if (f.globalAttrPolicies.isEmpty()) {
174       allGlobalAttrPolicies = this.globalAttrPolicies;
175     } else if (this.globalAttrPolicies.isEmpty()) {
176       allGlobalAttrPolicies = f.globalAttrPolicies;
177     } else {
178       ImmutableMap.Builder<String, AttributePolicy> ab = ImmutableMap.builder();
179       for (Map.Entry<String, AttributePolicy> e
180           : this.globalAttrPolicies.entrySet()) {
181         String attrName = e.getKey();
182         ab.put(
183             attrName,
184             AttributePolicy.Util.join(
185                 e.getValue(), f.globalAttrPolicies.get(attrName)));
186       }
187       for (Map.Entry<String, AttributePolicy> e
188           : f.globalAttrPolicies.entrySet()) {
189         String attrName = e.getKey();
190         if (!this.globalAttrPolicies.containsKey(attrName)) {
191           ab.put(attrName, e.getValue());
192         }
193       }
194       allGlobalAttrPolicies = ab.build();
195     }
196     return new PolicyFactory(b.build(), textContainers, allGlobalAttrPolicies);
197   }
198 }
199