Fix UploadBase::checkXMLEncodingMissmatch() on PHP 7.1+
[lhc/web/wiklou.git] / tests / phpunit / includes / upload / UploadBaseTest.php
1 <?php
2
3 /**
4 * @group Upload
5 */
6 class UploadBaseTest extends MediaWikiTestCase {
7
8 /** @var UploadTestHandler */
9 protected $upload;
10
11 protected function setUp() {
12 parent::setUp();
13
14 $this->upload = new UploadTestHandler;
15
16 $this->setMwGlobals( 'wgHooks', [
17 'InterwikiLoadPrefix' => [
18 function ( $prefix, &$data ) {
19 return false;
20 }
21 ],
22 ] );
23 }
24
25 /**
26 * First checks the return code
27 * of UploadBase::getTitle() and then the actual returned title
28 *
29 * @dataProvider provideTestTitleValidation
30 * @covers UploadBase::getTitle
31 */
32 public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) {
33 /* Check the result code */
34 $this->assertEquals( $code,
35 $this->upload->testTitleValidation( $srcFilename ),
36 "$msg code" );
37
38 /* If we expect a valid title, check the title itself. */
39 if ( $code == UploadBase::OK ) {
40 $this->assertEquals( $dstFilename,
41 $this->upload->getTitle()->getText(),
42 "$msg text" );
43 }
44 }
45
46 /**
47 * Test various forms of valid and invalid titles that can be supplied.
48 */
49 public static function provideTestTitleValidation() {
50 return [
51 /* Test a valid title */
52 [ 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK,
53 'upload valid title' ],
54 /* A title with a slash */
55 [ 'A/B.jpg', 'A-B.jpg', UploadBase::OK,
56 'upload title with slash' ],
57 /* A title with illegal char */
58 [ 'A:B.jpg', 'A-B.jpg', UploadBase::OK,
59 'upload title with colon' ],
60 /* Stripping leading File: prefix */
61 [ 'File:C.jpg', 'C.jpg', UploadBase::OK,
62 'upload title with File prefix' ],
63 /* Test illegal suggested title (r94601) */
64 [ '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME,
65 'illegal title for upload' ],
66 /* A title without extension */
67 [ 'A', null, UploadBase::FILETYPE_MISSING,
68 'upload title without extension' ],
69 /* A title with no basename */
70 [ '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME,
71 'upload title without basename' ],
72 /* A title that is longer than 255 bytes */
73 [ str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG,
74 'upload title longer than 255 bytes' ],
75 /* A title that is longer than 240 bytes */
76 [ str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG,
77 'upload title longer than 240 bytes' ],
78 ];
79 }
80
81 /**
82 * Test the upload verification functions
83 * @covers UploadBase::verifyUpload
84 */
85 public function testVerifyUpload() {
86 /* Setup with zero file size */
87 $this->upload->initializePathInfo( '', '', 0 );
88 $result = $this->upload->verifyUpload();
89 $this->assertEquals( UploadBase::EMPTY_FILE,
90 $result['status'],
91 'upload empty file' );
92 }
93
94 // Helper used to create an empty file of size $size.
95 private function createFileOfSize( $size ) {
96 $filename = $this->getNewTempFile();
97
98 $fh = fopen( $filename, 'w' );
99 ftruncate( $fh, $size );
100 fclose( $fh );
101
102 return $filename;
103 }
104
105 /**
106 * @covers UploadBase::verifyUpload
107 *
108 * test uploading a 100 bytes file with $wgMaxUploadSize = 100
109 *
110 * This method should be abstracted so we can test different settings.
111 */
112 public function testMaxUploadSize() {
113 $this->setMwGlobals( [
114 'wgMaxUploadSize' => 100,
115 'wgFileExtensions' => [
116 'txt',
117 ],
118 ] );
119
120 $filename = $this->createFileOfSize( 100 );
121 $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 );
122 $result = $this->upload->verifyUpload();
123
124 $this->assertEquals(
125 [ 'status' => UploadBase::OK ],
126 $result
127 );
128 }
129
130 /**
131 * @covers UploadBase::checkSvgScriptCallback
132 * @dataProvider provideCheckSvgScriptCallback
133 */
134 public function testCheckSvgScriptCallback( $svg, $wellFormed, $filterMatch, $message ) {
135 list( $formed, $match ) = $this->upload->checkSvgString( $svg );
136 $this->assertSame( $wellFormed, $formed, $message . " (well-formed)" );
137 $this->assertSame( $filterMatch, $match, $message . " (filter match)" );
138 }
139
140 public static function provideCheckSvgScriptCallback() {
141 // phpcs:disable Generic.Files.LineLength
142 return [
143 // html5sec SVG vectors
144 [
145 '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>',
146 true,
147 true,
148 'Script tag in svg (http://html5sec.org/#47)'
149 ],
150 [
151 '<svg xmlns="http://www.w3.org/2000/svg"><g onload="javascript:alert(1)"></g></svg>',
152 true,
153 true,
154 'SVG with onload property (http://html5sec.org/#11)'
155 ],
156 [
157 '<svg onload="javascript:alert(1)" xmlns="http://www.w3.org/2000/svg"></svg>',
158 true,
159 true,
160 'SVG with onload property (http://html5sec.org/#65)'
161 ],
162 [
163 '<svg xmlns="http://www.w3.org/2000/svg"> <a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="javascript:alert(1)"><rect width="1000" height="1000" fill="white"/></a> </svg>',
164 true,
165 true,
166 'SVG with javascript xlink (http://html5sec.org/#87)'
167 ],
168 [
169 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="data:application/xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGRlZnM+CjxjaXJjbGUgaWQ9InRlc3QiIHI9IjUwIiBjeD0iMTAwIiBjeT0iMTAwIiBzdHlsZT0iZmlsbDogI0YwMCI+CjxzZXQgYXR0cmlidXRlTmFtZT0iZmlsbCIgYXR0cmlidXRlVHlwZT0iQ1NTIiBvbmJlZ2luPSdhbGVydChkb2N1bWVudC5jb29raWUpJwpvbmVuZD0nYWxlcnQoIm9uZW5kIiknIHRvPSIjMDBGIiBiZWdpbj0iMXMiIGR1cj0iNXMiIC8+CjwvY2lyY2xlPgo8L2RlZnM+Cjx1c2UgeGxpbms6aHJlZj0iI3Rlc3QiLz4KPC9zdmc+#test"/> </svg>',
170 true,
171 true,
172 'SVG with Opera image xlink (http://html5sec.org/#88 - c)'
173 ],
174 [
175 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <animation xlink:href="javascript:alert(1)"/> </svg>',
176 true,
177 true,
178 'SVG with Opera animation xlink (http://html5sec.org/#88 - a)'
179 ],
180 [
181 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <animation xlink:href="data:text/xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' onload=\'alert(1)\'%3E%3C/svg%3E"/> </svg>',
182 true,
183 true,
184 'SVG with Opera animation xlink (http://html5sec.org/#88 - b)'
185 ],
186 [
187 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <image xlink:href="data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' onload=\'alert(1)\'%3E%3C/svg%3E"/> </svg>',
188 true,
189 true,
190 'SVG with Opera image xlink (http://html5sec.org/#88 - c)'
191 ],
192 [
193 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <foreignObject xlink:href="javascript:alert(1)"/> </svg>',
194 true,
195 true,
196 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - d)'
197 ],
198 [
199 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <foreignObject xlink:href="data:text/xml,%3Cscript xmlns=\'http://www.w3.org/1999/xhtml\'%3Ealert(1)%3C/script%3E"/> </svg>',
200 true,
201 true,
202 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - e)'
203 ],
204 [
205 '<svg xmlns="http://www.w3.org/2000/svg"> <set attributeName="onmouseover" to="alert(1)"/> </svg>',
206 true,
207 true,
208 'SVG with event handler set (http://html5sec.org/#89 - a)'
209 ],
210 [
211 '<svg xmlns="http://www.w3.org/2000/svg"> <animate attributeName="onunload" to="alert(1)"/> </svg>',
212 true,
213 true,
214 'SVG with event handler animate (http://html5sec.org/#89 - a)'
215 ],
216 [
217 '<svg xmlns="http://www.w3.org/2000/svg"> <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>',
218 true,
219 true,
220 'SVG with element handler (http://html5sec.org/#94)'
221 ],
222 [
223 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <feImage> <set attributeName="xlink:href" to="data:image/svg+xml;charset=utf-8;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ%2BYWxlcnQoMSk8L3NjcmlwdD48L3N2Zz4NCg%3D%3D"/> </feImage> </svg>',
224 true,
225 true,
226 'SVG with href to data: url (http://html5sec.org/#95)'
227 ],
228 [
229 '<svg xmlns="http://www.w3.org/2000/svg" id="foo"> <x xmlns="http://www.w3.org/2001/xml-events" event="load" observer="foo" handler="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Chandler%20xml%3Aid%3D%22bar%22%20type%3D%22application%2Fecmascript%22%3E alert(1) %3C%2Fhandler%3E%0A%3C%2Fsvg%3E%0A#bar"/> </svg>',
230 true,
231 true,
232 'SVG with Tiny handler (http://html5sec.org/#104)'
233 ],
234 [
235 '<svg xmlns="http://www.w3.org/2000/svg"> <a id="x"><rect fill="white" width="1000" height="1000"/></a> <rect fill="white" style="clip-path:url(test3.svg#a);fill:url(#b);filter:url(#c);marker:url(#d);mask:url(#e);stroke:url(#f);"/> </svg>',
236 true,
237 true,
238 'SVG with new CSS styles properties (http://html5sec.org/#109)'
239 ],
240 [
241 '<svg xmlns="http://www.w3.org/2000/svg"> <a id="x"><rect fill="white" width="1000" height="1000"/></a> <rect clip-path="url(test3.svg#a)" /> </svg>',
242 true,
243 true,
244 'SVG with new CSS styles properties as attributes'
245 ],
246 [
247 '<svg xmlns="http://www.w3.org/2000/svg"> <a id="x"> <rect fill="white" width="1000" height="1000"/> </a> <rect fill="url(http://html5sec.org/test3.svg#a)" /> </svg>',
248 true,
249 true,
250 'SVG with new CSS styles properties as attributes (2)'
251 ],
252 [
253 '<svg xmlns="http://www.w3.org/2000/svg"> <path d="M0,0" style="marker-start:url(test4.svg#a)"/> </svg>',
254 true,
255 true,
256 'SVG with path marker-start (http://html5sec.org/#110)'
257 ],
258 [
259 '<?xml version="1.0"?> <?xml-stylesheet type="text/xml" href="#stylesheet"?> <!DOCTYPE doc [ <!ATTLIST xsl:stylesheet id ID #REQUIRED>]> <svg xmlns="http://www.w3.org/2000/svg"> <xsl:stylesheet id="stylesheet" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:alert(1)"></iframe> </xsl:template> </xsl:stylesheet> <circle fill="red" r="40"></circle> </svg>',
260 false,
261 true,
262 'SVG with embedded stylesheet (http://html5sec.org/#125)'
263 ],
264 [
265 '<?xml version="1.0"?> <?xml-stylesheet type="text/xml" href="#stylesheet"?> <svg xmlns="http://www.w3.org/2000/svg"> <xsl:stylesheet id="stylesheet" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:alert(1)"></iframe> </xsl:template> </xsl:stylesheet> <circle fill="red" r="40"></circle> </svg>',
266 true,
267 true,
268 'SVG with embedded stylesheet no doctype'
269 ],
270 [
271 '<svg xmlns="http://www.w3.org/2000/svg" id="x"> <listener event="load" handler="#y" xmlns="http://www.w3.org/2001/xml-events" observer="x"/> <handler id="y">alert(1)</handler> </svg>',
272 true,
273 true,
274 'SVG with handler attribute (http://html5sec.org/#127)'
275 ],
276 [
277 // Haven't found a browser that accepts this particular example, but we
278 // don't want to allow embeded svgs, ever
279 '<svg> <image style=\'filter:url("data:image/svg+xml;charset=utf-8;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ/YWxlcnQoMSk8L3NjcmlwdD48L3N2Zz4NCg==")\' /> </svg>',
280 true,
281 true,
282 'SVG with image filter via style (http://html5sec.org/#129)'
283 ],
284 [
285 // This doesn't seem possible without embedding the svg, but just in case
286 '<svg> <a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"> <circle r="400"></circle> <animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="" /> </a></svg>',
287 true,
288 true,
289 'SVG with animate from (http://html5sec.org/#137)'
290 ],
291 [
292 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <a><text y="1em">Click me</text> <animate attributeName="xlink:href" values="javascript:alert(\'Bang!\')" begin="0s" dur="0.1s" fill="freeze" /> </a></svg>',
293 true,
294 true,
295 'SVG with animate xlink:href (http://html5sec.org/#137)'
296 ],
297 [
298 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:y="http://www.w3.org/1999/xlink"> <a y:href="#"> <text y="1em">Click me</text> <animate attributeName="y:href" values="javascript:alert(\'Bang!\')" begin="0s" dur="0.1s" fill="freeze" /> </a> </svg>',
299 true,
300 true,
301 'SVG with animate y:href (http://html5sec.org/#137)'
302 ],
303
304 // Other hostile SVG's
305 [
306 '<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:xlink="http://www.w3.org/1999/xlink"> <image xlink:href="https://upload.wikimedia.org/wikipedia/commons/3/34/Bahnstrecke_Zeitz-Camburg_1930.png" /> </svg>',
307 true,
308 true,
309 'SVG with non-local image href (T67839)'
310 ],
311 [
312 '<?xml version="1.0" ?> <?xml-stylesheet type="text/xsl" href="/w/index.php?title=User:Jeeves/test.xsl&amp;action=raw&amp;format=xml" ?> <svg> <height>50</height> <width>100</width> </svg>',
313 true,
314 true,
315 'SVG with remote stylesheet (T59550)'
316 ],
317 [
318 '<svg xmlns="http://www.w3.org/2000/svg" viewbox="-1 -1 15 15"> <rect y="0" height="13" width="12" stroke="#179" rx="1" fill="#2ac"/> <text x="1.5" y="11" font-family="courier" stroke="white" font-size="16"><![CDATA[B]]></text> <iframe xmlns="http://www.w3.org/1999/xhtml" srcdoc="&#x3C;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3E;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x45;&#x44;&#x20;&#x3D;&#x3E;&#x20;&#x44;&#x6F;&#x6D;&#x61;&#x69;&#x6E;&#x28;&#x27;&#x2B;&#x74;&#x6F;&#x70;&#x2E;&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x64;&#x6F;&#x6D;&#x61;&#x69;&#x6E;&#x2B;&#x27;&#x29;&#x27;&#x29;&#x3B;&#x3C;&#x2F;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3E;"></iframe> </svg>',
319 true,
320 true,
321 'SVG with rembeded iframe (T62771)'
322 ],
323 [
324 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="6 3 177 153" xmlns:xlink="http://www.w3.org/1999/xlink"> <style>@import url("https://fonts.googleapis.com/css?family=Bitter:700&amp;text=WebPlatform.org");</style> <g transform="translate(-.5,-.5)"> <text fill="#474747" x="95" y="150" text-anchor="middle" font-family="Bitter" font-size="20" font-weight="bold">WebPlatform.org</text> </g> </svg>',
325 true,
326 true,
327 'SVG with @import in style element (T71008)'
328 ],
329 [
330 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="6 3 177 153" xmlns:xlink="http://www.w3.org/1999/xlink"> <style>@import url("https://fonts.googleapis.com/css?family=Bitter:700&amp;text=WebPlatform.org");<foo/></style> <g transform="translate(-.5,-.5)"> <text fill="#474747" x="95" y="150" text-anchor="middle" font-family="Bitter" font-size="20" font-weight="bold">WebPlatform.org</text> </g> </svg>',
331 true,
332 true,
333 'SVG with @import in style element and child element (T71008#c11)'
334 ],
335 [
336 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="6 3 177 153" xmlns:xlink="http://www.w3.org/1999/xlink"> <style>@imporT "https://fonts.googleapis.com/css?family=Bitter:700&amp;text=WebPlatform.org";</style> <g transform="translate(-.5,-.5)"> <text fill="#474747" x="95" y="150" text-anchor="middle" font-family="Bitter" font-size="20" font-weight="bold">WebPlatform.org</text> </g> </svg>',
337 true,
338 true,
339 'SVG with case-insensitive @import in style element (bug T85349)'
340 ],
341 [
342 '<svg xmlns="http://www.w3.org/2000/svg"> <rect width="100" height="100" style="background-image:url(https://www.google.com/images/srpr/logo11w.png)"/> </svg>',
343 true,
344 true,
345 'SVG with remote background image (T71008)'
346 ],
347 [
348 '<svg xmlns="http://www.w3.org/2000/svg"> <rect width="100" height="100" style="background-image:\55rl(https://www.google.com/images/srpr/logo11w.png)"/> </svg>',
349 true,
350 true,
351 'SVG with remote background image, encoded (T71008)'
352 ],
353 [
354 '<svg xmlns="http://www.w3.org/2000/svg"> <style> #a { background-image:\55rl(\'https://www.google.com/images/srpr/logo11w.png\'); } </style> <rect width="100" height="100" id="a"/> </svg>',
355 true,
356 true,
357 'SVG with remote background image, in style element (T71008)'
358 ],
359 [
360 // This currently doesn't seem to work in any browsers, but in case
361 // https://www.w3.org/TR/css3-images/ is implemented for SVG files
362 '<svg xmlns="http://www.w3.org/2000/svg"> <rect width="100" height="100" style="background-image:image(\'sprites.svg#xywh=40,0,20,20\')"/> </svg>',
363 true,
364 true,
365 'SVG with remote background image using image() (T71008)'
366 ],
367 [
368 // As reported by Cure53
369 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <a xlink:href="data:text/html;charset=utf-8;base64, PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ%2BDQo%3D"> <circle r="400" fill="red"></circle> </a> </svg>',
370 true,
371 true,
372 'SVG with data:text/html link target (firefox only)'
373 ],
374 [
375 '<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!ENTITY lol "lol"> <!ENTITY lol2 "&#x3C;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3E;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x45;&#x44;&#x20;&#x3D;&#x3E;&#x20;&#x27;&#x2B;&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x64;&#x6F;&#x6D;&#x61;&#x69;&#x6E;&#x29;&#x3B;&#x3C;&#x2F;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3E;"> ]> <svg xmlns="http://www.w3.org/2000/svg" width="68" height="68" viewBox="-34 -34 68 68" version="1.1"> <circle cx="0" cy="0" r="24" fill="#c8c8c8"/> <text x="0" y="0" fill="black">&lol2;</text> </svg>',
376 false,
377 true,
378 'SVG with encoded script tag in internal entity (reported by Beyond Security)'
379 ],
380 [
381 '<?xml version="1.0"?> <!DOCTYPE svg [ <!ENTITY foo SYSTEM "file:///etc/passwd"> ]> <svg xmlns="http://www.w3.org/2000/svg" version="1.1"> <desc>&foo;</desc> <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,2)" /> </svg>',
382 false,
383 false,
384 'SVG with external entity'
385 ],
386 [
387 // The base64 = <script>alert(1)</script>. If for some reason
388 // entities actually do get loaded, this should trigger
389 // filterMatch to be true. So this test verifies that we
390 // are not loading external entities.
391 '<?xml version="1.0"?> <!DOCTYPE svg [ <!ENTITY foo SYSTEM "data:text/plain;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pgo="> ]> <svg xmlns="http://www.w3.org/2000/svg" version="1.1"> <desc>&foo;</desc> <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,2)" /> </svg>',
392 false,
393 false, /* False verifies entities aren't getting loaded */
394 'SVG with data: uri external entity'
395 ],
396 [
397 "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <g> <a xlink:href=\"javascript:alert('1&#10;https://google.com')\"> <rect width=\"300\" height=\"100\" style=\"fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,2)\" /> </a> </g> </svg>",
398 true,
399 true,
400 'SVG with javascript <a> link with newline (T122653)'
401 ],
402 // Test good, but strange files that we want to allow
403 [
404 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <g> <a xlink:href="http://en.wikipedia.org/wiki/Main_Page"> <path transform="translate(0,496)" id="path6706" d="m 112.09375,107.6875 -5.0625,3.625 -4.3125,5.03125 -0.46875,0.5 -4.09375,3.34375 -9.125,5.28125 -8.625,-3.375 z" style="fill:#cccccc;fill-opacity:1;stroke:#6e6e6e;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;display:inline" /> </a> </g> </svg>',
405 true,
406 false,
407 'SVG with <a> link to a remote site'
408 ],
409 [
410 '<svg> <defs> <filter id="filter6226" x="-0.93243687" width="2.8648737" y="-0.24250539" height="1.4850108"> <feGaussianBlur stdDeviation="3.2344681" id="feGaussianBlur6228" /> </filter> <clipPath id="clipPath2436"> <path d="M 0,0 L 0,0 L 0,0 L 0,0 z" id="path2438" /> </clipPath> </defs> <g clip-path="url(#clipPath2436)" id="g2460"> <text id="text2466"> <tspan>12345</tspan> </text> </g> <path style="fill:#346733;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;filter:url(\'#filter6226\');fill-opacity:1;opacity:0.79807692" d="M 236.82371,332.63732 C 236.92217,332.63732 z" id="path5618" /> </svg>',
411 true,
412 false,
413 'SVG with local urls, including filter: in style'
414 ],
415 [
416 '<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE x [<!ATTLIST image x:href CDATA "data:image/png,foo" onerror CDATA "alert(\'XSSED = \'+document.domain)" onload CDATA "alert(\'XSSED = \'+document.domain)"> ]> <svg xmlns:h="http://www.w3.org/1999/xhtml" xmlns:x="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"> <image /> </svg>',
417 false,
418 false,
419 'SVG with evil default attribute values'
420 ],
421 [
422 '<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg SYSTEM "data:application/xml-dtd;base64,PCFET0NUWVBFIHN2ZyBbPCFBVFRMSVNUIGltYWdlIHg6aHJlZiBDREFUQSAiZGF0YTppbWFnZS9wbmcsZm9vIiBvbmVycm9yIENEQVRBICJhbGVydCgnWFNTRUQgPSAnK2RvY3VtZW50LmRvbWFpbikiIG9ubG9hZCBDREFUQSAiYWxlcnQoJ1hTU0VEID0gJytkb2N1bWVudC5kb21haW4pIj4gXT4K"><svg xmlns:x="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"> <image /> </svg>',
423 true,
424 true,
425 'SVG with an evil external dtd'
426 ],
427 [
428 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//FOO/bar" "http://example.com"><svg></svg>',
429 true,
430 true,
431 'SVG with random public doctype'
432 ],
433 [
434 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg SYSTEM \'http://example.com/evil.dtd\' ><svg></svg>',
435 true,
436 true,
437 'SVG with random SYSTEM doctype'
438 ],
439 [
440 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY % foo "bar" >] ><svg></svg>',
441 false,
442 false,
443 'SVG with parameter entity'
444 ],
445 [
446 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY foo "bar%a;" ] ><svg></svg>',
447 false,
448 false,
449 'SVG with entity referencing parameter entity'
450 ],
451 [
452 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY foo "bar0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"> ] ><svg></svg>',
453 false,
454 false,
455 'SVG with long entity'
456 ],
457 [
458 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY foo \'"Hi", said bob\'> ] ><svg><g>&foo;</g></svg>',
459 true,
460 false,
461 'SVG with apostrophe quote entity'
462 ],
463 [
464 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY name "Bob"><!ENTITY foo \'"Hi", said &name;.\'> ] ><svg><g>&foo;</g></svg>',
465 false,
466 false,
467 'SVG with recursive entity',
468 ],
469 [
470 '<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [ <!ATTLIST svg xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink"> ]> <svg width="417pt" height="366pt"
471 viewBox="0.00 0.00 417.00 366.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>',
472 true, /* well-formed */
473 false, /* filter-hit */
474 'GraphViz-esque svg with #FIXED xlink ns (Should be allowed)'
475 ],
476 [
477 '<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [ <!ATTLIST svg xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink2"> ]> <svg width="417pt" height="366pt"
478 viewBox="0.00 0.00 417.00 366.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>',
479 false,
480 false,
481 'GraphViz ATLIST exception should match exactly'
482 ],
483 [
484 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!-- Comment-here --> <!ENTITY foo "#ff6666">]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
485 true,
486 false,
487 'DTD with comments (Should be allowed)'
488 ],
489 [
490 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!-- invalid--comment --> <!ENTITY foo "#ff6666">]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
491 false,
492 false,
493 'DTD with invalid comment'
494 ],
495 [
496 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!-- invalid ---> <!ENTITY foo "#ff6666">]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
497 false,
498 false,
499 'DTD with invalid comment 2'
500 ],
501 [
502 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!ENTITY bar "&foo;"> <!ENTITY foo "#ff6666">]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
503 true,
504 false,
505 'DTD with aliased entities (Should be allowed)'
506 ],
507 [
508 '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!ENTITY bar \'&foo;\'> <!ENTITY foo \'#ff6666\'>]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
509 true,
510 false,
511 'DTD with aliased entities apos (Should be allowed)'
512 ]
513 ];
514 // phpcs:enable
515 }
516
517 /**
518 * @covers UploadBase::detectScriptInSvg
519 * @dataProvider provideDetectScriptInSvg
520 */
521 public function testDetectScriptInSvg( $svg, $expected, $message ) {
522 // This only checks some weird cases, most tests are in testCheckSvgScriptCallback() above
523 $result = $this->upload->detectScriptInSvg( $svg, false );
524 $this->assertSame( $expected, $result, $message );
525 }
526
527 public static function provideDetectScriptInSvg() {
528 global $IP;
529 return [
530 [
531 "$IP/tests/phpunit/data/upload/buggynamespace-original.svg",
532 false,
533 'SVG with a weird but valid namespace definition created by Adobe Illustrator'
534 ],
535 [
536 "$IP/tests/phpunit/data/upload/buggynamespace-okay.svg",
537 false,
538 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape'
539 ],
540 [
541 "$IP/tests/phpunit/data/upload/buggynamespace-okay2.svg",
542 false,
543 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape (twice)'
544 ],
545 [
546 "$IP/tests/phpunit/data/upload/buggynamespace-bad.svg",
547 [ 'uploadscriptednamespace', 'i' ],
548 'SVG with a namespace definition using an undefined entity'
549 ],
550 [
551 "$IP/tests/phpunit/data/upload/buggynamespace-evilhtml.svg",
552 [ 'uploadscriptednamespace', 'http://www.w3.org/1999/xhtml' ],
553 'SVG with an html namespace encoded as an entity'
554 ],
555 ];
556 }
557
558 /**
559 * @covers UploadBase::checkXMLEncodingMissmatch
560 * @dataProvider provideCheckXMLEncodingMissmatch
561 */
562 public function testCheckXMLEncodingMissmatch( $fileContents, $evil ) {
563 $filename = $this->getNewTempFile();
564 file_put_contents( $filename, $fileContents );
565 $this->assertSame( $evil, UploadBase::checkXMLEncodingMissmatch( $filename ) );
566 }
567
568 public function provideCheckXMLEncodingMissmatch() {
569 return [
570 [ '<?xml version="1.0" encoding="utf-7"?><svg></svg>', true ],
571 [ '<?xml version="1.0" encoding="utf-8"?><svg></svg>', false ],
572 [ '<?xml version="1.0" encoding="WINDOWS-1252"?><svg></svg>', false ],
573 ];
574 }
575 }
576
577 class UploadTestHandler extends UploadBase {
578 public function initializeFromRequest( &$request ) {
579 }
580
581 public function testTitleValidation( $name ) {
582 $this->mTitle = false;
583 $this->mDesiredDestName = $name;
584 $this->mTitleError = UploadBase::OK;
585 $this->getTitle();
586
587 return $this->mTitleError;
588 }
589
590 /**
591 * Almost the same as UploadBase::detectScriptInSvg, except it's
592 * public, works on an xml string instead of filename, and returns
593 * the result instead of interpreting them.
594 */
595 public function checkSvgString( $svg ) {
596 $check = new XmlTypeCheck(
597 $svg,
598 [ $this, 'checkSvgScriptCallback' ],
599 false,
600 [
601 'processing_instruction_handler' => 'UploadBase::checkSvgPICallback',
602 'external_dtd_handler' => 'UploadBase::checkSvgExternalDTD'
603 ]
604 );
605 return [ $check->wellFormed, $check->filterMatch ];
606 }
607
608 /**
609 * Same as parent function, but override visibility to 'public'.
610 */
611 public function detectScriptInSvg( $filename, $partial ) {
612 return parent::detectScriptInSvg( $filename, $partial );
613 }
614 }