summaryrefslogtreecommitdiff
path: root/includes/upload
diff options
context:
space:
mode:
Diffstat (limited to 'includes/upload')
-rw-r--r--includes/upload/UploadBase.php148
-rw-r--r--includes/upload/UploadFromChunks.php122
-rw-r--r--includes/upload/UploadFromFile.php33
-rw-r--r--includes/upload/UploadFromStash.php28
-rw-r--r--includes/upload/UploadFromUrl.php86
-rw-r--r--includes/upload/UploadStash.php48
6 files changed, 361 insertions, 104 deletions
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 @@
<?php
/**
- * @defgroup Upload
+ * 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
+ */
+
+/**
+ * @defgroup Upload Upload related
*/
/**
@@ -41,6 +63,10 @@ abstract class UploadBase {
const WINDOWS_NONASCII_FILENAME = 13;
const FILENAME_TOO_LONG = 14;
+ /**
+ * @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',
@@ -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 ) ) {
@@ -710,26 +777,6 @@ abstract class UploadBase {
}
/**
- * 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( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
+ wfProfileOut( __METHOD__ );
return true;
}
@@ -944,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;
}
}
@@ -958,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;
@@ -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( "<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
@@ -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,5 +1,27 @@
<?php
/**
+ * Backend for uploading files from chunks.
+ *
+ * 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
+ */
+
+/**
* Implements uploading from chunks
*
* @ingroup Upload
@@ -7,10 +29,10 @@
*/
class UploadFromChunks extends UploadFromFile {
protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
-
+
/**
* Setup local pointers to stash, repo and user ( similar to UploadFromStash )
- *
+ *
* @param $user User
* @param $stash UploadStash
* @param $repo FileRepo
@@ -39,37 +61,37 @@ class UploadFromChunks extends UploadFromFile {
return true;
}
/**
- * Calls the parent stashFile and updates the uploadsession table to handle "chunks"
+ * Calls the parent stashFile and updates the uploadsession table to handle "chunks"
*
* @return UploadStashFile stashed file
*/
public function stashFile() {
- // Stash file is the called on creating a new chunk session:
+ // Stash file is the called on creating a new chunk session:
$this->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,5 +1,27 @@
<?php
/**
+ * Backend for regular 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
+ */
+
+/**
* Implements regular file uploads
*
* @ingroup Upload
@@ -16,12 +38,13 @@ class UploadFromFile extends UploadBase {
* @param $request WebRequest
*/
function initializeFromRequest( &$request ) {
- $upload = $request->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,5 +1,27 @@
<?php
/**
+ * Backend for uploading files from previously stored file.
+ *
+ * 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
+ */
+
+/**
* Implements uploading from previously stored file.
*
* @ingroup Upload
@@ -40,8 +62,6 @@ class UploadFromStash extends UploadBase {
$this->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,5 +1,27 @@
<?php
/**
+ * Backend for uploading files from a HTTP resource.
+ *
+ * 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
+ */
+
+/**
* Implements uploading from a HTTP resource.
*
* @ingroup Upload
@@ -14,11 +36,12 @@ class UploadFromUrl extends UploadBase {
/**
* Checks if the user is allowed to use the upload-by-URL feature. If the
- * user is allowed, pass on permissions checking to the parent.
+ * user is not allowed, return the name of the user right as a string. If
+ * the user is allowed, have the parent do further permissions checking.
*
* @param $user User
*
- * @return bool
+ * @return bool|string
*/
public static function isAllowed( $user ) {
if ( !$user->isAllowed( 'upload_by_url' ) ) {
@@ -37,6 +60,31 @@ class UploadFromUrl extends UploadBase {
}
/**
+ * 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
*
* @param $name string
@@ -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,5 +1,27 @@
<?php
/**
+ * Temporary storage for uploaded files.
+ *
+ * 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
+ */
+
+/**
* UploadStash is intended to accomplish a few things:
* - enable applications to temporarily stash files without publishing them to the wiki.
* - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension
@@ -46,9 +68,11 @@ class UploadStash {
/**
* Represents a temporary filestore, with metadata in the database.
- * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
+ * Designed to be compatible with the session stashing code in UploadBase
+ * (should replace it eventually).
*
* @param $repo FileRepo
+ * @param $user User (default null)
*/
public function __construct( FileRepo $repo, $user = null ) {
// this might change based on wiki's configuration.
@@ -215,10 +239,14 @@ class UploadStash {
}
}
// at this point, $error should contain the single "most important" error, plus any parameters.
- throw new UploadStashFileException( "Error storing file in '$path': " . wfMessage( $error )->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 );
}
}