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 29package org.owasp.html; 30 31import com.google.common.collect.ImmutableList; 32 33import junit.framework.TestCase; 34 35import org.junit.Test; 36import org.junit.Before; 37 38import static org.owasp.html.TagBalancingHtmlStreamEventReceiver 39 .isInterElementWhitespace; 40 41 42public class TagBalancingHtmlStreamRendererTest extends TestCase { 43 44 StringBuilder htmlOutputBuffer; 45 TagBalancingHtmlStreamEventReceiver balancer; 46 47 @Before @Override protected void setUp() throws Exception { 48 super.setUp(); 49 htmlOutputBuffer = new StringBuilder(); 50 balancer = new TagBalancingHtmlStreamEventReceiver( 51 HtmlStreamRenderer.create(htmlOutputBuffer, new Handler<String>() { 52 public void handle(String x) { 53 fail("An unexpected error was raised during the testcase"); 54 } 55 })); 56 } 57 58 @Test 59 public final void testTagBalancing() { 60 balancer.openDocument(); 61 balancer.openTag("html", ImmutableList.<String>of()); 62 balancer.openTag("head", ImmutableList.<String>of()); 63 balancer.openTag("title", ImmutableList.<String>of()); 64 balancer.text("Hello, <<World>>!"); 65 // TITLE closed with case-sensitively different name. 66 balancer.closeTag("TITLE"); 67 balancer.closeTag("head"); 68 balancer.openTag("body", ImmutableList.<String>of()); 69 balancer.openTag("p", ImmutableList.of("id", "p'0")); 70 balancer.text("Hello,"); 71 balancer.openTag("Br", ImmutableList.<String>of()); 72 balancer.text("<<World>>!"); 73 // HTML, P, and BODY unclosed, but BR not. 74 balancer.closeDocument(); 75 76 assertEquals( 77 "<html><head><title>Hello, <<World>>!</title></head>" 78 + "<body><p id=\"p'0\">Hello," 79 + "<br /><<World>>!</p></body></html>", 80 htmlOutputBuffer.toString()); 81 } 82 83 @Test 84 public final void testTagSoupIronedOut() { 85 balancer.openDocument(); 86 balancer.openTag("i", ImmutableList.<String>of()); 87 balancer.text("a"); 88 balancer.openTag("b", ImmutableList.<String>of()); 89 balancer.text("b"); 90 balancer.closeTag("i"); 91 balancer.text("c"); 92 balancer.closeDocument(); 93 94 assertEquals( 95 "<i>a<b>b</b></i><b>c</b>", 96 htmlOutputBuffer.toString()); 97 } 98 99 @Test 100 public final void testListInListDirectly() { 101 balancer.openDocument(); 102 balancer.openTag("ul", ImmutableList.<String>of()); 103 balancer.openTag("li", ImmutableList.<String>of()); 104 balancer.text("foo"); 105 balancer.closeTag("li"); 106 balancer.openTag("ul", ImmutableList.<String>of()); 107 balancer.openTag("li", ImmutableList.<String>of()); 108 balancer.text("bar"); 109 balancer.closeTag("li"); 110 balancer.closeTag("ul"); 111 balancer.closeTag("ul"); 112 balancer.closeDocument(); 113 114 assertEquals( 115 "<ul><li>foo</li><li><ul><li>bar</li></ul></li></ul>", 116 htmlOutputBuffer.toString()); 117 } 118 119 @Test 120 public final void testTextContent() { 121 balancer.openDocument(); 122 balancer.openTag("title", ImmutableList.<String>of()); 123 balancer.text("Hello, World!"); 124 balancer.closeTag("title"); 125 balancer.text("Hello, "); 126 balancer.openTag("b", ImmutableList.<String>of()); 127 balancer.text("World!"); 128 balancer.closeTag("b"); 129 balancer.openTag("p", ImmutableList.<String>of()); 130 balancer.text("Hello, "); 131 balancer.openTag("textarea", ImmutableList.<String>of()); 132 balancer.text("World!"); 133 balancer.closeTag("textarea"); 134 balancer.closeTag("p"); 135 balancer.openTag("h1", ImmutableList.<String>of()); 136 balancer.text("Hello"); 137 balancer.openTag("style", ImmutableList.<String>of("type", "text/css")); 138 balancer.text("\n.World {\n color: blue\n}\n"); 139 balancer.closeTag("style"); 140 balancer.closeTag("h1"); 141 balancer.openTag("ul", ImmutableList.<String>of()); 142 balancer.text("\n "); 143 balancer.openTag("li", ImmutableList.<String>of()); 144 balancer.text("Hello,"); 145 balancer.closeTag("li"); 146 balancer.text("\n "); 147 balancer.text("World!"); 148 balancer.closeDocument(); 149 150 assertEquals( 151 // Text and only text allowed in title 152 "<title>Hello, World!</title>" 153 // Text allowed at top level and in phrasing content 154 + "Hello, <b>World!</b>" 155 // Text allowed in block elements and in text areas. 156 + "<p>Hello, <textarea>World!</textarea></p>" 157 + "<h1>Hello" 158 // Text allowed in special style tag. 159 + "<style type=\"text/css\">\n.World {\n color: blue\n}\n</style></h1>" 160 // Whitespace allowed inside <ul> but non-whitespace text nodes are 161 // moved inside <li>. 162 + "<ul>\n <li>Hello,</li>\n <li>World!</li></ul>", 163 htmlOutputBuffer.toString()); 164 } 165 166 @Test 167 public final void testMismatchedHeaders() { 168 balancer.openDocument(); 169 balancer.openTag("H1", ImmutableList.<String>of()); 170 balancer.text("header"); 171 balancer.closeTag("h1"); 172 balancer.text("body"); 173 balancer.openTag("H2", ImmutableList.<String>of()); 174 balancer.text("sub-header"); 175 balancer.closeTag("h3"); 176 balancer.text("sub-body"); 177 balancer.openTag("h3", ImmutableList.<String>of()); 178 balancer.text("sub-sub-"); 179 balancer.closeTag("hr"); // hr is not a header tag so does not close an h3. 180 balancer.text("header"); 181 // <h3> is not allowed in h3. 182 balancer.openTag("hr", ImmutableList.<String>of()); 183 balancer.closeTag("hr"); 184 balancer.text("sub-sub-body"); 185 balancer.closeTag("H4"); 186 balancer.closeTag("h2"); 187 balancer.closeDocument(); 188 189 assertEquals( 190 "<h1>header</h1>body" 191 + "<h2>sub-header</h2>sub-body" 192 + "<h3>sub-sub-header</h3><hr />sub-sub-body", 193 htmlOutputBuffer.toString()); 194 } 195 196 @Test 197 public final void testListNesting() { 198 balancer.openDocument(); 199 balancer.openTag("ul", ImmutableList.<String>of()); 200 balancer.openTag("li", ImmutableList.<String>of()); 201 balancer.openTag("ul", ImmutableList.<String>of()); 202 balancer.openTag("li", ImmutableList.<String>of()); 203 balancer.text("foo"); 204 balancer.closeTag("li"); 205 // Does not closes the second <ul> since only </ol> and </ul> can close a 206 // <ul> based on the "has an element in list scope test" used by the HTML5 207 // tree building algo. 208 balancer.closeTag("li"); 209 // This would append inside a list, not an item. We insert an <li>. 210 balancer.openTag("ul", ImmutableList.<String>of()); 211 balancer.openTag("li", ImmutableList.<String>of()); 212 balancer.text("bar"); 213 balancer.closeDocument(); 214 215 assertEquals( 216 "<ul><li><ul><li>foo</li><li><ul><li>bar</li></ul></li></ul></li></ul>", 217 htmlOutputBuffer.toString()); 218 } 219 220 @Test 221 public final void testTableNesting() { 222 balancer.openDocument(); 223 balancer.openTag("table", ImmutableList.<String>of()); 224 balancer.openTag("tbody", ImmutableList.<String>of()); 225 balancer.openTag("tr", ImmutableList.<String>of()); 226 balancer.openTag("td", ImmutableList.<String>of()); 227 balancer.text("foo"); 228 balancer.closeTag("td"); 229 // Insert a td to contain this mis-nested table. 230 balancer.openTag("table", ImmutableList.<String>of()); 231 balancer.openTag("tbody", ImmutableList.<String>of()); 232 balancer.openTag("tr", ImmutableList.<String>of()); 233 balancer.openTag("th", ImmutableList.<String>of()); 234 balancer.text("bar"); 235 balancer.closeTag("table"); 236 balancer.closeTag("table"); 237 balancer.closeDocument(); 238 239 assertEquals( 240 "<table><tbody><tr><td>foo</td><td>" 241 + "<table><tbody><tr><th>bar</th></tr></tbody></table>" 242 + "</td></tr></tbody></table>", 243 htmlOutputBuffer.toString()); 244 } 245 246 @Test 247 public final void testNestingLimits() { 248 // Some browsers can be DoSed by deeply nested structures. 249 // See Issue 3, "Deeply nested elements crash FF 8, Chrome 11" 250 // @ http://code.google.com/p/owasp-java-html-sanitizer/issues/detail?id=3 251 252 balancer.setNestingLimit(10); 253 balancer.openDocument(); 254 ImmutableList<String> attrs = ImmutableList.<String>of(); 255 for (int i = 20000; --i >= 0;) { 256 balancer.openTag("div", attrs); 257 } 258 balancer.openTag("hr", attrs); 259 balancer.closeDocument(); 260 assertEquals( 261 "<div><div><div><div><div><div><div><div><div><div>" 262 + "</div></div></div></div></div></div></div></div></div></div>", 263 htmlOutputBuffer.toString()); 264 } 265 266 @Test 267 public final void testTablesGuarded() { 268 // Derived from issue 12. 269 balancer.openDocument(); 270 balancer.openTag("html", ImmutableList.<String>of()); 271 balancer.openTag("head", ImmutableList.<String>of()); 272 balancer.openTag("meta", ImmutableList.<String>of()); 273 balancer.closeTag("head"); 274 balancer.openTag("body", ImmutableList.<String>of()); 275 balancer.openTag("p", ImmutableList.<String>of()); 276 balancer.text("Hi"); 277 balancer.closeTag("p"); 278 balancer.openTag("p", ImmutableList.<String>of()); 279 balancer.text("How are you"); 280 balancer.closeTag("p"); 281 balancer.text("\n"); 282 balancer.openTag("p", ImmutableList.<String>of()); 283 balancer.openTag("table", ImmutableList.<String>of()); 284 balancer.openTag("tbody", ImmutableList.<String>of()); 285 balancer.openTag("tr", ImmutableList.<String>of()); 286 for (int i = 2; --i >= 0;) { 287 balancer.openTag("td", ImmutableList.<String>of()); 288 balancer.openTag("b", ImmutableList.<String>of()); 289 balancer.openTag("font", ImmutableList.<String>of()); 290 balancer.openTag("font", ImmutableList.<String>of()); 291 balancer.openTag("p", ImmutableList.<String>of()); 292 balancer.text("Cell"); 293 balancer.closeTag("b"); 294 balancer.closeTag("font"); 295 balancer.closeTag("font"); 296 balancer.closeTag("p"); 297 balancer.text("\n"); 298 balancer.closeTag("td"); 299 } 300 balancer.closeTag("tr"); 301 balancer.closeTag("tbody"); 302 balancer.closeTag("table"); 303 balancer.closeTag("p"); 304 balancer.text("\n"); 305 balancer.openTag("p", ImmutableList.<String>of()); 306 balancer.text("x"); 307 balancer.closeTag("p"); 308 balancer.closeTag("body"); 309 balancer.closeTag("html"); 310 balancer.closeDocument(); 311 312 assertEquals( 313 "<html><head><meta /></head><body><p>Hi</p><p>How are you</p>\n" 314 + "<p><table><tbody><tr>" 315 + "<td><b><font><font></font></font></b><b><p>Cell</p></b>\n</td>" 316 // The close </p> tag does not close the whole table. 317 + "<td><b><font><font></font></font></b><b><p>Cell</p></b>\n</td>" 318 + "</tr></tbody></table></p>\n" 319 + "<p>x</p></body></html>", 320 htmlOutputBuffer.toString()); 321 } 322 323 @Test 324 public final void testIsInterElementWhitespace() { 325 assertFalse(isInterElementWhitespace("foo")); 326 assertTrue(isInterElementWhitespace("")); 327 assertTrue(isInterElementWhitespace(" ")); 328 assertTrue(isInterElementWhitespace("\t")); 329 assertTrue(isInterElementWhitespace("\n")); 330 assertTrue(isInterElementWhitespace(" \n")); 331 assertTrue(isInterElementWhitespace("\r\n")); 332 assertTrue(isInterElementWhitespace("\r")); 333 assertTrue(isInterElementWhitespace(" ")); 334 assertTrue(isInterElementWhitespace(" \t ")); 335 assertFalse(isInterElementWhitespace(" foo ")); 336 assertFalse(isInterElementWhitespace("\u00A0")); 337 assertFalse(isInterElementWhitespace("\u0000")); 338 } 339 340} 341