From bc364fe0e5ca0c0b2d595257992da954864632c0 Mon Sep 17 00:00:00 2001 From: Bryan Tong Minh Date: Sat, 6 Sep 2008 21:26:46 +0000 Subject: [PATCH] Committing a work on progress on improvements to the new upload code. Still needs some work to allow extensions to interact with it and also needs documentation. Modified SpecialUpload.php not committed. --- includes/UploadBase.php | 95 ++++++++++++++++++++++------------- includes/UploadFromStash.php | 38 +++++++++++--- includes/UploadFromUpload.php | 22 +++++--- includes/UploadFromUrl.php | 77 +++++++++++----------------- 4 files changed, 135 insertions(+), 97 deletions(-) diff --git a/includes/UploadBase.php b/includes/UploadBase.php index 58f78cc171..665f413946 100644 --- a/includes/UploadBase.php +++ b/includes/UploadBase.php @@ -24,38 +24,74 @@ class UploadBase { const SESSION_VERSION = 2; + /* + * Returns true if uploads are enabled. + * Can be overriden by subclasses. + */ static function isEnabled() { global $wgEnableUploads; return $wgEnableUploads; } - static function isAllowed( User $user ) { + /* + * Returns true if the user can use this upload module or else a string + * identifying the missing permission. + * Can be overriden by subclasses. + */ + static function isAllowed( $user ) { if( !$user->isAllowed( 'upload' ) ) return 'upload'; return true; } - function __construct( $name ) { - $this->mDesiredDestName = $name; + static $uploadHandlers = array( 'Stash', 'Upload', 'Url' ); + static function createFromRequest( &$request, $type = null ) { + $type = $type ? $type : $request->getVal( 'wpSourceType' ); + if( !$type ) + return null; + $type = ucfirst($type); + $className = 'UploadFrom'.$type; + if( !in_array( $type, self::$uploadHandlers ) ) + return null; + if( !call_user_func( array( $className, 'isEnabled' ) ) ) + return null; + if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) + return null; + + $handler = new $className; + $handler->initializeFromRequest( $request ); + return $handler; } + static function isValidRequest( $request ) { + return false; + } + + function __construct() {} - function verifyUpload( &$resultDetails ) { + function initialize( $name, $tempPath, $fileSize, $removeTempFile = false ) { + $this->mDesiredDestName = $name; + $this->mTempPath = $tempPath; + $this->mFileSize = $fileSize; + $this->mRemoveTempFile = $removeTempFile; + } + + function verifyUpload() { global $wgUser; /** * If there was no filename or a zero size given, give up quick. */ - if( empty( $this->mFileSize ) ) { - return self::EMPTY_FILE; - } + if( empty( $this->mFileSize ) ) + return array( 'status' => self::EMPTY_FILE ); $nt = $this->getTitle(); if( is_null( $nt ) ) { + $result = array( 'status' => $this->mTitleError ); if( $this->mTitleError == self::ILLEGAL_FILENAME ) - $resultDetails = array( 'filtered' => $this->mFilteredName ); + $resul['filtered'] = $this->mFilteredName; if ( $this->mTitleError == self::FILETYPE_BADTYPE ) - $resultDetails = array( 'finalExt' => $this->mFinalExtension ); - return $this->mTitleError; + $result['finalExt'] = $this->mFinalExtension; + return $result; } $this->mLocalFile = wfLocalFile( $nt ); $this->mDestName = $this->mLocalFile->getName(); @@ -64,30 +100,27 @@ class UploadBase { * In some cases we may forbid overwriting of existing files. */ $overwrite = $this->checkOverwrite( $this->mDestName ); - if( $overwrite !== true ) { - $resultDetails = array( 'overwrite' => $overwrite ); - return self::OVERWRITE_EXISTING_FILE; - } + if( $overwrite !== true ) + return array( 'status' => self::OVERWRITE_EXISTING_FILE, 'overwrite' => $overwrite ); /** * Look at the contents of the file; if we can recognize the * type but it's corrupt or data of the wrong type, we should * probably not accept it. */ - $veri = $this->verifyFile( $this->mTempPath ); + $verification = $this->verifyFile( $this->mTempPath ); - if( $veri !== true ) { - if( !is_array( $veri ) ) - $veri = array( $veri ); - $resultDetails = array( 'veri' => $veri ); - return self::VERIFICATION_ERROR; + if( $verification !== true ) { + if( !is_array( $verification ) ) + $verification = array( $verification ); + $verification['status'] = self::VERIFICATION_ERROR; + return $verification; } $error = ''; if( !wfRunHooks( 'UploadVerification', array( $this->mDestName, $this->mTempPath, &$error ) ) ) { - $resultDetails = array( 'error' => $error ); - return self::UPLOAD_VERIFICATION_ERROR; + return array( 'status' => self::UPLOAD_VERIFICATION_ERROR, 'error' => $error ); } return self::OK; @@ -97,8 +130,7 @@ class UploadBase { * Verifies that it's ok to include the uploaded file * * @param string $tmpfile the full path of the temporary file to verify - * @param string $extension The filename extension that the file is to be served with - * @return mixed true of the file is verified, a WikiError object otherwise. + * @return mixed true of the file is verified, a string or array otherwise. */ protected function verifyFile( $tmpfile ) { $this->mFileProps = File::getPropsFromPath( $this->mTempPath, @@ -172,7 +204,6 @@ class UploadBase { global $wgCapitalLinks; if( $this->mDesiredDestName != $filename ) - // Use mFilteredName so that we don't have to bother about spaces $warning['badfilename'] = $filename; global $wgCheckFileExtensions, $wgFileExtensions; @@ -319,12 +350,7 @@ class UploadBase { global $wgOut; $repo = RepoGroup::singleton()->getLocalRepo(); $status = $repo->storeTemp( $saveName, $tempName ); - if ( !$status->isGood() ) { - $this->showError( $status->getWikiText() ); - return false; - } else { - return $status->value; - } + return $status; } /** @@ -337,18 +363,17 @@ class UploadBase { * @access private */ function stashSession() { - $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); + $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); - if( !$stash ) { + if( !$status->isGood() ) { # Couldn't save the file. return false; } $key = mt_rand( 0, 0x7fffffff ); $_SESSION['wsUploadData'][$key] = array( - 'mTempPath' => $stash, + 'mTempPath' => $status->value, 'mFileSize' => $this->mFileSize, - 'mSrcName' => $this->mSrcName, 'mFileProps' => $this->mFileProps, 'version' => self::SESSION_VERSION, ); diff --git a/includes/UploadFromStash.php b/includes/UploadFromStash.php index 34173f37f1..f6ffcf0a67 100644 --- a/includes/UploadFromStash.php +++ b/includes/UploadFromStash.php @@ -1,20 +1,46 @@ getSessionData('wsUploadData'); + return self::isValidSessionKey( + $request->getInt( 'wpSessionKey' ), + $sessionData + ); + } + + function initialize( $name, $sessionData ) { /** * Confirming a temporarily stashed upload. * We don't want path names to be forged, so we keep * them in the session on the server and just give * an opaque key to the user agent. */ + $this->initialize( $name, + $sessionData['mTempPath'], + $sessionData['mFileSize'], + false + ); - $this->mTempPath = $sessionData['mTempPath']; - $this->mFileSize = $sessionData['mFileSize']; - $this->mSrcName = $sessionData['mSrcName']; $this->mFileProps = $sessionData['mFileProps']; - $this->mStashed = true; - $this->mRemoveTempFile = false; + } + function initializeFromRequest( &$request ) { + $sessionKey = $request->getInt( 'wpSessionKey' ); + $sessionData = $request->getSessionData('wsUploadData'); + + $desiredDestName = $request->getText( 'wpDestFile' ); + if( !$desiredDestName ) + $desiredDestName = $request->getText( 'wpUploadFile' ); + + return $this->initialize( $desiredDestName, $sessionData[$sessionKey] ); } /* diff --git a/includes/UploadFromUpload.php b/includes/UploadFromUpload.php index c6ccd1733d..1b6762c6fb 100644 --- a/includes/UploadFromUpload.php +++ b/includes/UploadFromUpload.php @@ -1,12 +1,20 @@ mTempPath = $tempPath; - $this->mFileSize = $fileSize; - $this->mSrcName = $fileName; - $this->mSessionKey = false; - $this->mStashed = false; - $this->mRemoveTempFile = false; // PHP will handle this + + function initializeFromRequest( &$request ) { + $desiredDestName = $request->getText( 'wpDestFile' ); + if( !$desiredDestName ) + $desiredDestName = $request->getText( 'wpUploadFile' ); + + return $this->initialize( + $desiredDestName, + $request->getFileTempName( 'wpUploadFile' ), + $request->getFileSize( 'wpUploadFile' ) + ); + } + + static function isValidRequest( $request ) { + return (bool)$request->getFileTempName( 'wpUploadFile' ); } } diff --git a/includes/UploadFromUrl.php b/includes/UploadFromUrl.php index dd9010dd95..b908032d65 100644 --- a/includes/UploadFromUrl.php +++ b/includes/UploadFromUrl.php @@ -2,7 +2,7 @@ class UploadFromUrl extends UploadBase { - static function isAllowed( User $user ) { + static function isAllowed( $user ) { if( !$user->isAllowed( 'upload_by_url' ) ) return 'upload_by_url'; return parent::isAllowed( $user ); @@ -12,75 +12,62 @@ class UploadFromUrl extends UploadBase { return $wgAllowCopyUploads && parent::isEnabled(); } - function initialize( $url ) { + function initialize( $name, $url ) { global $wgTmpDirectory; $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); + $this-initialize( $name, $local_file, 0, true ); - $this->mTempPath = $local_file; - $this->mFileSize = 0; # Will be set by curlCopy - $this->mCurlError = $this->curlCopy( $url, $local_file ); - $pathParts = explode( '/', $url ); - $this->mSrcName = array_pop( $pathParts ); - $this->mSessionKey = false; - $this->mStashed = false; - - // PHP won't auto-cleanup the file - $this->mRemoveTempFile = file_exists( $local_file ); + $this->mUrl = trim( $url ); } + function verifyUpload() { + if( stripos($this->mUrl, 'http://') !== 0 && stripos($this->mUrl, 'ftp://') !== 0 ) { + return array( + 'status' => self::BEFORE_PROCESSING, + 'error' => 'upload-proto-error', + ); + } + $res = $this->curlCopy(); + if( $res !== true ) { + return array( + 'status' => self::BEFORE_PROCESSING, + 'error' => $res, + ); + } + return parent::verifyUpload(); + } /** * Safe copy from URL * Returns true if there was an error, false otherwise */ - private function curlCopy( $url, $dest ) { + private function curlCopy() { global $wgUser, $wgOut; - // Bad bad bad! - if( !$wgUser->isAllowed( 'upload_by_url' ) ) { - $wgOut->permissionRequired( 'upload_by_url' ); - return true; - } - - # Maybe remove some pasting blanks :-) - $url = trim( $url ); - if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) { - # Only HTTP or FTP URLs - $wgOut->showErrorPage( 'upload-proto-error', 'upload-proto-error-text' ); - return true; - } - # Open temporary file $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" ); if( $this->mCurlDestHandle === false ) { # Could not open temporary file to write in - $wgOut->showErrorPage( 'upload-file-error', 'upload-file-error-text'); - return true; + return 'upload-file-error'; } $ch = curl_init(); curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed - curl_setopt( $ch, CURLOPT_URL, $url); + curl_setopt( $ch, CURLOPT_URL, $this->mUrl); curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); curl_exec( $ch ); - $error = curl_errno( $ch ) ? true : false; - $errornum = curl_errno( $ch ); - // if ( $error ) print curl_error ( $ch ) ; # Debugging output + $error = curl_errno( $ch ); curl_close( $ch ); fclose( $this->mCurlDestHandle ); unset( $this->mCurlDestHandle ); - if( $error ) { - unlink( $dest ); - if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) ) - $wgOut->showErrorPage( 'upload-misc-error', 'upload-misc-error-text' ); - else - $wgOut->showErrorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" ); - } + + if( $error ) + return "upload-curl-error$errornum"; - return $error; + return true; } /** @@ -99,12 +86,4 @@ class UploadFromUrl extends UploadBase { fwrite( $this->mCurlDestHandle, $data ); return $length; } - - function execute( &$resultDetails ) { - /* Check for curl error */ - if( $this->mCurlError ) { - return self::BEFORE_PROCESSING; - } - return parent::execute( $resultDetails ); - } } -- 2.20.1