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 29 package org.owasp.html; 30 31 import org.junit.Test; 32 33 import junit.framework.TestCase; 34 35 public class SanitizersTest extends TestCase { 36 37 @Test testFormatting()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 testBlockElements()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 testBlockAndFormattingElements()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 testStylesAndFormatting()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 testAndIntersects()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 testImages()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 testLinks()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 testExplicitlyAllowedProtocolsAreCaseInsensitive()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's" 212 + "The fail protocol needs to happen"); 213 assertEquals(want, s.sanitize(input)); 214 } 215 216 @Test testIssue9StylesInTables()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:'arial' , 'geneva' , 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 testSkipIfEmptyUnionsProperly()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