// 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( "
  • foo
    • bar
", 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
  • . + "
      \n
    • Hello,
    • \n
    • World!
    ", 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
      since only and
    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( "
        • foo
          • bar
      ", 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( "
      foo" + "
      bar
      " + "
      ", 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" + "

      " + "" // The close

      tag does not close the whole table. + "" + "

      Cell

      \n

      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")); } }