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 org.junit.Test;
32
33import junit.framework.TestCase;
34
35public class SanitizersTest extends TestCase {
36
37  @Test
38  public static final void testFormatting() {
39    assertEquals("", Sanitizers.FORMATTING.sanitize(null));
40    assertEquals("", Sanitizers.FORMATTING.sanitize(""));
41    assertEquals(
42        "Hello, World!",
43        Sanitizers.FORMATTING.sanitize("Hello, World!"));
44    assertEquals(
45        "Hello, <b>World</b>!",
46        Sanitizers.FORMATTING.sanitize("Hello, <b>World</b>!"));
47    assertEquals(
48        "Hello, <b>World</b>!",
49        Sanitizers.FORMATTING.sanitize(
50            "<p>Hello, <b onclick=alert(1337)>World</b>!</p>"));
51  }
52
53  @Test
54  public static final void testBlockElements() {
55    assertEquals("", Sanitizers.BLOCKS.sanitize(null));
56    assertEquals(
57        "Hello, World!",
58        Sanitizers.BLOCKS.sanitize("Hello, World!"));
59    assertEquals(
60        "Hello, World!",
61        Sanitizers.BLOCKS.sanitize("Hello, <b>World</b>!"));
62    assertEquals(
63        "<p>Hello, World!</p>",
64        Sanitizers.BLOCKS.sanitize(
65            "<p onclick=alert(1337)>Hello, <b>World</b>!</p>"));
66  }
67
68  @Test
69  public static final void testBlockAndFormattingElements() {
70    PolicyFactory s = Sanitizers.BLOCKS.and(Sanitizers.FORMATTING);
71    PolicyFactory r1 = Sanitizers.BLOCKS.and(Sanitizers.FORMATTING)
72        .and(Sanitizers.BLOCKS);
73    PolicyFactory r2 = Sanitizers.BLOCKS.and(Sanitizers.FORMATTING)
74        .and(Sanitizers.FORMATTING);
75    for (PolicyFactory f : new PolicyFactory[] { s, r1, r2 }) {
76      assertEquals("", f.sanitize(null));
77      assertEquals("Hello, World!", f.sanitize("Hello, World!"));
78      assertEquals("Hello, <b>World</b>!", f.sanitize("Hello, <b>World</b>!"));
79      assertEquals(
80          "<p>Hello, <b>World</b>!</p>",
81          f.sanitize("<p onclick=alert(1337)>Hello, <b>World</b>!</p>"));
82    }
83  }
84
85  @Test
86  public static final void testStylesAndFormatting() {
87    PolicyFactory sanitizer = Sanitizers.FORMATTING
88      .and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS);
89    String input = "<span style=\"font-weight:bold;"
90      + "text-decoration:underline;background-color:yellow\""
91      + ">aaaaaaaaaaaaaaaaaaaaaaa</span>";
92    String got = sanitizer.sanitize(input);
93    String want = input;
94    assertEquals(want, got);
95  }
96
97  @Test
98  public static final void testAndIntersects() {
99    PolicyFactory restrictedLink = new HtmlPolicyBuilder()
100       .allowElements("a")
101       .allowUrlProtocols("https")
102       .allowAttributes("href", "title").onElements("a")
103       .toFactory();
104    PolicyFactory inline = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
105    String inputHtml =
106        "<a href='http://foo.com/'>Hello, <b>World</b></a>"
107        + "<a title='!' href='https://foo.com/#!'>!</a>";
108    PolicyFactory and1 = restrictedLink.and(inline);
109    PolicyFactory and2 = inline.and(restrictedLink);
110    assertEquals(
111        "https-only links",
112        "Hello, World<a title=\"!\" href=\"https://foo.com/#!\">!</a>",
113        restrictedLink.sanitize(inputHtml));
114    assertEquals(
115        "inline els",
116        "<a href=\"http://foo.com/\" rel=\"nofollow\">Hello, <b>World</b></a>"
117        + "<a href=\"https://foo.com/#!\" rel=\"nofollow\">!</a>",
118        inline.sanitize(inputHtml));
119    assertEquals(
120        "https-only links and inline els",
121        "Hello, <b>World</b>"
122        + "<a title=\"!\" href=\"https://foo.com/#!\" rel=\"nofollow\">!</a>",
123        and1.sanitize(inputHtml));
124    assertEquals(
125        "inline els and https-only links",
126        "Hello, <b>World</b>"
127        + "<a title=\"!\" href=\"https://foo.com/#!\" rel=\"nofollow\">!</a>",
128        and2.sanitize(inputHtml));
129  }
130
131  @Test
132  public static final void testImages() {
133    PolicyFactory s = Sanitizers.IMAGES;
134    assertEquals(
135        "foo", s.sanitize("<a href=\"javascript:alert(1337)\">foo</a>"));
136    assertEquals(
137        "<img src=\"foo.gif\" />", s.sanitize("<img src=\"foo.gif\">"));
138    assertEquals(
139        "", s.sanitize("<img src=\"javascript://alert(1337)\">"));
140    assertEquals(
141        "<img src=\"x.gif\" alt=\"y\""
142        + " width=\"96\" height=\"64\" border=\"0\" />",
143        s.sanitize(
144            "<img src=\"x.gif\" alt=\"y\" width=96 height=64 border=0>"));
145    assertEquals(
146        "<img src=\"x.png\" alt=\"y\" height=\"64\" border=\"0\" />",
147        s.sanitize(
148            "<img src=\"x.png\" alt=\"y\" width=\"widgy\" height=64 border=0>")
149        );
150  }
151
152  @Test
153  public static final void testLinks() {
154    PolicyFactory s = Sanitizers.LINKS;
155    assertEquals(
156        "<a href=\"foo.html\" rel=\"nofollow\">Link text</a>",
157        s.sanitize("<a href=\"foo.html\">Link text</a>"));
158    assertEquals(
159        "<a href=\"foo.html\" rel=\"nofollow\">Link text</a>",
160        s.sanitize(
161            "<a href=\"foo.html\" onclick=\"alert(1337)\">Link text</a>"));
162    assertEquals(
163        "<a href=\"http://example.com/x.html\" rel=\"nofollow\">Link text</a>",
164        s.sanitize(
165            "<a href=\"http://example.com/x.html\""
166            + " onclick=\"alert(1337)\">Link text</a>"));
167    assertEquals(
168        "<a href=\"https://example.com/x.html\" rel=\"nofollow\">Link text</a>",
169        s.sanitize(
170            "<a href=\"https://example.com/x.html\""
171            + " onclick=\"alert(1337)\">Link text</a>"));
172    assertEquals(
173        "<a href=\"HTTPS://example.com/x.html\" rel=\"nofollow\">Link text</a>",
174        s.sanitize(
175            "<a href=\"HTTPS://example.com/x.html\""
176            + " onclick=\"alert(1337)\">Link text</a>"));
177    assertEquals(
178        "<a href=\"//example.com/x.html\" rel=\"nofollow\">Link text</a>",
179        s.sanitize(
180            "<a href=\"//example.com/x.html\""
181            + " onclick=\"alert(1337)\">Link text</a>"));
182    assertEquals(
183        "Link text",
184        s.sanitize(
185            "<a href=\"javascript:alert(1337).html\""
186            + " onclick=\"alert(1337)\">Link text</a>"));
187    // Not a link.  Instead, an attempt to intercept URL references that has
188    // not been explicitly allowed.
189    assertEquals(
190        "Header text",
191        s.sanitize("<a name=\"header\" id=\"header\">Header text</a>"));
192  }
193
194  @Test
195  public static final void testExplicitlyAllowedProtocolsAreCaseInsensitive() {
196    // Issue 24.
197    PolicyFactory s = new HtmlPolicyBuilder()
198        .allowElements("a")
199        .allowAttributes("href").onElements("a")
200        .allowStandardUrlProtocols()
201        .allowUrlProtocols("file")  // Don't try this at home
202        .toFactory();
203    String input = (
204        "<a href='file:///etc/passwd'>Copy and paste this into email</a>"
205        + "<a href='FILE:///etc/passwd'>Or this one</a>"
206        + "<a href='F\u0130LE:///etc/passwd'>not with Turkish dotted I's</a>"
207        + "<a href='fail:///etc/passed'>The fail protocol needs to happen</a>");
208    String want = (
209        "<a href=\"file:///etc/passwd\">Copy and paste this into email</a>"
210        + "<a href=\"FILE:///etc/passwd\">Or this one</a>"
211        + "not with Turkish dotted I&#39;s"
212        + "The fail protocol needs to happen");
213    assertEquals(want, s.sanitize(input));
214  }
215
216  @Test
217  public static final void testIssue9StylesInTables() {
218    String input = ""
219        + "<table style=\"color: rgb(0, 0, 0);"
220        + " font-family: Arial, Geneva, sans-serif;\">"
221        + "<tbody>"
222        + "<tr>"
223        + "<th>Column One</th><th>Column Two</th>"
224        + "</tr>"
225        + "<tr>"
226        + "<td align=\"center\""
227        + " style=\"background-color: rgb(255, 255, 254);\">"
228        + "<font size=\"2\">Size 2</font></td>"
229        + "<td align=\"center\""
230        + " style=\"background-color: rgb(255, 255, 254);\">"
231        + "<font size=\"7\">Size 7</font></td>"
232        + "</tr>"
233        + "</tbody>"
234        + "</table>";
235    PolicyFactory s = new HtmlPolicyBuilder()
236        .allowElements("table", "tbody", "thead", "tr", "td", "th")
237        .allowCommonBlockElements()
238        .allowCommonInlineFormattingElements()
239        .allowStyling()
240        .allowAttributes("align").matching(true, "left", "center", "right")
241          .onElements("table", "tr", "td", "th")
242        .allowAttributes("size").onElements("font", "img")
243        .toFactory();
244    String sanitized = ""
245        + "<table style=\"color:rgb( 0 , 0 , 0 );"
246        + "font-family:&#39;arial&#39; , &#39;geneva&#39; , sans-serif\">"
247        + "<tbody>"
248        + "<tr>"
249        + "<th>Column One</th><th>Column Two</th>"
250        + "</tr>"
251        + "<tr>"
252        + "<td align=\"center\""
253        + " style=\"background-color:rgb( 255 , 255 , 254 )\">"
254        + "<font size=\"2\">Size 2</font></td>"
255        + "<td align=\"center\""
256        + " style=\"background-color:rgb( 255 , 255 , 254 )\">"
257        + "<font size=\"7\">Size 7</font></td>"
258        + "</tr>"
259        + "</tbody>"
260        + "</table>";
261    assertEquals(sanitized, s.sanitize(input));
262  }
263
264  @Test
265  public static final void testSkipIfEmptyUnionsProperly() {
266    // Issue 23
267    PolicyFactory extras = new HtmlPolicyBuilder()
268        .allowWithoutAttributes("span", "div")
269        .allowElements("span", "div", "textarea")
270        // This is not the proper way to require the attribute disabled on
271        // textareas.  This is a test.  This is only a test.
272        .allowAttributes("disabled").onElements("textarea")
273        .disallowWithoutAttributes("textarea")
274        .toFactory();
275    PolicyFactory policy = Sanitizers.FORMATTING
276        .and(Sanitizers.BLOCKS)
277        .and(Sanitizers.IMAGES)
278        .and(Sanitizers.STYLES)
279        .and(extras);
280    String input =
281        "<textarea>text</textarea><textarea disabled></textarea>"
282        + "<div onclick='redirect()'><span>Styled by span</span></div>";
283    String want = "text<textarea disabled=\"disabled\"></textarea>"
284        + "<div><span>Styled by span</span></div>";
285    assertEquals(want, policy.sanitize(input));
286  }
287}
288