blob: 9c2ce0e7a9b8b1c97f31e3434d6bca0a7fac7715 [file] [log] [blame]
// Copyright (c) 2011, Mike Samuel
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the OWASP nor the names of its contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package org.owasp.html;
import java.util.List;
import org.junit.Test;
import com.google.common.base.Joiner;
import junit.framework.TestCase;
public class HtmlPolicyBuilderTest extends TestCase {
static final String EXAMPLE = Joiner.on('\n').join(
"<h1 id='foo'>Header</h1>",
"<p onclick='alert(42)'>Paragraph 1<script>evil()</script></p>",
("<p><a href='java\0script:bad()'>Click</a> <a href='foo.html'>me</a>"
+ " <a href='http://outside.org/'>out</a></p>"),
("<p><img src=canary.png alt=local-canary>" +
"<img src='http://canaries.org/canary.png'></p>"),
"<p><b style=font-size:bigger>Fancy</b> with <i><b>soupy</i> tags</b>.",
"<p style='color: expression(foo()); text-align: center;",
" /* direction: ltr */; font-weight: bold'>Stylish Para 1</p>",
"<p style='color: red; font-weight; expression(foo());",
" direction: rtl; font-weight: bold'>Stylish Para 2</p>",
"");
@Test
public static final void testTextFilter() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click me out",
"",
"Fancy with soupy tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()));
}
@Test
public static final void testCannedFormattingTagFilter() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click me out",
"",
"<b>Fancy</b> with <i><b>soupy</b></i><b> tags</b>.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowCommonInlineFormattingElements()));
}
@Test
public static final void testCannedFormattingTagFilterNoItalics()
throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click me out",
"",
"<b>Fancy</b> with <b>soupy</b><b> tags</b>.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowCommonInlineFormattingElements()
.disallowElements("I")));
}
@Test
public static final void testSimpleTagFilter() throws Exception {
assertEquals(
Joiner.on('\n').join(
"<h1>Header</h1>",
"Paragraph 1",
"Click me out",
"",
"Fancy with <i>soupy</i> tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowElements("h1", "i")));
}
@Test
public static final void testLinksAllowed() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
// We haven't allowed any protocols so only relative URLs are OK.
"Click <a href=\"foo.html\">me</a> out",
"",
"Fancy with soupy tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowElements("a")
.allowAttributes("href").onElements("a")));
}
@Test
public static final void testExternalLinksAllowed() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click <a href=\"foo.html\">me</a>"
+ " <a href=\"http://outside.org/\">out</a>",
"",
"Fancy with soupy tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowElements("a")
// Allows http.
.allowStandardUrlProtocols()
.allowAttributes("href").onElements("a")));
}
@Test
public static final void testLinksWithNofollow() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click <a href=\"foo.html\" rel=\"nofollow\">me</a> out",
"",
"Fancy with soupy tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowElements("a")
// Allows http.
.allowAttributes("href").onElements("a")
.requireRelNofollowOnLinks()));
}
@Test
public static final void testImagesAllowed() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click me out",
"<img src=\"canary.png\" alt=\"local-canary\" />",
// HTTP img not output because only HTTPS allowed.
"Fancy with soupy tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowElements("img")
.allowAttributes("src", "alt").onElements("img")
.allowUrlProtocols("https")));
}
@Test
public static final void testStyleFiltering() throws Exception {
assertEquals(
Joiner.on('\n').join(
"<h1>Header</h1>",
"<p>Paragraph 1</p>",
"<p>Click me out</p>",
"<p></p>",
"<p><b>Fancy</b> with <i><b>soupy</b></i><b> tags</b>.",
("</p><p style=\"text-align:center;font-weight:bold\">"
+ "Stylish Para 1</p>"),
("<p style=\"color:red;direction:rtl;font-weight:bold\">"
+ "Stylish Para 2</p>"),
""),
apply(new HtmlPolicyBuilder()
.allowCommonInlineFormattingElements()
.allowCommonBlockElements()
.allowStyling()
.allowStandardUrlProtocols()));
}
@Test
public static final void testElementTransforming() throws Exception {
assertEquals(
Joiner.on('\n').join(
"<div class=\"header-h1\">Header</div>",
"<p>Paragraph 1</p>",
"<p>Click me out</p>",
"<p></p>",
"<p>Fancy with soupy tags.",
"</p><p>Stylish Para 1</p>",
"<p>Stylish Para 2</p>",
""),
apply(new HtmlPolicyBuilder()
.allowElements("h1", "p", "div")
.allowElements(
new ElementPolicy() {
public String apply(
String elementName, List<String> attrs) {
attrs.add("class");
attrs.add("header-" + elementName);
return "div";
}
}, "h1")));
}
@Test
public static final void testAllowUrlProtocols() throws Exception {
assertEquals(
Joiner.on('\n').join(
"Header",
"Paragraph 1",
"Click me out",
"<img src=\"canary.png\" alt=\"local-canary\" />"
+ "<img src=\"http://canaries.org/canary.png\" />",
"Fancy with soupy tags.",
"Stylish Para 1",
"Stylish Para 2",
""),
apply(new HtmlPolicyBuilder()
.allowElements("img")
.allowAttributes("src", "alt").onElements("img")
.allowUrlProtocols("http")));
}
@Test
public static final void testPossibleFalloutFromIssue5() throws Exception {
assertEquals(
"Bad",
apply(
new HtmlPolicyBuilder()
.allowElements("a")
.allowAttributes("href").onElements("a")
.allowUrlProtocols("http"),
"<a href='javascript:alert(1337)//:http'>Bad</a>"));
}
@Test
public static final void testTextInOption() throws Exception {
assertEquals(
"<select><option>1</option><option>2</option></select>",
apply(
new HtmlPolicyBuilder()
.allowElements("select", "option"),
"<select>\n <option>1</option>\n <option>2</option>\n</select>"));
}
private static String apply(HtmlPolicyBuilder b) throws Exception {
return apply(b, EXAMPLE);
}
private static String apply(HtmlPolicyBuilder b, String src)
throws Exception {
StringBuilder sb = new StringBuilder();
HtmlSanitizer.Policy policy = b.build(HtmlStreamRenderer.create(sb,
new Handler<String>() {
public void handle(String x) { fail(x); }
}));
HtmlSanitizer.sanitize(src, policy);
return sb.toString();
}
}