Skip to content

Commit 778d5c2

Browse files
committed
test previously untested global policy merging code in PolicyFactory
1 parent c983b9c commit 778d5c2

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// Copyright (c) 2019, 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.io.IOException;
32+
import java.util.ArrayList;
33+
import java.util.Arrays;
34+
import java.util.List;
35+
36+
import org.junit.Test;
37+
38+
import com.google.common.base.Joiner;
39+
40+
import junit.framework.TestCase;
41+
42+
@SuppressWarnings({ "javadoc" })
43+
public final class PolicyFactoryTest extends TestCase {
44+
45+
@Test
46+
public static void testAnd() {
47+
// Filters srcset to only contain URLs with the substring "foo"
48+
PolicyFactory f = new HtmlPolicyBuilder()
49+
.allowElements("img")
50+
.allowAttributes("srcset")
51+
.matching(new SubstringFilter("foo"))
52+
.globally()
53+
.allowStandardUrlProtocols()
54+
.toFactory();
55+
// Filters srcset to only contain URLs with the substring "bar"
56+
PolicyFactory g = new HtmlPolicyBuilder()
57+
.allowElements("img")
58+
.allowAttributes("srcset")
59+
.matching(new SubstringFilter("bar"))
60+
.globally()
61+
.allowStandardUrlProtocols()
62+
.toFactory();
63+
64+
// The javascript URL will be allowed if the extra policies are not
65+
// preserved.
66+
String html = "<img"
67+
+ " srcset=\"/foo.png , /bar.png , javascript:alert('foobar') , /foobar.png\""
68+
// title is not whitelisted.
69+
+ " title=Hi>!";
70+
71+
PolicyFactory[] factories = {
72+
f,
73+
g,
74+
// Test that .and() intersects regardless of order.
75+
f.and(g),
76+
g.and(f),
77+
};
78+
String[] expectedOutputs = {
79+
// f
80+
"<img srcset=\"/foo.png , /foobar.png\" />",
81+
82+
// g
83+
"<img srcset=\"/bar.png , /foobar.png\" />",
84+
85+
// f and g
86+
"<img srcset=\"/foobar.png\" />",
87+
88+
// g and f
89+
"<img srcset=\"/foobar.png\" />",
90+
};
91+
String[] expectedLogs = {
92+
// f
93+
""
94+
+ "discardedAttributes img, [title]\n"
95+
+ "Handled IOException BANG\n",
96+
97+
// g
98+
""
99+
+ "discardedAttributes img, [title]\n"
100+
+ "Handled IOException BANG\n",
101+
102+
// f and g
103+
""
104+
+ "discardedAttributes img, [title]\n"
105+
+ "Handled IOException BANG\n",
106+
107+
// g and f
108+
""
109+
+ "discardedAttributes img, [title]\n"
110+
+ "Handled IOException BANG\n",
111+
};
112+
113+
for (int i = 0; i < factories.length; ++i) {
114+
PolicyFactory factory = factories[i];
115+
String expectedOutput = expectedOutputs[i];
116+
String expectedLog = expectedLogs[i];
117+
118+
// A dummy value that lets us check that context is properly threaded
119+
// through joined policies.
120+
final Object context = new Object();
121+
// Collect events from callbacks.
122+
final StringBuilder log = new StringBuilder();
123+
// Collects output HTML.
124+
final StringBuilder out = new StringBuilder();
125+
126+
// A noisy listener that logs.
127+
HtmlChangeListener<Object> listener = new HtmlChangeListener<Object>() {
128+
129+
public void discardedTag(Object ctx, String elementName) {
130+
assertEquals(context, ctx);
131+
log.append("discardedTag " + elementName + "\n");
132+
}
133+
134+
public void discardedAttributes(
135+
Object ctx, String tagName, String... attributeNames) {
136+
assertEquals(context, ctx);
137+
log.append(
138+
"discardedAttributes " + tagName
139+
+ ", " + Arrays.asList(attributeNames)
140+
+ "\n");
141+
}
142+
143+
};
144+
145+
Handler<IOException> ioHandler = new Handler<IOException>() {
146+
147+
public void handle(IOException x) {
148+
log.append("Handled IOException " + x.getMessage() + "\n");
149+
}
150+
151+
};
152+
153+
// Should not be called.
154+
Handler<String> badHtmlHandler = new Handler<String>() {
155+
156+
public void handle(String x) {
157+
throw new AssertionError(x);
158+
}
159+
160+
};
161+
162+
// Wraps out to throw when a '!' is written to test the ioHandler.
163+
// There is a '!' at the end of the output.
164+
Appendable throwingOut = new Appendable() {
165+
166+
public Appendable append(CharSequence csq) throws IOException {
167+
return append(csq, 0, csq.length());
168+
}
169+
170+
public Appendable append(CharSequence csq, int start, int end) throws IOException {
171+
for (int j = start; j < end; ++j) {
172+
if (csq.charAt(j) == '!') {
173+
throw new IOException("BANG");
174+
}
175+
}
176+
out.append(csq, start, end);
177+
return this;
178+
}
179+
180+
public Appendable append(char c) throws IOException {
181+
if (c == '!') {
182+
throw new IOException("BANG");
183+
}
184+
out.append(c);
185+
return this;
186+
}
187+
188+
};
189+
190+
HtmlStreamEventReceiver receiver = new HtmlStreamRenderer(
191+
throwingOut, ioHandler, badHtmlHandler);
192+
HtmlSanitizer.Policy policy = factory.apply(
193+
receiver, listener, context);
194+
HtmlSanitizer.sanitize(html, policy);
195+
196+
assertEquals(
197+
"i:" + i,
198+
199+
"Out:\n" + expectedOutput + "\n\nLog:\n" + expectedLog,
200+
"Out:\n" + out + "\n\nLog:\n" + log);
201+
}
202+
}
203+
204+
static final class SubstringFilter implements AttributePolicy {
205+
final String substr;
206+
207+
SubstringFilter(String substr) {
208+
this.substr = substr;
209+
}
210+
211+
public String apply(
212+
String elementName, String attributeName, String value) {
213+
List<String> outParts = new ArrayList<String>();
214+
for (String part : value.split(",")) {
215+
part = part.trim();
216+
if (part.contains(substr)) {
217+
outParts.add(part);
218+
}
219+
}
220+
return Joiner.on(" , ").join(outParts);
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)