* Edit token no longer passed through htmlspecialchars()
[lhc/web/wiklou.git] / includes / SpecialUpload.php
index 5497b50..1f16e57 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, $mDestFile;
+       /**#@-*/
 
        /**
         * Constructor : initialise object
@@ -40,11 +40,13 @@ class UploadForm {
         * @param $request Data posted.
         */
        function UploadForm( &$request ) {
+               $this->mDestFile          = $request->getText( 'wpDestFile' );
+               
                if( !$request->wasPosted() ) {
-                       # GET requests just give the main form; no data.
+                       # GET requests just give the main form; no data except wpDestfile.
                        return;
                }
-               
+
                $this->mUploadAffirm      = $request->getCheck( 'wpUploadAffirm' );
                $this->mIgnoreWarning     = $request->getCheck( 'wpIgnoreWarning');
                $this->mReUpload          = $request->getCheck( 'wpReUpload' );
@@ -69,6 +71,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 +80,7 @@ class UploadForm {
                        $this->mUploadSize     = $request->getFileSize( 'wpUploadFile' );
                        $this->mOname          = $request->getFileName( 'wpUploadFile' );
                        $this->mSessionKey     = false;
+                       $this->mStashed        = false;
                }
        }
 
@@ -86,16 +90,16 @@ class UploadForm {
         */
        function execute() {
                global $wgUser, $wgOut;
-               global $wgDisableUploads;
+               global $wgEnableUploads, $wgUploadDirectory;
 
                /** Show an error message if file upload is disabled */ 
-               if( $wgDisableUploads ) {
+               if( ! $wgEnableUploads ) {
                        $wgOut->addWikiText( wfMsg( 'uploaddisabled' ) );
                        return;
                }
-               
+
                /** Various rights checks */
-               if( ( $wgUser->getID() == 0 )
+               if( ( $wgUser->isAnon() )
                         OR $wgUser->isBlocked() ) {
                        $wgOut->errorpage( 'uploadnologin', 'uploadnologintext' );
                        return;
@@ -105,6 +109,12 @@ class UploadForm {
                        return;
                }
                
+               /** Check if the image directory is writeable, this is a common mistake */
+               if ( !is_writeable( $wgUploadDirectory ) ) {
+                       $wgOut->addWikiText( wfMsg( 'upload_directory_read_only', $wgUploadDirectory ) );
+                       return;
+               }
+
                if( $this->mReUpload ) {
                        $this->unsaveUploadedFile();
                        $this->mainUploadForm();
@@ -130,7 +140,7 @@ class UploadForm {
                /**
                 * If there was no filename or a zero size given, give up quick.
                 */
-               if( ( trim( $this->mOname ) == '' ) || empty( $this->mUploadSize ) ) {
+               if( trim( $this->mOname ) == '' || empty( $this->mUploadSize ) ) {
                        return $this->mainUploadForm('<li>'.wfMsg( 'emptyfile' ).'</li>');
                }
                
@@ -154,7 +164,11 @@ class UploadForm {
                }
 
                # Chop off any directories in the given filename
-               $basename = basename( $this->mOname );
+               if ( $this->mDestFile ) {
+                       $basename = basename( $this->mDestFile );
+               } else {
+                       $basename = basename( $this->mOname );
+               }
 
                /**
                 * We'll want to blacklist against *any* 'extension', and use
@@ -207,7 +221,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, $finalExt ) ) {
+               if( !$this->mStashed && !$this->verify( $this->mUploadTempName, $finalExt ) ) {
                        return $this->uploadError( wfMsg( 'uploadcorrupt' ) );
                }
                
@@ -229,7 +243,9 @@ class UploadForm {
        
                        global $wgUploadSizeWarning;
                        if ( $wgUploadSizeWarning && ( $this->mUploadSize > $wgUploadSizeWarning ) ) {
-                               $warning .= '<li>'.wfMsg( 'largefile' ).'</li>';
+                               # TODO: Format $wgUploadSizeWarning to something that looks better than the raw byte
+                               # value, perhaps add GB,MB and KB suffixes?
+                               $warning .= '<li>'.wfMsg( 'largefile', $wgUploadSizeWarning, $this->mUploadSize ).'</li>';
                        }
                        if ( $this->mUploadSize == 0 ) {
                                $warning .= '<li>'.wfMsg( 'emptyfile' ).'</li>';
@@ -262,13 +278,19 @@ class UploadForm {
                         * Update the upload log and create the description page
                         * if it's a new file.
                         */
-                       wfRecordUpload( $this->mUploadSaveName,
-                                       $this->mUploadOldVersion,
-                                       $this->mUploadSize, 
-                                       $this->mUploadDescription,
-                                       $this->mUploadCopyStatus,
-                                       $this->mUploadSource );
-                       $this->showSuccess();
+                       $img = Image::newFromName( $this->mUploadSaveName );
+                       $success = $img->recordUpload( $this->mUploadOldVersion,
+                                                       $this->mUploadDescription,
+                                                       $this->mUploadCopyStatus,
+                                                       $this->mUploadSource );
+
+                       if ( $success ) {
+                               $this->showSuccess();
+                       } else {
+                               // Image::recordUpload() fails if the image went missing, which is 
+                               // unlikely, hence the lack of a specialised message
+                               $wgOut->fileNotFoundError( $this->mUploadSaveName );
+                       }
                }
        }
 
@@ -293,8 +315,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;
@@ -304,12 +329,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;
                        }
@@ -331,8 +364,7 @@ class UploadForm {
         * @access private
         */
        function saveTempUploadedFile( $saveName, $tempName ) {
-               global $wgOut;
-
+               global $wgOut;          
                $archive = wfImageArchiveDir( $saveName, 'temp' );
                $stash = $archive . '/' . gmdate( "YmdHis" ) . '!' . $saveName;
 
@@ -353,7 +385,7 @@ class UploadForm {
         * @return int
         * @access private
         */
-       function stashSession() {
+       function stashSession() {               
                $stash = $this->saveTempUploadedFile(
                        $this->mUploadSaveName, $this->mUploadTempName );
 
@@ -375,7 +407,10 @@ class UploadForm {
         * @access private
         */
        function unsaveUploadedFile() {
-               if ( ! @unlink( $this->mUploadTempName ) ) {
+               wfSuppressWarnings();
+               $success = unlink( $this->mUploadTempName );
+               wfRestoreWarnings();
+               if ( ! $success ) {
                        $wgOut->fileDeleteError( $this->mUploadTempName );
                }
        }
@@ -390,8 +425,8 @@ class UploadForm {
                global $wgUser, $wgOut, $wgContLang;
                
                $sk = $wgUser->getSkin();
-               $ilink = $sk->makeMediaLink( $this->mUploadSaveName, Image::wfImageUrl( $this->mUploadSaveName ) );
-               $dname = $wgContLang->getNsText( Namespace::getImage() ) . ':'.$this->mUploadSaveName;
+               $ilink = $sk->makeMediaLink( $this->mUploadSaveName, Image::imageUrl( $this->mUploadSaveName ) );
+               $dname = $wgContLang->getNsText( NS_IMAGE ) . ':'.$this->mUploadSaveName;
                $dlink = $sk->makeKnownLink( $dname, $dname );
 
                $wgOut->addHTML( '<h2>' . wfMsg( 'successfulupload' ) . "</h2>\n" );
@@ -431,7 +466,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' );
@@ -443,28 +478,37 @@ class UploadForm {
                if ( $wgUseCopyrightUpload )
                {
                        $copyright =  "
-       <input type='hidden' name=\"wpUploadCopyStatus\" value=\"" . htmlspecialchars( $this->mUploadCopyStatus ) . "\" />
-       <input type='hidden' name=\"wpUploadSource\" value=\"" . htmlspecialchars( $this->mUploadSource ) . "\" />
+       <input type='hidden' name='wpUploadCopyStatus' value=\"" . htmlspecialchars( $this->mUploadCopyStatus ) . "\" />
+       <input type='hidden' name='wpUploadSource' value=\"" . htmlspecialchars( $this->mUploadSource ) . "\" />
        ";
                } else {
                        $copyright = "";
                }
 
                $wgOut->addHTML( "
-       <form id=\"uploadwarning\" method=\"post\" enctype=\"multipart/form-data\"
-       action=\"{$action}\">
-       <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 ) . "\" />
+       <form id='uploadwarning' method='post' enctype='multipart/form-data' action='$action'>
+               <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 ) . "\" />
+               <input type='hidden' name='wpDestFile' value=\"" . htmlspecialchars( $this->mDestFile ) . "\" />
        {$copyright}
-       <table border='0'><tr>
-       <tr><td align='right'>
-       <input tabindex='2' type='submit' name=\"wpUpload\" value=\"{$save}\" />
-       </td><td align='left'>{$iw}</td></tr>
-       <tr><td align='right'>
-       <input tabindex='2' type='submit' name=\"wpReUpload\" value=\"{$reupload}\" />
-       </td><td align='left'>{$reup}</td></tr></table></form>\n" );
+       <table border='0'>
+               <tr>
+                       <tr>
+                               <td align='right'>
+                                       <input tabindex='2' type='submit' name='wpUpload' value='$save' />
+                               </td>
+                               <td align='left'>$iw</td>
+                       </tr>
+                       <tr>
+                               <td align='right'>
+                                       <input tabindex='2' type='submit' name='wpReUpload' value='{$reupload}' />
+                               </td>
+                               <td align='left'>$reup</td>
+                       </tr>
+               </tr>
+       </table></form>\n" );
        }
 
        /**
@@ -477,6 +521,11 @@ class UploadForm {
        function mainUploadForm( $msg='' ) {
                global $wgOut, $wgUser, $wgLang, $wgUploadDirectory, $wgRequest;
                global $wgUseCopyrightUpload;
+               
+               $cols = intval($wgUser->getOption( 'cols' ));
+               $ew = $wgUser->getOption( 'editwidth' );
+               if ( $ew ) $ew = " style=\"width:100%\"";
+               else $ew = '';
 
                if ( '' != $msg ) {
                        $sub = wfMsg( 'uploaderror' );
@@ -489,7 +538,10 @@ class UploadForm {
                $wgOut->addWikiText( wfMsg( 'uploadtext' ) );
                $sk = $wgUser->getSkin();
 
-               $fn = wfMsg( 'filename' );
+
+               $sourcefilename = wfMsg( 'sourcefilename' );
+               $destfilename = wfMsg( 'destfilename' );
+               
                $fd = wfMsg( 'filedesc' );
                $ulb = wfMsg( 'uploadbtn' );
 
@@ -501,10 +553,12 @@ class UploadForm {
                $titleObj = Title::makeTitle( NS_SPECIAL, 'Upload' );
                $action = $titleObj->escapeLocalURL();
 
+               $encDestFile = htmlspecialchars( $this->mDestFile );
+
                $source = "
        <td align='right'>
-       <input tabindex='3' type='checkbox' name=\"wpUploadAffirm\" value=\"1\" id=\"wpUploadAffirm\" />
-       </td><td align='left'><label for=\"wpUploadAffirm\">{$ca}</label></td>
+       <input tabindex='3' type='checkbox' name='wpUploadAffirm' value='1' id='wpUploadAffirm' />
+       </td><td align='left'><label for='wpUploadAffirm'>{$ca}</label></td>
        " ;
                if ( $wgUseCopyrightUpload )
                  {
@@ -514,26 +568,32 @@ class UploadForm {
        htmlspecialchars($this->mUploadCopyStatus). "\" size='40' /></td>
        </tr><tr>
        <td align='right'>". wfMsg ( 'filesource' ) . ":</td>
-       <td><input tabindex='4' type='text' name=\"wpUploadSource\" value=\"" .
+       <td><input tabindex='4' type='text' name='wpUploadSource' value=\"" .
        htmlspecialchars($this->mUploadSource). "\" size='40' /></td>
        " ;
                  }
 
                $wgOut->addHTML( "
-       <form id=\"upload\" method=\"post\" enctype=\"multipart/form-data\"
-       action=\"{$action}\">
+       <form id='upload' method='post' enctype='multipart/form-data' action=\"$action\">
        <table border='0'><tr>
-       <td align='right'>{$fn}:</td><td align='left'>
-       <input tabindex='1' type='file' name=\"wpUploadFile\" size='40' />
+
+       <td align='right'>{$sourcefilename}:</td><td align='left'>
+       <input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' onchange='fillDestFilename()' size='40' />
        </td></tr><tr>
+
+       <td align='right'>{$destfilename}:</td><td align='left'>
+       <input tabindex='1' type='text' name='wpDestFile' id='wpDestFile' size='40' value=\"$encDestFile\" />
+       </td></tr><tr>
+       
        <td align='right'>{$fd}:</td><td align='left'>
-       <input tabindex='2' type='text' name=\"wpUploadDescription\" value=\""
-         . htmlspecialchars( $this->mUploadDescription ) . "\" size='40' />
+       <textarea tabindex='2' name='wpUploadDescription' rows='6' cols='{$cols}'{$ew}>"        
+         . htmlspecialchars( $this->mUploadDescription ) .
+       "</textarea>
        </td></tr><tr>
        {$source}
        </tr>
        <tr><td></td><td align='left'>
-       <input tabindex='5' type='submit' name=\"wpUpload\" value=\"{$ulb}\" />
+       <input tabindex='5' type='submit' name='wpUpload' value=\"{$ulb}\" />
        </td></tr></table></form>\n" );
        }
        
@@ -593,7 +653,8 @@ class UploadForm {
         * @return bool
         */
        function verify( $tmpfile, $extension ) {
-               if( $this->triggersIEbug( $tmpfile ) ) {
+               if( $this->triggersIEbug( $tmpfile ) ||
+                   $this->triggersSafariBug( $tmpfile ) ) {
                        return false;
                }
                
@@ -634,7 +695,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
@@ -679,10 +742,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;
@@ -690,5 +761,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;
+       }
+       
 }
 ?>