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(
+ 					'&' => '&amp;',
+ 					'"' => '&quot;',
++					'>' => '&gt;',
+ 					"\n" => '&#10;',
+ 					"\r" => '&#13;',
+ 					"\t" => '&#9;'
+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

Reply via email to