[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

RFS: mediawiki (was Re: [Secure-testing-team] mediawiki: NMU to fix CVE-2008-5249, CVE-2008-5250, CVE-2008-5252)



Nico Golde ha scritto:
> As far as I can judge that the patch looks fine to me.
> Please upload to testing-security as described on:
> http://testing-security.debian.net/uploading.html
> 
> Cheers and thanks for the work!
> Nico

Hi,

I need a sponsor to an approved[1] NMU to fix CVE-2008-5249, CVE-2008-5250,
CVE-2008-5252.
Patch is huge (attached) but substantially this is the upstream backported
security patch[2] to mediawiki 1.12 branch.
This should go on testing-security[3].

The upload would fix these bugs: 508869, 508870

The package can be found on mentors.debian.net:
- URL: http://mentors.debian.net/debian/pool/main/m/mediawiki
- Source repository: deb-src http://mentors.debian.net/debian unstable main
contrib non-free
- dget
http://mentors.debian.net/debian/pool/main/m/mediawiki/mediawiki_1.12.0-2lenny2.dsc

[1]http://lists.alioth.debian.org/pipermail/secure-testing-team/2009-January/002022.html
[2]http://svn.wikimedia.org/viewvc/mediawiki?view=rev&revision=44599
[3]http://testing-security.debian.net/uploading.html

Cheers,
Giuseppe.
diff -u mediawiki-1.12.0/debian/changelog mediawiki-1.12.0/debian/changelog
--- mediawiki-1.12.0/debian/changelog
+++ mediawiki-1.12.0/debian/changelog
@@ -1,3 +1,31 @@
+mediawiki (1:1.12.0-2lenny2) testing-security; urgency=high
+
+  * Security update, NMU to fix fix CVE-2008-5249, CVE-2008-5250, CVE-2008-5252
+  * debian/patches/CVE-2008-5249_CVE-2008-5250_CVE-2008-5252.patch:
+    - Fixed output escaping for reporting of non-MediaWiki exceptions. 
+      Potential XSS if an extension throws one of these with user input.
+    - Avoid fatal error in profileinfo.php when not configured.
+    - Fixed CSRF vulnerability in Special:Import. Fixed input validation in 
+      transwiki import feature.
+    - Add a .htaccess to deleted images directory for additional protection
+      against exposure of deleted files with known SHA-1 hashes on default
+      installations.
+    - Fixed XSS vulnerability for Internet Explorer clients, via file uploads
+      which are interpreted by IE as HTML.
+    - Fixed XSS vulnerability for clients with SVG scripting, on wikis where SVG
+      uploads are enabled. Firefox 1.5+ is affected.
+    - Avoid streaming uploaded files to the user via index.php. This allows 
+      security-conscious users to serve uploaded files via a different domain,
+      and thus client-side scripts executed from that domain cannot access the
+      login cookies. Affects Special:Undelete, img_auth.php and thumb.php.
+    - When streaming files via index.php, use the MIME type detected from the
+      file extension, not from the data. This reduces the XSS attack surface.
+    - Blacklist redirects via Special:Filepath. Such redirects exacerbate any 
+      XSS vulnerabilities involving uploads of files containing scripts.
+  Closes: #508869, #508870
+
+ -- Giuseppe Iuculano <giuseppe@iuculano.it>  Sun, 18 Jan 2009 11:54:02 +0100
+
 mediawiki (1:1.12.0-2lenny1) testing-security; urgency=high
 
   * Security update, fix CVE-2008-4408:
diff -u mediawiki-1.12.0/debian/patches/series mediawiki-1.12.0/debian/patches/series
--- mediawiki-1.12.0/debian/patches/series
+++ mediawiki-1.12.0/debian/patches/series
@@ -5,0 +6 @@
+CVE-2008-5249_CVE-2008-5250_CVE-2008-5252.patch
only in patch2:
unchanged:
--- mediawiki-1.12.0.orig/debian/patches/CVE-2008-5249_CVE-2008-5250_CVE-2008-5252.patch
+++ mediawiki-1.12.0/debian/patches/CVE-2008-5249_CVE-2008-5250_CVE-2008-5252.patch
@@ -0,0 +1,1516 @@
+Backported fix for CVE-2008-5249 CVE-2008-5250 CVE-2008-5252
+--- a/img_auth.php
++++ b/img_auth.php
+@@ -17,6 +17,12 @@ require_once( dirname( __FILE__ ) . '/in
+ wfProfileIn( 'img_auth.php' );
+ require_once( dirname( __FILE__ ) . '/includes/StreamFile.php' );
+ 
++$perms = User::getGroupPermissions( array( '*' ) );
++if ( in_array( 'read', $perms, true ) ) {
++	wfDebugLog( 'img_auth', 'Public wiki' );
++	wfPublicError();
++}
++
+ // Extract path and image information
+ if( !isset( $_SERVER['PATH_INFO'] ) ) {
+ 	wfDebugLog( 'img_auth', 'Missing PATH_INFO' );
+@@ -88,3 +94,25 @@ ENDS;
+ 	wfLogProfilingData();
+ 	exit();
+ }
++
++/**
++ * Show a 403 error for use when the wiki is public
++ */
++function wfPublicError() {
++	header( 'HTTP/1.0 403 Forbidden' );
++	header( 'Content-Type: text/html; charset=utf-8' );
++	echo <<<ENDS
++<html>
++<body>
++<h1>Access Denied</h1>
++<p>The function of img_auth.php is to output files from a private wiki. This wiki
++is configured as a public wiki. For optimal security, img_auth.php is disabled in
++this case.
++</p>
++</body>
++</html>
++ENDS;
++	wfLogProfilingData();
++	exit;
++}
++
+--- a/includes/AutoLoader.php
++++ b/includes/AutoLoader.php
+@@ -99,6 +99,7 @@ function __autoload($className) {
+ 		'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
+ 		'HTMLCacheUpdate' => 'includes/HTMLCacheUpdate.php',
+ 		'Http' => 'includes/HttpFunctions.php',
++		'IEContentAnalyzer' => 'includes/IEContentAnalyzer.php',
+ 		'IP' => 'includes/IP.php',
+ 		'ImageGallery' => 'includes/ImageGallery.php',
+ 		'ImagePage' => 'includes/ImagePage.php',
+--- a/includes/DefaultSettings.php
++++ b/includes/DefaultSettings.php
+@@ -1666,6 +1666,8 @@ $wgMimeTypeBlacklist= array(
+ 	'application/x-php', 'text/x-php',
+ 	# Other types that may be interpreted by some servers
+ 	'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh',
++	# Client-side hazards on Internet Explorer
++	'text/scriptlet', 'application/x-msdownload',
+ 	# Windows metafile, client-side vulnerability on some systems
+ 	'application/x-msmetafile'
+ );
+--- a/includes/Exception.php
++++ b/includes/Exception.php
+@@ -227,7 +227,16 @@ function wfReportException( Exception $e
+ 			 }
+ 		 }
+ 	 } else {
+-		 echo $e->__toString();
++		 $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
++			 $e->__toString() . "\n";
++		 if ( $GLOBALS['wgShowExceptionDetails'] ) {
++			 $message .= "\n" . $e->getTraceAsString() ."\n";
++		 }
++		 if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) {
++			 wfPrintError( $message );
++		 } else {
++			 echo nl2br( htmlspecialchars( $message ) ). "\n";
++		 }
+ 	 }
+ }
+ 
+--- /dev/null
++++ b/includes/IEContentAnalyzer.php
+@@ -0,0 +1,823 @@
++<?php
++
++/**
++ * This class simulates Microsoft Internet Explorer's terribly broken and
++ * insecure MIME type detection algorithm. It can be used to check web uploads
++ * with an apparently safe type, to see if IE will reinterpret them to produce
++ * something dangerous.
++ *
++ * It is full of bugs and strange design choices should not under any
++ * circumstances be used to determine a MIME type to present to a user or
++ * client. (Apple Safari developers, this means you too.)
++ *
++ * This class is based on a disassembly of IE 5.0, 6.0 and 7.0. Although I have
++ * attempted to ensure that this code works in exactly the same way as Internet
++ * Explorer, it does not share any source code, or creative choices such as
++ * variable names, thus I (Tim Starling) claim copyright on it.
++ *
++ * It may be redistributed without restriction. To aid reuse, this class does
++ * not depend on any MediaWiki module.
++ */
++class IEContentAnalyzer {
++	/**
++	 * Relevant data taken from the type table in IE 5
++	 */
++	protected $baseTypeTable = array(
++		'ambiguous' /*1*/ => array(
++			'text/plain',
++			'application/octet-stream',
++			'application/x-netcdf', // [sic]
++		),
++		'text' /*3*/ => array(
++			'text/richtext', 'image/x-bitmap', 'application/postscript', 'application/base64',
++			'application/macbinhex40', 'application/x-cdf', 'text/scriptlet'
++		),
++		'binary' /*4*/ => array(
++			'application/pdf', 'audio/x-aiff', 'audio/basic', 'audio/wav', 'image/gif',
++			'image/pjpeg', 'image/jpeg', 'image/tiff', 'image/x-png', 'image/png', 'image/bmp',
++			'image/x-jg', 'image/x-art', 'image/x-emf', 'image/x-wmf', 'video/avi',
++			'video/x-msvideo', 'video/mpeg', 'application/x-compressed',
++			'application/x-zip-compressed', 'application/x-gzip-compressed', 'application/java',
++			'application/x-msdownload'
++		),
++		'html' /*5*/ => array( 'text/html' ),
++	);
++
++	/**
++	 * Changes to the type table in later versions of IE
++	 */
++	protected $addedTypes = array(
++		'ie07' => array(
++			'text' => array( 'text/xml', 'application/xml' )
++		),
++	);
++
++	/**
++	 * An approximation of the "Content Type" values in HKEY_CLASSES_ROOT in a
++	 * typical Windows installation.
++	 *
++	 * Used for extension to MIME type mapping if detection fails.
++	 */
++	protected $registry = array(
++		'.323' => 'text/h323',
++		'.3g2' => 'video/3gpp2',
++		'.3gp' => 'video/3gpp',
++		'.3gp2' => 'video/3gpp2',
++		'.3gpp' => 'video/3gpp',
++		'.aac' => 'audio/aac',
++		'.ac3' => 'audio/ac3',
++		'.accda' => 'application/msaccess',
++		'.accdb' => 'application/msaccess',
++		'.accdc' => 'application/msaccess',
++		'.accde' => 'application/msaccess',
++		'.accdr' => 'application/msaccess',
++		'.accdt' => 'application/msaccess',
++		'.ade' => 'application/msaccess',
++		'.adp' => 'application/msaccess',
++		'.adts' => 'audio/aac',
++		'.ai' => 'application/postscript',
++		'.aif' => 'audio/aiff',
++		'.aifc' => 'audio/aiff',
++		'.aiff' => 'audio/aiff',
++		'.amc' => 'application/x-mpeg',
++		'.application' => 'application/x-ms-application',
++		'.asf' => 'video/x-ms-asf',
++		'.asx' => 'video/x-ms-asf',
++		'.au' => 'audio/basic',
++		'.avi' => 'video/avi',
++		'.bmp' => 'image/bmp',
++		'.caf' => 'audio/x-caf',
++		'.cat' => 'application/vnd.ms-pki.seccat',
++		'.cbo' => 'application/sha',
++		'.cdda' => 'audio/aiff',
++		'.cer' => 'application/x-x509-ca-cert',
++		'.conf' => 'text/plain',
++		'.crl' => 'application/pkix-crl',
++		'.crt' => 'application/x-x509-ca-cert',
++		'.css' => 'text/css',
++		'.csv' => 'application/vnd.ms-excel',
++		'.der' => 'application/x-x509-ca-cert',
++		'.dib' => 'image/bmp',
++		'.dif' => 'video/x-dv',
++		'.dll' => 'application/x-msdownload',
++		'.doc' => 'application/msword',
++		'.docm' => 'application/vnd.ms-word.document.macroEnabled.12',
++		'.docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
++		'.dot' => 'application/msword',
++		'.dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
++		'.dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
++		'.dv' => 'video/x-dv',
++		'.dwfx' => 'model/vnd.dwfx+xps',
++		'.edn' => 'application/vnd.adobe.edn',
++		'.eml' => 'message/rfc822',
++		'.eps' => 'application/postscript',
++		'.etd' => 'application/x-ebx',
++		'.exe' => 'application/x-msdownload',
++		'.fdf' => 'application/vnd.fdf',
++		'.fif' => 'application/fractals',
++		'.gif' => 'image/gif',
++		'.gsm' => 'audio/x-gsm',
++		'.hqx' => 'application/mac-binhex40',
++		'.hta' => 'application/hta',
++		'.htc' => 'text/x-component',
++		'.htm' => 'text/html',
++		'.html' => 'text/html',
++		'.htt' => 'text/webviewhtml',
++		'.hxa' => 'application/xml',
++		'.hxc' => 'application/xml',
++		'.hxd' => 'application/octet-stream',
++		'.hxe' => 'application/xml',
++		'.hxf' => 'application/xml',
++		'.hxh' => 'application/octet-stream',
++		'.hxi' => 'application/octet-stream',
++		'.hxk' => 'application/xml',
++		'.hxq' => 'application/octet-stream',
++		'.hxr' => 'application/octet-stream',
++		'.hxs' => 'application/octet-stream',
++		'.hxt' => 'application/xml',
++		'.hxv' => 'application/xml',
++		'.hxw' => 'application/octet-stream',
++		'.ico' => 'image/x-icon',
++		'.iii' => 'application/x-iphone',
++		'.ins' => 'application/x-internet-signup',
++		'.iqy' => 'text/x-ms-iqy',
++		'.isp' => 'application/x-internet-signup',
++		'.jfif' => 'image/jpeg',
++		'.jnlp' => 'application/x-java-jnlp-file',
++		'.jpe' => 'image/jpeg',
++		'.jpeg' => 'image/jpeg',
++		'.jpg' => 'image/jpeg',
++		'.jtx' => 'application/x-jtx+xps',
++		'.latex' => 'application/x-latex',
++		'.log' => 'text/plain',
++		'.m1v' => 'video/mpeg',
++		'.m2v' => 'video/mpeg',
++		'.m3u' => 'audio/x-mpegurl',
++		'.mac' => 'image/x-macpaint',
++		'.man' => 'application/x-troff-man',
++		'.mda' => 'application/msaccess',
++		'.mdb' => 'application/msaccess',
++		'.mde' => 'application/msaccess',
++		'.mfp' => 'application/x-shockwave-flash',
++		'.mht' => 'message/rfc822',
++		'.mhtml' => 'message/rfc822',
++		'.mid' => 'audio/mid',
++		'.midi' => 'audio/mid',
++		'.mod' => 'video/mpeg',
++		'.mov' => 'video/quicktime',
++		'.mp2' => 'video/mpeg',
++		'.mp2v' => 'video/mpeg',
++		'.mp3' => 'audio/mpeg',
++		'.mp4' => 'video/mp4',
++		'.mpa' => 'video/mpeg',
++		'.mpe' => 'video/mpeg',
++		'.mpeg' => 'video/mpeg',
++		'.mpf' => 'application/vnd.ms-mediapackage',
++		'.mpg' => 'video/mpeg',
++		'.mpv2' => 'video/mpeg',
++		'.mqv' => 'video/quicktime',
++		'.NMW' => 'application/nmwb',
++		'.nws' => 'message/rfc822',
++		'.odc' => 'text/x-ms-odc',
++		'.ols' => 'application/vnd.ms-publisher',
++		'.p10' => 'application/pkcs10',
++		'.p12' => 'application/x-pkcs12',
++		'.p7b' => 'application/x-pkcs7-certificates',
++		'.p7c' => 'application/pkcs7-mime',
++		'.p7m' => 'application/pkcs7-mime',
++		'.p7r' => 'application/x-pkcs7-certreqresp',
++		'.p7s' => 'application/pkcs7-signature',
++		'.pct' => 'image/pict',
++		'.pdf' => 'application/pdf',
++		'.pdx' => 'application/vnd.adobe.pdx',
++		'.pfx' => 'application/x-pkcs12',
++		'.pic' => 'image/pict',
++		'.pict' => 'image/pict',
++		'.pinstall' => 'application/x-picasa-detect',
++		'.pko' => 'application/vnd.ms-pki.pko',
++		'.png' => 'image/png',
++		'.pnt' => 'image/x-macpaint',
++		'.pntg' => 'image/x-macpaint',
++		'.pot' => 'application/vnd.ms-powerpoint',
++		'.potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
++		'.potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
++		'.ppa' => 'application/vnd.ms-powerpoint',
++		'.ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
++		'.pps' => 'application/vnd.ms-powerpoint',
++		'.ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
++		'.ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
++		'.ppt' => 'application/vnd.ms-powerpoint',
++		'.pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
++		'.pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
++		'.prf' => 'application/pics-rules',
++		'.ps' => 'application/postscript',
++		'.pub' => 'application/vnd.ms-publisher',
++		'.pwz' => 'application/vnd.ms-powerpoint',
++		'.py' => 'text/plain',
++		'.pyw' => 'text/plain',
++		'.qht' => 'text/x-html-insertion',
++		'.qhtm' => 'text/x-html-insertion',
++		'.qt' => 'video/quicktime',
++		'.qti' => 'image/x-quicktime',
++		'.qtif' => 'image/x-quicktime',
++		'.qtl' => 'application/x-quicktimeplayer',
++		'.rat' => 'application/rat-file',
++		'.rmf' => 'application/vnd.adobe.rmf',
++		'.rmi' => 'audio/mid',
++		'.rqy' => 'text/x-ms-rqy',
++		'.rtf' => 'application/msword',
++		'.sct' => 'text/scriptlet',
++		'.sd2' => 'audio/x-sd2',
++		'.sdp' => 'application/sdp',
++		'.shtml' => 'text/html',
++		'.sit' => 'application/x-stuffit',
++		'.sldm' => 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
++		'.sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
++		'.slk' => 'application/vnd.ms-excel',
++		'.snd' => 'audio/basic',
++		'.so' => 'application/x-apachemodule',
++		'.sol' => 'text/plain',
++		'.sor' => 'text/plain',
++		'.spc' => 'application/x-pkcs7-certificates',
++		'.spl' => 'application/futuresplash',
++		'.sst' => 'application/vnd.ms-pki.certstore',
++		'.stl' => 'application/vnd.ms-pki.stl',
++		'.swf' => 'application/x-shockwave-flash',
++		'.thmx' => 'application/vnd.ms-officetheme',
++		'.tif' => 'image/tiff',
++		'.tiff' => 'image/tiff',
++		'.txt' => 'text/plain',
++		'.uls' => 'text/iuls',
++		'.vcf' => 'text/x-vcard',
++		'.vdx' => 'application/vnd.ms-visio.viewer',
++		'.vsd' => 'application/vnd.ms-visio.viewer',
++		'.vss' => 'application/vnd.ms-visio.viewer',
++		'.vst' => 'application/vnd.ms-visio.viewer',
++		'.vsx' => 'application/vnd.ms-visio.viewer',
++		'.vtx' => 'application/vnd.ms-visio.viewer',
++		'.wav' => 'audio/wav',
++		'.wax' => 'audio/x-ms-wax',
++		'.wbk' => 'application/msword',
++		'.wdp' => 'image/vnd.ms-photo',
++		'.wiz' => 'application/msword',
++		'.wm' => 'video/x-ms-wm',
++		'.wma' => 'audio/x-ms-wma',
++		'.wmd' => 'application/x-ms-wmd',
++		'.wmv' => 'video/x-ms-wmv',
++		'.wmx' => 'video/x-ms-wmx',
++		'.wmz' => 'application/x-ms-wmz',
++		'.wpl' => 'application/vnd.ms-wpl',
++		'.wsc' => 'text/scriptlet',
++		'.wvx' => 'video/x-ms-wvx',
++		'.xaml' => 'application/xaml+xml',
++		'.xbap' => 'application/x-ms-xbap',
++		'.xdp' => 'application/vnd.adobe.xdp+xml',
++		'.xfdf' => 'application/vnd.adobe.xfdf',
++		'.xht' => 'application/xhtml+xml',
++		'.xhtml' => 'application/xhtml+xml',
++		'.xla' => 'application/vnd.ms-excel',
++		'.xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
++		'.xlk' => 'application/vnd.ms-excel',
++		'.xll' => 'application/vnd.ms-excel',
++		'.xlm' => 'application/vnd.ms-excel',
++		'.xls' => 'application/vnd.ms-excel',
++		'.xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
++		'.xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
++		'.xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
++		'.xlt' => 'application/vnd.ms-excel',
++		'.xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
++		'.xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
++		'.xlw' => 'application/vnd.ms-excel',
++		'.xml' => 'text/xml',
++		'.xps' => 'application/vnd.ms-xpsdocument',
++		'.xsl' => 'text/xml',
++	);
++
++	/**
++	 * IE versions which have been analysed to bring you this class, and for
++	 * which some substantive difference exists. These will appear as keys
++	 * in the return value of getRealMimesFromData(). The names are chosen to sort correctly.
++	 */
++	protected $versions = array( 'ie05', 'ie06', 'ie07', 'ie07.strict', 'ie07.nohtml' );
++
++	/**
++	 * Type table with versions expanded
++	 */
++	protected $typeTable = array();
++
++	/** constructor */
++	function __construct() {
++		// Construct versioned type arrays from the base type array plus additions
++		$types = $this->baseTypeTable;
++		foreach ( $this->versions as $version ) {
++			if ( isset( $this->addedTypes[$version] ) ) {
++				foreach ( $this->addedTypes[$version] as $format => $addedTypes ) {
++					$types[$format] = array_merge( $types[$format], $addedTypes );
++				}
++			}
++			$this->typeTable[$version] = $types;
++		}
++	}
++
++	/**
++	 * Get the MIME types from getMimesFromData(), but convert the result from IE's
++	 * idiosyncratic private types into something other apps will understand.
++	 *
++	 * @param string $fileName The file name (unused at present)
++	 * @param string $chunk The first 256 bytes of the file
++	 * @param string $proposed The MIME type proposed by the server
++	 *
++	 * @return array Map of IE version to detected mime type
++	 */
++	public function getRealMimesFromData( $fileName, $chunk, $proposed ) {
++		$types = $this->getMimesFromData( $fileName, $chunk, $proposed );
++		$types = array_map( array( $this, 'translateMimeType' ), $types );
++		return $types;
++	}
++
++	/**
++	 * Translate a MIME type from IE's idiosyncratic private types into
++	 * more commonly understood type strings
++	 */
++	public function translateMimeType( $type ) {
++		static $table = array(
++			'image/pjpeg' => 'image/jpeg',
++			'image/x-png' => 'image/png',
++			'image/x-wmf' => 'application/x-msmetafile',
++			'image/bmp' => 'image/x-bmp',
++			'application/x-zip-compressed' => 'application/zip',
++			'application/x-compressed' => 'application/x-compress',
++			'application/x-gzip-compressed' => 'application/x-gzip',
++			'audio/mid' => 'audio/midi',
++		);
++		if ( isset( $table[$type] ) ) {
++			$type = $table[$type];
++		}
++		return $type;
++	}
++
++	/**
++	 * Get the untranslated MIME types for all known versions
++	 *
++	 * @param string $fileName The file name (unused at present)
++	 * @param string $chunk The first 256 bytes of the file
++	 * @param string $proposed The MIME type proposed by the server
++	 *
++	 * @return array Map of IE version to detected mime type
++	 */
++	public function getMimesFromData( $fileName, $chunk, $proposed ) {
++		$types = array();
++		foreach ( $this->versions as $version ) {
++			$types[$version] = $this->getMimeTypeForVersion( $version, $fileName, $chunk, $proposed );
++		}
++		return $types;
++	}
++
++	/**
++	 * Get the MIME type for a given named version
++	 */
++	protected function getMimeTypeForVersion( $version, $fileName, $chunk, $proposed ) {
++		// Strip text after a semicolon
++		$semiPos = strpos( $proposed, ';' );
++		if ( $semiPos !== false ) {
++			$proposed = substr( $proposed, 0, $semiPos );
++		}
++
++		$proposedFormat = $this->getDataFormat( $version, $proposed );
++		if ( $proposedFormat == 'unknown'
++			&& $proposed != 'multipart/mixed'
++			&& $proposed != 'multipart/x-mixed-replace' )
++		{
++			return $proposed;
++		}
++		if ( strval( $chunk ) === '' ) {
++			return $proposed;
++		}
++
++		// Truncate chunk at 255 bytes
++		$chunk = substr( $chunk, 0, 255 );
++
++		// IE does the Check*Headers() calls last, and instead does the following image
++		// type checks by directly looking for the magic numbers. What I do here should
++		// have the same effect since the magic number checks are identical in both cases.
++		$result = $this->sampleData( $version, $chunk );
++		$sampleFound = $result['found'];
++		$counters = $result['counters'];
++		$binaryType = $this->checkBinaryHeaders( $version, $chunk );
++		$textType = $this->checkTextHeaders( $version, $chunk );
++
++		if ( $proposed == 'text/html' && isset( $sampleFound['html'] ) ) {
++			return 'text/html';
++		}
++		if ( $proposed == 'image/gif' && $binaryType == 'image/gif' ) {
++			return 'image/gif';
++		}
++		if ( ( $proposed == 'image/pjpeg' || $proposed == 'image/jpeg' )
++			&& $binaryType == 'image/pjpeg' )
++		{
++			return $proposed;
++		}
++		// PNG check added in IE 7
++		if ( $version >= 'ie07'
++			&& ( $proposed == 'image/x-png' || $proposed == 'image/png' )
++			&& $binaryType == 'image/x-png' )
++		{
++			return $proposed;
++		}
++
++		// CDF was removed in IE 7 so it won't be in $sampleFound for later versions
++		if ( isset( $sampleFound['cdf'] ) ) {
++			return 'application/x-cdf';
++		}
++
++		// RSS and Atom were added in IE 7 so they won't be in $sampleFound for
++		// previous versions
++		if ( isset( $sampleFound['rss'] ) ) {
++			return 'application/rss+xml';
++		}
++		if ( isset( $sampleFound['rdf-tag'] )
++			&& isset( $sampleFound['rdf-url'] )
++			&& isset( $sampleFound['rdf-purl'] ) )
++		{
++			return 'application/rss+xml';
++		}
++		if ( isset( $sampleFound['atom'] ) ) {
++			return 'application/atom+xml';
++		}
++
++		if ( isset( $sampleFound['xml'] ) ) {
++			// TODO: I'm not sure under what circumstances this flag is enabled
++			if ( strpos( $version, 'strict' ) !== false ) {
++				if ( $proposed == 'text/html' || $proposed == 'text/xml' ) {
++					return 'text/xml';
++				}
++			} else {
++				return 'text/xml';
++			}
++		}
++		if ( isset( $sampleFound['html'] ) ) {
++			// TODO: I'm not sure under what circumstances this flag is enabled
++			if ( strpos( $version, 'nohtml' ) !== false ) {
++				if ( $proposed == 'text/plain' ) {
++					return 'text/html';
++				}
++			} else {
++				return 'text/html';
++			}
++		}
++		if ( isset( $sampleFound['xbm'] ) ) {
++			return 'image/x-bitmap';
++		}
++		if ( isset( $sampleFound['binhex'] ) ) {
++			return 'application/macbinhex40';
++		}
++		if ( isset( $sampleFound['scriptlet'] ) ) {
++			if ( strpos( $version, 'strict' ) !== false ) {
++				if ( $proposed == 'text/plain' || $proposed == 'text/scriptlet' ) {
++					return 'text/scriptlet';
++				}
++			} else {
++				return 'text/scriptlet';
++			}
++		}
++
++		// Freaky heuristics to determine if the data is text or binary
++		// The heuristic is of course broken for non-ASCII text
++		if ( $counters['ctrl'] != 0 && ( $counters['ff'] + $counters['low'] )
++			< ( $counters['ctrl'] + $counters['high'] ) * 16 )
++		{
++			$kindOfBinary = true;
++			$type = $binaryType ? $binaryType : $textType;
++			if ( $type === false ) {
++				$type = 'application/octet-stream';
++			}
++		} else {
++			$kindOfBinary = false;
++			$type = $textType ? $textType : $binaryType;
++			if ( $type === false ) {
++				$type = 'text/plain';
++			}
++		}
++
++		// Check if the output format is ambiguous
++		// This generally means that detection failed, real types aren't ambiguous
++		$detectedFormat = $this->getDataFormat( $version, $type );
++		if ( $detectedFormat != 'ambiguous' ) {
++			return $type;
++		}
++
++		if ( $proposedFormat != 'ambiguous' ) {
++			// FormatAgreesWithData()
++			if ( $proposedFormat == 'text' && !$kindOfBinary ) {
++				return $proposed;
++			}
++			if ( $proposedFormat == 'binary' && $kindOfBinary ) {
++				return $proposed;
++			}
++			if ( $proposedFormat == 'html' ) {
++				return $proposed;
++			}
++		}
++
++		// Find a MIME type by searching the registry for the file extension.
++		$dotPos = strrpos( $fileName, '.' );
++		if ( $dotPos === false ) {
++			return $type;
++		}
++		$ext = substr( $fileName, $dotPos );
++		if ( isset( $this->registry[$ext] ) ) {
++			return $this->registry[$ext];
++		}
++
++		// TODO: If the extension has an application registered to it, IE will return
++		// application/octet-stream. We'll skip that, so we could erroneously
++		// return text/plain or application/x-netcdf where application/octet-stream
++		// would be correct.
++
++		return $type;
++	}
++
++	/**
++	 * Check for text headers at the start of the chunk
++	 * Confirmed same in 5 and 7.
++	 */
++	private function checkTextHeaders( $version, $chunk ) {
++		$chunk2 = substr( $chunk, 0, 2 );
++		$chunk4 = substr( $chunk, 0, 4 );
++		$chunk5 = substr( $chunk, 0, 5 );
++		if ( $chunk4 == '%PDF' ) {
++			return 'application/pdf';
++		}
++		if ( $chunk2 == '%!' ) {
++			return 'application/postscript';
++		}
++		if ( $chunk5 == '{\\rtf' ) {
++			return 'text/richtext';
++		}
++		if ( $chunk5 == 'begin' ) {
++			return 'application/base64';
++		}
++		return false;
++	}
++
++	/**
++	 * Check for binary headers at the start of the chunk
++	 * Confirmed same in 5 and 7.
++	 */
++	private function checkBinaryHeaders( $version, $chunk ) {
++		$chunk2 = substr( $chunk, 0, 2 );
++		$chunk3 = substr( $chunk, 0, 3 );
++		$chunk4 = substr( $chunk, 0, 4 );
++		$chunk5 = substr( $chunk, 0, 5 );
++		$chunk8 = substr( $chunk, 0, 8 );
++		if ( $chunk5 == 'GIF87' || $chunk5 == 'GIF89' ) {
++			return 'image/gif';
++		}
++		if ( $chunk2 == "\xff\xd8" ) {
++			return 'image/pjpeg'; // actually plain JPEG but this is what IE returns
++		}
++
++		if ( $chunk2 == 'BM'
++			&& substr( $chunk, 6, 2 ) == "\000\000"
++			&& substr( $chunk, 8, 2 ) != "\000\000" )
++		{
++			return 'image/bmp'; // another non-standard MIME
++		}
++		if ( $chunk4 == 'RIFF'
++			&& substr( $chunk, 8, 4 ) == 'WAVE' )
++		{
++			return 'audio/wav';
++		}
++		// These were integer literals in IE
++		// Perhaps the author was not sure what the target endianness was
++		if ( $chunk4 == ".sd\000"
++			|| $chunk4 == ".snd"
++			|| $chunk4 == "\000ds."
++			|| $chunk4 == "dns." )
++		{
++			return 'audio/basic';
++		}
++		if ( $chunk3 == "MM\000" ) {
++			return 'image/tiff';
++		}
++		if ( $chunk2 == 'MZ' ) {
++			return 'application/x-msdownload';
++		}
++		if ( $chunk8 == "\x89PNG\x0d\x0a\x1a\x0a" ) {
++			return 'image/x-png'; // [sic]
++		}
++		if ( strlen( $chunk ) >= 5 ) {
++			$byte2 = ord( $chunk[2] );
++			$byte4 = ord( $chunk[4] );
++			if ( $byte2 >= 3 && $byte2 <= 31 && $byte4 == 0 && $chunk2 == 'JG' ) {
++				return 'image/x-jg';
++			}
++		}
++		// More endian confusion?
++		if ( $chunk4 == 'MROF' ) {
++			return 'audio/x-aiff';
++		}
++		$chunk4_8 = substr( $chunk, 8, 4 );
++		if ( $chunk4 == 'FORM' && ( $chunk4_8 == 'AIFF' || $chunk4_8 == 'AIFC' ) ) {
++			return 'audio/x-aiff';
++		}
++		if ( $chunk4 == 'RIFF' && $chunk4_8 == 'AVI ' ) {
++			return 'video/avi';
++		}
++		if ( $chunk4 == "\x00\x00\x01\xb3" || $chunk4 == "\x00\x00\x01\xba" ) {
++			return 'video/mpeg';
++		}
++		if ( $chunk4 == "\001\000\000\000"
++			&& substr( $chunk, 40, 4 ) == ' EMF' )
++		{
++			return 'image/x-emf';
++		}
++		if ( $chunk4 == "\xd7\xcd\xc6\x9a" ) {
++			return 'image/x-wmf';
++		}
++		if ( $chunk4 == "\xca\xfe\xba\xbe" ) {
++			return 'application/java';
++		}
++		if ( $chunk2 == 'PK' ) {
++			return 'application/x-zip-compressed';
++		}
++		if ( $chunk2 == "\x1f\x9d" ) {
++			return 'application/x-compressed';
++		}
++		if ( $chunk2 == "\x1f\x8b" ) {
++			return 'application/x-gzip-compressed';
++		}
++		// Skip redundant check for ZIP
++		if ( $chunk5 == "MThd\000" ) {
++			return 'audio/mid';
++		}
++		if ( $chunk4 == '%PDF' ) {
++			return 'application/pdf';
++		}
++		return false;
++	}
++
++	/**
++	 * Do heuristic checks on the bulk of the data sample.
++	 * Search for HTML tags.
++	 */
++	protected function sampleData( $version, $chunk ) {
++		$found = array();
++		$counters = array(
++			'ctrl' => 0,
++			'high' => 0,
++			'low' => 0,
++			'lf' => 0,
++			'cr' => 0,
++			'ff' => 0
++		);
++		$htmlTags = array(
++			'html',
++			'head',
++			'title',
++			'body',
++			'script',
++			'a href',
++			'pre',
++			'img',
++			'plaintext',
++			'table'
++		);
++		$rdfUrl = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
++		$rdfPurl = 'http://purl.org/rss/1.0/';
++		$xbmMagic1 = '#define';
++		$xbmMagic2 = '_width';
++		$xbmMagic3 = '_bits';
++		$binhexMagic = 'converted with BinHex';
++
++		for ( $offset = 0; $offset < strlen( $chunk ); $offset++ ) {
++			$curChar = $chunk[$offset];
++			if ( $curChar == "\x0a" ) {
++				$counters['lf']++;
++				continue;
++			} elseif ( $curChar == "\x0d" ) {
++				$counters['cr']++;
++				continue;
++			} elseif ( $curChar == "\x0c" ) {
++				$counters['ff']++;
++				continue;
++			} elseif ( $curChar == "\t" ) {
++				$counters['low']++;
++				continue;
++			} elseif ( ord( $curChar ) < 32 ) {
++				$counters['ctrl']++;
++				continue;
++			} elseif ( ord( $curChar ) >= 128 ) {
++				$counters['high']++;
++				continue;
++			}
++
++			$counters['low']++;
++			if ( $curChar == '<' ) {
++				// XML
++				$remainder = substr( $chunk, $offset + 1 );
++				if ( !strncasecmp( $remainder, '?XML', 4 ) ) {
++					$nextChar = substr( $chunk, $offset + 5, 1 );
++					if ( $nextChar == ':' || $nextChar == ' ' || $nextChar == "\t" ) {
++						$found['xml'] = true;
++					}
++				}
++				// Scriptlet (JSP)
++				if ( !strncasecmp( $remainder, 'SCRIPTLET', 9 ) ) {
++					$found['scriptlet'] = true;
++					break;
++				}
++				// HTML
++				foreach ( $htmlTags as $tag ) {
++					if ( !strncasecmp( $remainder, $tag, strlen( $tag ) ) ) {
++						$found['html'] = true;
++					}
++				}
++				// Skip broken check for additional tags (HR etc.)
++
++				// CHANNEL replaced by RSS, RDF and FEED in IE 7
++				if ( $version < 'ie07' ) {
++					if ( !strncasecmp( $remainder, 'CHANNEL', 7 ) ) {
++						$found['cdf'] = true;
++					}
++				} else {
++					// RSS
++					if ( !strncasecmp( $remainder, 'RSS', 3 ) ) {
++						$found['rss'] = true;
++						break; // return from SampleData
++					}
++					if ( !strncasecmp( $remainder, 'rdf:RDF', 7 ) ) {
++						$found['rdf-tag'] = true;
++						// no break
++					}
++					if ( !strncasecmp( $remainder, 'FEED', 4 ) ) {
++						$found['atom'] = true;
++						break;
++					}
++				}
++				continue;
++			}
++			// Skip broken check for -->
++
++			// RSS URL checks
++			// For some reason both URLs must appear before it is recognised
++			$remainder = substr( $chunk, $offset );
++			if ( !strncasecmp( $remainder, $rdfUrl, strlen( $rdfUrl ) ) ) {
++				$found['rdf-url'] = true;
++				if ( isset( $found['rdf-tag'] )
++					&& isset( $found['rdf-purl'] ) ) // [sic]
++				{
++					break;
++				}
++				continue;
++			}
++
++			if ( !strncasecmp( $remainder, $rdfPurl, strlen( $rdfPurl ) ) ) {
++				if ( isset( $found['rdf-tag'] )
++					&& isset( $found['rdf-url'] ) ) // [sic]
++				{
++					break;
++				}
++				continue;
++			}
++
++			// XBM checks
++			if ( !strncasecmp( $remainder, $xbmMagic1, strlen( $xbmMagic1 ) ) ) {
++				$found['xbm1'] = true;
++				continue;
++			}
++			if ( $curChar == '_' ) {
++				if ( isset( $found['xbm2'] ) ) {
++					if ( !strncasecmp( $remainder, $xbmMagic3, strlen( $xbmMagic3 ) ) ) {
++						$found['xbm'] = true;
++						break;
++					}
++				} elseif ( isset( $found['xbm1'] ) ) {
++					if ( !strncasecmp( $remainder, $xbmMagic2, strlen( $xbmMagic2 ) ) ) {
++						$found['xbm2'] = true;
++					}
++				}
++			}
++
++			// BinHex
++			if ( !strncasecmp( $remainder, $binhexMagic, strlen( $binhexMagic ) ) ) {
++				$found['binhex'] = true;
++			}
++		}
++		return array( 'found' => $found, 'counters' => $counters );
++	}
++
++	protected function getDataFormat( $version, $type ) {
++		$types = $this->typeTable[$version];
++		if ( $type == '(null)' || strval( $type ) === '' ) {
++			return 'ambiguous';
++		}
++		foreach ( $types as $format => $list ) {
++			if ( in_array( $type, $list ) ) {
++				return $format;
++			}
++		}
++		return 'unknown';
++	}
++}
++
+--- a/includes/MimeMagic.php
++++ b/includes/MimeMagic.php
+@@ -100,6 +100,10 @@ class MimeMagic {
+ 	*/
+ 	var $mExtToMime= NULL;
+ 
++	/** IEContentAnalyzer instance
++	 */
++	var $mIEAnalyzer;
++
+ 	/** The singleton instance
+ 	 */
+ 	private static $instance;
+@@ -733,6 +737,29 @@ class MimeMagic {
+ 
+ 		return MEDIATYPE_UNKNOWN;
+ 	}
++
++	/**
++	 * Get the MIME types that various versions of Internet Explorer would
++	 * detect from a chunk of the content.
++	 *
++	 * @param string $fileName The file name (unused at present)
++	 * @param string $chunk The first 256 bytes of the file
++	 * @param string $proposed The MIME type proposed by the server
++	 */
++	public function getIEMimeTypes( $fileName, $chunk, $proposed ) {
++		$ca = $this->getIEContentAnalyzer();
++		return $ca->getRealMimesFromData( $fileName, $chunk, $proposed );
++	}
++
++	/**
++	 * Get a cached instance of IEContentAnalyzer
++	 */
++	protected function getIEContentAnalyzer() {
++		if ( is_null( $this->mIEAnalyzer ) ) {
++			$this->mIEAnalyzer = new IEContentAnalyzer;
++		}
++		return $this->mIEAnalyzer;
++	}
+ }
+ 
+ 
+--- a/includes/SpecialImport.php
++++ b/includes/SpecialImport.php
+@@ -42,26 +42,30 @@ function wfSpecialImport( $page = '' ) {
+ 	if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') {
+ 		$isUpload = false;
+ 		$namespace = $wgRequest->getIntOrNull( 'namespace' );
++		$sourceName = $wgRequest->getVal( "source" );
+ 
+-		switch( $wgRequest->getVal( "source" ) ) {
+-		case "upload":
++		if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) {
++			$source = new WikiErrorMsg( 'import-token-mismatch' );
++		} elseif ( $sourceName == 'upload' ) {
+ 			$isUpload = true;
+ 			if( $wgUser->isAllowed( 'importupload' ) ) {
+ 				$source = ImportStreamSource::newFromUpload( "xmlimport" );
+ 			} else {
+ 				return $wgOut->permissionRequired( 'importupload' );
+ 			}
+-			break;
+-		case "interwiki":
++		} elseif ( $sourceName == "interwiki" ) {
+ 			$interwiki = $wgRequest->getVal( 'interwiki' );
+-			$history = $wgRequest->getCheck( 'interwikiHistory' );
+-			$frompage = $wgRequest->getText( "frompage" );
+-			$source = ImportStreamSource::newFromInterwiki(
+-				$interwiki,
+-				$frompage,
+-				$history );
+-			break;
+-		default:
++			if ( !in_array( $interwiki, $wgImportSources ) ) {
++				$source = new WikiErrorMsg( "import-invalid-interwiki" );
++			} else {
++				$history = $wgRequest->getCheck( 'interwikiHistory' );
++				$frompage = $wgRequest->getText( "frompage" );
++				$source = ImportStreamSource::newFromInterwiki(
++					$interwiki,
++					$frompage,
++					$history );
++			}
++		} else {
+ 			$source = new WikiErrorMsg( "importunknownsource" );
+ 		}
+ 
+@@ -105,6 +109,7 @@ function wfSpecialImport( $page = '' ) {
+ 			Xml::hidden( 'action', 'submit' ) .
+ 			Xml::hidden( 'source', 'upload' ) .
+ 			"<input type='file' name='xmlimport' value='' size='30' />" . // No Xml function for type=file? Todo?
++			Xml::hidden( 'editToken', $wgUser->editToken() ) .
+ 			Xml::submitButton( wfMsg( 'uploadbtn' ) ) . 
+ 			Xml::closeElement( 'form' ) .
+ 			Xml::closeElement( 'fieldset' )
+@@ -123,6 +128,7 @@ function wfSpecialImport( $page = '' ) {
+ 			wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) .
+ 			Xml::hidden( 'action', 'submit' ) .
+ 			Xml::hidden( 'source', 'interwiki' ) .
++			Xml::hidden( 'editToken', $wgUser->editToken() ) .
+ 			Xml::openElement( 'table' ) .
+ 			"<tr>
+ 				<td>" .
+--- a/includes/SpecialUndelete.php
++++ b/includes/SpecialUndelete.php
+@@ -530,7 +530,7 @@ class PageArchive {
+  */
+ class UndeleteForm {
+ 	var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj;
+-	var $mTargetTimestamp, $mAllowed, $mComment;
++	var $mTargetTimestamp, $mAllowed, $mComment, $mToken;
+ 
+ 	function UndeleteForm( $request, $par = "" ) {
+ 		global $wgUser;
+@@ -547,6 +547,7 @@ class UndeleteForm {
+ 		$this->mPreview = $request->getCheck( 'preview' ) && $posted;
+ 		$this->mDiff = $request->getCheck( 'diff' );
+ 		$this->mComment = $request->getText( 'wpComment' );
++		$this->mToken = $request->getVal( 'token' );
+ 		
+ 		if( $par != "" ) {
+ 			$this->mTarget = $par;
+@@ -604,7 +605,12 @@ class UndeleteForm {
+ 			return $this->showRevision( $this->mTimestamp );
+ 		}
+ 		if( $this->mFile !== null ) {
+-			return $this->showFile( $this->mFile );
++			if ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) {
++				$this->showFileConfirmationForm( $this->mFile );
++				return false;
++ 			} else {
++				return $this->showFile( $this->mFile );
++			}
+ 		}
+ 		if( $this->mRestore && $this->mAction == "submit" ) {
+ 			return $this->undelete();
+@@ -810,6 +816,29 @@ class UndeleteForm {
+ 	}
+ 	
+ 	/**
++	 * Show a form confirming whether a tokenless user really wants to see a file
++	 */
++	private function showFileConfirmationForm( $key ) {
++		global $wgOut, $wgUser, $wgLang;
++		$file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
++		$wgOut->addWikiMsg( 'undelete-show-file-confirm',
++			$this->mTargetObj->getText(),
++			$wgLang->timeanddate( $file->getTimestamp() ) );
++		$wgOut->addHTML(
++			Xml::openElement( 'form', array(
++				'method' => 'POST',
++				'action' => SpecialPage::getTitleFor( 'Undelete' )->getLocalUrl(
++					'target=' . urlencode( $this->mTarget ) .
++					'&file=' . urlencode( $key ) .
++					'&token=' . urlencode( $wgUser->editToken( $key ) ) )
++				)
++			) .
++			Xml::submitButton( wfMsg( 'undelete-show-file-submit' ) ) .
++			'</form>'
++		);
++	}
++
++	/**
+ 	 * Show a deleted file version requested by the visitor.
+ 	 */
+ 	function showFile( $key ) {
+@@ -997,7 +1026,9 @@ class UndeleteForm {
+ 					$target = urlencode( $this->mTarget );
+ 					$pageLink = $sk->makeKnownLinkObj( $titleObj,
+ 						$wgLang->timeanddate( $ts, true ),
+-						"target=$target&file=$key" );
++						"target=$target" .
++						"&file=$key" .
++						"&token=" . urlencode( $wgUser->editToken( $key ) ) );
+ 				} else {
+ 					$checkBox = '';
+ 					$pageLink = $wgLang->timeanddate( $ts, true );
+--- a/includes/SpecialUpload.php
++++ b/includes/SpecialUpload.php
+@@ -1214,11 +1214,11 @@ EOT
+ 		$magic=& MimeMagic::singleton();
+ 		$mime= $magic->guessMimeType($tmpfile,false);
+ 
++
+ 		#check mime type, if desired
+ 		global $wgVerifyMimeType;
+ 		if ($wgVerifyMimeType) {
+-
+-		  wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n");
++			wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n");
+ 			#check mime type against file extension
+ 			if( !$this->verifyExtension( $mime, $extension ) ) {
+ 				return new WikiErrorMsg( 'uploadcorrupt' );
+@@ -1226,9 +1226,22 @@ EOT
+ 
+ 			#check mime type blacklist
+ 			global $wgMimeTypeBlacklist;
+-			if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist)
+-				&& $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
+-				return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) );
++			if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) {
++				if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
++					return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) );
++				}
++
++				# Check IE type
++				$fp = fopen( $tmpfile, 'rb' );
++				$chunk = fread( $fp, 256 );
++				fclose( $fp );
++				$extMime = $magic->guessTypesForExtension( $extension );
++				$ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime );
++				foreach ( $ieTypes as $ieType ) {
++					if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
++						return new WikiErrorMsg( 'filetype-bad-ie-mime', $ieType );
++					}
++				}
+ 			}
+ 		}
+ 
+@@ -1236,6 +1249,11 @@ EOT
+ 		if( $this->detectScript ( $tmpfile, $mime, $extension ) ) {
+ 			return new WikiErrorMsg( 'uploadscripted' );
+ 		}
++		if( $extension == 'svg' || $mime == 'image/svg+xml' ) {
++			if( $this->detectScriptInSvg( $tmpfile ) ) {
++				return new WikiErrorMsg( 'uploadscripted' );
++			}
++		}
+ 
+ 		/**
+ 		* Scan the uploaded file for viruses
+@@ -1249,6 +1267,7 @@ EOT
+ 		return true;
+ 	}
+ 
++
+ 	/**
+ 	 * Checks if the mime type of the uploaded file matches the file extension.
+ 	 *
+@@ -1347,6 +1366,7 @@ EOT
+ 		*/
+ 
+ 		$tags = array(
++			'<a href',
+ 			'<body',
+ 			'<head',
+ 			'<html',   #also in safari
+@@ -1385,6 +1405,41 @@ EOT
+ 		return false;
+ 	}
+ 
++	function detectScriptInSvg( $filename ) {
++		$check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
++		return $check->filterMatch;
++	}
++
++ 	/**
++	 * @todo Replace this with a whitelist filter!
++	 */
++	function checkSvgScriptCallback( $element, $attribs ) {
++		$stripped = $this->stripXmlNamespace( $element );
++
++		if( $stripped == 'script' ) {
++			wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
++			return true;
++		}
++
++		foreach( $attribs as $attrib => $value ) {
++			$stripped = $this->stripXmlNamespace( $attrib );
++			if( substr( $stripped, 0, 2 ) == 'on' ) {
++				wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" );
++				return true;
++			}
++			if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
++				wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" );
++				return true;
++			}
++		}
++	}
++
++	private function stripXmlNamespace( $name ) {
++		// 'http://www.w3.org/2000/svg:script' -> 'script'
++		$parts = explode( ':', strtolower( $name ) );
++		return array_pop( $parts );
++	}
++
+ 	/** 
+ 	 * Generic wrapper function for a virus scanner program.
+ 	 * This relies on the $wgAntivirus and $wgAntivirusSetup variables.
+--- a/includes/StreamFile.php
++++ b/includes/StreamFile.php
+@@ -31,6 +31,12 @@ function wfStreamFile( $fname, $headers 
+ 		header('Content-type: application/x-wiki');
+ 	}
+ 
++	// Don't stream it out as text/html if there was a PHP error
++	if ( headers_sent() ) {
++		echo "Headers already sent, terminating.\n";
++		return;
++	}
++
+ 	global $wgContLanguageCode;
+ 	header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( basename( $fname ) ) );
+ 
+@@ -53,27 +59,54 @@ function wfStreamFile( $fname, $headers 
+ }
+ 
+ /** */
+-function wfGetType( $filename ) {
++function wfGetType( $filename, $safe = true ) {
+ 	global $wgTrivialMimeDetection;
+ 
++	$ext = strrchr($filename, '.');
++	$ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) );
++
+ 	# trivial detection by file extension,
+ 	# used for thumbnails (thumb.php)
+ 	if ($wgTrivialMimeDetection) {
+-		$ext= strtolower(strrchr($filename, '.'));
+ 
+ 		switch ($ext) {
+-			case '.gif': return 'image/gif';
+-			case '.png': return 'image/png';
+-			case '.jpg': return 'image/jpeg';
+-			case '.jpeg': return 'image/jpeg';
++			case 'gif': return 'image/gif';
++			case 'png': return 'image/png';
++			case 'jpg': return 'image/jpeg';
++			case 'jpeg': return 'image/jpeg';
+ 		}
+ 
+ 		return 'unknown/unknown';
+ 	}
+-	else {
+-		$magic=& MimeMagic::singleton();
+-		return $magic->guessMimeType($filename); //full fancy mime detection
++
++	$magic = MimeMagic::singleton();
++	// Use the extension only, rather than magic numbers, to avoid opening
++	// up vulnerabilities due to uploads of files with allowed extensions
++	// but disallowed types.
++	$type = $magic->guessTypesForExtension( $ext );
++
++	/**
++	 * Double-check some security settings that were done on upload but might
++	 * have changed since.
++	 */
++	if ( $safe ) {
++		global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions,
++			$wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist, $wgRequest;
++		$form = new UploadForm( $wgRequest );
++		list( $partName, $extList ) = $form->splitExtensions( $filename );
++		if ( $form->checkFileExtensionList( $extList, $wgFileBlacklist ) ) {
++			return 'unknown/unknown';
++		}
++		if ( $wgCheckFileExtensions && $wgStrictFileExtensions
++			&& !$form->checkFileExtensionList( $extList, $wgFileExtensions ) )
++		{
++			return 'unknown/unknown';
++		}
++		if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) {
++			return 'unknown/unknown';
++		}
+ 	}
++	return $type;
+ }
+ 
+ 
+--- a/includes/Title.php
++++ b/includes/Title.php
+@@ -298,9 +298,13 @@ class Title {
+ 					$m[1] = urldecode( ltrim( $m[1], ':' ) );
+ 				}
+ 				$title = Title::newFromText( $m[1] );
+-				// Redirects to Special:Userlogout are not permitted
+-				if( $title instanceof Title && !$title->isSpecial( 'Userlogout' ) )
++				// Redirects to some special pages are not permitted
++				if( $title instanceof Title
++						&& !$title->isSpecial( 'Userlogout' )
++						&& !$title->isSpecial( 'Filepath' ) )
++				{
+ 					return $title;
++				}
+ 			}
+ 		}
+ 		return null;
+--- a/includes/XmlTypeCheck.php
++++ b/includes/XmlTypeCheck.php
+@@ -8,38 +8,37 @@ class XmlTypeCheck {
+ 	public $wellFormed = false;
+ 	
+ 	/**
++	 * Will be set to true if the optional element filter returned
++	 * a match at some point.
++	 */
++	public $filterMatch = false;
++
++	/**
+ 	 * Name of the document's root element, including any namespace
+ 	 * as an expanded URL.
+ 	 */
+ 	public $rootElement = '';
+ 	
+-	private $softNamespaces;
+-	private $namespaces = array();
+-	
+ 	/**
+ 	 * @param $file string filename
+-	 * @param $softNamespaces bool
+-	 *        If set to true, use of undeclared XML namespaces will be ignored.
+-	 *        This matches the behavior of rsvg, but more compliant consumers
+-	 *        such as Firefox will reject such files.
+-	 *        Leave off for the default, stricter checks.
++	 * @param $filterCallback callable (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, and attributes, but not to text contents.
++	 *        Filter should return 'true' to toggle on $this->filterMatch
+ 	 */
+-	function __construct( $file, $softNamespaces=false ) {
+-		$this->softNamespaces = $softNamespaces;
++	function __construct( $file, $filterCallback=null ) {
++		$this->filterCallback = $filterCallback;
+ 		$this->run( $file );
+ 	}
+ 	
+ 	private function run( $fname ) {
+-		if( $this->softNamespaces ) {
+-			$parser = xml_parser_create( 'UTF-8' );
+-		} else {
+-			$parser = xml_parser_create_ns( 'UTF-8' );
+-		}
++		$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 );
+ 		
+-		xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false );
++		xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false );
+ 
+ 		$file = fopen( $fname, "rb" );
+ 		do {
+@@ -59,35 +58,22 @@ class XmlTypeCheck {
+ 		xml_parser_free( $parser );
+ 	}
+ 
+-	private function elementOpen( $parser, $name, $attribs ) {
+-		if( $this->softNamespaces ) {
+-			// Check namespaces manually, so expat doesn't throw
+-			// errors on use of undeclared namespaces.
+-			foreach( $attribs as $attrib => $val ) {
+-				if( $attrib == 'xmlns' ) {
+-					$this->namespaces[''] = $val;
+-				} elseif( substr( $attrib, 0, strlen( 'xmlns:' ) ) == 'xmlns:' ) {
+-					$this->namespaces[substr( $attrib, strlen( 'xmlns:' ) )] = $val;
+-				}
+-			}
+-			
+-			if( strpos( $name, ':' ) === false ) {
+-				$ns = '';
+-				$subname = $name;
+-			} else {
+-				list( $ns, $subname ) = explode( ':', $name, 2 );
+-			}
+-			
+-			if( isset( $this->namespaces[$ns] ) ) {
+-				$name = $this->namespaces[$ns] . ':' . $subname;
+-			} else {
+-				// Technically this is invalid for XML with Namespaces.
+-				// But..... we'll just let it slide in soft mode.
+-			}
+-		}
+-		
+-		// We only need the first open element
++	private function rootElementOpen( $parser, $name, $attribs ) {
+ 		$this->rootElement = $name;
+-		xml_set_element_handler( $parser, false, false );
++
++		if( is_callable( $this->filterCallback ) ) {
++			xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false );
++			$this->elementOpen( $parser, $name, $attribs );
++		} else {
++			// We only need the first open element
++			xml_set_element_handler( $parser, false, false );
++		}
++	}
++
++	private function elementOpen( $parser, $name, $attribs ) {
++		if( call_user_func( $this->filterCallback, $name, $attribs ) ) {
++			// Filter hit!
++			$this->filterMatch = true;
++ 		}
+ 	}
+ }
+--- a/includes/filerepo/FSRepo.php
++++ b/includes/filerepo/FSRepo.php
+@@ -146,10 +146,8 @@ class FSRepo extends FileRepo {
+ 				if ( !wfMkdirParents( $dstDir ) ) {
+ 					return $this->newFatal( 'directorycreateerror', $dstDir );
+ 				}
+-				// In the deleted zone, seed new directories with a blank 
+-				// index.html, to prevent crawling
+ 				if ( $dstZone == 'deleted' ) {
+-					file_put_contents( "$dstDir/index.html", '' );
++					$this->initDeletedDir( $dstDir );
+ 				}
+ 			}
+ 			
+@@ -212,6 +210,20 @@ class FSRepo extends FileRepo {
+ 	}
+ 
+ 	/**
++	 * Take all available measures to prevent web accessibility of new deleted
++	 * directories, in case the user has not configured offline storage
++	 */
++	protected function initDeletedDir( $dir ) {
++		// Add a .htaccess file to the root of the deleted zone
++		$root = $this->getZonePath( 'deleted' );
++		if ( !file_exists( "$root/.htaccess" ) ) {
++			file_put_contents( "$root/.htaccess", "Deny from all\n" );
++		}
++		// Seed new directories with a blank index.html, to prevent crawling
++		file_put_contents( "$dir/index.html", '' );
++	}
++
++	/**
+ 	 * Pick a random name in the temp zone and store a file to it.
+ 	 * @param string $originalName The base name of the file as specified 
+ 	 *     by the user. The file extension will be maintained.
+@@ -387,8 +399,7 @@ class FSRepo extends FileRepo {
+ 					$status->fatal( 'directorycreateerror', $archiveDir );
+ 					continue;
+ 				}
+-				// Seed new directories with a blank index.html, to prevent crawling
+-				file_put_contents( "$archiveDir/index.html", '' );
++				$this->initDeletedDir( $archiveDir );
+ 			}
+ 			// Check if the archive directory is writable
+ 			// This doesn't appear to work on NTFS
+--- a/languages/messages/MessagesEn.php
++++ b/languages/messages/MessagesEn.php
+@@ -1414,6 +1414,7 @@ To include a file in a page, use a link 
+ 'illegalfilename'             => 'The filename "$1" contains characters that are not allowed in page titles. Please rename the file and try uploading it again.',
+ 'badfilename'                 => 'File name has been changed to "$1".',
+ 'filetype-badmime'            => 'Files of the MIME type "$1" are not allowed to be uploaded.',
++'filetype-bad-ie-mime'        => 'Cannot upload this file because Internet Explorer would detect it as "$1", which is a disallowed and potentially dangerous file type.',
+ 'filetype-unwanted-type'      => "'''\".\$1\"''' is an unwanted file type.  Preferred file types are \$2.",
+ 'filetype-banned-type'        => "'''\".\$1\"''' is not a permitted file type.  Permitted file types are \$2.",
+ 'filetype-missing'            => 'The file has no extension (like ".jpg").',
+@@ -1978,6 +1979,8 @@ Consult the [[Special:Log/delete|deletio
+ 'undelete-error-long'          => 'Errors were encountered while undeleting the file:
+ 
+ $1',
++'undelete-show-file-confirm'   => 'Are you sure you want to view a deleted revision of the file "<nowiki>$1</nowiki>" from $2?',
++'undelete-show-file-submit'    => 'Yes',
+ 
+ # Namespace form on various pages
+ 'namespace'      => 'Namespace:',
+@@ -2241,6 +2244,8 @@ All transwiki import actions are logged 
+ 'import-noarticle'           => 'No page to import!',
+ 'import-nonewrevisions'      => 'All revisions were previously imported.',
+ 'xml-error-string'           => '$1 at line $2, col $3 (byte $4): $5',
++'import-token-mismatch'      => 'Loss of session data. Please try again.',
++'import-invalid-interwiki'   => 'Cannot import from the specified wiki.',
+ 
+ # Import log
+ 'importlogpage'                    => 'Import log',
+--- a/maintenance/language/messages.inc
++++ b/maintenance/language/messages.inc
+@@ -848,6 +848,7 @@ $wgMessageStructure = array(
+ 		'illegalfilename',
+ 		'badfilename',
+ 		'filetype-badmime',
++		'filetype-bad-ie-mime',
+ 		'filetype-unwanted-type',
+ 		'filetype-banned-type',
+ 		'filetype-missing',
+@@ -1350,6 +1351,8 @@ $wgMessageStructure = array(
+ 		'undelete-missing-filearchive',
+ 		'undelete-error-short',
+ 		'undelete-error-long',
++		'undelete-show-file-confirm',
++		'undelete-show-file-submit',
+ 	),
+ 	'nsform' => array(
+ 		'namespace',
+@@ -1577,6 +1580,8 @@ $wgMessageStructure = array(
+ 		'import-noarticle',
+ 		'import-nonewrevisions',
+ 		'xml-error-string',
++		'import-token-mismatch',
++		'import-invalid-interwiki',
+ 	),
+ 	'importlog' => array(
+ 		'importlogpage',
+--- a/profileinfo.php
++++ b/profileinfo.php
+@@ -48,7 +48,7 @@ $wgDBadminuser = $wgDBadminpassword = $w
+ 
+ define( 'MW_NO_SETUP', 1 );
+ require_once( './includes/WebStart.php' );
+-require_once("./AdminSettings.php");
++@include_once("./AdminSettings.php");
+ require_once( './includes/GlobalFunctions.php' );
+ 
+ if (!$wgEnableProfileInfo) {

Attachment: signature.asc
Description: OpenPGP digital signature


Reply to: