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, &lt;&lt;World&gt;&gt;!</title></head>"
78        + "<body><p id=\"p&#39;0\">Hello,"
79        + "<br />&lt;&lt;World&gt;&gt;!</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