From 63601400e476c6cf43d985f3e7b9864681695ed4 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 18 Jan 2013 16:46:04 +0100 Subject: Update to MediaWiki 1.20.2 this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024 --- includes/upload/UploadBase.php | 148 ++++++++++++++++++++++++++++------- includes/upload/UploadFromChunks.php | 122 ++++++++++++++++++----------- includes/upload/UploadFromFile.php | 33 ++++++-- includes/upload/UploadFromStash.php | 28 ++++++- includes/upload/UploadFromUrl.php | 86 +++++++++++++++++--- includes/upload/UploadStash.php | 48 +++++++++--- 6 files changed, 361 insertions(+), 104 deletions(-) (limited to 'includes/upload') diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 6cc2287a..d40b53d3 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -1,6 +1,28 @@ 'empty-file', self::FILE_TOO_LARGE => 'file-too-large', @@ -64,6 +90,7 @@ abstract class UploadBase { /** * Returns true if uploads are enabled. * Can be override by subclasses. + * @return bool */ public static function isEnabled() { global $wgEnableUploads; @@ -82,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 ) { @@ -100,6 +128,7 @@ abstract class UploadBase { * * @param $request WebRequest * @param $type + * @return null */ public static function createFromRequest( &$request, $type = null ) { $type = $type ? $type : $request->getVal( 'wpSourceType', 'File' ); @@ -140,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; @@ -161,7 +192,7 @@ 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; @@ -180,6 +211,7 @@ abstract class UploadBase { /** * Fetch the file. Usually a no-op + * @return Status */ public function fetchFile() { return Status::newGood(); @@ -203,17 +235,20 @@ abstract class UploadBase { /** * @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 ) ) { // @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; } @@ -222,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 ); } @@ -234,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, @@ -247,6 +286,7 @@ abstract class UploadBase { */ $verification = $this->verifyFile(); if( $verification !== true ) { + wfProfileOut( __METHOD__ ); return array( 'status' => self::VERIFICATION_ERROR, 'details' => $verification @@ -258,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 ); } @@ -304,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 ); } @@ -326,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; } @@ -341,6 +390,8 @@ abstract class UploadBase { */ protected function verifyFile() { global $wgAllowJavaUploads, $wgDisableUploadScriptChecks; + wfProfileIn( __METHOD__ ); + # get the title, even though we are doing nothing with it, because # we need to populate mFinalExtension $this->getTitle(); @@ -351,16 +402,19 @@ abstract class UploadBase { $mime = $this->mFileProps[ 'file-mime' ]; $status = $this->verifyMimeType( $mime ); if ( $status !== true ) { + wfProfileOut( __METHOD__ ); return $status; } # check for htmlish code and javascript 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' ); } } @@ -376,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' ); } } @@ -387,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 ); } @@ -395,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; } @@ -490,6 +550,7 @@ abstract class UploadBase { */ public function checkWarnings() { global $wgLang; + wfProfileIn( __METHOD__ ); $warnings = array(); @@ -550,6 +611,7 @@ abstract class UploadBase { $warnings['duplicate-archive'] = $archivedImage->getName(); } + wfProfileOut( __METHOD__ ); return $warnings; } @@ -557,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, @@ -576,10 +643,10 @@ abstract class UploadBase { if ( $watch ) { $user->addWatch( $this->getLocalFile()->getTitle() ); } - wfRunHooks( 'UploadComplete', array( &$this ) ); } + wfProfileOut( __METHOD__ ); return $status; } @@ -699,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 ) ) { @@ -709,26 +776,6 @@ 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. @@ -742,9 +789,13 @@ abstract class UploadBase { */ public function stashFile() { // was stashSessionFile + wfProfileIn( __METHOD__ ); + $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() ); $this->mLocalFile = $file; + + wfProfileOut( __METHOD__ ); return $file; } @@ -787,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 ) { @@ -870,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. @@ -885,6 +938,7 @@ abstract class UploadBase { $chunk = strtolower( $chunk ); if( !$chunk ) { + wfProfileOut( __METHOD__ ); return false; } @@ -908,6 +962,7 @@ abstract class UploadBase { # check for HTML doctype if ( preg_match( "/filterMatch; @@ -984,6 +1048,9 @@ abstract class UploadBase { /** * @todo Replace this with a whitelist filter! + * @param $element string + * @param $attribs array + * @return bool */ public function checkSvgScriptCallback( $element, $attribs ) { $strippedElement = $this->stripXmlNamespace( $element ); @@ -1054,7 +1121,7 @@ abstract class UploadBase { } - # use handler attribute with remote / data / script + # 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; @@ -1082,6 +1149,10 @@ abstract class UploadBase { 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 ) ); @@ -1100,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; } @@ -1110,7 +1183,8 @@ abstract class UploadBase { wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" ); $wgOut->wrapWikiMsg( "
\n$1\n
", array( 'virus-badscanner', $wgAntivirus ) ); - return wfMsg( 'virus-unknownscanner' ) . " $wgAntivirus"; + wfProfileOut( __METHOD__ ); + return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus"; } # look up scanner configuration @@ -1152,17 +1226,21 @@ abstract class UploadBase { wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" ); if ( $wgAntivirusRequired ) { - return wfMsg( 'virus-scanfailed', array( $exitCode ) ); + wfProfileOut( __METHOD__ ); + return wfMessage( 'virus-scanfailed', array( $exitCode ) )->text(); } 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 ); @@ -1179,6 +1257,7 @@ abstract class UploadBase { } wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" ); + wfProfileOut( __METHOD__ ); return $output; } } @@ -1325,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, '.' ); @@ -1387,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; diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php index ec83f7d3..0542bba5 100644 --- a/includes/upload/UploadFromChunks.php +++ b/includes/upload/UploadFromChunks.php @@ -1,4 +1,26 @@ mChunkIndex = 0; $this->mOffset = 0; // Create a local stash target $this->mLocalFile = parent::stashFile(); - // Update the initial file offset ( based on file size ) + // Update the initial file offset ( based on file size ) $this->mOffset = $this->mLocalFile->getSize(); $this->mFileKey = $this->mLocalFile->getFileKey(); // Output a copy of this first to chunk 0 location: $status = $this->outputChunk( $this->mLocalFile->getPath() ); - - // Update db table to reflect initial "chunk" state + + // Update db table to reflect initial "chunk" state $this->updateChunkStatus(); return $this->mLocalFile; } - + /** * Continue chunk uploading - */ + */ public function continueChunks( $name, $key, $webRequestUpload ) { $this->mFileKey = $key; $this->mUpload = $webRequestUpload; - // Get the chunk status form the db: + // Get the chunk status form the db: $this->getChunkStatus(); - + $metadata = $this->stash->getMetadata( $key ); $this->initializePathInfo( $name, $this->getRealPath( $metadata['us_path'] ), @@ -77,13 +99,13 @@ class UploadFromChunks extends UploadFromFile { false ); } - + /** * Append the final chunk and ready file for parent::performUpload() * @return FileRepoStatus */ public function concatenateChunks() { - wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" . + wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" . $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); // Concatenate all the chunks to mVirtualTempPath @@ -103,10 +125,10 @@ class UploadFromChunks extends UploadFromFile { // Concatenate the chunks at the temp file $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE ); if( !$status->isOk() ){ - return $status; + return $status; } // Update the mTempPath and mLocalFile - // ( for FileUpload or normal Stash to take over ) + // ( for FileUpload or normal Stash to take over ) $this->mTempPath = $tmpPath; // file system path $this->mLocalFile = parent::stashFile(); @@ -127,42 +149,44 @@ class UploadFromChunks extends UploadFromFile { } /** - * Returns the virtual chunk location: - * @param unknown_type $index + * Returns the virtual chunk location: + * @param $index + * @return string */ function getVirtualChunkLocation( $index ){ - return $this->repo->getVirtualUrl( 'temp' ) . + return $this->repo->getVirtualUrl( 'temp' ) . '/' . - $this->repo->getHashPath( + $this->repo->getHashPath( $this->getChunkFileKey( $index ) - ) . + ) . $this->getChunkFileKey( $index ); } + /** * Add a chunk to the temporary directory * - * @param $chunkPath path to temporary chunk file - * @param $chunkSize size of the current chunk - * @param $offset offset of current chunk ( mutch match database chunk offset ) + * @param $chunkPath string path to temporary chunk file + * @param $chunkSize int size of the current chunk + * @param $offset int offset of current chunk ( mutch match database chunk offset ) * @return Status */ public function addChunk( $chunkPath, $chunkSize, $offset ) { // Get the offset before we add the chunk to the file system $preAppendOffset = $this->getOffset(); - + if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) { $status = Status::newFatal( 'file-too-large' ); } else { // Make sure the client is uploading the correct chunk with a matching offset. if ( $preAppendOffset == $offset ) { - // Update local chunk index for the current chunk + // Update local chunk index for the current chunk $this->mChunkIndex++; $status = $this->outputChunk( $chunkPath ); if( $status->isGood() ){ - // Update local offset: + // Update local offset: $this->mOffset = $preAppendOffset + $chunkSize; - // Update chunk table status db - $this->updateChunkStatus(); + // Update chunk table status db + $this->updateChunkStatus(); } } else { $status = Status::newFatal( 'invalid-chunk-offset' ); @@ -170,18 +194,18 @@ class UploadFromChunks extends UploadFromFile { } return $status; } - + /** - * Update the chunk db table with the current status: + * Update the chunk db table with the current status: */ private function updateChunkStatus(){ - wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" . + wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" . $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); - + $dbw = $this->repo->getMasterDb(); $dbw->update( 'uploadstash', - array( + array( 'us_status' => 'chunks', 'us_chunk_inx' => $this->getChunkIndex(), 'us_size' => $this->getOffset() @@ -190,16 +214,17 @@ class UploadFromChunks extends UploadFromFile { __METHOD__ ); } + /** * Get the chunk db state and populate update relevant local values */ private function getChunkStatus(){ - // get Master db to avoid race conditions. + // get Master db to avoid race conditions. // Otherwise, if chunk upload time < replag there will be spurious errors $dbw = $this->repo->getMasterDb(); $row = $dbw->selectRow( - 'uploadstash', - array( + 'uploadstash', + array( 'us_chunk_inx', 'us_size', 'us_path', @@ -214,8 +239,9 @@ class UploadFromChunks extends UploadFromFile { $this->mVirtualTempPath = $row->us_path; } } + /** - * Get the current Chunk index + * Get the current Chunk index * @return Integer index of the current chunk */ private function getChunkIndex(){ @@ -224,10 +250,10 @@ class UploadFromChunks extends UploadFromFile { } return 0; } - + /** - * Gets the current offset in fromt the stashedupload table - * @return Integer current byte offset of the chunk file set + * Gets the current offset in fromt the stashedupload table + * @return Integer current byte offset of the chunk file set */ private function getOffset(){ if ( $this->mOffset !== null ){ @@ -235,20 +261,23 @@ class UploadFromChunks extends UploadFromFile { } return 0; } - + /** * Output the chunk to disk - * + * * @param $chunkPath string + * @throws UploadChunkFileException + * @return FileRepoStatus */ private function outputChunk( $chunkPath ){ // Key is fileKey + chunk index $fileKey = $this->getChunkFileKey(); - - // Store the chunk per its indexed fileKey: + + // Store the chunk per its indexed fileKey: $hashPath = $this->repo->getHashPath( $fileKey ); - $storeStatus = $this->repo->store( $chunkPath, 'temp', "$hashPath$fileKey" ); - + $storeStatus = $this->repo->quickImport( $chunkPath, + $this->repo->getZonePath( 'temp' ) . "/{$hashPath}{$fileKey}" ); + // Check for error in stashing the chunk: if ( ! $storeStatus->isOK() ) { $error = $storeStatus->getErrorsArray(); @@ -264,6 +293,7 @@ class UploadFromChunks extends UploadFromFile { } return $storeStatus; } + private function getChunkFileKey( $index = null ){ if( $index === null ){ $index = $this->getChunkIndex(); diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php index 23ec2ef4..aa0cc77b 100644 --- a/includes/upload/UploadFromFile.php +++ b/includes/upload/UploadFromFile.php @@ -1,4 +1,26 @@ getUpload( 'wpUploadFile' ); + $upload = $request->getUpload( 'wpUploadFile' ); $desiredDestName = $request->getText( 'wpDestFile' ); - if( !$desiredDestName ) + if( !$desiredDestName ) { $desiredDestName = $upload->getName(); - - return $this->initialize( $desiredDestName, $upload ); + } + + $this->initialize( $desiredDestName, $upload ); } /** @@ -31,7 +54,7 @@ class UploadFromFile extends UploadBase { */ function initialize( $name, $webRequestUpload ) { $this->mUpload = $webRequestUpload; - return $this->initializePathInfo( $name, + $this->initializePathInfo( $name, $this->mUpload->getTempName(), $this->mUpload->getSize() ); } diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php index f7589bd2..607965f3 100644 --- a/includes/upload/UploadFromStash.php +++ b/includes/upload/UploadFromStash.php @@ -1,4 +1,26 @@ stash = new UploadStash( $this->repo, $this->user ); } - - return true; } /** @@ -99,7 +119,7 @@ class UploadFromStash extends UploadBase { // chooses one of wpDestFile, wpUploadFile, filename in that order. $desiredDestName = $request->getText( 'wpDestFile', $request->getText( 'wpUploadFile', $request->getText( 'filename' ) ) ); - return $this->initialize( $fileKey, $desiredDestName ); + $this->initialize( $fileKey, $desiredDestName ); } /** @@ -140,7 +160,7 @@ class UploadFromStash extends UploadBase { /** * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @return success + * @return bool success */ public function unsaveUploadedFile() { return $this->stash->removeFile( $this->mFileKey ); diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php index da772fe2..927c3cd9 100644 --- a/includes/upload/UploadFromUrl.php +++ b/includes/upload/UploadFromUrl.php @@ -1,4 +1,26 @@ isAllowed( 'upload_by_url' ) ) { @@ -36,6 +59,31 @@ class UploadFromUrl extends UploadBase { return $wgAllowCopyUploads && parent::isEnabled(); } + /** + * Checks whether the URL is for an allowed host + * + * @param $url string + * @return bool + */ + public static function isAllowedHost( $url ) { + global $wgCopyUploadsDomains; + if ( !count( $wgCopyUploadsDomains ) ) { + return true; + } + $parsedUrl = wfParseUrl( $url ); + if ( !$parsedUrl ) { + return false; + } + $valid = false; + foreach( $wgCopyUploadsDomains as $domain ) { + if ( $parsedUrl['host'] === $domain ) { + $valid = true; + break; + } + } + return $valid; + } + /** * Entry point for API upload * @@ -44,6 +92,7 @@ class UploadFromUrl extends UploadBase { * @param $async mixed Whether the download should be performed * asynchronous. False for synchronous, async or async-leavemessage for * asynchronous download. + * @throws MWException */ public function initialize( $name, $url, $async = false ) { global $wgAllowAsyncCopyUploads; @@ -68,7 +117,7 @@ class UploadFromUrl extends UploadBase { if ( !$desiredDestName ) { $desiredDestName = $request->getText( 'wpUploadFileURL' ); } - return $this->initialize( + $this->initialize( $desiredDestName, trim( $request->getVal( 'wpUploadFileURL' ) ), false @@ -101,6 +150,9 @@ class UploadFromUrl extends UploadBase { return Status::newFatal( 'http-invalid-url' ); } + if( !self::isAllowedHost( $this->mUrl ) ) { + return Status::newFatal( 'upload-copy-upload-invalid-domain' ); + } if ( !$this->mAsync ) { return $this->reallyFetchFile(); } @@ -155,9 +207,14 @@ class UploadFromUrl extends UploadBase { $this->mRemoveTempFile = true; $this->mFileSize = 0; - $req = MWHttpRequest::factory( $this->mUrl, array( + $options = array( 'followRedirects' => true - ) ); + ); + global $wgCopyUploadProxy; + if ( $wgCopyUploadProxy !== false ) { + $options['proxy'] = $wgCopyUploadProxy; + } + $req = MWHttpRequest::factory( $this->mUrl, $options ); $req->setCallback( array( $this, 'saveTempFileChunk' ) ); $status = $req->execute(); @@ -180,6 +237,7 @@ class UploadFromUrl extends UploadBase { /** * Wrapper around the parent function in order to defer verifying the * upload until the file really has been fetched. + * @return array|mixed */ public function verifyUpload() { if ( $this->mAsync ) { @@ -191,6 +249,7 @@ class UploadFromUrl extends UploadBase { /** * Wrapper around the parent function in order to defer checking warnings * until the file really has been fetched. + * @return Array */ public function checkWarnings() { if ( $this->mAsync ) { @@ -203,6 +262,8 @@ class UploadFromUrl extends UploadBase { /** * Wrapper around the parent function in order to defer checking protection * until we are sure that the file can actually be uploaded + * @param $user User + * @return bool|mixed */ public function verifyTitlePermissions( $user ) { if ( $this->mAsync ) { @@ -214,6 +275,11 @@ class UploadFromUrl extends UploadBase { /** * Wrapper around the parent function in order to defer uploading to the * job queue for asynchronous uploads + * @param $comment string + * @param $pageText string + * @param $watch bool + * @param $user User + * @return Status */ public function performUpload( $comment, $pageText, $watch, $user ) { if ( $this->mAsync ) { @@ -226,11 +292,11 @@ class UploadFromUrl extends UploadBase { } /** - * @param $comment - * @param $pageText - * @param $watch - * @param $user User - * @return + * @param $comment + * @param $pageText + * @param $watch + * @param $user User + * @return String */ protected function insertJob( $comment, $pageText, $watch, $user ) { $sessionKey = $this->stashSession(); diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php index ad153d2f..c7fd23a9 100644 --- a/includes/upload/UploadStash.php +++ b/includes/upload/UploadStash.php @@ -1,4 +1,26 @@ text() ); + $errorMsg = array_shift( $error ); + throw new UploadStashFileException( "Error storing file in '$path': " . wfMessage( $errorMsg, $error )->text() ); } $stashPath = $storeStatus->value; + // we have renamed the file so we have to cleanup once done + unlink($path); + // fetch the current user ID if ( !$this->isLoggedIn ) { throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' ); @@ -391,6 +419,7 @@ class UploadStash { * with an extension. * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming * uploads versus the desired filename. Maybe we can get that passed to us... + * @return string */ public static function getExtensionForPath( $path ) { // Does this have an extension? @@ -419,6 +448,7 @@ class UploadStash { * Helper function: do the actual database query to fetch file metadata. * * @param $key String: key + * @param $readFromDB: constant (default: DB_SLAVE) * @return boolean */ protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) { @@ -451,7 +481,6 @@ class UploadStash { /** * Helper function: Initialize the UploadStashFile for a given file. * - * @param $path String: path to file * @param $key String: key under which to store the object * @throws UploadStashZeroLengthFileException * @return bool @@ -498,7 +527,7 @@ class UploadStashFile extends UnregisteredLocalFile { } // check if path exists! and is a plain file. - if ( ! $repo->fileExists( $path, FileRepo::FILES_ONLY ) ) { + if ( ! $repo->fileExists( $path ) ) { wfDebug( "UploadStash: tried to construct an UploadStashFile from a file that should already exist at '$path', but path is not found\n" ); throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' ); } @@ -543,16 +572,17 @@ class UploadStashFile extends UnregisteredLocalFile { * ugly file name. * * @param $params Array: handler-specific parameters + * @param $flags integer Bitfield that supports THUMB_* constants * @return String: base name for URL, like '120px-12345.jpg', or null if there is no handler */ - function thumbName( $params ) { + function thumbName( $params, $flags = 0 ) { return $this->generateThumbName( $this->getUrlName(), $params ); } /** * Helper function -- given a 'subpage', return the local URL e.g. /wiki/Special:UploadStash/subpage - * @param {String} $subPage - * @return {String} local URL for this subpage in the Special:UploadStash space. + * @param $subPage String + * @return String: local URL for this subpage in the Special:UploadStash space. */ private function getSpecialUrl( $subPage ) { return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL(); @@ -622,7 +652,7 @@ class UploadStashFile extends UnregisteredLocalFile { * @return Status: success */ public function remove() { - if ( !$this->repo->fileExists( $this->path, FileRepo::FILES_ONLY ) ) { + if ( !$this->repo->fileExists( $this->path ) ) { // Maybe the file's already been removed? This could totally happen in UploadBase. return true; } @@ -631,7 +661,7 @@ class UploadStashFile extends UnregisteredLocalFile { } public function exists() { - return $this->repo->fileExists( $this->path, FileRepo::FILES_ONLY ); + return $this->repo->fileExists( $this->path ); } } -- cgit v1.2.2