Merge "Instrument edit failures"
[lhc/web/wiklou.git] / includes / upload / UploadBase.php
index fccb5e1..f17681c 100644 (file)
@@ -622,6 +622,7 @@ abstract class UploadBase {
                $warnings = array();
 
                $localFile = $this->getLocalFile();
+               $localFile->load( File::READ_LATEST );
                $filename = $localFile->getName();
 
                /**
@@ -701,6 +702,7 @@ abstract class UploadBase {
         * @return Status Indicating the whether the upload succeeded.
         */
        public function performUpload( $comment, $pageText, $watch, $user ) {
+               $this->getLocalFile()->load( File::READ_LATEST );
 
                $status = $this->getLocalFile()->upload(
                        $this->mTempPath,
@@ -744,11 +746,11 @@ abstract class UploadBase {
                $file = $this->getLocalFile();
 
                foreach ( $sizes as $size ) {
-                       if ( $file->isVectorized()
-                               || $file->getWidth() > $size ) {
-                                       $jobs[] = new ThumbnailRenderJob( $file->getTitle(), array(
-                                               'transformParams' => array( 'width' => $size ),
-                                       ) );
+                       if ( $file->isVectorized() || $file->getWidth() > $size ) {
+                               $jobs[] = new ThumbnailRenderJob(
+                                       $file->getTitle(),
+                                       array( 'transformParams' => array( 'width' => $size ) )
+                               );
                        }
                }
 
@@ -1219,9 +1221,9 @@ abstract class UploadBase {
                // detect the encoding in case is specifies an encoding not whitelisted in self::$safeXmlEncodings
                $attemptEncodings = array( 'UTF-16', 'UTF-16BE', 'UTF-32', 'UTF-32BE' );
                foreach ( $attemptEncodings as $encoding ) {
-                       wfSuppressWarnings();
+                       MediaWiki\suppressWarnings();
                        $str = iconv( $encoding, 'UTF-8', $contents );
-                       wfRestoreWarnings();
+                       MediaWiki\restoreWarnings();
                        if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
                                if ( preg_match( $encodingRegex, $matches[1], $encMatch )
                                        && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
@@ -1264,7 +1266,7 @@ abstract class UploadBase {
                                return array( 'uploadscriptednamespace', $this->mSVGNSError );
                        }
 
-                       return array( 'uploadscripted' );
+                       return $check->filterMatchType;
                }
 
                return false;
@@ -1279,7 +1281,7 @@ abstract class UploadBase {
        public static function checkSvgPICallback( $target, $data ) {
                // Don't allow external stylesheets (bug 57550)
                if ( preg_match( '/xml-stylesheet/i', $target ) ) {
-                       return true;
+                       return array( 'upload-scripted-pi-callback' );
                }
 
                return false;
@@ -1351,7 +1353,7 @@ abstract class UploadBase {
                if ( $strippedElement == 'script' ) {
                        wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
 
-                       return true;
+                       return array( 'uploaded-script-svg', $strippedElement );
                }
 
                # e.g., <svg xmlns="http://www.w3.org/2000/svg">
@@ -1359,21 +1361,21 @@ abstract class UploadBase {
                if ( $strippedElement == 'handler' ) {
                        wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
 
-                       return true;
+                       return array( 'uploaded-script-svg', $strippedElement );
                }
 
                # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
                if ( $strippedElement == 'stylesheet' ) {
                        wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
 
-                       return true;
+                       return array( 'uploaded-script-svg', $strippedElement );
                }
 
                # Block iframes, in case they pass the namespace check
                if ( $strippedElement == 'iframe' ) {
                        wfDebug( __METHOD__ . ": iframe in uploaded file.\n" );
 
-                       return true;
+                       return array( 'uploaded-script-svg', $strippedElement );
                }
 
                # Check <style> css
@@ -1381,7 +1383,7 @@ abstract class UploadBase {
                        && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
                ) {
                        wfDebug( __METHOD__ . ": hostile css in style element.\n" );
-                       return true;
+                       return array( 'uploaded-hostile-svg' );
                }
 
                foreach ( $attribs as $attrib => $value ) {
@@ -1392,7 +1394,7 @@ abstract class UploadBase {
                                wfDebug( __METHOD__
                                        . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
 
-                               return true;
+                               return array( 'uploaded-event-handler-on-svg', $attrib, $value );
                        }
 
                        # href with non-local target (don't allow http://, javascript:, etc)
@@ -1406,36 +1408,31 @@ abstract class UploadBase {
                                        wfDebug( __METHOD__ . ": Found href attribute <$strippedElement "
                                                . "'$attrib'='$value' in uploaded file.\n" );
 
-                                       return true;
+                                       return array( 'uploaded-href-attribute-svg', $strippedElement, $attrib, $value );
                                }
                        }
 
-                       # href with embedded 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" );
-
-                               return true;
-                       }
-
-                       # href with embedded (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;
+                       # 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 array( 'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value );
+                               }
                        }
 
-                       # 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" );
 
-                               return true;
+                               return array( 'uploaded-animate-svg', $strippedElement, $attrib, $value );
                        }
 
                        # use set/animate to add event-handler attribute to parent
@@ -1446,7 +1443,7 @@ abstract class UploadBase {
                                wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with "
                                        . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
 
-                               return true;
+                               return array( 'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value );
                        }
 
                        # use set to add href attribute to parent element
@@ -1456,7 +1453,7 @@ abstract class UploadBase {
                        ) {
                                wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" );
 
-                               return true;
+                               return array( 'uploaded-setting-href-svg' );
                        }
 
                        # use set to add a remote / data / script target to an element
@@ -1466,7 +1463,7 @@ abstract class UploadBase {
                        ) {
                                wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" );
 
-                               return true;
+                               return array( 'uploaded-wrong-setting-svg', $value );
                        }
 
                        # use handler attribute with remote / data / script
@@ -1474,7 +1471,7 @@ abstract class UploadBase {
                                wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script "
                                        . "'$attrib'='$value' in uploaded file.\n" );
 
-                               return true;
+                               return array( 'uploaded-setting-handler-svg', $attrib, $value );
                        }
 
                        # use CSS styles to bring in remote code
@@ -1483,7 +1480,7 @@ abstract class UploadBase {
                        ) {
                                wfDebug( __METHOD__ . ": Found svg setting a style with "
                                        . "remote url '$attrib'='$value' in uploaded file.\n" );
-                               return true;
+                               return array( 'uploaded-remote-url-svg', $attrib, $value );
                        }
 
                        # Several attributes can include css, css character escaping isn't allowed
@@ -1494,7 +1491,7 @@ abstract class UploadBase {
                        ) {
                                wfDebug( __METHOD__ . ": Found svg setting a style with "
                                        . "remote url '$attrib'='$value' in uploaded file.\n" );
-                               return true;
+                               return array( 'uploaded-remote-url-svg', $attrib, $value );
                        }
 
                        # image filters can pull in url, which could be svg that executes scripts
@@ -1505,7 +1502,7 @@ abstract class UploadBase {
                                wfDebug( __METHOD__ . ": Found image filter with url: "
                                        . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
 
-                               return true;
+                               return array( 'uploaded-image-filter-svg', $strippedElement, $stripped, $value );
                        }
                }
 
@@ -1522,7 +1519,7 @@ abstract class UploadBase {
        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;
                }
 
@@ -1699,6 +1696,7 @@ abstract class UploadBase {
        private function checkOverwrite( $user ) {
                // First check whether the local file can be overwritten
                $file = $this->getLocalFile();
+               $file->load( File::READ_LATEST );
                if ( $file->exists() ) {
                        if ( !self::userCanReUpload( $user, $file ) ) {
                                return array( 'fileexists-forbidden', $file->getName() );
@@ -1710,7 +1708,7 @@ abstract class UploadBase {
                /* Check shared conflicts: if the local file does not exist, but
                 * wfFindFile finds a file, it exists in a shared repository.
                 */
-               $file = wfFindFile( $this->getTitle() );
+               $file = wfFindFile( $this->getTitle(), array( 'latest' => true ) );
                if ( $file && !$user->isAllowed( 'reupload-shared' ) ) {
                        return array( 'fileexists-shared-forbidden', $file->getName() );
                }
@@ -1739,6 +1737,8 @@ abstract class UploadBase {
                        return false;
                }
 
+               $img->load( File::READ_LATEST );
+
                return $user->getId() == $img->getUser( 'id' );
        }