1+ // Copyright (c) 2019, 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 .io .IOException ;
32+ import  java .util .ArrayList ;
33+ import  java .util .Arrays ;
34+ import  java .util .List ;
35+ 
36+ import  org .junit .Test ;
37+ 
38+ import  com .google .common .base .Joiner ;
39+ 
40+ import  junit .framework .TestCase ;
41+ 
42+ @ SuppressWarnings ({ "javadoc"  })
43+ public  final  class  PolicyFactoryTest  extends  TestCase  {
44+ 
45+  @ Test 
46+  public  static  void  testAnd () {
47+  // Filters srcset to only contain URLs with the substring "foo" 
48+  PolicyFactory  f  = new  HtmlPolicyBuilder ()
49+  .allowElements ("img" )
50+  .allowAttributes ("srcset" )
51+  .matching (new  SubstringFilter ("foo" ))
52+  .globally ()
53+  .allowStandardUrlProtocols ()
54+  .toFactory ();
55+  // Filters srcset to only contain URLs with the substring "bar" 
56+  PolicyFactory  g  = new  HtmlPolicyBuilder ()
57+  .allowElements ("img" )
58+  .allowAttributes ("srcset" )
59+  .matching (new  SubstringFilter ("bar" ))
60+  .globally ()
61+  .allowStandardUrlProtocols ()
62+  .toFactory ();
63+ 
64+  // The javascript URL will be allowed if the extra policies are not 
65+  // preserved. 
66+  String  html  = "<img" 
67+  + " srcset=\" /foo.png , /bar.png , javascript:alert('foobar') , /foobar.png\" " 
68+  // title is not whitelisted. 
69+  + " title=Hi>!" ;
70+ 
71+  PolicyFactory [] factories  = {
72+  f ,
73+  g ,
74+  // Test that .and() intersects regardless of order. 
75+  f .and (g ),
76+  g .and (f ),
77+  };
78+  String [] expectedOutputs  = {
79+  // f 
80+  "<img srcset=\" /foo.png , /foobar.png\"  />" ,
81+ 
82+  // g 
83+  "<img srcset=\" /bar.png , /foobar.png\"  />" ,
84+ 
85+  // f and g 
86+  "<img srcset=\" /foobar.png\"  />" ,
87+ 
88+  // g and f 
89+  "<img srcset=\" /foobar.png\"  />" ,
90+  };
91+  String [] expectedLogs  = {
92+  // f 
93+  "" 
94+  + "discardedAttributes img, [title]\n " 
95+  + "Handled IOException BANG\n " ,
96+ 
97+  // g 
98+  "" 
99+  + "discardedAttributes img, [title]\n " 
100+  + "Handled IOException BANG\n " ,
101+ 
102+  // f and g 
103+  "" 
104+  + "discardedAttributes img, [title]\n " 
105+  + "Handled IOException BANG\n " ,
106+ 
107+  // g and f 
108+  "" 
109+  + "discardedAttributes img, [title]\n " 
110+  + "Handled IOException BANG\n " ,
111+  };
112+ 
113+  for  (int  i  = 0 ; i  < factories .length ; ++i ) {
114+  PolicyFactory  factory  = factories [i ];
115+  String  expectedOutput  = expectedOutputs [i ];
116+  String  expectedLog  = expectedLogs [i ];
117+ 
118+  // A dummy value that lets us check that context is properly threaded 
119+  // through joined policies. 
120+  final  Object  context  = new  Object ();
121+  // Collect events from callbacks. 
122+  final  StringBuilder  log  = new  StringBuilder ();
123+  // Collects output HTML. 
124+  final  StringBuilder  out  = new  StringBuilder ();
125+ 
126+  // A noisy listener that logs. 
127+  HtmlChangeListener <Object > listener  = new  HtmlChangeListener <Object >() {
128+ 
129+  public  void  discardedTag (Object  ctx , String  elementName ) {
130+  assertEquals (context , ctx );
131+  log .append ("discardedTag "  + elementName  + "\n " );
132+  }
133+ 
134+  public  void  discardedAttributes (
135+  Object  ctx , String  tagName , String ... attributeNames ) {
136+  assertEquals (context , ctx );
137+  log .append (
138+  "discardedAttributes "  + tagName 
139+  + ", "  + Arrays .asList (attributeNames )
140+  + "\n " );
141+  }
142+ 
143+  };
144+ 
145+  Handler <IOException > ioHandler  = new  Handler <IOException >() {
146+ 
147+  public  void  handle (IOException  x ) {
148+  log .append ("Handled IOException "  + x .getMessage () + "\n " );
149+  }
150+ 
151+  };
152+ 
153+  // Should not be called. 
154+  Handler <String > badHtmlHandler  = new  Handler <String >() {
155+ 
156+  public  void  handle (String  x ) {
157+  throw  new  AssertionError (x );
158+  }
159+ 
160+  };
161+ 
162+  // Wraps out to throw when a '!' is written to test the ioHandler. 
163+  // There is a '!' at the end of the output. 
164+  Appendable  throwingOut  = new  Appendable () {
165+ 
166+  public  Appendable  append (CharSequence  csq ) throws  IOException  {
167+  return  append (csq , 0 , csq .length ());
168+  }
169+ 
170+  public  Appendable  append (CharSequence  csq , int  start , int  end ) throws  IOException  {
171+  for  (int  j  = start ; j  < end ; ++j ) {
172+  if  (csq .charAt (j ) == '!' ) {
173+  throw  new  IOException ("BANG" );
174+  }
175+  }
176+  out .append (csq , start , end );
177+  return  this ;
178+  }
179+ 
180+  public  Appendable  append (char  c ) throws  IOException  {
181+  if  (c  == '!' ) {
182+  throw  new  IOException ("BANG" );
183+  }
184+  out .append (c );
185+  return  this ;
186+  }
187+ 
188+  };
189+ 
190+  HtmlStreamEventReceiver  receiver  = new  HtmlStreamRenderer (
191+  throwingOut , ioHandler , badHtmlHandler );
192+  HtmlSanitizer .Policy  policy  = factory .apply (
193+  receiver , listener , context );
194+  HtmlSanitizer .sanitize (html , policy );
195+ 
196+  assertEquals (
197+  "i:"  + i ,
198+ 
199+  "Out:\n "  + expectedOutput  + "\n \n Log:\n "  + expectedLog ,
200+  "Out:\n "  + out  + "\n \n Log:\n "  + log );
201+  }
202+  }
203+ 
204+  static  final  class  SubstringFilter  implements  AttributePolicy  {
205+  final  String  substr ;
206+ 
207+  SubstringFilter (String  substr ) {
208+  this .substr  = substr ;
209+  }
210+ 
211+  public  String  apply (
212+  String  elementName , String  attributeName , String  value ) {
213+  List <String > outParts  = new  ArrayList <String >();
214+  for  (String  part  : value .split ("," )) {
215+  part  = part .trim ();
216+  if  (part .contains (substr )) {
217+  outParts .add (part );
218+  }
219+  }
220+  return  Joiner .on (" , " ).join (outParts );
221+  }
222+  }
223+ }
0 commit comments