Update message initialiser to use Revision functions for backend-independence. No...
[lhc/web/wiklou.git] / includes / SpecialUpload.php
index 5a01331..3045f79 100644 (file)
@@ -31,8 +31,8 @@ class UploadForm {
        var $mUploadAffirm, $mUploadFile, $mUploadDescription, $mIgnoreWarning;
        var $mUploadSaveName, $mUploadTempName, $mUploadSize, $mUploadOldVersion;
        var $mUploadCopyStatus, $mUploadSource, $mReUpload, $mAction, $mUpload;
-       var $mOname, $mSessionKey;
-       /**#@- */
+       var $mOname, $mSessionKey, $mStashed;
+       /**#@-*/
 
        /**
         * Constructor : initialise object
@@ -69,6 +69,7 @@ class UploadForm {
                        $this->mUploadTempName   = $data['mUploadTempName'];
                        $this->mUploadSize       = $data['mUploadSize'];
                        $this->mOname            = $data['mOname'];
+                       $this->mStashed          = true;
                } else {
                        /**
                         *Check for a newly uploaded file.
@@ -77,6 +78,7 @@ class UploadForm {
                        $this->mUploadSize     = $request->getFileSize( 'wpUploadFile' );
                        $this->mOname          = $request->getFileName( 'wpUploadFile' );
                        $this->mSessionKey     = false;
+                       $this->mStashed        = false;
                }
        }
 
@@ -95,7 +97,7 @@ class UploadForm {
                }
                
                /** Various rights checks */
-               if( ( $wgUser->getID() == 0 )
+               if( ( $wgUser->isAnon() )
                         OR $wgUser->isBlocked() ) {
                        $wgOut->errorpage( 'uploadnologin', 'uploadnologintext' );
                        return;
@@ -127,6 +129,13 @@ class UploadForm {
                global $wgUploadDirectory;
                global $wgUseCopyrightUpload, $wgCheckCopyrightUpload;
 
+               /**
+                * If there was no filename or a zero size given, give up quick.
+                */
+               if( ( trim( $this->mOname ) == '' ) || empty( $this->mUploadSize ) ) {
+                       return $this->mainUploadForm('<li>'.wfMsg( 'emptyfile' ).'</li>');
+               }
+               
                /**
                 * When using detailed copyright, if user filled field, assume he
                 * confirmed the upload
@@ -142,26 +151,25 @@ class UploadForm {
 
                /** User need to confirm his upload */
                if( !$this->mUploadAffirm ) {
-                       $this->mainUploadForm( WfMsg( 'noaffirmation' ) );
+                       $this->mainUploadForm( wfMsg( 'noaffirmation' ) );
                        return;
                }
 
-               if ( $this->mOname == '' && !isset($this->mUploadSaveName) ) {
-                       // no filename given!
-                       return $this->uploadError('<li>'.wfMsg( 'emptyfile' ).'</li>');
-               }
-               
                # Chop off any directories in the given filename
                $basename = basename( $this->mOname );
 
-               if( preg_match( '/^(.*)\.([^.]*)$/', $basename, $matches ) ) {
-                       $partname = $matches[1];
-                       $ext      = $matches[2];
+               /**
+                * We'll want to blacklist against *any* 'extension', and use
+                * only the final one for the whitelist.
+                */
+               list( $partname, $ext ) = $this->splitExtensions( $basename );
+               if( count( $ext ) ) {
+                       $finalExt = $ext[count( $ext ) - 1];
                } else {
-                   $partname = $basename;
-                   $ext = '';
+                       $finalExt = '';
                }
-
+               $fullExt = implode( '.', $ext );
+               
                if ( strlen( $partname ) < 3 ) {
                        $this->mainUploadForm( wfMsg( 'minlength' ) );
                        return;
@@ -171,12 +179,12 @@ class UploadForm {
                 * Filter out illegal characters, and try to make a legible name
                 * out of it. We'll strip some silently that Title would die on.
                 */
-               $filtered = preg_replace ( "/[^".Title::legalChars()."]/", '-', $basename );
+               $filtered = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $basename );
                $nt = Title::newFromText( $filtered );
                if( is_null( $nt ) ) {
                        return $this->uploadError( wfMsg( 'illegalfilename', htmlspecialchars( $filtered ) ) );
                }
-               $nt->setNamespace( NS_IMAGE );
+               $nt =& Title::makeTitle( NS_IMAGE, $nt->getDBkey() );
                $this->mUploadSaveName = $nt->getDBkey();
                
                /**
@@ -190,9 +198,10 @@ class UploadForm {
                /* Don't allow users to override the blacklist */
                global $wgStrictFileExtensions;
                global $wgFileExtensions, $wgFileBlacklist;
-               if( $this->checkFileExtension( $ext, $wgFileBlacklist ) ||
-                       ($wgStrictFileExtensions && !$this->checkFileExtension( $ext, $wgFileExtensions ) ) ) {
-                       return $this->uploadError( wfMsg( 'badfiletype', htmlspecialchars( $ext ) ) );
+               if( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
+                       ($wgStrictFileExtensions &&
+                               !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) {
+                       return $this->uploadError( wfMsg( 'badfiletype', htmlspecialchars( $fullExt ) ) );
                }
                
                /**
@@ -200,7 +209,7 @@ class UploadForm {
                 * type but it's corrupt or data of the wrong type, we should
                 * probably not accept it.
                 */
-               if( !$this->verify( $this->mUploadTempName, $ext ) ) {
+               if( !$this->mStashed && !$this->verify( $this->mUploadTempName, $finalExt ) ) {
                        return $this->uploadError( wfMsg( 'uploadcorrupt' ) );
                }
                
@@ -215,8 +224,8 @@ class UploadForm {
        
                        global $wgCheckFileExtensions;
                        if ( $wgCheckFileExtensions ) {
-                               if ( ! $this->checkFileExtension( $ext, $wgFileExtensions ) ) {
-                                       $warning .= '<li>'.wfMsg( 'badfiletype', htmlspecialchars( $ext ) ).'</li>';
+                               if ( ! $this->checkFileExtension( $finalExt, $wgFileExtensions ) ) {
+                                       $warning .= '<li>'.wfMsg( 'badfiletype', htmlspecialchars( $fullExt ) ).'</li>';
                                }
                        }
        
@@ -261,6 +270,10 @@ class UploadForm {
                                        $this->mUploadDescription,
                                        $this->mUploadCopyStatus,
                                        $this->mUploadSource );
+
+                       /* refresh image metadata cache */
+                       new Image( $this->mUploadSaveName, true );
+
                        $this->showSuccess();
                }
        }
@@ -286,8 +299,11 @@ class UploadForm {
 
                if( is_file( $this->mSavedFile ) ) {
                        $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}";
+                       wfSuppressWarnings();
+                       $success = rename( $this->mSavedFile, "${archive}/{$this->mUploadOldVersion}" );
+                       wfRestoreWarnings();
 
-                       if( !rename( $this->mSavedFile, "${archive}/{$this->mUploadOldVersion}" ) ) { 
+                       if( ! $success ) { 
                                $wgOut->fileRenameError( $this->mSavedFile,
                                  "${archive}/{$this->mUploadOldVersion}" );
                                return false;
@@ -297,12 +313,20 @@ class UploadForm {
                }
                
                if( $useRename ) {
-                       if( !rename( $tempName, $this->mSavedFile ) ) {
+                       wfSuppressWarnings();
+                       $success = rename( $tempName, $this->mSavedFile );
+                       wfRestoreWarnings();
+
+                       if( ! $success ) ) {
                                $wgOut->fileCopyError( $tempName, $this->mSavedFile );
                                return false;
                        }
                } else {
-                       if( !move_uploaded_file( $tempName, $this->mSavedFile ) ) {
+                       wfSuppressWarnings();
+                       $success = move_uploaded_file( $tempName, $this->mSavedFile );
+                       wfRestoreWarnings();
+
+                       if( ! $success ) {
                                $wgOut->fileCopyError( $tempName, $this->mSavedFile );
                                return false;
                        }
@@ -324,8 +348,7 @@ class UploadForm {
         * @access private
         */
        function saveTempUploadedFile( $saveName, $tempName ) {
-               global $wgOut;
-
+               global $wgOut;          
                $archive = wfImageArchiveDir( $saveName, 'temp' );
                $stash = $archive . '/' . gmdate( "YmdHis" ) . '!' . $saveName;
 
@@ -346,7 +369,7 @@ class UploadForm {
         * @return int
         * @access private
         */
-       function stashSession() {
+       function stashSession() {               
                $stash = $this->saveTempUploadedFile(
                        $this->mUploadSaveName, $this->mUploadTempName );
 
@@ -368,7 +391,10 @@ class UploadForm {
         * @access private
         */
        function unsaveUploadedFile() {
-               if ( ! @unlink( $this->mUploadTempName ) ) {
+               wfSuppressWarnings();
+               $success = unlink( $this->mUploadTempName );
+               wfRestoreWarnings();
+               if ( ! $success ) {
                        $wgOut->fileDeleteError( $this->mUploadTempName );
                }
        }
@@ -384,7 +410,7 @@ class UploadForm {
                
                $sk = $wgUser->getSkin();
                $ilink = $sk->makeMediaLink( $this->mUploadSaveName, Image::wfImageUrl( $this->mUploadSaveName ) );
-               $dname = $wgContLang->getNsText( Namespace::getImage() ) . ':'.$this->mUploadSaveName;
+               $dname = $wgContLang->getNsText( NS_IMAGE ) . ':'.$this->mUploadSaveName;
                $dlink = $sk->makeKnownLink( $dname, $dname );
 
                $wgOut->addHTML( '<h2>' . wfMsg( 'successfulupload' ) . "</h2>\n" );
@@ -401,7 +427,7 @@ class UploadForm {
                global $wgOut;
                $sub = wfMsg( 'uploadwarning' );
                $wgOut->addHTML( "<h2>{$sub}</h2>\n" );
-               $wgOut->addHTML( "<h4 style='error'>{$error}</h4>\n" );
+               $wgOut->addHTML( "<h4 class='error'>{$error}</h4>\n" );
        }
 
        /**
@@ -424,7 +450,7 @@ class UploadForm {
 
                $sub = wfMsg( 'uploadwarning' );
                $wgOut->addHTML( "<h2>{$sub}</h2>\n" );
-               $wgOut->addHTML( "<ul class='warning'>{$warning}</ul><br/>\n" );
+               $wgOut->addHTML( "<ul class='warning'>{$warning}</ul><br />\n" );
 
                $save = wfMsg( 'savefile' );
                $reupload = wfMsg( 'reupload' );
@@ -449,6 +475,7 @@ class UploadForm {
        <input type=hidden name=\"wpUploadAffirm\" value=\"1\" />
        <input type=hidden name=\"wpIgnoreWarning\" value=\"1\" />
        <input type=hidden name=\"wpSessionKey\" value=\"" . htmlspecialchars( $this->mSessionKey ) . "\" />
+       <input type=hidden name=\"wpUploadDescription\" value=\"" . htmlspecialchars( $this->mUploadDescription ) . "\" />
        {$copyright}
        <table border='0'><tr>
        <tr><td align='right'>
@@ -473,7 +500,7 @@ class UploadForm {
                if ( '' != $msg ) {
                        $sub = wfMsg( 'uploaderror' );
                        $wgOut->addHTML( "<h2>{$sub}</h2>\n" .
-                         "<h4 style='error'>{$msg}</h4>\n" );
+                         "<h4 class='error'>{$msg}</h4>\n" );
                } else {
                        $sub = wfMsg( 'uploadfile' );
                        $wgOut->addHTML( "<h2>{$sub}</h2>\n" );
@@ -531,6 +558,20 @@ class UploadForm {
        
        /* -------------------------------------------------------------- */
 
+       /**
+        * Split a file into a base name and all dot-delimited 'extensions'
+        * on the end. Some web server configurations will fall back to
+        * earlier pseudo-'extensions' to determine type and execute
+        * scripts, so the blacklist needs to check them all.
+        *
+        * @return array
+        */
+       function splitExtensions( $filename ) {
+               $bits = explode( '.', $filename );
+               $basename = array_shift( $bits );
+               return array( $basename, $bits );
+       }
+       
        /**
         * Perform case-insensitive match against a list of file extensions.
         * Returns true if the extension is in the list.
@@ -543,6 +584,23 @@ class UploadForm {
                return in_array( strtolower( $ext ), $list );
        }
 
+       /**
+        * Perform case-insensitive match against a list of file extensions.
+        * Returns true if any of the extensions are in the list.
+        *
+        * @param array $ext
+        * @param array $list
+        * @return bool
+        */
+       function checkFileExtensionList( $ext, $list ) {
+               foreach( $ext as $e ) {
+                       if( in_array( strtolower( $e ), $list ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
        /**
         * Returns false if the file is of a known type but can't be recognized,
         * indicating a corrupt file.
@@ -554,7 +612,8 @@ class UploadForm {
         * @return bool
         */
        function verify( $tmpfile, $extension ) {
-               if( $this->triggersIEbug( $tmpfile ) ) {
+               if( $this->triggersIEbug( $tmpfile ) ||
+                   $this->triggersSafariBug( $tmpfile ) ) {
                        return false;
                }
                
@@ -595,7 +654,9 @@ class UploadForm {
                        return true;
                }
                
-               $data = @getimagesize( $tmpfile );
+               wfSuppressWarnings();
+               $data = getimagesize( $tmpfile );
+               wfRestoreWarnings();
                if( false === $data ) {
                        # Didn't recognize the image type.
                        # Either the image is corrupt or someone's slipping us some
@@ -640,10 +701,18 @@ class UploadForm {
         */
        function triggersIEbug( $filename ) {
                $file = fopen( $filename, 'rb' );
-               $chunk = strtolower( fread( $file, 200 ) );
+               $chunk = strtolower( fread( $file, 256 ) );
                fclose( $file );
                
-               $tags = array( '<html', '<head', '<body', '<script' );
+               $tags = array(
+                       '<body',
+                       '<head',
+                       '<html',
+                       '<img',
+                       '<pre',
+                       '<script',
+                       '<table',
+                       '<title' );
                foreach( $tags as $tag ) {
                        if( false !== strpos( $chunk, $tag ) ) {
                                return true;
@@ -651,5 +720,35 @@ class UploadForm {
                }
                return false;
        }
+
+       /**
+        * Apple's Safari browser performs some unsafe file type autodetection
+        * which can cause legitimate files to be interpreted as HTML if the
+        * web server is not correctly configured to send the right content-type
+        * (or if you're really uploading plain text and octet streams!)
+        *
+        * Returns true if Safari would mistake the given file for HTML
+        * when served with a generic content-type.
+        *
+        * @param string $filename
+        * @return bool
+        */
+       function triggersSafariBug( $filename ) {
+               $file = fopen( $filename, 'rb' );
+               $chunk = strtolower( fread( $file, 1024 ) );
+               fclose( $file );
+               
+               $tags = array(
+                       '<html',
+                       '<script',
+                       '<title' );
+               foreach( $tags as $tag ) {
+                       if( false !== strpos( $chunk, $tag ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
 }
 ?>