Home | History | Annotate | Download | only in html
      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 java.util.List;
     32 
     33 import org.junit.Test;
     34 
     35 import com.google.common.base.Joiner;
     36 
     37 import junit.framework.TestCase;
     38 
     39 public 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