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