upload = new UploadTestHandler; $this->setMwGlobals( 'wgHooks', [ 'InterwikiLoadPrefix' => [ function ( $prefix, &$data ) { return false; } ], ] ); } /** * First checks the return code * of UploadBase::getTitle() and then the actual returned title * * @dataProvider provideTestTitleValidation * @covers UploadBase::getTitle */ public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) { /* Check the result code */ $this->assertEquals( $code, $this->upload->testTitleValidation( $srcFilename ), "$msg code" ); /* If we expect a valid title, check the title itself. */ if ( $code == UploadBase::OK ) { $this->assertEquals( $dstFilename, $this->upload->getTitle()->getText(), "$msg text" ); } } /** * Test various forms of valid and invalid titles that can be supplied. */ public static function provideTestTitleValidation() { return [ /* Test a valid title */ [ 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, 'upload valid title' ], /* A title with a slash */ [ 'A/B.jpg', 'A-B.jpg', UploadBase::OK, 'upload title with slash' ], /* A title with illegal char */ [ 'A:B.jpg', 'A-B.jpg', UploadBase::OK, 'upload title with colon' ], /* Stripping leading File: prefix */ [ 'File:C.jpg', 'C.jpg', UploadBase::OK, 'upload title with File prefix' ], /* Test illegal suggested title (r94601) */ [ '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME, 'illegal title for upload' ], /* A title without extension */ [ 'A', null, UploadBase::FILETYPE_MISSING, 'upload title without extension' ], /* A title with no basename */ [ '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME, 'upload title without basename' ], /* A title that is longer than 255 bytes */ [ str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, 'upload title longer than 255 bytes' ], /* A title that is longer than 240 bytes */ [ str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, 'upload title longer than 240 bytes' ], ]; } /** * Test the upload verification functions * @covers UploadBase::verifyUpload */ public function testVerifyUpload() { /* Setup with zero file size */ $this->upload->initializePathInfo( '', '', 0 ); $result = $this->upload->verifyUpload(); $this->assertEquals( UploadBase::EMPTY_FILE, $result['status'], 'upload empty file' ); } // Helper used to create an empty file of size $size. private function createFileOfSize( $size ) { $filename = $this->getNewTempFile(); $fh = fopen( $filename, 'w' ); ftruncate( $fh, $size ); fclose( $fh ); return $filename; } /** * test uploading a 100 bytes file with $wgMaxUploadSize = 100 * * This method should be abstracted so we can test different settings. */ public function testMaxUploadSize() { $this->setMwGlobals( [ 'wgMaxUploadSize' => 100, 'wgFileExtensions' => [ 'txt', ], ] ); $filename = $this->createFileOfSize( 100 ); $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 ); $result = $this->upload->verifyUpload(); $this->assertEquals( [ 'status' => UploadBase::OK ], $result ); } /** * @dataProvider provideCheckSvgScriptCallback */ public function testCheckSvgScriptCallback( $svg, $wellFormed, $filterMatch, $message ) { list( $formed, $match ) = $this->upload->checkSvgString( $svg ); $this->assertSame( $wellFormed, $formed, $message ); $this->assertSame( $filterMatch, $match, $message ); } public static function provideCheckSvgScriptCallback() { // @codingStandardsIgnoreStart Generic.Files.LineLength return [ // html5sec SVG vectors [ '', true, true, 'Script tag in svg (http://html5sec.org/#47)' ], [ '', true, true, 'SVG with onload property (http://html5sec.org/#11)' ], [ '', true, true, 'SVG with onload property (http://html5sec.org/#65)' ], [ ' ', true, true, 'SVG with javascript xlink (http://html5sec.org/#87)' ], [ ' ', true, true, 'SVG with Opera image xlink (http://html5sec.org/#88 - c)' ], [ ' ', true, true, 'SVG with Opera animation xlink (http://html5sec.org/#88 - a)' ], [ ' ', true, true, 'SVG with Opera animation xlink (http://html5sec.org/#88 - b)' ], [ ' ', true, true, 'SVG with Opera image xlink (http://html5sec.org/#88 - c)' ], [ ' ', true, true, 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - d)' ], [ ' ', true, true, 'SVG with Opera foreignObject xlink (http://html5sec.org/#88 - e)' ], [ ' ', true, true, 'SVG with event handler set (http://html5sec.org/#89 - a)' ], [ ' ', true, true, 'SVG with event handler animate (http://html5sec.org/#89 - a)' ], [ ' alert(1) ', true, true, 'SVG with element handler (http://html5sec.org/#94)' ], [ ' ', true, true, 'SVG with href to data: url (http://html5sec.org/#95)' ], [ ' ', true, true, 'SVG with Tiny handler (http://html5sec.org/#104)' ], [ ' ', true, true, 'SVG with new CSS styles properties (http://html5sec.org/#109)' ], [ ' ', true, true, 'SVG with new CSS styles properties as attributes' ], [ ' ', true, true, 'SVG with new CSS styles properties as attributes (2)' ], [ ' ', true, true, 'SVG with path marker-start (http://html5sec.org/#110)' ], [ ' ]> ', true, true, 'SVG with embedded stylesheet (http://html5sec.org/#125)' ], [ ' alert(1) ', true, true, 'SVG with handler attribute (http://html5sec.org/#127)' ], [ // Haven't found a browser that accepts this particular example, but we // don't want to allow embeded svgs, ever ' ', true, true, 'SVG with image filter via style (http://html5sec.org/#129)' ], [ // This doesn't seem possible without embedding the svg, but just in case ' ', true, true, 'SVG with animate from (http://html5sec.org/#137)' ], [ ' Click me ', true, true, 'SVG with animate xlink:href (http://html5sec.org/#137)' ], [ ' Click me ', true, true, 'SVG with animate y:href (http://html5sec.org/#137)' ], // Other hostile SVG's [ ' ', true, true, 'SVG with non-local image href (bug 65839)' ], [ ' 50 100 ', true, true, 'SVG with remote stylesheet (bug 57550)' ], [ ' B ', true, true, 'SVG with rembeded iframe (bug 60771)' ], [ ' WebPlatform.org ', true, true, 'SVG with @import in style element (bug 69008)' ], [ ' WebPlatform.org ', true, true, 'SVG with @import in style element and child element (bug 69008#c11)' ], [ ' WebPlatform.org ', true, true, 'SVG with case-insensitive @import in style element (bug T85349)' ], [ ' ', true, true, 'SVG with remote background image (bug 69008)' ], [ ' ', true, true, 'SVG with remote background image, encoded (bug 69008)' ], [ ' ', true, true, 'SVG with remote background image, in style element (bug 69008)' ], [ // This currently doesn't seem to work in any browsers, but in case // https://www.w3.org/TR/css3-images/ is implemented for SVG files ' ', true, true, 'SVG with remote background image using image() (bug 69008)' ], [ // As reported by Cure53 ' ', true, true, 'SVG with data:text/html link target (firefox only)' ], [ ' ]> &lol2; ', true, true, 'SVG with encoded script tag in internal entity (reported by Beyond Security)' ], [ ' ]> &foo; ', false, false, 'SVG with external entity' ], [ " ", true, true, 'SVG with javascript link with newline (T122653)' ], // Test good, but strange files that we want to allow [ ' ', true, false, 'SVG with link to a remote site' ], [ ' 12345 ', true, false, 'SVG with local urls, including filter: in style' ], ]; // @codingStandardsIgnoreEnd } /** * @dataProvider provideDetectScriptInSvg */ public function testDetectScriptInSvg( $svg, $expected, $message ) { // This only checks some weird cases, most tests are in testCheckSvgScriptCallback() above $result = $this->upload->detectScriptInSvg( $svg, false ); $this->assertSame( $expected, $result, $message ); } public static function provideDetectScriptInSvg() { global $IP; return [ [ "$IP/tests/phpunit/data/upload/buggynamespace-original.svg", false, 'SVG with a weird but valid namespace definition created by Adobe Illustrator' ], [ "$IP/tests/phpunit/data/upload/buggynamespace-okay.svg", false, 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape' ], [ "$IP/tests/phpunit/data/upload/buggynamespace-okay2.svg", false, 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape (twice)' ], [ "$IP/tests/phpunit/data/upload/buggynamespace-bad.svg", [ 'uploadscriptednamespace', 'i' ], 'SVG with a namespace definition using an undefined entity' ], [ "$IP/tests/phpunit/data/upload/buggynamespace-evilhtml.svg", [ 'uploadscriptednamespace', 'http://www.w3.org/1999/xhtml' ], 'SVG with an html namespace encoded as an entity' ], ]; } /** * @dataProvider provideCheckXMLEncodingMissmatch */ public function testCheckXMLEncodingMissmatch( $fileContents, $evil ) { $filename = $this->getNewTempFile(); file_put_contents( $filename, $fileContents ); $this->assertSame( UploadBase::checkXMLEncodingMissmatch( $filename ), $evil ); } public function provideCheckXMLEncodingMissmatch() { return [ [ '', true ], [ '', false ], [ '', false ], ]; } } class UploadTestHandler extends UploadBase { public function initializeFromRequest( &$request ) { } public function testTitleValidation( $name ) { $this->mTitle = false; $this->mDesiredDestName = $name; $this->mTitleError = UploadBase::OK; $this->getTitle(); return $this->mTitleError; } /** * Almost the same as UploadBase::detectScriptInSvg, except it's * public, works on an xml string instead of filename, and returns * the result instead of interpreting them. */ public function checkSvgString( $svg ) { $check = new XmlTypeCheck( $svg, [ $this, 'checkSvgScriptCallback' ], false, [ 'processing_instruction_handler' => 'UploadBase::checkSvgPICallback' ] ); return [ $check->wellFormed, $check->filterMatch ]; } /** * Same as parent function, but override visibility to 'public'. */ public function detectScriptInSvg( $filename, $partial ) { return parent::detectScriptInSvg( $filename, $partial ); } }