UploadBaseTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. <?php
  2. /**
  3. * @group Upload
  4. */
  5. class UploadBaseTest extends MediaWikiTestCase {
  6. /** @var UploadTestHandler */
  7. protected $upload;
  8. protected function setUp() {
  9. parent::setUp();
  10. $this->upload = new UploadTestHandler;
  11. $this->setMwGlobals( 'wgHooks', [
  12. 'InterwikiLoadPrefix' => [
  13. function ( $prefix, &$data ) {
  14. return false;
  15. }
  16. ],
  17. ] );
  18. }
  19. /**
  20. * First checks the return code
  21. * of UploadBase::getTitle() and then the actual returned title
  22. *
  23. * @dataProvider provideTestTitleValidation
  24. * @covers UploadBase::getTitle
  25. */
  26. public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) {
  27. /* Check the result code */
  28. $this->assertEquals( $code,
  29. $this->upload->testTitleValidation( $srcFilename ),
  30. "$msg code" );
  31. /* If we expect a valid title, check the title itself. */
  32. if ( $code == UploadBase::OK ) {
  33. $this->assertEquals( $dstFilename,
  34. $this->upload->getTitle()->getText(),
  35. "$msg text" );
  36. }
  37. }
  38. /**
  39. * Test various forms of valid and invalid titles that can be supplied.
  40. */
  41. public static function provideTestTitleValidation() {
  42. return [
  43. /* Test a valid title */
  44. [ 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK,
  45. 'upload valid title' ],
  46. /* A title with a slash */
  47. [ 'A/B.jpg', 'A-B.jpg', UploadBase::OK,
  48. 'upload title with slash' ],
  49. /* A title with illegal char */
  50. [ 'A:B.jpg', 'A-B.jpg', UploadBase::OK,
  51. 'upload title with colon' ],
  52. /* Stripping leading File: prefix */
  53. [ 'File:C.jpg', 'C.jpg', UploadBase::OK,
  54. 'upload title with File prefix' ],
  55. /* Test illegal suggested title (r94601) */
  56. [ '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME,
  57. 'illegal title for upload' ],
  58. /* A title without extension */
  59. [ 'A', null, UploadBase::FILETYPE_MISSING,
  60. 'upload title without extension' ],
  61. /* A title with no basename */
  62. [ '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME,
  63. 'upload title without basename' ],
  64. /* A title that is longer than 255 bytes */
  65. [ str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG,
  66. 'upload title longer than 255 bytes' ],
  67. /* A title that is longer than 240 bytes */
  68. [ str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG,
  69. 'upload title longer than 240 bytes' ],
  70. ];
  71. }
  72. /**
  73. * Test the upload verification functions
  74. * @covers UploadBase::verifyUpload
  75. */
  76. public function testVerifyUpload() {
  77. /* Setup with zero file size */
  78. $this->upload->initializePathInfo( '', '', 0 );
  79. $result = $this->upload->verifyUpload();
  80. $this->assertEquals( UploadBase::EMPTY_FILE,
  81. $result['status'],
  82. 'upload empty file' );
  83. }
  84. // Helper used to create an empty file of size $size.
  85. private function createFileOfSize( $size ) {
  86. $filename = $this->getNewTempFile();
  87. $fh = fopen( $filename, 'w' );
  88. ftruncate( $fh, $size );
  89. fclose( $fh );
  90. return $filename;
  91. }
  92. /**
  93. * @covers UploadBase::verifyUpload
  94. *
  95. * test uploading a 100 bytes file with $wgMaxUploadSize = 100
  96. *
  97. * This method should be abstracted so we can test different settings.
  98. */
  99. public function testMaxUploadSize() {
  100. $this->setMwGlobals( [
  101. 'wgMaxUploadSize' => 100,
  102. 'wgFileExtensions' => [
  103. 'txt',
  104. ],
  105. ] );
  106. $filename = $this->createFileOfSize( 100 );
  107. $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 );
  108. $result = $this->upload->verifyUpload();
  109. $this->assertEquals(
  110. [ 'status' => UploadBase::OK ],
  111. $result
  112. );
  113. }
  114. /**
  115. * @covers UploadBase::checkSvgScriptCallback
  116. * @dataProvider provideCheckSvgScriptCallback
  117. */
  118. public function testCheckSvgScriptCallback( $svg, $wellFormed, $filterMatch, $message ) {
  119. list( $formed, $match ) = $this->upload->checkSvgString( $svg );
  120. $this->assertSame( $wellFormed, $formed, $message . " (well-formed)" );
  121. $this->assertSame( $filterMatch, $match, $message . " (filter match)" );
  122. }
  123. public static function provideCheckSvgScriptCallback() {
  124. // phpcs:disable Generic.Files.LineLength
  125. return [
  126. // html5sec SVG vectors
  127. [
  128. '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>',
  129. true,
  130. true,
  131. 'Script tag in svg (http://html5sec.org/#47)'
  132. ],
  133. [
  134. '<svg xmlns="http://www.w3.org/2000/svg"><g onload="javascript:alert(1)"></g></svg>',
  135. true,
  136. true,
  137. 'SVG with onload property (http://html5sec.org/#11)'
  138. ],
  139. [
  140. '<svg onload="javascript:alert(1)" xmlns="http://www.w3.org/2000/svg"></svg>',
  141. true,
  142. true,
  143. 'SVG with onload property (http://html5sec.org/#65)'
  144. ],
  145. [
  146. '<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>',
  147. true,
  148. true,
  149. 'SVG with javascript xlink (http://html5sec.org/#87)'
  150. ],
  151. [
  152. '<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>',
  153. true,
  154. true,
  155. 'SVG with Opera image xlink (http://html5sec.org/#88 - c)'
  156. ],
  157. [
  158. '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <animation xlink:href="javascript:alert(1)"/> </svg>',
  159. true,
  160. true,
  161. 'SVG with Opera animation xlink (http://html5sec.org/#88 - a)'
  162. ],
  163. [
  164. '<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>',
  165. true,
  166. true,
  167. 'SVG with Opera animation xlink (http://html5sec.org/#88 - b)'
  168. ],
  169. [
  170. '<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>',
  171. true,
  172. true,
  173. 'SVG with Opera image xlink (http://html5sec.org/#88 - c)'
  174. ],
  175. [
  176. '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <foreignObject xlink:href="javascript:alert(1)"/> </svg>',
  177. true,
  178. true,
  179. 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - d)'
  180. ],
  181. [
  182. '<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>',
  183. true,
  184. true,
  185. 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - e)'
  186. ],
  187. [
  188. '<svg xmlns="http://www.w3.org/2000/svg"> <set attributeName="onmouseover" to="alert(1)"/> </svg>',
  189. true,
  190. true,
  191. 'SVG with event handler set (http://html5sec.org/#89 - a)'
  192. ],
  193. [
  194. '<svg xmlns="http://www.w3.org/2000/svg"> <animate attributeName="onunload" to="alert(1)"/> </svg>',
  195. true,
  196. true,
  197. 'SVG with event handler animate (http://html5sec.org/#89 - a)'
  198. ],
  199. [
  200. '<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>',
  201. true,
  202. true,
  203. 'SVG with element handler (http://html5sec.org/#94)'
  204. ],
  205. [
  206. '<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>',
  207. true,
  208. true,
  209. 'SVG with href to data: url (http://html5sec.org/#95)'
  210. ],
  211. [
  212. '<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>',
  213. true,
  214. true,
  215. 'SVG with Tiny handler (http://html5sec.org/#104)'
  216. ],
  217. [
  218. '<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>',
  219. true,
  220. true,
  221. 'SVG with new CSS styles properties (http://html5sec.org/#109)'
  222. ],
  223. [
  224. '<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>',
  225. true,
  226. true,
  227. 'SVG with new CSS styles properties as attributes'
  228. ],
  229. [
  230. '<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>',
  231. true,
  232. true,
  233. 'SVG with new CSS styles properties as attributes (2)'
  234. ],
  235. [
  236. '<svg xmlns="http://www.w3.org/2000/svg"> <path d="M0,0" style="marker-start:url(test4.svg#a)"/> </svg>',
  237. true,
  238. true,
  239. 'SVG with path marker-start (http://html5sec.org/#110)'
  240. ],
  241. [
  242. '<?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>',
  243. false,
  244. true,
  245. 'SVG with embedded stylesheet (http://html5sec.org/#125)'
  246. ],
  247. [
  248. '<?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>',
  249. true,
  250. true,
  251. 'SVG with embedded stylesheet no doctype'
  252. ],
  253. [
  254. '<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>',
  255. true,
  256. true,
  257. 'SVG with handler attribute (http://html5sec.org/#127)'
  258. ],
  259. [
  260. // Haven't found a browser that accepts this particular example, but we
  261. // don't want to allow embeded svgs, ever
  262. '<svg> <image style=\'filter:url("data:image/svg+xml;charset=utf-8;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ/YWxlcnQoMSk8L3NjcmlwdD48L3N2Zz4NCg==")\' /> </svg>',
  263. true,
  264. true,
  265. 'SVG with image filter via style (http://html5sec.org/#129)'
  266. ],
  267. [
  268. // This doesn't seem possible without embedding the svg, but just in case
  269. '<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>',
  270. true,
  271. true,
  272. 'SVG with animate from (http://html5sec.org/#137)'
  273. ],
  274. [
  275. '<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>',
  276. true,
  277. true,
  278. 'SVG with animate xlink:href (http://html5sec.org/#137)'
  279. ],
  280. [
  281. '<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>',
  282. true,
  283. true,
  284. 'SVG with animate y:href (http://html5sec.org/#137)'
  285. ],
  286. // Other hostile SVG's
  287. [
  288. '<?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>',
  289. true,
  290. true,
  291. 'SVG with non-local image href (T67839)'
  292. ],
  293. [
  294. '<?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>',
  295. true,
  296. true,
  297. 'SVG with remote stylesheet (T59550)'
  298. ],
  299. [
  300. '<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>',
  301. true,
  302. true,
  303. 'SVG with rembeded iframe (T62771)'
  304. ],
  305. [
  306. '<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>',
  307. true,
  308. true,
  309. 'SVG with @import in style element (T71008)'
  310. ],
  311. [
  312. '<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>',
  313. true,
  314. true,
  315. 'SVG with @import in style element and child element (T71008#c11)'
  316. ],
  317. [
  318. '<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>',
  319. true,
  320. true,
  321. 'SVG with case-insensitive @import in style element (bug T85349)'
  322. ],
  323. [
  324. '<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>',
  325. true,
  326. true,
  327. 'SVG with remote background image (T71008)'
  328. ],
  329. [
  330. '<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>',
  331. true,
  332. true,
  333. 'SVG with remote background image, encoded (T71008)'
  334. ],
  335. [
  336. '<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>',
  337. true,
  338. true,
  339. 'SVG with remote background image, in style element (T71008)'
  340. ],
  341. [
  342. // This currently doesn't seem to work in any browsers, but in case
  343. // https://www.w3.org/TR/css3-images/ is implemented for SVG files
  344. '<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>',
  345. true,
  346. true,
  347. 'SVG with remote background image using image() (T71008)'
  348. ],
  349. [
  350. // As reported by Cure53
  351. '<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>',
  352. true,
  353. true,
  354. 'SVG with data:text/html link target (firefox only)'
  355. ],
  356. [
  357. '<?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>',
  358. false,
  359. true,
  360. 'SVG with encoded script tag in internal entity (reported by Beyond Security)'
  361. ],
  362. [
  363. '<?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>',
  364. false,
  365. false,
  366. 'SVG with external entity'
  367. ],
  368. [
  369. // The base64 = <script>alert(1)</script>. If for some reason
  370. // entities actually do get loaded, this should trigger
  371. // filterMatch to be true. So this test verifies that we
  372. // are not loading external entities.
  373. '<?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>',
  374. false,
  375. false, /* False verifies entities aren't getting loaded */
  376. 'SVG with data: uri external entity'
  377. ],
  378. [
  379. "<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>",
  380. true,
  381. true,
  382. 'SVG with javascript <a> link with newline (T122653)'
  383. ],
  384. // Test good, but strange files that we want to allow
  385. [
  386. '<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>',
  387. true,
  388. false,
  389. 'SVG with <a> link to a remote site'
  390. ],
  391. [
  392. '<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>',
  393. true,
  394. false,
  395. 'SVG with local urls, including filter: in style'
  396. ],
  397. [
  398. '<?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>',
  399. false,
  400. false,
  401. 'SVG with evil default attribute values'
  402. ],
  403. [
  404. '<?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>',
  405. true,
  406. true,
  407. 'SVG with an evil external dtd'
  408. ],
  409. [
  410. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//FOO/bar" "http://example.com"><svg></svg>',
  411. true,
  412. true,
  413. 'SVG with random public doctype'
  414. ],
  415. [
  416. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg SYSTEM \'http://example.com/evil.dtd\' ><svg></svg>',
  417. true,
  418. true,
  419. 'SVG with random SYSTEM doctype'
  420. ],
  421. [
  422. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY % foo "bar" >] ><svg></svg>',
  423. false,
  424. false,
  425. 'SVG with parameter entity'
  426. ],
  427. [
  428. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY foo "bar%a;" ] ><svg></svg>',
  429. false,
  430. false,
  431. 'SVG with entity referencing parameter entity'
  432. ],
  433. [
  434. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY foo "bar0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"> ] ><svg></svg>',
  435. false,
  436. false,
  437. 'SVG with long entity'
  438. ],
  439. [
  440. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY foo \'"Hi", said bob\'> ] ><svg><g>&foo;</g></svg>',
  441. true,
  442. false,
  443. 'SVG with apostrophe quote entity'
  444. ],
  445. [
  446. '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg [<!ENTITY name "Bob"><!ENTITY foo \'"Hi", said &name;.\'> ] ><svg><g>&foo;</g></svg>',
  447. false,
  448. false,
  449. 'SVG with recursive entity',
  450. ],
  451. [
  452. '<?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"
  453. 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>',
  454. true, /* well-formed */
  455. false, /* filter-hit */
  456. 'GraphViz-esque svg with #FIXED xlink ns (Should be allowed)'
  457. ],
  458. [
  459. '<?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"
  460. 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>',
  461. false,
  462. false,
  463. 'GraphViz ATLIST exception should match exactly'
  464. ],
  465. [
  466. '<?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>',
  467. true,
  468. false,
  469. 'DTD with comments (Should be allowed)'
  470. ],
  471. [
  472. '<?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>',
  473. false,
  474. false,
  475. 'DTD with invalid comment'
  476. ],
  477. [
  478. '<?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>',
  479. false,
  480. false,
  481. 'DTD with invalid comment 2'
  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" [ <!ENTITY bar "&foo;"> <!ENTITY foo "#ff6666">]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
  485. true,
  486. false,
  487. 'DTD with aliased entities (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" [ <!ENTITY bar \'&foo;\'> <!ENTITY foo \'#ff6666\'>]><svg xmlns="http://www.w3.org/2000/svg"></svg>',
  491. true,
  492. false,
  493. 'DTD with aliased entities apos (Should be allowed)'
  494. ]
  495. ];
  496. // phpcs:enable
  497. }
  498. /**
  499. * @covers UploadBase::detectScriptInSvg
  500. * @dataProvider provideDetectScriptInSvg
  501. */
  502. public function testDetectScriptInSvg( $svg, $expected, $message ) {
  503. // This only checks some weird cases, most tests are in testCheckSvgScriptCallback() above
  504. $result = $this->upload->detectScriptInSvg( $svg, false );
  505. $this->assertSame( $expected, $result, $message );
  506. }
  507. public static function provideDetectScriptInSvg() {
  508. global $IP;
  509. return [
  510. [
  511. "$IP/tests/phpunit/data/upload/buggynamespace-original.svg",
  512. false,
  513. 'SVG with a weird but valid namespace definition created by Adobe Illustrator'
  514. ],
  515. [
  516. "$IP/tests/phpunit/data/upload/buggynamespace-okay.svg",
  517. false,
  518. 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape'
  519. ],
  520. [
  521. "$IP/tests/phpunit/data/upload/buggynamespace-okay2.svg",
  522. false,
  523. 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape (twice)'
  524. ],
  525. [
  526. "$IP/tests/phpunit/data/upload/buggynamespace-bad.svg",
  527. [ 'uploadscriptednamespace', 'i' ],
  528. 'SVG with a namespace definition using an undefined entity'
  529. ],
  530. [
  531. "$IP/tests/phpunit/data/upload/buggynamespace-evilhtml.svg",
  532. [ 'uploadscriptednamespace', 'http://www.w3.org/1999/xhtml' ],
  533. 'SVG with an html namespace encoded as an entity'
  534. ],
  535. ];
  536. }
  537. /**
  538. * @covers UploadBase::checkXMLEncodingMissmatch
  539. * @dataProvider provideCheckXMLEncodingMissmatch
  540. */
  541. public function testCheckXMLEncodingMissmatch( $fileContents, $evil ) {
  542. $filename = $this->getNewTempFile();
  543. file_put_contents( $filename, $fileContents );
  544. $this->assertSame( $evil, UploadBase::checkXMLEncodingMissmatch( $filename ) );
  545. }
  546. public function provideCheckXMLEncodingMissmatch() {
  547. return [
  548. [ '<?xml version="1.0" encoding="utf-7"?><svg></svg>', true ],
  549. [ '<?xml version="1.0" encoding="utf-8"?><svg></svg>', false ],
  550. [ '<?xml version="1.0" encoding="WINDOWS-1252"?><svg></svg>', false ],
  551. ];
  552. }
  553. }
  554. class UploadTestHandler extends UploadBase {
  555. public function initializeFromRequest( &$request ) {
  556. }
  557. public function testTitleValidation( $name ) {
  558. $this->mTitle = false;
  559. $this->mDesiredDestName = $name;
  560. $this->mTitleError = UploadBase::OK;
  561. $this->getTitle();
  562. return $this->mTitleError;
  563. }
  564. /**
  565. * Almost the same as UploadBase::detectScriptInSvg, except it's
  566. * public, works on an xml string instead of filename, and returns
  567. * the result instead of interpreting them.
  568. */
  569. public function checkSvgString( $svg ) {
  570. $check = new XmlTypeCheck(
  571. $svg,
  572. [ $this, 'checkSvgScriptCallback' ],
  573. false,
  574. [
  575. 'processing_instruction_handler' => 'UploadBase::checkSvgPICallback',
  576. 'external_dtd_handler' => 'UploadBase::checkSvgExternalDTD'
  577. ]
  578. );
  579. return [ $check->wellFormed, $check->filterMatch ];
  580. }
  581. /**
  582. * Same as parent function, but override visibility to 'public'.
  583. */
  584. public function detectScriptInSvg( $filename, $partial ) {
  585. return parent::detectScriptInSvg( $filename, $partial );
  586. }
  587. }