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 java.util.List; 32 33import org.junit.Test; 34 35import com.google.common.base.Joiner; 36 37import junit.framework.TestCase; 38 39public class HtmlPolicyBuilderTest extends TestCase { 40 41 static final String EXAMPLE = Joiner.on('\n').join( 42 "<h1 id='foo'>Header</h1>", 43 "<p onclick='alert(42)'>Paragraph 1<script>evil()</script></p>", 44 ("<p><a href='java\0script:bad()'>Click</a> <a href='foo.html'>me</a>" 45 + " <a href='http://outside.org/'>out</a></p>"), 46 ("<p><img src=canary.png alt=local-canary>" + 47 "<img src='http://canaries.org/canary.png'></p>"), 48 "<p><b style=font-size:bigger>Fancy</b> with <i><b>soupy</i> tags</b>.", 49 "<p style='color: expression(foo()); text-align: center;", 50 " /* direction: ltr */; font-weight: bold'>Stylish Para 1</p>", 51 "<p style='color: red; font-weight; expression(foo());", 52 " direction: rtl; font-weight: bold'>Stylish Para 2</p>", 53 ""); 54 55 @Test 56 public static final void testTextFilter() throws Exception { 57 assertEquals( 58 Joiner.on('\n').join( 59 "Header", 60 "Paragraph 1", 61 "Click me out", 62 "", 63 "Fancy with soupy tags.", 64 "Stylish Para 1", 65 "Stylish Para 2", 66 ""), 67 apply(new HtmlPolicyBuilder())); 68 } 69 70 @Test 71 public static final void testCannedFormattingTagFilter() throws Exception { 72 assertEquals( 73 Joiner.on('\n').join( 74 "Header", 75 "Paragraph 1", 76 "Click me out", 77 "", 78 "<b>Fancy</b> with <i><b>soupy</b></i><b> tags</b>.", 79 "Stylish Para 1", 80 "Stylish Para 2", 81 ""), 82 apply(new HtmlPolicyBuilder() 83 .allowCommonInlineFormattingElements())); 84 } 85 86 @Test 87 public static final void testCannedFormattingTagFilterNoItalics() 88 throws Exception { 89 assertEquals( 90 Joiner.on('\n').join( 91 "Header", 92 "Paragraph 1", 93 "Click me out", 94 "", 95 "<b>Fancy</b> with <b>soupy</b><b> tags</b>.", 96 "Stylish Para 1", 97 "Stylish Para 2", 98 ""), 99 apply(new HtmlPolicyBuilder() 100 .allowCommonInlineFormattingElements() 101 .disallowElements("I"))); 102 } 103 104 @Test 105 public static final void testSimpleTagFilter() throws Exception { 106 assertEquals( 107 Joiner.on('\n').join( 108 "<h1>Header</h1>", 109 "Paragraph 1", 110 "Click me out", 111 "", 112 "Fancy with <i>soupy</i> tags.", 113 "Stylish Para 1", 114 "Stylish Para 2", 115 ""), 116 apply(new HtmlPolicyBuilder() 117 .allowElements("h1", "i"))); 118 } 119 120 @Test 121 public static final void testLinksAllowed() throws Exception { 122 assertEquals( 123 Joiner.on('\n').join( 124 "Header", 125 "Paragraph 1", 126 // We haven't allowed any protocols so only relative URLs are OK. 127 "Click <a href=\"foo.html\">me</a> out", 128 "", 129 "Fancy with soupy tags.", 130 "Stylish Para 1", 131 "Stylish Para 2", 132 ""), 133 apply(new HtmlPolicyBuilder() 134 .allowElements("a") 135 .allowAttributes("href").onElements("a"))); 136 } 137 138 @Test 139 public static final void testExternalLinksAllowed() throws Exception { 140 assertEquals( 141 Joiner.on('\n').join( 142 "Header", 143 "Paragraph 1", 144 "Click <a href=\"foo.html\">me</a>" 145 + " <a href=\"http://outside.org/\">out</a>", 146 "", 147 "Fancy with soupy tags.", 148 "Stylish Para 1", 149 "Stylish Para 2", 150 ""), 151 apply(new HtmlPolicyBuilder() 152 .allowElements("a") 153 // Allows http. 154 .allowStandardUrlProtocols() 155 .allowAttributes("href").onElements("a"))); 156 } 157 158 @Test 159 public static final void testLinksWithNofollow() throws Exception { 160 assertEquals( 161 Joiner.on('\n').join( 162 "Header", 163 "Paragraph 1", 164 "Click <a href=\"foo.html\" rel=\"nofollow\">me</a> out", 165 "", 166 "Fancy with soupy tags.", 167 "Stylish Para 1", 168 "Stylish Para 2", 169 ""), 170 apply(new HtmlPolicyBuilder() 171 .allowElements("a") 172 // Allows http. 173 .allowAttributes("href").onElements("a") 174 .requireRelNofollowOnLinks())); 175 } 176 177 @Test 178 public static final void testImagesAllowed() throws Exception { 179 assertEquals( 180 Joiner.on('\n').join( 181 "Header", 182 "Paragraph 1", 183 "Click me out", 184 "<img src=\"canary.png\" alt=\"local-canary\" />", 185 // HTTP img not output because only HTTPS allowed. 186 "Fancy with soupy tags.", 187 "Stylish Para 1", 188 "Stylish Para 2", 189 ""), 190 apply(new HtmlPolicyBuilder() 191 .allowElements("img") 192 .allowAttributes("src", "alt").onElements("img") 193 .allowUrlProtocols("https"))); 194 } 195 196 @Test 197 public static final void testStyleFiltering() throws Exception { 198 assertEquals( 199 Joiner.on('\n').join( 200 "<h1>Header</h1>", 201 "<p>Paragraph 1</p>", 202 "<p>Click me out</p>", 203 "<p></p>", 204 "<p><b>Fancy</b> with <i><b>soupy</b></i><b> tags</b>.", 205 ("</p><p style=\"text-align:center;font-weight:bold\">" 206 + "Stylish Para 1</p>"), 207 ("<p style=\"color:red;direction:rtl;font-weight:bold\">" 208 + "Stylish Para 2</p>"), 209 ""), 210 apply(new HtmlPolicyBuilder() 211 .allowCommonInlineFormattingElements() 212 .allowCommonBlockElements() 213 .allowStyling() 214 .allowStandardUrlProtocols())); 215 } 216 217 @Test 218 public static final void testElementTransforming() throws Exception { 219 assertEquals( 220 Joiner.on('\n').join( 221 "<div class=\"header-h1\">Header</div>", 222 "<p>Paragraph 1</p>", 223 "<p>Click me out</p>", 224 "<p></p>", 225 "<p>Fancy with soupy tags.", 226 "</p><p>Stylish Para 1</p>", 227 "<p>Stylish Para 2</p>", 228 ""), 229 apply(new HtmlPolicyBuilder() 230 .allowElements("h1", "p", "div") 231 .allowElements( 232 new ElementPolicy() { 233 public String apply( 234 String elementName, List<String> attrs) { 235 attrs.add("class"); 236 attrs.add("header-" + elementName); 237 return "div"; 238 } 239 }, "h1"))); 240 } 241 242 @Test 243 public static final void testAllowUrlProtocols() throws Exception { 244 assertEquals( 245 Joiner.on('\n').join( 246 "Header", 247 "Paragraph 1", 248 "Click me out", 249 "<img src=\"canary.png\" alt=\"local-canary\" />" 250 + "<img src=\"http://canaries.org/canary.png\" />", 251 "Fancy with soupy tags.", 252 "Stylish Para 1", 253 "Stylish Para 2", 254 ""), 255 apply(new HtmlPolicyBuilder() 256 .allowElements("img") 257 .allowAttributes("src", "alt").onElements("img") 258 .allowUrlProtocols("http"))); 259 } 260 261 @Test 262 public static final void testPossibleFalloutFromIssue5() throws Exception { 263 assertEquals( 264 "Bad", 265 apply( 266 new HtmlPolicyBuilder() 267 .allowElements("a") 268 .allowAttributes("href").onElements("a") 269 .allowUrlProtocols("http"), 270 271 "<a href='javascript:alert(1337)//:http'>Bad</a>")); 272 } 273 274 @Test 275 public static final void testTextInOption() throws Exception { 276 assertEquals( 277 "<select><option>1</option><option>2</option></select>", 278 apply( 279 new HtmlPolicyBuilder() 280 .allowElements("select", "option"), 281 282 "<select>\n <option>1</option>\n <option>2</option>\n</select>")); 283 } 284 285 private static String apply(HtmlPolicyBuilder b) throws Exception { 286 return apply(b, EXAMPLE); 287 } 288 289 private static String apply(HtmlPolicyBuilder b, String src) 290 throws Exception { 291 StringBuilder sb = new StringBuilder(); 292 HtmlSanitizer.Policy policy = b.build(HtmlStreamRenderer.create(sb, 293 new Handler<String>() { 294 public void handle(String x) { fail(x); } 295 })); 296 HtmlSanitizer.sanitize(src, policy); 297 return sb.toString(); 298 } 299} 300