Merge "Added unicode encoding support flags to FileBackend"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 15 May 2014 15:06:19 +0000 (15:06 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 15 May 2014 15:06:19 +0000 (15:06 +0000)
1  2 
includes/upload/UploadBase.php

@@@ -44,13 -44,7 +44,13 @@@ abstract class UploadBase 
        protected $mBlackListedExtensions;
        protected $mJavaDetected, $mSVGNSError;
  
 -      protected static $safeXmlEncodings = array( 'UTF-8', 'ISO-8859-1', 'ISO-8859-2', 'UTF-16', 'UTF-32' );
 +      protected static $safeXmlEncodings = array(
 +              'UTF-8',
 +              'ISO-8859-1',
 +              'ISO-8859-2',
 +              'UTF-16',
 +              'UTF-32'
 +      );
  
        const SUCCESS = 0;
        const OK = 0;
                                return $permission;
                        }
                }
 +
                return true;
        }
  
        // Upload handlers. Should probably just be a global.
 -      static $uploadHandlers = array( 'Stash', 'File', 'Url' );
 +      private static $uploadHandlers = array( 'Stash', 'File', 'Url' );
  
        /**
         * Create a form of UploadBase depending on wpSourceType and initializes it
                $handler = new $className;
  
                $handler->initializeFromRequest( $request );
 +
                return $handler;
        }
  
                return false;
        }
  
 -      public function __construct() {}
 +      public function __construct() {
 +      }
  
        /**
         * Returns the upload type. Should be overridden by child classes
                wfProfileIn( __METHOD__ );
                $repo = RepoGroup::singleton()->getLocalRepo();
                if ( $repo->isVirtualUrl( $srcPath ) ) {
 -                      // @todo just make uploads work with storage paths
 -                      // UploadFromStash loads files via virtual URLs
 +                      /** @todo Just make uploads work with storage paths UploadFromStash
 +                       *  loads files via virtual URLs.
 +                       */
                        $tmpFile = $repo->getLocalCopy( $srcPath );
                        if ( $tmpFile ) {
                                $tmpFile->bind( $this ); // keep alive with $this
                        $path = $srcPath;
                }
                wfProfileOut( __METHOD__ );
 +
                return $path;
        }
  
                 */
                if ( $this->isEmptyFile() ) {
                        wfProfileOut( __METHOD__ );
 +
                        return array( 'status' => self::EMPTY_FILE );
                }
  
                $maxSize = self::getMaxUploadSize( $this->getSourceType() );
                if ( $this->mFileSize > $maxSize ) {
                        wfProfileOut( __METHOD__ );
 +
                        return array(
                                'status' => self::FILE_TOO_LARGE,
                                'max' => $maxSize,
                $verification = $this->verifyFile();
                if ( $verification !== true ) {
                        wfProfileOut( __METHOD__ );
 +
                        return array(
                                'status' => self::VERIFICATION_ERROR,
                                'details' => $verification
                $result = $this->validateName();
                if ( $result !== true ) {
                        wfProfileOut( __METHOD__ );
 +
                        return $result;
                }
  
                        array( $this->mDestName, $this->mTempPath, &$error ) )
                ) {
                        wfProfileOut( __METHOD__ );
 +
                        return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
                }
  
                wfProfileOut( __METHOD__ );
 +
                return array( 'status' => self::OK );
        }
  
                                        $result['blacklistedExt'] = $this->mBlackListedExtensions;
                                }
                        }
 +
                        return $result;
                }
                $this->mDestName = $this->getLocalFile()->getName();
                        global $wgMimeTypeBlacklist;
                        if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
                                wfProfileOut( __METHOD__ );
 +
                                return array( 'filetype-badmime', $mime );
                        }
  
                        foreach ( $ieTypes as $ieType ) {
                                if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
                                        wfProfileOut( __METHOD__ );
 +
                                        return array( 'filetype-bad-ie-mime', $ieType );
                                }
                        }
                }
  
                wfProfileOut( __METHOD__ );
 +
                return true;
        }
  
                $status = $this->verifyPartialFile();
                if ( $status !== true ) {
                        wfProfileOut( __METHOD__ );
 +
                        return $status;
                }
  
                        # XXX: Missing extension will be caught by validateName() via getTitle()
                        if ( $this->mFinalExtension != '' && !$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
                                wfProfileOut( __METHOD__ );
 +
                                return array( 'filetype-mime-mismatch', $this->mFinalExtension, $mime );
                        }
                }
                        if ( !$handlerStatus->isOK() ) {
                                $errors = $handlerStatus->getErrorsArray();
                                wfProfileOut( __METHOD__ );
 +
                                return reset( $errors );
                        }
                }
                wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status ) );
                if ( $status !== true ) {
                        wfProfileOut( __METHOD__ );
 +
                        return $status;
                }
  
                wfDebug( __METHOD__ . ": all clear; passing.\n" );
                wfProfileOut( __METHOD__ );
 +
                return true;
        }
  
                $status = $this->verifyMimeType( $mime );
                if ( $status !== true ) {
                        wfProfileOut( __METHOD__ );
 +
                        return $status;
                }
  
                if ( !$wgDisableUploadScriptChecks ) {
                        if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
                                wfProfileOut( __METHOD__ );
 +
                                return array( 'uploadscripted' );
                        }
                        if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
                                $svgStatus = $this->detectScriptInSvg( $this->mTempPath );
                                if ( $svgStatus !== false ) {
                                        wfProfileOut( __METHOD__ );
 +
                                        return $svgStatus;
                                }
                        }
                                $error = reset( $errors );
                                if ( $error[0] !== 'zip-wrong-format' ) {
                                        wfProfileOut( __METHOD__ );
 +
                                        return $error;
                                }
                        }
                        if ( $this->mJavaDetected ) {
                                wfProfileOut( __METHOD__ );
 +
                                return array( 'uploadjava' );
                        }
                }
                $virus = $this->detectVirus( $this->mTempPath );
                if ( $virus ) {
                        wfProfileOut( __METHOD__ );
 +
                        return array( 'uploadvirus', $virus );
                }
  
                wfProfileOut( __METHOD__ );
 +
                return true;
        }
  
        }
  
        /**
 -       * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions'
 -       * but that suggests it's checking the user, when it's really checking the title + user combination.
 +       * Alias for verifyTitlePermissions. The function was originally
 +       * 'verifyPermissions', but that suggests it's checking the user, when it's
 +       * really checking the title + user combination.
 +       *
         * @param User $user User object to verify the permissions against
         * @return mixed An array as returned by getUserPermissionsErrors or true
         *   in case the user has proper permissions.
                if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
                        $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
                        $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
 +
                        return $permErrors;
                }
  
                if ( $this->mDesiredDestName != $filename && $comparableName != $filename ) {
                        $warnings['badfilename'] = $filename;
                        // Debugging for bug 62241
 -                      wfDebugLog( 'upload', "Filename: '$filename', mDesiredDestName: '$this->mDesiredDestName', comparableName: '$comparableName'" );
 +                      wfDebugLog( 'upload', "Filename: '$filename', mDesiredDestName: "
 +                              . "'$this->mDesiredDestName', comparableName: '$comparableName'" );
                }
  
                // Check whether the file extension is on the unwanted list
                }
  
                wfProfileOut( __METHOD__ );
 +
                return $warnings;
        }
  
  
                if ( $status->isGood() ) {
                        if ( $watch ) {
 -                              WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS );
 +                              WatchAction::doWatch(
 +                                      $this->getLocalFile()->getTitle(),
 +                                      $user,
 +                                      WatchedItem::IGNORE_USER_RIGHTS
 +                              );
                        }
                        wfRunHooks( 'UploadComplete', array( &$this ) );
                }
  
                wfProfileOut( __METHOD__ );
 +
                return $status;
        }
  
                if ( strlen( $this->mFilteredName ) > 240 ) {
                        $this->mTitleError = self::FILENAME_TOO_LONG;
                        $this->mTitle = null;
 +
                        return $this->mTitle;
                }
  
                if ( is_null( $nt ) ) {
                        $this->mTitleError = self::ILLEGAL_FILENAME;
                        $this->mTitle = null;
 +
                        return $this->mTitle;
                }
                $this->mFilteredName = $nt->getDBkey();
                if ( $this->mFinalExtension == '' ) {
                        $this->mTitleError = self::FILETYPE_MISSING;
                        $this->mTitle = null;
 +
                        return $this->mTitle;
                } elseif ( $blackListedExtensions ||
 -                              ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
 -                                      !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) {
 +                      ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
 +                              !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) )
 +              ) {
                        $this->mBlackListedExtensions = $blackListedExtensions;
                        $this->mTitleError = self::FILETYPE_BADTYPE;
                        $this->mTitle = null;
 +
                        return $this->mTitle;
                }
  
-               // Windows may be broken with special characters, see bug XXX
-               if ( wfIsWindows() && !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() ) ) {
+               // Windows may be broken with special characters, see bug 1780
+               if ( !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() )
+                       && !RepoGroup::singleton()->getLocalRepo()->backendSupportsUnicodePaths()
+               ) {
                        $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
                        $this->mTitle = null;
 +
                        return $this->mTitle;
                }
  
                # If there was more than one "extension", reassemble the base
                # filename to prevent bogus complaints about length
                if ( count( $ext ) > 1 ) {
 -                      for ( $i = 0; $i < count( $ext ) - 1; $i++ ) {
 +                      $iterations = count( $ext ) - 1;
 +                      for ( $i = 0; $i < $iterations; $i++ ) {
                                $partname .= '.' . $ext[$i];
                        }
                }
                if ( strlen( $partname ) < 1 ) {
                        $this->mTitleError = self::MIN_LENGTH_PARTNAME;
                        $this->mTitle = null;
 +
                        return $this->mTitle;
                }
  
                $this->mTitle = $nt;
 +
                return $this->mTitle;
        }
  
                        $nt = $this->getTitle();
                        $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt );
                }
 +
                return $this->mLocalFile;
        }
  
        /**
 -       * If the user does not supply all necessary information in the first upload form submission (either by accident or
 -       * by design) then we may want to stash the file temporarily, get more information, and publish the file later.
 +       * If the user does not supply all necessary information in the first upload
 +       * form submission (either by accident or by design) then we may want to
 +       * stash the file temporarily, get more information, and publish the file
 +       * later.
         *
 -       * This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
 -       * into the database.
 -       * This method returns the file object, which also has a 'fileKey' property which can be passed through a form or
 -       * API request to find this stashed file again.
 +       * This method will stash a file in a temporary directory for later
 +       * processing, and save the necessary descriptive info into the database.
 +       * This method returns the file object, which also has a 'fileKey' property
 +       * which can be passed through a form or API request to find this stashed
 +       * file again.
         *
         * @param User $user
         * @return UploadStashFile Stashed file
                $this->mLocalFile = $file;
  
                wfProfileOut( __METHOD__ );
 +
                return $file;
        }
  
        /**
 -       * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashFile().
 +       * Stash a file in a temporary directory, returning a key which can be used
 +       * to find the file again. See stashFile().
         *
         * @return string File key
         */
        public static function splitExtensions( $filename ) {
                $bits = explode( '.', $filename );
                $basename = array_shift( $bits );
 +
                return array( $basename, $bits );
        }
  
                        if ( !$magic->isRecognizableExtension( $extension ) ) {
                                wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
                                        "unrecognized extension '$extension', can't verify\n" );
 +
                                return true;
                        } else {
                                wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
                                        "recognized extension '$extension', so probably invalid file\n" );
 +
                                return false;
                        }
                }
                if ( $match === null ) {
                        if ( $magic->getTypesForExtension( $extension ) !== null ) {
                                wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension\n" );
 +
                                return false;
                        } else {
                                wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
 +
                                return true;
                        }
                } elseif ( $match === true ) {
                        wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file\n" );
  
 -                      #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
 +                      /** @todo If it's a bitmap, make sure PHP or ImageMagick resp. can handle it! */
                        return true;
 -
                } else {
 -                      wfDebug( __METHOD__ . ": mime type $mime mismatches file extension $extension, rejecting file\n" );
 +                      wfDebug( __METHOD__
 +                              . ": mime type $mime mismatches file extension $extension, rejecting file\n" );
 +
                        return false;
                }
        }
  
                if ( !$chunk ) {
                        wfProfileOut( __METHOD__ );
 +
                        return false;
                }
  
  
                $chunk = trim( $chunk );
  
 -              # @todo FIXME: Convert from UTF-16 if necessary!
 +              /** @todo FIXME: Convert from UTF-16 if necessary! */
                wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff\n" );
  
                # check for HTML doctype
                if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
                        wfProfileOut( __METHOD__ );
 +
                        return true;
                }
  
                if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) {
                        if ( self::checkXMLEncodingMissmatch( $file ) ) {
                                wfProfileOut( __METHOD__ );
 +
                                return true;
                        }
                }
                        '<a href',
                        '<body',
                        '<head',
 -                      '<html',   #also in safari
 +                      '<html', #also in safari
                        '<img',
                        '<pre',
                        '<script', #also in safari
                        if ( false !== strpos( $chunk, $tag ) ) {
                                wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
                                wfProfileOut( __METHOD__ );
 +
                                return true;
                        }
                }
                if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
                        wfDebug( __METHOD__ . ": found script types\n" );
                        wfProfileOut( __METHOD__ );
 +
                        return true;
                }
  
                if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
                        wfDebug( __METHOD__ . ": found html-style script urls\n" );
                        wfProfileOut( __METHOD__ );
 +
                        return true;
                }
  
                if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
                        wfDebug( __METHOD__ . ": found css-style script urls\n" );
                        wfProfileOut( __METHOD__ );
 +
                        return true;
                }
  
                wfDebug( __METHOD__ . ": no scripts found\n" );
                wfProfileOut( __METHOD__ );
 +
                return false;
        }
  
                                && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
                        ) {
                                wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'\n" );
 +
                                return true;
                        }
                } elseif ( preg_match( "!<\?xml\b!si", $contents ) ) {
                        // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
                        // bytes. There shouldn't be a legitimate reason for this to happen.
                        wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
 +
                        return true;
                } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
                        // EBCDIC encoded XML
                        wfDebug( __METHOD__ . ": EBCDIC Encoded XML\n" );
 +
                        return true;
                }
  
                        wfSuppressWarnings();
                        $str = iconv( $encoding, 'UTF-8', $contents );
                        wfRestoreWarnings();
 -                      if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
 +                      if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
                                if ( preg_match( $encodingRegex, $matches[1], $encMatch )
                                        && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
                                ) {
                                        wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'\n" );
 +
                                        return true;
                                }
                        } elseif ( $str != '' && preg_match( "!<\?xml\b!si", $str ) ) {
                                // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
                                // bytes. There shouldn't be a legitimate reason for this to happen.
                                wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
 +
                                return true;
                        }
                }
                        if ( $this->mSVGNSError ) {
                                return array( 'uploadscriptednamespace', $this->mSVGNSError );
                        }
 +
                        return array( 'uploadscripted' );
                }
 +
                return false;
        }
  
                if ( preg_match( '/xml-stylesheet/i', $target ) ) {
                        return true;
                }
 +
                return false;
        }
  
  
                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+
 +                      /** @todo Return a status object to a closure in XmlTypeCheck, for MW1.21+ */
                        $this->mSVGNSError = $namespace;
 +
                        return true;
                }
  
                 */
                if ( $strippedElement == 'script' ) {
                        wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
 +
                        return true;
                }
  
 -              # e.g., <svg xmlns="http://www.w3.org/2000/svg"> <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
 +              # e.g., <svg xmlns="http://www.w3.org/2000/svg">
 +              #  <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
                if ( $strippedElement == 'handler' ) {
                        wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
 +
                        return true;
                }
  
                # 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;
                }
  
                # Block iframes, in case they pass the namespace check
                if ( $strippedElement == 'iframe' ) {
                        wfDebug( __METHOD__ . ": iframe in uploaded file.\n" );
 +
                        return true;
                }
  
                        $value = strtolower( $value );
  
                        if ( substr( $stripped, 0, 2 ) == 'on' ) {
 -                              wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
 +                              wfDebug( __METHOD__
 +                                      . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # href with javascript target
                        if ( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
 -                              wfDebug( __METHOD__ . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" );
 +                              wfDebug( __METHOD__
 +                                      . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # 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" );
 +                              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" );
 +                              wfDebug( __METHOD__ . ": Found href to embedded svg "
 +                                      . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # use set/animate to add event-handler attribute to parent
 -                      if ( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) {
 -                              wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
 +                      if ( ( $strippedElement == 'set' || $strippedElement == 'animate' )
 +                              && $stripped == 'attributename'
 +                              && substr( $value, 0, 2 ) == 'on'
 +                      ) {
 +                              wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with "
 +                                      . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # use set to add href attribute to parent element
 -                      if ( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
 +                      if ( $strippedElement == 'set'
 +                              && $stripped == 'attributename'
 +                              && strpos( $value, 'href' ) !== false
 +                      ) {
                                wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # use set to add a remote / data / script target to an element
 -                      if ( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
 +                      if ( $strippedElement == 'set'
 +                              && $stripped == 'to'
 +                              && preg_match( '!(http|https|data|script):!sim', $value )
 +                      ) {
                                wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # use handler attribute with remote / data / script
                        if ( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
 -                              wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script '$attrib'='$value' in uploaded file.\n" );
 +                              wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script "
 +                                      . "'$attrib'='$value' in uploaded file.\n" );
 +
                                return true;
                        }
  
                        # use CSS styles to bring in remote code
                        # catch url("http:..., url('http:..., url(http:..., but not url("#..., url('#..., url(#....
 -                      if ( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) {
 +                      $tagsList = "font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke";
 +                      if ( $stripped == 'style'
 +                              && preg_match_all(
 +                                      '!((?:' . $tagsList . ')\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim',
 +                                      $value,
 +                                      $matches
 +                              )
 +                      ) {
                                foreach ( $matches[1] as $match ) {
 -                                      if ( !preg_match( '!(?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) {
 -                                              wfDebug( __METHOD__ . ": Found svg setting a style with remote url '$attrib'='$value' in uploaded file.\n" );
 +                                      if ( !preg_match( '!(?:' . $tagsList . ')\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) {
 +                                              wfDebug( __METHOD__ . ": Found svg setting a style with "
 +                                                      . "remote url '$attrib'='$value' in uploaded file.\n" );
 +
                                                return true;
                                        }
                                }
                        }
  
                        # image filters can pull in url, which could be svg that executes scripts
 -                      if ( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) {
 -                              wfDebug( __METHOD__ . ": Found image filter with url: \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
 +                      if ( $strippedElement == 'image'
 +                              && $stripped == 'filter'
 +                              && preg_match( '!url\s*\(!sim', $value )
 +                      ) {
 +                              wfDebug( __METHOD__ . ": Found image filter with url: "
 +                                      . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
 +
                                return true;
                        }
 -
                }
  
                return false; //No scripts detected
                $parts = explode( ':', strtolower( $element ) );
                $name = array_pop( $parts );
                $ns = implode( ':', $parts );
 +
                return array( $ns, $name );
        }
  
        private function stripXmlNamespace( $name ) {
                // 'http://www.w3.org/2000/svg:script' -> 'script'
                $parts = explode( ':', strtolower( $name ) );
 +
                return array_pop( $parts );
        }
  
                if ( !$wgAntivirus ) {
                        wfDebug( __METHOD__ . ": virus scanner disabled\n" );
                        wfProfileOut( __METHOD__ );
 +
                        return null;
                }
  
                        $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
                                array( 'virus-badscanner', $wgAntivirus ) );
                        wfProfileOut( __METHOD__ );
 +
                        return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
                }
  
                        # scan failed (code was mapped to false by $exitCodeMap)
                        wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
  
 -                      $output = $wgAntivirusRequired ? wfMessage( 'virus-scanfailed', array( $exitCode ) )->text() : null;
 +                      $output = $wgAntivirusRequired
 +                              ? wfMessage( 'virus-scanfailed', array( $exitCode ) )->text()
 +                              : null;
                } elseif ( $mappedCode === AV_SCAN_ABORTED ) {
                        # scan failed because filetype is unknown (probably imune)
                        wfDebug( __METHOD__ . ": unsupported file type $file (code $exitCode).\n" );
                }
  
                wfProfileOut( __METHOD__ );
 +
                return $output;
        }
  
  
                // Check for files with the same name but a different extension
                $similarFiles = RepoGroup::singleton()->getLocalRepo()->findFilesByPrefix(
 -                              "{$partname}.", 1 );
 +                      "{$partname}.", 1 );
                if ( count( $similarFiles ) ) {
                        return array(
                                'warning' => 'exists-normalized',
  
                if ( self::isThumbName( $file->getName() ) ) {
                        # Check for filenames like 50px- or 180px-, these are mostly thumbnails
 -                      $nt_thb = Title::newFromText( substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension, NS_FILE );
 +                      $nt_thb = Title::newFromText(
 +                              substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension,
 +                              NS_FILE
 +                      );
                        $file_thb = wfLocalFile( $nt_thb );
                        if ( $file_thb->exists() ) {
                                return array(
        public static function isThumbName( $filename ) {
                $n = strrpos( $filename, '.' );
                $partname = $n ? substr( $filename, 0, $n ) : $filename;
 +
                return (
 -                                      substr( $partname, 3, 3 ) == 'px-' ||
 -                                      substr( $partname, 2, 3 ) == 'px-'
 -                              ) &&
 -                              preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
 +                      substr( $partname, 3, 3 ) == 'px-' ||
 +                      substr( $partname, 2, 3 ) == 'px-'
 +              ) &&
 +              preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
        }
  
        /**
                                $blacklist[] = trim( $line );
                        }
                }
 +
                return $blacklist;
        }
  
        /**
         * Gets image info about the file just uploaded.
         *
 -       * Also has the effect of setting metadata to be an 'indexed tag name' in returned API result if
 -       * 'metadata' was requested. Oddly, we have to pass the "result" object down just so it can do that
 -       * with the appropriate format, presumably.
 +       * Also has the effect of setting metadata to be an 'indexed tag name' in
 +       * returned API result if 'metadata' was requested. Oddly, we have to pass
 +       * the "result" object down just so it can do that with the appropriate
 +       * format, presumably.
         *
         * @param ApiResult $result
         * @return array Image info
         */
        public function getImageInfo( $result ) {
                $file = $this->getLocalFile();
 -              // TODO This cries out for refactoring. We really want to say $file->getAllInfo(); here.
 -              // Perhaps "info" methods should be moved into files, and the API should just wrap them in queries.
 +              /** @todo This cries out for refactoring.
 +               *  We really want to say $file->getAllInfo(); here.
 +               * Perhaps "info" methods should be moved into files, and the API should
 +               * just wrap them in queries.
 +               */
                if ( $file instanceof UploadStashFile ) {
                        $imParam = ApiQueryStashImageInfo::getPropertyNames();
                        $info = ApiQueryStashImageInfo::getInfo( $file, array_flip( $imParam ), $result );
                        $imParam = ApiQueryImageInfo::getPropertyNames();
                        $info = ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result );
                }
 +
                return $info;
        }
  
        public function convertVerifyErrorToStatus( $error ) {
                $code = $error['status'];
                unset( $code['status'] );
 +
                return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
        }