Revert "merged master"
[lhc/web/wiklou.git] / includes / upload / UploadBase.php
index 8efe94d..6ef3be2 100644 (file)
@@ -1,7 +1,32 @@
 <?php
 /**
+ * Base class for the backend of file upload.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
  * @file
- * @ingroup upload
+ * @ingroup Upload
+ */
+
+/**
+ * @defgroup Upload Upload related
+ */
+
+/**
+ * @ingroup Upload
  *
  * UploadBase and subclasses are the backend of MediaWiki's file uploads.
  * The frontends are formed by ApiUpload and SpecialUpload.
@@ -12,7 +37,6 @@
  * @author Bryan Tong Minh
  * @author Michael Dale
  */
-
 abstract class UploadBase {
        protected $mTempPath;
        protected $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
@@ -37,14 +61,12 @@ abstract class UploadBase {
        const HOOK_ABORTED = 11;
        const FILE_TOO_LARGE = 12;
        const WINDOWS_NONASCII_FILENAME = 13;
+       const FILENAME_TOO_LONG = 14;
 
-       const SESSION_VERSION = 2;
-       const SESSION_KEYNAME = 'wsUploadData';
-
-       static public function getSessionKeyname() {
-               return self::SESSION_KEYNAME;
-       }
-
+       /**
+        * @param $error int
+        * @return string
+        */
        public function getVerificationErrorCode( $error ) {
                $code_to_status = array(self::EMPTY_FILE => 'empty-file',
                                                                self::FILE_TOO_LARGE => 'file-too-large',
@@ -56,6 +78,7 @@ abstract class UploadBase {
                                                                self::VERIFICATION_ERROR => 'verification-error',
                                                                self::HOOK_ABORTED =>  'hookaborted',
                                                                self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
+                                                               self::FILENAME_TOO_LONG => 'filename-toolong',
                );
                if( isset( $code_to_status[$error] ) ) {
                        return $code_to_status[$error];
@@ -67,18 +90,17 @@ abstract class UploadBase {
        /**
         * Returns true if uploads are enabled.
         * Can be override by subclasses.
+        * @return bool
         */
        public static function isEnabled() {
                global $wgEnableUploads;
+
                if ( !$wgEnableUploads ) {
                        return false;
                }
 
                # Check php's file_uploads setting
-               if( !wfIniGetBool( 'file_uploads' ) ) {
-                       return false;
-               }
-               return true;
+               return wfIsHipHop() || wfIniGetBool( 'file_uploads' );
        }
 
        /**
@@ -87,6 +109,7 @@ abstract class UploadBase {
         * Can be overriden by subclasses.
         *
         * @param $user User
+        * @return bool
         */
        public static function isAllowed( $user ) {
                foreach ( array( 'upload', 'edit' ) as $permission ) {
@@ -104,6 +127,8 @@ abstract class UploadBase {
         * Create a form of UploadBase depending on wpSourceType and initializes it
         *
         * @param $request WebRequest
+        * @param $type
+        * @return null
         */
        public static function createFromRequest( &$request, $type = null ) {
                $type = $type ? $type : $request->getVal( 'wpSourceType', 'File' );
@@ -144,6 +169,8 @@ abstract class UploadBase {
 
        /**
         * Check whether a request if valid for this handler
+        * @param $request
+        * @return bool
         */
        public static function isValidRequest( $request ) {
                return false;
@@ -165,10 +192,13 @@ abstract class UploadBase {
         * @param $tempPath string the temporary path
         * @param $fileSize int the file size
         * @param $removeTempFile bool (false) remove the temporary file?
-        * @return null
+        * @throws MWException
         */
        public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
                $this->mDesiredDestName = $name;
+               if ( FileBackend::isStoragePath( $tempPath ) ) {
+                       throw new MWException( __METHOD__ . " given storage path `$tempPath`." );
+               }
                $this->mTempPath = $tempPath;
                $this->mFileSize = $fileSize;
                $this->mRemoveTempFile = $removeTempFile;
@@ -181,6 +211,7 @@ abstract class UploadBase {
 
        /**
         * Fetch the file. Usually a no-op
+        * @return Status
         */
        public function fetchFile() {
                return Status::newGood();
@@ -202,28 +233,22 @@ abstract class UploadBase {
                return $this->mFileSize;
        }
 
-       /**
-        * Append a file to the Repo file
-        *
-        * @param $srcPath String: path to source file
-        * @param $toAppendPath String: path to the Repo file that will be appended to.
-        * @return Status Status
-        */
-       protected function appendToUploadFile( $srcPath, $toAppendPath ) {
-               $repo = RepoGroup::singleton()->getLocalRepo();
-               $status = $repo->append( $srcPath, $toAppendPath );
-               return $status;
-       }
-
        /**
         * @param $srcPath String: the source path
-        * @return the real path if it was a virtual URL
+        * @return string the real path if it was a virtual URL
         */
        function getRealPath( $srcPath ) {
+               wfProfileIn( __METHOD__ );
                $repo = RepoGroup::singleton()->getLocalRepo();
                if ( $repo->isVirtualUrl( $srcPath ) ) {
-                       return $repo->resolveVirtualUrl( $srcPath );
-               }
+                       // @TODO: just make uploads work with storage paths
+                       // UploadFromStash loads files via virtuals URLs
+                       $tmpFile = $repo->getLocalCopy( $srcPath );
+                       $tmpFile->bind( $this ); // keep alive with $thumb
+                       wfProfileOut( __METHOD__ );
+                       return $tmpFile->getPath();
+               }
+               wfProfileOut( __METHOD__ );
                return $srcPath;
        }
 
@@ -232,10 +257,13 @@ abstract class UploadBase {
         * @return mixed self::OK or else an array with error information
         */
        public function verifyUpload() {
+               wfProfileIn( __METHOD__ );
+
                /**
                 * If there was no filename or a zero size given, give up quick.
                 */
                if( $this->isEmptyFile() ) {
+                       wfProfileOut( __METHOD__ );
                        return array( 'status' => self::EMPTY_FILE );
                }
 
@@ -244,6 +272,7 @@ abstract class UploadBase {
                 */
                $maxSize = self::getMaxUploadSize( $this->getSourceType() );
                if( $this->mFileSize > $maxSize ) {
+                       wfProfileOut( __METHOD__ );
                        return array(
                                'status' => self::FILE_TOO_LARGE,
                                'max' => $maxSize,
@@ -257,6 +286,7 @@ abstract class UploadBase {
                 */
                $verification = $this->verifyFile();
                if( $verification !== true ) {
+                       wfProfileOut( __METHOD__ );
                        return array(
                                'status' => self::VERIFICATION_ERROR,
                                'details' => $verification
@@ -268,15 +298,19 @@ abstract class UploadBase {
                 */
                $result = $this->validateName();
                if( $result !== true ) {
+                       wfProfileOut( __METHOD__ );
                        return $result;
                }
 
                $error = '';
                if( !wfRunHooks( 'UploadVerification',
-                               array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
+                       array( $this->mDestName, $this->mTempPath, &$error ) ) )
+               {
+                       wfProfileOut( __METHOD__ );
                        return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
                }
 
+               wfProfileOut( __METHOD__ );
                return array( 'status' => self::OK );
        }
 
@@ -314,15 +348,18 @@ abstract class UploadBase {
         */
        protected function verifyMimeType( $mime ) {
                global $wgVerifyMimeType;
+               wfProfileIn( __METHOD__ );
                if ( $wgVerifyMimeType ) {
                        wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n");
                        global $wgMimeTypeBlacklist;
                        if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
+                               wfProfileOut( __METHOD__ );
                                return array( 'filetype-badmime', $mime );
                        }
 
                        # 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 );
                        }
 
@@ -336,11 +373,13 @@ abstract class UploadBase {
                        $ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
                        foreach ( $ieTypes as $ieType ) {
                                if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
+                                       wfProfileOut( __METHOD__ );
                                        return array( 'filetype-bad-ie-mime', $ieType );
                                }
                        }
                }
 
+               wfProfileOut( __METHOD__ );
                return true;
        }
 
@@ -350,29 +389,35 @@ abstract class UploadBase {
         * @return mixed true of the file is verified, array otherwise.
         */
        protected function verifyFile() {
-               global $wgAllowJavaUploads;
+               global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
+               wfProfileIn( __METHOD__ );
+
                # get the title, even though we are doing nothing with it, because
                # we need to populate mFinalExtension
                $this->getTitle();
 
-               $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
-               $this->checkMacBinary();
+               $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
 
                # check mime type, if desired
                $mime = $this->mFileProps[ 'file-mime' ];
                $status = $this->verifyMimeType( $mime );
                if ( $status !== true ) {
+                       wfProfileOut( __METHOD__ );
                        return $status;
                }
 
                # check for htmlish code and javascript
-               if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
-                       return array( 'uploadscripted' );
-               }
-               if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
-                       if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+               if ( !$wgDisableUploadScriptChecks ) {
+                       if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
+                               wfProfileOut( __METHOD__ );
                                return array( 'uploadscripted' );
                        }
+                       if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
+                               if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+                                       wfProfileOut( __METHOD__ );
+                                       return array( 'uploadscripted' );
+                               }
+                       }
                }
 
                # Check for Java applets, which if uploaded can bypass cross-site
@@ -385,10 +430,12 @@ abstract class UploadBase {
                                $errors = $zipStatus->getErrorsArray();
                                $error = reset( $errors );
                                if ( $error[0] !== 'zip-wrong-format' ) {
+                                       wfProfileOut( __METHOD__ );
                                        return $error;
                                }
                        }
                        if ( $this->mJavaDetected ) {
+                               wfProfileOut( __METHOD__ );
                                return array( 'uploadjava' );
                        }
                }
@@ -396,6 +443,7 @@ abstract class UploadBase {
                # Scan the uploaded file for viruses
                $virus = $this->detectVirus( $this->mTempPath );
                if ( $virus ) {
+                       wfProfileOut( __METHOD__ );
                        return array( 'uploadvirus', $virus );
                }
 
@@ -404,16 +452,19 @@ abstract class UploadBase {
                        $handlerStatus = $handler->verifyUpload( $this->mTempPath );
                        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;
        }
 
@@ -441,7 +492,7 @@ abstract class UploadBase {
        }
 
        /**
-        * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions' 
+        * 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 object to verify the permissions against
         * @return mixed An array as returned by getUserPermissionsErrors or true
@@ -474,7 +525,7 @@ abstract class UploadBase {
                $permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
                $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user );
                if ( !$nt->exists() ) {
-                       $permErrorsCreate = $nt->getUserPermissionsErrors( 'createpage', $user );
+                       $permErrorsCreate = $nt->getUserPermissionsErrors( 'create', $user );
                } else {
                        $permErrorsCreate = array();
                }
@@ -498,6 +549,9 @@ abstract class UploadBase {
         * @return Array of warnings
         */
        public function checkWarnings() {
+               global $wgLang;
+               wfProfileIn( __METHOD__ );
+
                $warnings = array();
 
                $localFile = $this->getLocalFile();
@@ -518,7 +572,8 @@ abstract class UploadBase {
                global $wgCheckFileExtensions, $wgFileExtensions;
                if ( $wgCheckFileExtensions ) {
                        if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) {
-                               $warnings['filetype-unwanted-type'] = $this->mFinalExtension;
+                               $warnings['filetype-unwanted-type'] = array( $this->mFinalExtension,
+                                       $wgLang->commaList( $wgFileExtensions ), count( $wgFileExtensions ) );
                        }
                }
 
@@ -537,7 +592,7 @@ abstract class UploadBase {
                }
 
                // Check dupes against existing files
-               $hash = File::sha1Base36( $this->mTempPath );
+               $hash = FSFile::getSha1Base36FromPath( $this->mTempPath );
                $dupes = RepoGroup::singleton()->findBySha1( $hash );
                $title = $this->getTitle();
                // Remove all matches against self
@@ -556,6 +611,7 @@ abstract class UploadBase {
                        $warnings['duplicate-archive'] = $archivedImage->getName();
                }
 
+               wfProfileOut( __METHOD__ );
                return $warnings;
        }
 
@@ -563,11 +619,16 @@ abstract class UploadBase {
         * Really perform the upload. Stores the file in the local repo, watches
         * if necessary and runs the UploadComplete hook.
         *
+        * @param $comment
+        * @param $pageText
+        * @param $watch
         * @param $user User
         *
         * @return Status indicating the whether the upload succeeded.
         */
        public function performUpload( $comment, $pageText, $watch, $user ) {
+               wfProfileIn( __METHOD__ );
+
                $status = $this->getLocalFile()->upload(
                        $this->mTempPath,
                        $comment,
@@ -582,10 +643,10 @@ abstract class UploadBase {
                        if ( $watch ) {
                                $user->addWatch( $this->getLocalFile()->getTitle() );
                        }
-
                        wfRunHooks( 'UploadComplete', array( &$this ) );
                }
 
+               wfProfileOut( __METHOD__ );
                return $status;
        }
 
@@ -599,17 +660,24 @@ abstract class UploadBase {
                if ( $this->mTitle !== false ) {
                        return $this->mTitle;
                }
-               
+
                /* Assume that if a user specified File:Something.jpg, this is an error
                 * and that the namespace prefix needs to be stripped of.
                 */
                $title = Title::newFromText( $this->mDesiredDestName );
-               if ( $title->getNamespace() == NS_FILE ) {
+               if ( $title && $title->getNamespace() == NS_FILE ) {
                        $this->mFilteredName = $title->getDBkey();
                } else {
                        $this->mFilteredName = $this->mDesiredDestName;
                }
 
+               # oi_archive_name is max 255 bytes, which include a timestamp and an
+               # exclamation mark, so restrict file name to 240 bytes.
+               if ( strlen( $this->mFilteredName ) > 240 ) {
+                       $this->mTitleError = self::FILENAME_TOO_LONG;
+                       return $this->mTitle = null;
+               }
+
                /**
                 * Chop off any directories in the given filename. Then
                 * filter out illegal characters, and try to make a legible name
@@ -624,6 +692,8 @@ abstract class UploadBase {
                }
                $this->mFilteredName = $nt->getDBkey();
 
+
+
                /**
                 * We'll want to blacklist against *any* 'extension', and use
                 * only the final one for the whitelist.
@@ -665,12 +735,12 @@ abstract class UploadBase {
                        return $this->mTitle = null;
                } elseif ( $blackListedExtensions ||
                                ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
-                                       !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) {
+                                       !$this->checkFileExtensionList( $ext, $wgFileExtensions ) ) ) {
                        $this->mBlackListedExtensions = $blackListedExtensions;
                        $this->mTitleError = self::FILETYPE_BADTYPE;
                        return $this->mTitle = null;
                }
-               
+
                // Windows may be broken with special characters, see bug XXX
                if ( wfIsWindows() && !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() ) ) {
                        $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
@@ -696,7 +766,7 @@ abstract class UploadBase {
        /**
         * Return the local file and initializes if necessary.
         *
-        * @return LocalFile
+        * @return LocalFile|null
         */
        public function getLocalFile() {
                if( is_null( $this->mLocalFile ) ) {
@@ -706,57 +776,45 @@ abstract class UploadBase {
                return $this->mLocalFile;
        }
 
-       /**
-        * NOTE: Probably should be deprecated in favor of UploadStash, but this is sometimes
-        * called outside that context.
-        *
-        * Stash a file in a temporary directory for later processing
-        * after the user has confirmed it.
-        *
-        * If the user doesn't explicitly cancel or accept, these files
-        * can accumulate in the temp directory.
-        *
-        * @param $saveName String: the destination filename
-        * @param $tempSrc String: the source temporary file to save
-        * @return String: full path the stashed file, or false on failure
-        */
-       protected function saveTempUploadedFile( $saveName, $tempSrc ) {
-               $repo = RepoGroup::singleton()->getLocalRepo();
-               $status = $repo->storeTemp( $saveName, $tempSrc );
-               return $status;
-       }
-
        /**
         * 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 user's session.
-        * This method returns the file object, which also has a 'sessionKey' property which can be passed through a form or
+        * 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 $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
         * @return UploadStashFile stashed file
         */
-       public function stashSessionFile( $key = null ) {
+       public function stashFile() {
+               // was stashSessionFile
+               wfProfileIn( __METHOD__ );
+
                $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
-               $data = array(
-                       'mFileProps' => $this->mFileProps,
-                       'mSourceType' => $this->getSourceType(),
-               );
-               $file = $stash->stashFile( $this->mTempPath, $data, $key );
+               $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
                $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 stashSessionFile().
+        * 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 function stashFileGetKey() {
+               return $this->stashFile()->getFileKey();
+       }
+
+       /**
+        * alias for stashFileGetKey, for backwards compatibility
         *
-        * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
-        * @return String: session key
+        * @return String: file key
         */
-       public function stashSession( $key = null ) {
-               return $this->stashSessionFile( $key )->getSessionKey();
+       public function stashSession() {
+               return $this->stashFileGetKey();
        }
 
        /**
@@ -780,6 +838,7 @@ abstract class UploadBase {
         * earlier pseudo-'extensions' to determine type and execute
         * scripts, so the blacklist needs to check them all.
         *
+        * @param $filename string
         * @return array
         */
        public static function splitExtensions( $filename ) {
@@ -863,6 +922,7 @@ abstract class UploadBase {
         */
        public static function detectScript( $file, $mime, $extension ) {
                global $wgAllowTitlesInSVG;
+               wfProfileIn( __METHOD__ );
 
                # ugly hack: for text files, always look at the entire file.
                # For binary field, just check the first K.
@@ -878,6 +938,7 @@ abstract class UploadBase {
                $chunk = strtolower( $chunk );
 
                if( !$chunk ) {
+                       wfProfileOut( __METHOD__ );
                        return false;
                }
 
@@ -896,11 +957,12 @@ abstract class UploadBase {
 
                $chunk = trim( $chunk );
 
-               # FIXME: convert from UTF-16 if necessarry!
+               # @todo FIXME: Convert from UTF-16 if necessarry!
                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;
                }
 
@@ -937,6 +999,7 @@ abstract class UploadBase {
                foreach( $tags as $tag ) {
                        if( false !== strpos( $chunk, $tag ) ) {
                                wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
+                               wfProfileOut( __METHOD__ );
                                return true;
                        }
                }
@@ -951,25 +1014,33 @@ abstract class UploadBase {
                # look for script-types
                if( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
                        wfDebug( __METHOD__ . ": found script types\n" );
+                       wfProfileOut( __METHOD__ );
                        return true;
                }
 
                # look for html-style script-urls
                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;
                }
 
                # look for css-style script-urls
                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;
        }
 
+       /**
+        * @param $filename string
+        * @return bool
+        */
        protected function detectScriptInSvg( $filename ) {
                $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
                return $check->filterMatch;
@@ -977,28 +1048,111 @@ abstract class UploadBase {
 
        /**
         * @todo Replace this with a whitelist filter!
+        * @param $element string
+        * @param $attribs array
+        * @return bool
         */
        public function checkSvgScriptCallback( $element, $attribs ) {
-               $stripped = $this->stripXmlNamespace( $element );
+               $strippedElement = $this->stripXmlNamespace( $element );
 
-               if( $stripped == 'script' ) {
+               /*
+                * check for elements that can contain javascript
+                */
+               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>
+               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;
+               }
+
                foreach( $attribs as $attrib => $value ) {
                        $stripped = $this->stripXmlNamespace( $attrib );
+                       $value = strtolower($value);
+
                        if( substr( $stripped, 0, 2 ) == 'on' ) {
-                               wfDebug( __METHOD__ . ": Found script 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 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 embeded svg as target
+                       if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) {
+                               wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
+                               return true;
+                       }
+
+                       # href with embeded (text/xml) svg as target
+                       if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) {
+                               wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
                                return true;
                        }
+
+                       # 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" );
+                               return true;
+                       }
+
+                       # use set to add href attribute to parent element
+                       if( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
+                               wfDebug( __METHOD__ . ": Found svg setting href attibute '$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 ) ) {
+                               wfDebug( __METHOD__ . ": Found svg setting attibute 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" );
+                               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 ) ) {
+                               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" );
+                                               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" );
+                               return true;
+                       }
+
                }
+
+               return false; //No scripts detected
        }
 
+       /**
+        * @param $name string
+        * @return string
+        */
        private function stripXmlNamespace( $name ) {
                // 'http://www.w3.org/2000/svg:script' -> 'script'
                $parts = explode( ':', strtolower( $name ) );
@@ -1017,9 +1171,11 @@ abstract class UploadBase {
         */
        public static function detectVirus( $file ) {
                global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
+               wfProfileIn( __METHOD__ );
 
                if ( !$wgAntivirus ) {
                        wfDebug( __METHOD__ . ": virus scanner disabled\n" );
+                       wfProfileOut( __METHOD__ );
                        return null;
                }
 
@@ -1027,7 +1183,8 @@ abstract class UploadBase {
                        wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" );
                        $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
                                array( 'virus-badscanner', $wgAntivirus ) );
-                       return wfMsg( 'virus-unknownscanner' ) . " $wgAntivirus";
+                       wfProfileOut( __METHOD__ );
+                       return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
                }
 
                # look up scanner configuration
@@ -1069,17 +1226,21 @@ abstract class UploadBase {
                        wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
 
                        if ( $wgAntivirusRequired ) {
+                               wfProfileOut( __METHOD__ );
                                return wfMsg( 'virus-scanfailed', array( $exitCode ) );
                        } else {
+                               wfProfileOut( __METHOD__ );
                                return 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 null;
                } elseif ( $mappedCode === AV_NO_VIRUS ) {
                        # no virus found
                        wfDebug( __METHOD__ . ": file passed virus scan.\n" );
+                       wfProfileOut( __METHOD__ );
                        return false;
                } else {
                        $output = trim( $output );
@@ -1096,34 +1257,11 @@ abstract class UploadBase {
                        }
 
                        wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" );
+                       wfProfileOut( __METHOD__ );
                        return $output;
                }
        }
 
-       /**
-        * Check if the temporary file is MacBinary-encoded, as some uploads
-        * from Internet Explorer on Mac OS Classic and Mac OS X will be.
-        * If so, the data fork will be extracted to a second temporary file,
-        * which will then be checked for validity and either kept or discarded.
-        */
-       private function checkMacBinary() {
-               $macbin = new MacBinary( $this->mTempPath );
-               if( $macbin->isValid() ) {
-                       $dataFile = tempnam( wfTempDir(), 'WikiMacBinary' );
-                       $dataHandle = fopen( $dataFile, 'wb' );
-
-                       wfDebug( __METHOD__ . ": Extracting MacBinary data fork to $dataFile\n" );
-                       $macbin->extractData( $dataHandle );
-
-                       $this->mTempPath = $dataFile;
-                       $this->mFileSize = $macbin->dataForkLength();
-
-                       // We'll have to manually remove the new file if it's not kept.
-                       $this->mRemoveTempFile = true;
-               }
-               $macbin->close();
-       }
-
        /**
         * Check if there's an overwrite conflict and, if so, if restrictions
         * forbid this user from performing the upload.
@@ -1266,6 +1404,8 @@ abstract class UploadBase {
 
        /**
         * Helper function that checks whether the filename looks like a thumbnail
+        * @param $filename string
+        * @return bool
         */
        public static function isThumbName( $filename ) {
                $n = strrpos( $filename, '.' );
@@ -1328,13 +1468,20 @@ abstract class UploadBase {
                return $info;
        }
 
-
+       /**
+        * @param $error array
+        * @return Status
+        */
        public function convertVerifyErrorToStatus( $error ) {
                $code = $error['status'];
                unset( $code['status'] );
                return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
        }
 
+       /**
+        * @param $forType null|string
+        * @return int
+        */
        public static function getMaxUploadSize( $forType = null ) {
                global $wgMaxUploadSize;