Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock
Please unblock package mediawiki. The only change is an addition of an upstream patch for this release branch which fixes a number of security issues. unblock mediawiki/1:1.19.20+dfsg-2.3 Thanks, Thijs
diff -Nru mediawiki-1.19.20+dfsg/debian/changelog mediawiki-1.19.20+dfsg/debian/changelog --- mediawiki-1.19.20+dfsg/debian/changelog 2014-12-21 12:11:10.000000000 +0000 +++ mediawiki-1.19.20+dfsg/debian/changelog 2015-04-06 16:55:57.000000000 +0000 @@ -1,3 +1,21 @@ +mediawiki (1:1.19.20+dfsg-2.3) unstable; urgency=high + + * Non-maintainer upload. + * Add patch fixing several security issues: + - (bug T85848, bug T71210) SECURITY: Don't parse XMP blocks that + contain XML entities, to prevent various DoS attacks. + - (bug T88310) SECURITY: Always expand xml entities when checking + SVG's. + - (bug T73394) SECURITY: Escape > in Html::expandAttributes to + prevent XSS. + - (bug T85855) SECURITY: Don't execute another user's CSS or JS + on preview. + - (bug T85349, bug T85850, bug T86711) SECURITY: Multiple issues + fixed in SVG filtering to prevent XSS and protect viewer's + privacy. + + -- Thijs Kinkhorst <th...@debian.org> Mon, 06 Apr 2015 16:53:54 +0000 + mediawiki (1:1.19.20+dfsg-2.2) unstable; urgency=medium * Non-maintainer upload. diff -Nru mediawiki-1.19.20+dfsg/debian/patches/security_1.19.24.patch mediawiki-1.19.20+dfsg/debian/patches/security_1.19.24.patch --- mediawiki-1.19.20+dfsg/debian/patches/security_1.19.24.patch 1970-01-01 00:00:00.000000000 +0000 +++ mediawiki-1.19.20+dfsg/debian/patches/security_1.19.24.patch 2015-04-06 17:03:41.000000000 +0000 @@ -0,0 +1,636 @@ +From: Mediawiki +Subject: Fix security issues as fixed in upstream security fix release 1.19.24: +(bug T85848, bug T71210) SECURITY: Don't parse XMP blocks that contain XML entities, to prevent various DoS attacks. +(bug T88310) SECURITY: Always expand xml entities when checking SVG's. +(bug T73394) SECURITY: Escape > in Html::expandAttributes to prevent XSS. +(bug T85855) SECURITY: Don't execute another user's CSS or JS on preview. +(bug T85349, bug T85850, bug T86711) SECURITY: Multiple issues fixed in SVG filtering to prevent XSS and protect viewer's privacy. +Origin: upstream, https://lists.wikimedia.org/pipermail/mediawiki-announce/2015-March/000175.html + +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/EditPage.php mediawiki-1.19.24/includes/EditPage.php +--- mediawiki-1.19.23/includes/EditPage.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/EditPage.php 2015-03-31 13:23:38.000000000 +0000 +@@ -1988,14 +1988,19 @@ + if ( $this->isWrongCaseCssJsPage ) { + $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) ); + } ++ if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) { + if ( $this->formtype !== 'preview' ) { +- if ( $this->isCssSubpage ) ++ if ( $this->isCssSubpage ) { + $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) ); +- if ( $this->isJsSubpage ) ++ } ++ ++ if ( $this->isJsSubpage ) { + $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) ); + } + } + } ++ } ++ } + + if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) { + # Is the title semi-protected? +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/Html.php mediawiki-1.19.24/includes/Html.php +--- mediawiki-1.19.23/includes/Html.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/Html.php 2015-03-31 13:23:38.000000000 +0000 +@@ -525,17 +525,20 @@ + } else { + # Apparently we need to entity-encode \n, \r, \t, although the + # spec doesn't mention that. Since we're doing strtr() anyway, +- # and we don't need <> escaped here, we may as well not call +- # htmlspecialchars(). ++ # we may as well not call htmlspecialchars(). + # @todo FIXME: Verify that we actually need to + # escape \n\r\t here, and explain why, exactly. + # + # We could call Sanitizer::encodeAttribute() for this, but we + # don't because we're stubborn and like our marginal savings on + # byte size from not having to encode unnecessary quotes. ++ # The only difference between this transform and the one by ++ # Sanitizer::encodeAttribute() is '<' is only encoded here if ++ # $wgWellFormedXml is set, and ' is not encoded. + $map = array( + '&' => '&', + '"' => '"', ++ '>' => '>', + "\n" => ' ', + "\r" => ' ', + "\t" => '	' +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/media/BitmapMetadataHandler.php mediawiki-1.19.24/includes/media/BitmapMetadataHandler.php +--- mediawiki-1.19.23/includes/media/BitmapMetadataHandler.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/media/BitmapMetadataHandler.php 2015-03-31 13:23:38.000000000 +0000 +@@ -126,7 +126,7 @@ + * @throws MWException on invalid file. + */ + static function Jpeg ( $filename ) { +- $showXMP = function_exists( 'xml_parser_create_ns' ); ++ $showXMP = XMPReader::isSupported(); + $meta = new self(); + + $seg = JpegMetadataExtractor::segmentSplitter( $filename ); +@@ -168,7 +168,7 @@ + * @return Array Array for storage in img_metadata. + */ + static public function PNG ( $filename ) { +- $showXMP = function_exists( 'xml_parser_create_ns' ); ++ $showXMP = XMPReader::isSupported(); + + $meta = new self(); + $array = PNGMetadataExtractor::getMetadata( $filename ); +@@ -205,7 +205,7 @@ + $meta->addMetadata( array( 'GIFFileComment' => $baseArray['comment'] ), 'native' ); + } + +- if ( $baseArray['xmp'] !== '' && function_exists( 'xml_parser_create_ns' ) ) { ++ if ( $baseArray['xmp'] !== '' && XMPReader::isSupported() ) { + $xmp = new XMPReader(); + $xmp->parse( $baseArray['xmp'] ); + $xmpRes = $xmp->getResults(); +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/media/JpegMetadataExtractor.php mediawiki-1.19.24/includes/media/JpegMetadataExtractor.php +--- mediawiki-1.19.23/includes/media/JpegMetadataExtractor.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/media/JpegMetadataExtractor.php 2015-03-31 13:23:38.000000000 +0000 +@@ -24,7 +24,7 @@ + * @throws MWException if given invalid file. + */ + static function segmentSplitter ( $filename ) { +- $showXMP = function_exists( 'xml_parser_create_ns' ); ++ $showXMP = XMPReader::isSupported(); + + $segmentCount = 0; + +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/media/XMP.php mediawiki-1.19.24/includes/media/XMP.php +--- mediawiki-1.19.23/includes/media/XMP.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/media/XMP.php 2015-03-31 13:23:38.000000000 +0000 +@@ -40,6 +40,12 @@ + + protected $items; + ++ /** @var int Flag determining if the XMP is safe to parse **/ ++ private $parsable = 0; ++ ++ /** @var string Buffer of XML to parse **/ ++ private $xmlParsableBuffer = ''; ++ + /** + * These are various mode constants. + * they are used to figure out what to do +@@ -69,6 +75,12 @@ + const NS_XML = 'http://www.w3.org/XML/1998/namespace'; + + ++ // States used while determining if XML is safe to parse ++ const PARSABLE_UNKNOWN = 0; ++ const PARSABLE_OK = 1; ++ const PARSABLE_BUFFERING = 2; ++ const PARSABLE_NO = 3; ++ + /** + * Constructor. + * +@@ -106,6 +118,9 @@ + array( $this, 'endElement' ) ); + + xml_set_character_data_handler( $this->xmlParser, array( $this, 'char' ) ); ++ ++ $this->parsable = self::PARSABLE_UNKNOWN; ++ $this->xmlParsableBuffer = ''; + } + + /** Destroy the xml parser +@@ -117,6 +132,13 @@ + xml_parser_free( $this->xmlParser ); + } + ++ /** ++ * Check if this instance supports using this class ++ */ ++ public static function isSupported() { ++ return function_exists( 'xml_parser_create_ns' ) && class_exists( 'XMLReader' ); ++ } ++ + /** Get the result array. Do some post-processing before returning + * the array, and transform any metadata that is special-cased. + * +@@ -263,6 +285,27 @@ + wfRestoreWarnings(); + } + ++ // Ensure the XMP block does not have an xml doctype declaration, which ++ // could declare entities unsafe to parse with xml_parse (T85848/T71210). ++ if ( $this->parsable !== self::PARSABLE_OK ) { ++ if ( $this->parsable === self::PARSABLE_NO ) { ++ throw new MWException( 'Unsafe doctype declaration in XML.' ); ++ } ++ ++ $content = $this->xmlParsableBuffer . $content; ++ if ( !$this->checkParseSafety( $content ) ) { ++ if ( !$allOfIt && $this->parsable !== self::PARSABLE_NO ) { ++ // parse wasn't Unsuccessful yet, so return true ++ // in this case. ++ return true; ++ } ++ $msg = ( $this->parsable === self::PARSABLE_NO ) ? ++ 'Unsafe doctype declaration in XML.' : ++ 'No root element found in XML.'; ++ throw new MWException( $msg ); ++ } ++ } ++ + $ok = xml_parse( $this->xmlParser, $content, $allOfIt ); + if ( !$ok ) { + $error = xml_error_string( xml_get_error_code( $this->xmlParser ) ); +@@ -385,6 +428,59 @@ + + } + ++ /** ++ * Check if a block of XML is safe to pass to xml_parse, i.e. doesn't ++ * contain a doctype declaration which could contain a dos attack if we ++ * parse it and expand internal entities (T85848). ++ * ++ * @param string $content xml string to check for parse safety ++ * @return bool true if the xml is safe to parse, false otherwise ++ */ ++ private function checkParseSafety( $content ) { ++ $reader = new XMLReader(); ++ $result = null; ++ ++ // For XMLReader to parse incomplete/invalid XML, it has to be open()'ed ++ // instead of using XML(). ++ $reader->open( ++ 'data://text/plain,' . urlencode( $content ), ++ null, ++ LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET ++ ); ++ ++ $oldDisable = libxml_disable_entity_loader( true ); ++ $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false ); ++ ++ // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning ++ // when parsing truncated XML, which causes unit tests to fail. ++ wfSuppressWarnings(); ++ while ( $reader->read() ) { ++ if ( $reader->nodeType === XMLReader::ELEMENT ) { ++ // Reached the first element without hitting a doctype declaration ++ $this->parsable = self::PARSABLE_OK; ++ $result = true; ++ break; ++ } ++ if ( $reader->nodeType === XMLReader::DOC_TYPE ) { ++ $this->parsable = self::PARSABLE_NO; ++ $result = false; ++ break; ++ } ++ } ++ wfRestoreWarnings(); ++ libxml_disable_entity_loader( $oldDisable ); ++ ++ if ( !is_null( $result ) ) { ++ return $result; ++ } ++ ++ // Reached the end of the parsable xml without finding an element ++ // or doctype. Buffer and try again. ++ $this->parsable = self::PARSABLE_BUFFERING; ++ $this->xmlParsableBuffer = $content; ++ return false; ++ } ++ + /** When we hit a closing element in MODE_IGNORE + * Check to see if this is the element we started to ignore, + * in which case we get out of MODE_IGNORE +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/OutputPage.php mediawiki-1.19.24/includes/OutputPage.php +--- mediawiki-1.19.23/includes/OutputPage.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/OutputPage.php 2015-03-31 13:23:38.000000000 +0000 +@@ -2975,6 +2975,10 @@ + if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { + return false; + } ++ if ( !$this->getTitle()->isSubpageOf( $this->getUser()->getUserPage() ) ) { ++ // Don't execute another user's CSS or JS on preview (T85855) ++ return false; ++ } + + return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); + } +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/upload/UploadBase.php mediawiki-1.19.24/includes/upload/UploadBase.php +--- mediawiki-1.19.23/includes/upload/UploadBase.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/upload/UploadBase.php 2015-03-31 13:23:38.000000000 +0000 +@@ -1210,23 +1210,22 @@ + } + } + +- # href with embeded svg as target +- if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) { +- wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); ++ # only allow data: targets that should be safe. This prevents vectors like, ++ # image/svg, text/xml, application/xml, and text/html, which can contain scripts ++ if ( $stripped == 'href' && strncasecmp( 'data:', $value, 5 ) === 0 ) { ++ // rfc2397 parameters. This is only slightly slower than (;[\w;]+)*. ++ $parameters = '(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?'; ++ if ( !preg_match( "!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) { ++ wfDebug( __METHOD__ . ": Found href to unwhitelisted data: uri " ++ . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); + return true; + } +- +- # href with embeded (text/xml) svg as target +- if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) { +- wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); +- return true; + } + +- # Change href with animate from (http://html5sec.org/#137). This doesn't seem +- # possible without embedding the svg, but filter here in case. +- if ( $stripped == 'from' ++ # Change href with animate from (http://html5sec.org/#137). ++ if ( $stripped === 'attributename' + && $strippedElement === 'animate' +- && !preg_match( '!^https?://!im', $value ) ++ && $this->stripXmlNamespace( $value ) == 'href' + ) { + wfDebug( __METHOD__ . ": Found animate that might be changing href using from " + . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); +@@ -1300,7 +1299,7 @@ + private static function checkCssFragment( $value ) { + + # Forbid external stylesheets, for both reliability and to protect viewer's privacy +- if ( strpos( $value, '@import' ) !== false ) { ++ if ( stripos( $value, '@import' ) !== false ) { + return true; + } + +diff -Nruw -x '*~' -x '.js*' -x '.git*' -x '*.xcf' -x '#*#' -x '.#*' -x '.rubocop*' -x .travis.yml -x package.json -x messages -x Gemfile -x '*.png' -x '*.jpg' -x '*.xcf' -x '*.gif' -x '*.svg' -x '*.tiff' -x '*.zip' -x '*.xmp' mediawiki-1.19.23/includes/XmlTypeCheck.php mediawiki-1.19.24/includes/XmlTypeCheck.php +--- mediawiki-1.19.23/includes/XmlTypeCheck.php 2015-03-31 13:24:03.000000000 +0000 ++++ mediawiki-1.19.24/includes/XmlTypeCheck.php 2015-03-31 13:23:38.000000000 +0000 +@@ -1,11 +1,36 @@ + <?php ++/** ++ * XML syntax and type checker. ++ * ++ * Since 1.24.2, it uses XMLReader instead of xml_parse, which gives us ++ * more control over the expansion of XML entities. When passed to the ++ * callback, entities will be fully expanded, but may report the XML is ++ * invalid if expanding the entities are likely to cause a DoS. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ * http://www.gnu.org/copyleft/gpl.html ++ * ++ * @file ++ */ + + class XmlTypeCheck { + /** + * Will be set to true or false to indicate whether the file is + * well-formed XML. Note that this doesn't check schema validity. + */ +- public $wellFormed = false; ++ public $wellFormed = null; + + /** + * Will be set to true if the optional element filter returned +@@ -44,8 +69,8 @@ + ); + + /** +- * @param $file string filename +- * @param $filterCallback callable (optional) ++ * @param string $input a filename ++ * @param callable $filterCallback (optional) + * Function to call to do additional custom validity checks from the + * SAX element handler event. This gives you access to the element + * namespace, name, attributes, and text contents. +@@ -53,10 +78,10 @@ + * @param array $options list of additional parsing options: + * processing_instruction_handler: Callback for xml_set_processing_instruction_handler + */ +- function __construct( $file, $filterCallback=null, $options=array() ) { ++ function __construct( $input, $filterCallback = null, $options = array() ) { + $this->filterCallback = $filterCallback; + $this->parserOptions = array_merge( $this->parserOptions, $options ); +- $this->run( $file ); ++ $this->validateFromInput( $input, true ); + } + + /** +@@ -68,119 +93,211 @@ + return $this->rootElement; + } + ++ + /** +- * @param $fname ++ * @param string $fname the filename + */ +- private function run( $fname ) { +- $parser = xml_parser_create_ns( 'UTF-8' ); +- +- // case folding violates XML standard, turn it off +- xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); ++ private function validateFromInput( $xml, $isFile ) { ++ $reader = new XMLReader(); ++ if ( $isFile ) { ++ $s = $reader->open( $xml, null, LIBXML_NOERROR | LIBXML_NOWARNING ); ++ } else { ++ $s = $reader->XML( $xml, null, LIBXML_NOERROR | LIBXML_NOWARNING ); ++ } ++ if ( $s !== true ) { ++ // Couldn't open the XML ++ $this->wellFormed = false; ++ } else { ++ $oldDisable = libxml_disable_entity_loader( true ); ++ $reader->setParserProperty( XMLReader::SUBST_ENTITIES, true ); ++ try { ++ $this->validate( $reader ); ++ } catch ( Exception $e ) { ++ // Calling this malformed, because we didn't parse the whole ++ // thing. Maybe just an external entity refernce. ++ $this->wellFormed = false; ++ $reader->close(); ++ libxml_disable_entity_loader( $oldDisable ); ++ throw $e; ++ } ++ $reader->close(); ++ libxml_disable_entity_loader( $oldDisable ); ++ } ++ } + +- xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false ); ++ private function readNext( XMLReader $reader ) { ++ set_error_handler( array( $this, 'XmlErrorHandler' ) ); ++ $ret = $reader->read(); ++ restore_error_handler(); ++ return $ret; ++ } + +- if ( $this->parserOptions['processing_instruction_handler'] ) { +- xml_set_processing_instruction_handler( +- $parser, +- array( $this, 'processingInstructionHandler' ) +- ); ++ public function XmlErrorHandler( $errno, $errstr ) { ++ $this->wellFormed = false; + } + +- if ( file_exists( $fname ) ) { +- $file = fopen( $fname, "rb" ); +- if ( $file ) { ++ private function validate( $reader ) { ++ ++ // First, move through anything that isn't an element, and ++ // handle any processing instructions with the callback + do { +- $chunk = fread( $file, 32768 ); +- $ret = xml_parse( $parser, $chunk, feof( $file ) ); +- if( $ret == 0 ) { +- // XML isn't well-formed! +- fclose( $file ); +- xml_parser_free( $parser ); ++ if( !$this->readNext( $reader ) ) { ++ // Hit the end of the document before any elements ++ $this->wellFormed = false; + return; + } +- } while( !feof( $file ) ); ++ if ( $reader->nodeType === XMLReader::PI ) { ++ $this->processingInstructionHandler( $reader->name, $reader->value ); ++ } ++ } while ( $reader->nodeType != XMLReader::ELEMENT ); + +- fclose( $file ); ++ // Process the rest of the document ++ do { ++ switch ( $reader->nodeType ) { ++ case XMLReader::ELEMENT: ++ $name = $this->expandNS( ++ $reader->name, ++ $reader->namespaceURI ++ ); ++ if ( $this->rootElement === '' ) { ++ $this->rootElement = $name; + } ++ $empty = $reader->isEmptyElement; ++ $attrs = $this->getAttributesArray( $reader ); ++ $this->elementOpen( $name, $attrs ); ++ if ( $empty ) { ++ $this->elementClose(); ++ } ++ break; ++ ++ case XMLReader::END_ELEMENT: ++ $this->elementClose(); ++ break; ++ ++ case XMLReader::WHITESPACE: ++ case XMLReader::SIGNIFICANT_WHITESPACE: ++ case XMLReader::CDATA: ++ case XMLReader::TEXT: ++ $this->elementData( $reader->value ); ++ break; ++ ++ case XMLReader::ENTITY_REF: ++ // Unexpanded entity (maybe external?), ++ // don't send to the filter (xml_parse didn't) ++ break; ++ ++ case XMLReader::COMMENT: ++ // Don't send to the filter (xml_parse didn't) ++ break; ++ ++ case XMLReader::PI: ++ // Processing instructions can happen after the header too ++ $this->processingInstructionHandler( ++ $reader->name, ++ $reader->value ++ ); ++ break; ++ default: ++ // One of DOC, DOC_TYPE, ENTITY, END_ENTITY, ++ // NOTATION, or XML_DECLARATION ++ // xml_parse didn't send these to the filter, so we won't. + } + ++ } while ( $this->readNext( $reader ) ); ++ ++ if ( $this->stackDepth !== 0 ) { ++ $this->wellFormed = false; ++ } elseif ( $this->wellFormed === null ) { + $this->wellFormed = true; ++ } + +- xml_parser_free( $parser ); + } + + /** +- * @param $parser +- * @param $name +- * @param $attribs ++ * Get all of the attributes for an XMLReader's current node ++ * @param $r XMLReader ++ * @return array of attributes + */ +- private function rootElementOpen( $parser, $name, $attribs ) { +- $this->rootElement = $name; ++ private function getAttributesArray( XMLReader $r ) { ++ $attrs = array(); ++ while ( $r->moveToNextAttribute() ) { ++ if ( $r->namespaceURI === 'http://www.w3.org/2000/xmlns/' ) { ++ // XMLReader treats xmlns attributes as normal ++ // attributes, while xml_parse doesn't ++ continue; ++ } ++ $name = $this->expandNS( $r->name, $r->namespaceURI ); ++ $attrs[$name] = $r->value; ++ } ++ return $attrs; ++ } + +- if( is_callable( $this->filterCallback ) ) { +- xml_set_element_handler( +- $parser, +- array( $this, 'elementOpen' ), +- array( $this, 'elementClose' ) +- ); +- xml_set_character_data_handler( $parser, array( $this, 'elementData' ) ); +- $this->elementOpen( $parser, $name, $attribs ); +- } else { +- // We only need the first open element +- xml_set_element_handler( $parser, false, false ); ++ /** ++ * @param $name element or attribute name, maybe with a full or short prefix ++ * @param $namespaceURI the namespaceURI ++ * @return string the name prefixed with namespaceURI ++ */ ++ private function expandNS( $name, $namespaceURI ) { ++ if ( $namespaceURI ) { ++ $parts = explode( ':', $name ); ++ $localname = array_pop( $parts ); ++ return "$namespaceURI:$localname"; + } ++ return $name; + } + + /** +- * @param $parser + * @param $name + * @param $attribs + */ +- private function elementOpen( $parser, $name, $attribs ) { ++ private function elementOpen( $name, $attribs ) { + $this->elementDataContext[] = array( $name, $attribs ); + $this->elementData[] = ''; + $this->stackDepth++; + } + + /** +- * @param $parser +- * @param $name + */ +- private function elementClose( $parser, $name ) { ++ private function elementClose() { + list( $name, $attribs ) = array_pop( $this->elementDataContext ); + $data = array_pop( $this->elementData ); + $this->stackDepth--; + +- if ( call_user_func( ++ if ( is_callable( $this->filterCallback ) ++ && call_user_func( + $this->filterCallback, + $name, + $attribs, + $data +- ) ) { +- // Filter hit! ++ ) ++ ) { ++ // Filter hit + $this->filterMatch = true; + } + } + + /** +- * @param $parser + * @param $data + */ +- private function elementData( $parser, $data ) { +- // xml_set_character_data_handler breaks the data on & characters, so +- // we collect any data here, and we'll run the callback in elementClose ++ private function elementData( $data ) { ++ // Collect any data here, and we'll run the callback in elementClose + $this->elementData[ $this->stackDepth - 1 ] .= trim( $data ); + } + + /** +- * @param $parser + * @param $target + * @param $data + */ +- private function processingInstructionHandler( $parser, $target, $data ) { +- if ( call_user_func( $this->parserOptions['processing_instruction_handler'], $target, $data ) ) { ++ private function processingInstructionHandler( $target, $data ) { ++ if ( $this->parserOptions['processing_instruction_handler'] ) { ++ if ( call_user_func( ++ $this->parserOptions['processing_instruction_handler'], ++ $target, ++ $data ++ ) ) { + // Filter hit! + $this->filterMatch = true; + } + } + } ++} diff -Nru mediawiki-1.19.20+dfsg/debian/patches/series mediawiki-1.19.20+dfsg/debian/patches/series --- mediawiki-1.19.20+dfsg/debian/patches/series 2014-12-21 12:10:23.000000000 +0000 +++ mediawiki-1.19.20+dfsg/debian/patches/series 2015-04-06 17:02:16.000000000 +0000 @@ -12,3 +12,4 @@ CVE-2014-9277_1.patch CVE-2014-9277_2.patch T76686.patch +security_1.19.24.patch