From a4edbfa031eb4cd72678051f1510afde4f77951e Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 28 Feb 2014 08:36:29 +0100 Subject: Update to MediaWiki 1.22.3 --- includes/DefaultSettings.php | 2 +- includes/User.php | 22 +++++++++- includes/Wiki.php | 13 ++++-- includes/api/ApiFormatBase.php | 18 +++++++- includes/db/DatabaseOracle.php | 32 +++++++++++++- includes/db/DatabasePostgres.php | 16 +++---- includes/installer/WebInstallerPage.php | 9 +++- includes/specials/SpecialPrefixindex.php | 1 + includes/upload/UploadBase.php | 71 +++++++++++++++++++++++++++++++- 9 files changed, 165 insertions(+), 19 deletions(-) (limited to 'includes') diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index e9b4f490..820d6093 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -63,7 +63,7 @@ $wgConf = new SiteConfiguration; * MediaWiki version number * @since 1.2 */ -$wgVersion = '1.22.2'; +$wgVersion = '1.22.3'; /** * Name of the site. It must be changed in LocalSettings.php diff --git a/includes/User.php b/includes/User.php index 12912e1c..62324043 100644 --- a/includes/User.php +++ b/includes/User.php @@ -984,7 +984,8 @@ class User { # Get the token from DB/cache and clean it up to remove garbage padding. # This deals with historical problems with bugs and the default column value. $token = rtrim( $proposedUser->getToken( false ) ); // correct token - $passwordCorrect = ( strlen( $token ) && $token === $request->getCookie( 'Token' ) ); + // Make comparison in constant time (bug 61346) + $passwordCorrect = strlen( $token ) && $this->compareSecrets( $token, $request->getCookie( 'Token' ) ); $from = 'cookie'; } else { // No session or persistent login cookie @@ -1003,6 +1004,25 @@ class User { } } + /** + * A comparison of two strings, not vulnerable to timing attacks + * @param string $answer the secret string that you are comparing against. + * @param string $test compare this string to the $answer. + * @return bool True if the strings are the same, false otherwise + */ + protected function compareSecrets( $answer, $test ) { + if ( strlen( $answer ) !== strlen( $test ) ) { + $passwordCorrect = false; + } else { + $result = 0; + for ( $i = 0; $i < strlen( $answer ); $i++ ) { + $result |= ord( $answer{$i} ) ^ ord( $test{$i} ); + } + $passwordCorrect = ( $result == 0 ); + } + return $passwordCorrect; + } + /** * Load user and user_group data from the database. * $this->mId must be set, this is how the user is identified. diff --git a/includes/Wiki.php b/includes/Wiki.php index ae75bf33..074ec1ab 100644 --- a/includes/Wiki.php +++ b/includes/Wiki.php @@ -653,12 +653,19 @@ class MediaWiki { } if ( !wfShellExecDisabled() && is_executable( $wgPhpCli ) ) { - // Start a background process to run some of the jobs. - // This will be asynchronous on *nix though not on Windows. + // Start a background process to run some of the jobs wfProfileIn( __METHOD__ . '-exec' ); $retVal = 1; $cmd = wfShellWikiCmd( "$IP/maintenance/runJobs.php", array( '--maxjobs', $n ) ); - wfShellExec( "$cmd &", $retVal ); + $cmd .= " >" . wfGetNull() . " 2>&1"; // don't hang PHP on pipes + if ( wfIsWindows() ) { + // Using START makes this async and also works around a bug where using + // wfShellExec() with a quoted script name causes a filename syntax error. + $cmd = "START /B \"bg\" $cmd"; + } else { + $cmd = "$cmd &"; + } + wfShellExec( $cmd, $retVal ); wfProfileOut( __METHOD__ . '-exec' ); } else { try { diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index b89fb3a7..70495439 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -272,17 +272,33 @@ See the complete documentation, // encode all comments or tags as safe blue strings $text = str_replace( '<', '<', $text ); $text = str_replace( '>', '>', $text ); + // identify requests to api.php - $text = preg_replace( "#api\\.php\\?[^ <\n\t]+#", '\\0', $text ); + $text = preg_replace( '#^(\s*)(api\.php\?[^ <\n\t]+)$#m', '\1\2', $text ); if ( $this->mHelp ) { // make strings inside * bold $text = preg_replace( "#\\*[^<>\n]+\\*#", '\\0', $text ); } + + // Armor links (bug 61362) + $masked = array(); + $text = preg_replace_callback( '##', function ( $matches ) use ( &$masked ) { + $sha = sha1( $matches[0] ); + $masked[$sha] = $matches[0]; + return "<$sha>"; + }, $text ); + // identify URLs $protos = wfUrlProtocolsWithoutProtRel(); // This regex hacks around bug 13218 (" included in the URL) $text = preg_replace( "#(((?i)$protos).*?)(")?([ \\'\"<>\n]|<|>|")#", '\\1\\3\\4', $text ); + // Unarmor links + $text = preg_replace_callback( '#<([0-9a-f]{40})>#', function ( $matches ) use ( &$masked ) { + $sha = $matches[1]; + return isset( $masked[$sha] ) ? $masked[$sha] : $matches[0]; + }, $text ); + /** * Temporary fix for bad links in help messages. As a special case, * XML-escaped metachars are de-escaped one level in the help message diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 32d4d984..fb2d4359 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -551,8 +551,12 @@ class DatabaseOracle extends DatabaseBase { } else { $first = false; } - - $sql .= $this->fieldBindStatement( $table, $col, $val ); + if ( $this->isQuotedIdentifier( $val ) ) { + $sql .= $this->removeIdentifierQuotes( $val ); + unset( $row[$col] ); + } else { + $sql .= $this->fieldBindStatement( $table, $col, $val ); + } } $sql .= ')'; @@ -679,6 +683,30 @@ class DatabaseOracle extends DatabaseBase { return $retval; } + public function upsert( $table, array $rows, array $uniqueIndexes, array $set, + $fname = __METHOD__ + ) { + if ( !count( $rows ) ) { + return true; // nothing to do + } + + if ( !is_array( reset( $rows ) ) ) { + $rows = array( $rows ); + } + + $sequenceData = $this->getSequenceData( $table ); + if ( $sequenceData !== false ) { + // add sequence column to each list of columns, when not set + foreach ( $rows as &$row ) { + if ( !isset( $row[$sequenceData['column']] ) ) { + $row[$sequenceData['column']] = $this->addIdentifierQuotes('GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')'); + } + } + } + + return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname ); + } + function tableName( $name, $format = 'quoted' ) { /* Replace reserved words with better ones diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php index aed35f10..0bd966ba 100644 --- a/includes/db/DatabasePostgres.php +++ b/includes/db/DatabasePostgres.php @@ -729,13 +729,15 @@ __INDEXATTR__; * so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly. */ function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) { - $forUpdateKey = array_search( 'FOR UPDATE', $options ); - if ( $forUpdateKey !== false && $join_conds ) { - unset( $options[$forUpdateKey] ); - - foreach ( $join_conds as $table => $join_cond ) { - if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) { - $options['FOR UPDATE'][] = $table; + if ( is_array( $options ) ) { + $forUpdateKey = array_search( 'FOR UPDATE', $options ); + if ( $forUpdateKey !== false && $join_conds ) { + unset( $options[$forUpdateKey] ); + + foreach ( $join_conds as $table_cond => $join_cond ) { + if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) { + $options['FOR UPDATE'][] = $table_cond; + } } } } diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php index ad399133..d3b550fe 100644 --- a/includes/installer/WebInstallerPage.php +++ b/includes/installer/WebInstallerPage.php @@ -955,11 +955,16 @@ class WebInstaller_Options extends WebInstallerPage { LinkCache::singleton()->useDatabase( false ); foreach ( $extensions as $ext ) { + if ( isset( $ext['descriptionmsg'] ) ) { + $desc = wfMessage( $ext['descriptionmsg'] )->useDatabase( false )->parse(); + } else { + $desc = ''; + } $extHtml .= $this->parent->getCheckBox( array( 'var' => "ext-{$ext['name']}", 'rawtext' => "{$ext['name']}: " . - wfMessage( $ext['descriptionmsg'] )->useDatabase( false )->parse(), - ) ); + $desc, + ) ); } diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index 28d07ffc..0d065b09 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -264,6 +264,7 @@ class SpecialPrefixindex extends SpecialAllpages { 'from' => $s->page_title, 'prefix' => $prefix, 'hideredirects' => $this->hideRedirects, + 'stripprefix' => $this->stripPrefix, ); if ( $namespace || $prefix == '' ) { diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 916ad6c1..c0c37b3f 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -44,7 +44,7 @@ abstract class UploadBase { protected $mFilteredName, $mFinalExtension; protected $mLocalFile, $mFileSize, $mFileProps; protected $mBlackListedExtensions; - protected $mJavaDetected; + protected $mJavaDetected, $mSVGNSError; protected static $safeXmlEncodings = array( 'UTF-8', 'ISO-8859-1', 'ISO-8859-2', 'UTF-16', 'UTF-32' ); @@ -1162,6 +1162,7 @@ abstract class UploadBase { * @return bool */ protected function detectScriptInSvg( $filename ) { + $this->mSVGNSError = false; $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ), @@ -1172,6 +1173,9 @@ abstract class UploadBase { // Invalid xml (bug 58553) return array( 'uploadinvalidxml' ); } elseif ( $check->filterMatch ) { + if ( $this->mSVGNSError ) { + return array( 'uploadscriptednamespace', $this->mSVGNSError ); + } return array( 'uploadscripted' ); } return false; @@ -1198,7 +1202,51 @@ abstract class UploadBase { * @return bool */ public function checkSvgScriptCallback( $element, $attribs ) { - $strippedElement = $this->stripXmlNamespace( $element ); + list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element ); + + static $validNamespaces = array( + '', + 'adobe:ns:meta/', + 'http://creativecommons.org/ns#', + 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd', + 'http://ns.adobe.com/adobeillustrator/10.0/', + 'http://ns.adobe.com/adobesvgviewerextensions/3.0/', + 'http://ns.adobe.com/extensibility/1.0/', + 'http://ns.adobe.com/flows/1.0/', + 'http://ns.adobe.com/illustrator/1.0/', + 'http://ns.adobe.com/imagereplacement/1.0/', + 'http://ns.adobe.com/pdf/1.3/', + 'http://ns.adobe.com/photoshop/1.0/', + 'http://ns.adobe.com/saveforweb/1.0/', + 'http://ns.adobe.com/variables/1.0/', + 'http://ns.adobe.com/xap/1.0/', + 'http://ns.adobe.com/xap/1.0/g/', + 'http://ns.adobe.com/xap/1.0/g/img/', + 'http://ns.adobe.com/xap/1.0/mm/', + 'http://ns.adobe.com/xap/1.0/rights/', + 'http://ns.adobe.com/xap/1.0/stype/dimensions#', + 'http://ns.adobe.com/xap/1.0/stype/font#', + 'http://ns.adobe.com/xap/1.0/stype/manifestitem#', + 'http://ns.adobe.com/xap/1.0/stype/resourceevent#', + 'http://ns.adobe.com/xap/1.0/stype/resourceref#', + 'http://ns.adobe.com/xap/1.0/t/pg/', + 'http://purl.org/dc/elements/1.1/', + 'http://purl.org/dc/elements/1.1', + 'http://schemas.microsoft.com/visio/2003/svgextensions/', + 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd', + 'http://web.resource.org/cc/', + 'http://www.freesoftware.fsf.org/bkchem/cdml', + 'http://www.inkscape.org/namespaces/inkscape', + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'http://www.w3.org/2000/svg', + ); + + if ( !in_array( $namespace, $validNamespaces ) ) { + wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file.\n" ); + // @TODO return a status object to a closure in XmlTypeCheck, for MW1.21+ + $this->mSVGNSError = $namespace; + return true; + } /* * check for elements that can contain javascript @@ -1220,6 +1268,12 @@ abstract class UploadBase { return true; } + # Block iframes, in case they pass the namespace check + if ( $strippedElement == 'iframe' ) { + wfDebug( __METHOD__ . ": iframe in uploaded file.\n" ); + return true; + } + foreach ( $attribs as $attrib => $value ) { $stripped = $this->stripXmlNamespace( $attrib ); $value = strtolower( $value ); @@ -1293,6 +1347,19 @@ abstract class UploadBase { return false; //No scripts detected } + /** + * Divide the element name passed by the xml parser to the callback into URI and prifix. + * @param $name string + * @return array containing the namespace URI and prefix + */ + private static function splitXmlNamespace( $element ) { + // 'http://www.w3.org/2000/svg:script' -> array( 'http://www.w3.org/2000/svg', 'script' ) + $parts = explode( ':', strtolower( $element ) ); + $name = array_pop( $parts ); + $ns = implode( ':', $parts ); + return array( $ns, $name ); + } + /** * @param $name string * @return string -- cgit v1.2.2