// 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 com.google.common.collect.ImmutableList;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.Before;
import static org.owasp.html.TagBalancingHtmlStreamEventReceiver
.isInterElementWhitespace;
public class TagBalancingHtmlStreamRendererTest extends TestCase {
StringBuilder htmlOutputBuffer;
TagBalancingHtmlStreamEventReceiver balancer;
@Before @Override protected void setUp() throws Exception {
super.setUp();
htmlOutputBuffer = new StringBuilder();
balancer = new TagBalancingHtmlStreamEventReceiver(
HtmlStreamRenderer.create(htmlOutputBuffer, new Handler() {
public void handle(String x) {
fail("An unexpected error was raised during the testcase");
}
}));
}
@Test
public final void testTagBalancing() {
balancer.openDocument();
balancer.openTag("html", ImmutableList.of());
balancer.openTag("head", ImmutableList.of());
balancer.openTag("title", ImmutableList.of());
balancer.text("Hello, <>!");
// TITLE closed with case-sensitively different name.
balancer.closeTag("TITLE");
balancer.closeTag("head");
balancer.openTag("body", ImmutableList.of());
balancer.openTag("p", ImmutableList.of("id", "p'0"));
balancer.text("Hello,");
balancer.openTag("Br", ImmutableList.of());
balancer.text("<>!");
// HTML, P, and BODY unclosed, but BR not.
balancer.closeDocument();
assertEquals(
"Hello, <<World>>!"
+ "Hello,"
+ "
<<World>>!
",
htmlOutputBuffer.toString());
}
@Test
public final void testTagSoupIronedOut() {
balancer.openDocument();
balancer.openTag("i", ImmutableList.of());
balancer.text("a");
balancer.openTag("b", ImmutableList.of());
balancer.text("b");
balancer.closeTag("i");
balancer.text("c");
balancer.closeDocument();
assertEquals(
"abc",
htmlOutputBuffer.toString());
}
@Test
public final void testListInListDirectly() {
balancer.openDocument();
balancer.openTag("ul", ImmutableList.of());
balancer.openTag("li", ImmutableList.of());
balancer.text("foo");
balancer.closeTag("li");
balancer.openTag("ul", ImmutableList.of());
balancer.openTag("li", ImmutableList.of());
balancer.text("bar");
balancer.closeTag("li");
balancer.closeTag("ul");
balancer.closeTag("ul");
balancer.closeDocument();
assertEquals(
"",
htmlOutputBuffer.toString());
}
@Test
public final void testTextContent() {
balancer.openDocument();
balancer.openTag("title", ImmutableList.of());
balancer.text("Hello, World!");
balancer.closeTag("title");
balancer.text("Hello, ");
balancer.openTag("b", ImmutableList.of());
balancer.text("World!");
balancer.closeTag("b");
balancer.openTag("p", ImmutableList.of());
balancer.text("Hello, ");
balancer.openTag("textarea", ImmutableList.of());
balancer.text("World!");
balancer.closeTag("textarea");
balancer.closeTag("p");
balancer.openTag("h1", ImmutableList.of());
balancer.text("Hello");
balancer.openTag("style", ImmutableList.of("type", "text/css"));
balancer.text("\n.World {\n color: blue\n}\n");
balancer.closeTag("style");
balancer.closeTag("h1");
balancer.openTag("ul", ImmutableList.of());
balancer.text("\n ");
balancer.openTag("li", ImmutableList.of());
balancer.text("Hello,");
balancer.closeTag("li");
balancer.text("\n ");
balancer.text("World!");
balancer.closeDocument();
assertEquals(
// Text and only text allowed in title
"Hello, World!"
// Text allowed at top level and in phrasing content
+ "Hello, World!"
// Text allowed in block elements and in text areas.
+ "Hello,
"
+ "Hello"
// Text allowed in special style tag.
+ "
"
// Whitespace allowed inside but non-whitespace text nodes are
// moved inside - .
+ "",
htmlOutputBuffer.toString());
}
@Test
public final void testMismatchedHeaders() {
balancer.openDocument();
balancer.openTag("H1", ImmutableList.of());
balancer.text("header");
balancer.closeTag("h1");
balancer.text("body");
balancer.openTag("H2", ImmutableList.of());
balancer.text("sub-header");
balancer.closeTag("h3");
balancer.text("sub-body");
balancer.openTag("h3", ImmutableList.of());
balancer.text("sub-sub-");
balancer.closeTag("hr"); // hr is not a header tag so does not close an h3.
balancer.text("header");
//
is not allowed in h3.
balancer.openTag("hr", ImmutableList.of());
balancer.closeTag("hr");
balancer.text("sub-sub-body");
balancer.closeTag("H4");
balancer.closeTag("h2");
balancer.closeDocument();
assertEquals(
"header
body"
+ "sub-header
sub-body"
+ "sub-sub-header
sub-sub-body",
htmlOutputBuffer.toString());
}
@Test
public final void testListNesting() {
balancer.openDocument();
balancer.openTag("ul", ImmutableList.of());
balancer.openTag("li", ImmutableList.of());
balancer.openTag("ul", ImmutableList.of());
balancer.openTag("li", ImmutableList.of());
balancer.text("foo");
balancer.closeTag("li");
// Does not closes the second can close a
// based on the "has an element in list scope test" used by the HTML5
// tree building algo.
balancer.closeTag("li");
// This would append inside a list, not an item. We insert an - .
balancer.openTag("ul", ImmutableList.of());
balancer.openTag("li", ImmutableList.of());
balancer.text("bar");
balancer.closeDocument();
assertEquals(
"",
htmlOutputBuffer.toString());
}
@Test
public final void testTableNesting() {
balancer.openDocument();
balancer.openTag("table", ImmutableList.of());
balancer.openTag("tbody", ImmutableList.of());
balancer.openTag("tr", ImmutableList.of());
balancer.openTag("td", ImmutableList.of());
balancer.text("foo");
balancer.closeTag("td");
// Insert a td to contain this mis-nested table.
balancer.openTag("table", ImmutableList.of());
balancer.openTag("tbody", ImmutableList.of());
balancer.openTag("tr", ImmutableList.of());
balancer.openTag("th", ImmutableList.of());
balancer.text("bar");
balancer.closeTag("table");
balancer.closeTag("table");
balancer.closeDocument();
assertEquals(
"",
htmlOutputBuffer.toString());
}
@Test
public final void testNestingLimits() {
// Some browsers can be DoSed by deeply nested structures.
// See Issue 3, "Deeply nested elements crash FF 8, Chrome 11"
// @ http://code.google.com/p/owasp-java-html-sanitizer/issues/detail?id=3
balancer.setNestingLimit(10);
balancer.openDocument();
ImmutableList attrs = ImmutableList.of();
for (int i = 20000; --i >= 0;) {
balancer.openTag("div", attrs);
}
balancer.openTag("hr", attrs);
balancer.closeDocument();
assertEquals(
"",
htmlOutputBuffer.toString());
}
@Test
public final void testTablesGuarded() {
// Derived from issue 12.
balancer.openDocument();
balancer.openTag("html", ImmutableList.of());
balancer.openTag("head", ImmutableList.of());
balancer.openTag("meta", ImmutableList.of());
balancer.closeTag("head");
balancer.openTag("body", ImmutableList.of());
balancer.openTag("p", ImmutableList.of());
balancer.text("Hi");
balancer.closeTag("p");
balancer.openTag("p", ImmutableList.of());
balancer.text("How are you");
balancer.closeTag("p");
balancer.text("\n");
balancer.openTag("p", ImmutableList.of());
balancer.openTag("table", ImmutableList.of());
balancer.openTag("tbody", ImmutableList.of());
balancer.openTag("tr", ImmutableList.of());
for (int i = 2; --i >= 0;) {
balancer.openTag("td", ImmutableList.of());
balancer.openTag("b", ImmutableList.of());
balancer.openTag("font", ImmutableList.of());
balancer.openTag("font", ImmutableList.of());
balancer.openTag("p", ImmutableList.of());
balancer.text("Cell");
balancer.closeTag("b");
balancer.closeTag("font");
balancer.closeTag("font");
balancer.closeTag("p");
balancer.text("\n");
balancer.closeTag("td");
}
balancer.closeTag("tr");
balancer.closeTag("tbody");
balancer.closeTag("table");
balancer.closeTag("p");
balancer.text("\n");
balancer.openTag("p", ImmutableList.of());
balancer.text("x");
balancer.closeTag("p");
balancer.closeTag("body");
balancer.closeTag("html");
balancer.closeDocument();
assertEquals(
"
Hi
How are you
\n"
+ ""
+ "Cell \n | "
// The close tag does not close the whole table.
+ "Cell \n | "
+ "
\n"
+ "x
",
htmlOutputBuffer.toString());
}
@Test
public final void testIsInterElementWhitespace() {
assertFalse(isInterElementWhitespace("foo"));
assertTrue(isInterElementWhitespace(""));
assertTrue(isInterElementWhitespace(" "));
assertTrue(isInterElementWhitespace("\t"));
assertTrue(isInterElementWhitespace("\n"));
assertTrue(isInterElementWhitespace(" \n"));
assertTrue(isInterElementWhitespace("\r\n"));
assertTrue(isInterElementWhitespace("\r"));
assertTrue(isInterElementWhitespace(" "));
assertTrue(isInterElementWhitespace(" \t "));
assertFalse(isInterElementWhitespace(" foo "));
assertFalse(isInterElementWhitespace("\u00A0"));
assertFalse(isInterElementWhitespace("\u0000"));
}
}