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