summaryrefslogtreecommitdiff
path: root/includes/upload
diff options
context:
space:
mode:
Diffstat (limited to 'includes/upload')
-rw-r--r--includes/upload/UploadBase.php202
-rw-r--r--includes/upload/UploadFromChunks.php26
-rw-r--r--includes/upload/UploadFromFile.php2
-rw-r--r--includes/upload/UploadFromStash.php6
-rw-r--r--includes/upload/UploadFromUrl.php49
-rw-r--r--includes/upload/UploadStash.php29
6 files changed, 178 insertions, 136 deletions
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index e72669d4..2260241d 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -72,19 +72,20 @@ abstract class UploadBase {
* @return string
*/
public function getVerificationErrorCode( $error ) {
- $code_to_status = array(self::EMPTY_FILE => 'empty-file',
- self::FILE_TOO_LARGE => 'file-too-large',
- self::FILETYPE_MISSING => 'filetype-missing',
- self::FILETYPE_BADTYPE => 'filetype-banned',
- self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
- self::ILLEGAL_FILENAME => 'illegal-filename',
- self::OVERWRITE_EXISTING_FILE => 'overwrite',
- self::VERIFICATION_ERROR => 'verification-error',
- self::HOOK_ABORTED => 'hookaborted',
- self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
- self::FILENAME_TOO_LONG => 'filename-toolong',
+ $code_to_status = array(
+ self::EMPTY_FILE => 'empty-file',
+ self::FILE_TOO_LARGE => 'file-too-large',
+ self::FILETYPE_MISSING => 'filetype-missing',
+ self::FILETYPE_BADTYPE => 'filetype-banned',
+ self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
+ self::ILLEGAL_FILENAME => 'illegal-filename',
+ self::OVERWRITE_EXISTING_FILE => 'overwrite',
+ self::VERIFICATION_ERROR => 'verification-error',
+ self::HOOK_ABORTED => 'hookaborted',
+ self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
+ self::FILENAME_TOO_LONG => 'filename-toolong',
);
- if( isset( $code_to_status[$error] ) ) {
+ if ( isset( $code_to_status[$error] ) ) {
return $code_to_status[$error];
}
@@ -137,7 +138,7 @@ abstract class UploadBase {
public static function createFromRequest( &$request, $type = null ) {
$type = $type ? $type : $request->getVal( 'wpSourceType', 'File' );
- if( !$type ) {
+ if ( !$type ) {
return null;
}
@@ -150,18 +151,18 @@ abstract class UploadBase {
if ( is_null( $className ) ) {
$className = 'UploadFrom' . $type;
wfDebug( __METHOD__ . ": class name: $className\n" );
- if( !in_array( $type, self::$uploadHandlers ) ) {
+ if ( !in_array( $type, self::$uploadHandlers ) ) {
return null;
}
}
// Check whether this upload class is enabled
- if( !call_user_func( array( $className, 'isEnabled' ) ) ) {
+ if ( !call_user_func( array( $className, 'isEnabled' ) ) ) {
return null;
}
// Check whether the request is valid
- if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) {
+ if ( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) {
return null;
}
@@ -188,7 +189,9 @@ abstract class UploadBase {
* @since 1.18
* @return string
*/
- public function getSourceType() { return null; }
+ public function getSourceType() {
+ return null;
+ }
/**
* Initialize the path information
@@ -253,7 +256,7 @@ abstract class UploadBase {
wfProfileIn( __METHOD__ );
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->isVirtualUrl( $srcPath ) ) {
- // @TODO: just make uploads work with storage paths
+ // @todo just make uploads work with storage paths
// UploadFromStash loads files via virtual URLs
$tmpFile = $repo->getLocalCopy( $srcPath );
$tmpFile->bind( $this ); // keep alive with $this
@@ -274,7 +277,7 @@ abstract class UploadBase {
/**
* If there was no filename or a zero size given, give up quick.
*/
- if( $this->isEmptyFile() ) {
+ if ( $this->isEmptyFile() ) {
wfProfileOut( __METHOD__ );
return array( 'status' => self::EMPTY_FILE );
}
@@ -283,7 +286,7 @@ abstract class UploadBase {
* Honor $wgMaxUploadSize
*/
$maxSize = self::getMaxUploadSize( $this->getSourceType() );
- if( $this->mFileSize > $maxSize ) {
+ if ( $this->mFileSize > $maxSize ) {
wfProfileOut( __METHOD__ );
return array(
'status' => self::FILE_TOO_LARGE,
@@ -297,7 +300,7 @@ abstract class UploadBase {
* probably not accept it.
*/
$verification = $this->verifyFile();
- if( $verification !== true ) {
+ if ( $verification !== true ) {
wfProfileOut( __METHOD__ );
return array(
'status' => self::VERIFICATION_ERROR,
@@ -309,13 +312,13 @@ abstract class UploadBase {
* Make sure this file can be created
*/
$result = $this->validateName();
- if( $result !== true ) {
+ if ( $result !== true ) {
wfProfileOut( __METHOD__ );
return $result;
}
$error = '';
- if( !wfRunHooks( 'UploadVerification',
+ if ( !wfRunHooks( 'UploadVerification',
array( $this->mDestName, $this->mTempPath, &$error ) ) )
{
wfProfileOut( __METHOD__ );
@@ -332,11 +335,11 @@ abstract class UploadBase {
* @return mixed true if valid, otherwise and array with 'status'
* and other keys
**/
- protected function validateName() {
+ public function validateName() {
$nt = $this->getTitle();
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
$result = array( 'status' => $this->mTitleError );
- if( $this->mTitleError == self::ILLEGAL_FILENAME ) {
+ if ( $this->mTitleError == self::ILLEGAL_FILENAME ) {
$result['filtered'] = $this->mFilteredName;
}
if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
@@ -353,7 +356,7 @@ abstract class UploadBase {
}
/**
- * Verify the mime type
+ * Verify the mime type.
*
* @note Only checks that it is not an evil mime. The does it have
* correct extension given its mime type check is in verifyFile.
@@ -364,7 +367,7 @@ abstract class UploadBase {
global $wgVerifyMimeType;
wfProfileIn( __METHOD__ );
if ( $wgVerifyMimeType ) {
- wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n" );
+ wfDebug( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n" );
global $wgMimeTypeBlacklist;
if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
wfProfileOut( __METHOD__ );
@@ -391,6 +394,7 @@ abstract class UploadBase {
return true;
}
+
/**
* Verifies that it's ok to include the uploaded file
*
@@ -417,6 +421,7 @@ abstract class UploadBase {
}
}
+
$handler = MediaHandler::getHandler( $mime );
if ( $handler ) {
$handlerStatus = $handler->verifyUpload( $this->mTempPath );
@@ -450,8 +455,7 @@ abstract class UploadBase {
global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
wfProfileIn( __METHOD__ );
- # get the title, even though we are doing nothing with it, because
- # we need to populate mFinalExtension
+ # getTitle() sets some internal parameters like $this->mFinalExtension
$this->getTitle();
$this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
@@ -466,12 +470,12 @@ abstract class UploadBase {
# check for htmlish code and javascript
if ( !$wgDisableUploadScriptChecks ) {
- if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
+ 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 ) ) {
+ if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
+ if ( $this->detectScriptInSvg( $this->mTempPath ) ) {
wfProfileOut( __METHOD__ );
return array( 'uploadscripted' );
}
@@ -560,7 +564,7 @@ abstract class UploadBase {
* to modify it by uploading a new revision.
*/
$nt = $this->getTitle();
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
return true;
}
$permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
@@ -570,7 +574,7 @@ abstract class UploadBase {
} else {
$permErrorsCreate = array();
}
- if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
+ if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
return $permErrors;
@@ -607,16 +611,17 @@ abstract class UploadBase {
$comparableName = str_replace( ' ', '_', $this->mDesiredDestName );
$comparableName = Title::capitalize( $comparableName, NS_FILE );
- if( $this->mDesiredDestName != $filename && $comparableName != $filename ) {
+ if ( $this->mDesiredDestName != $filename && $comparableName != $filename ) {
$warnings['badfilename'] = $filename;
}
// Check whether the file extension is on the unwanted list
global $wgCheckFileExtensions, $wgFileExtensions;
if ( $wgCheckFileExtensions ) {
- if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) {
+ $extensions = array_unique( $wgFileExtensions );
+ if ( !$this->checkFileExtension( $this->mFinalExtension, $extensions ) ) {
$warnings['filetype-unwanted-type'] = array( $this->mFinalExtension,
- $wgLang->commaList( $wgFileExtensions ), count( $wgFileExtensions ) );
+ $wgLang->commaList( $extensions ), count( $extensions ) );
}
}
@@ -630,7 +635,7 @@ abstract class UploadBase {
}
$exists = self::getExistsWarning( $localFile );
- if( $exists !== false ) {
+ if ( $exists !== false ) {
$warnings['exists'] = $exists;
}
@@ -640,11 +645,11 @@ abstract class UploadBase {
$title = $this->getTitle();
// Remove all matches against self
foreach ( $dupes as $key => $dupe ) {
- if( $title->equals( $dupe->getTitle() ) ) {
+ if ( $title->equals( $dupe->getTitle() ) ) {
unset( $dupes[$key] );
}
}
- if( $dupes ) {
+ if ( $dupes ) {
$warnings['duplicate'] = $dupes;
}
@@ -682,9 +687,9 @@ abstract class UploadBase {
$user
);
- if( $status->isGood() ) {
+ if ( $status->isGood() ) {
if ( $watch ) {
- $user->addWatch( $this->getLocalFile()->getTitle() );
+ WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS );
}
wfRunHooks( 'UploadComplete', array( &$this ) );
}
@@ -703,7 +708,6 @@ abstract class UploadBase {
if ( $this->mTitle !== false ) {
return $this->mTitle;
}
-
/* Assume that if a user specified File:Something.jpg, this is an error
* and that the namespace prefix needs to be stripped of.
*/
@@ -729,7 +733,7 @@ abstract class UploadBase {
$this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName );
/* Normalize to title form before we do any further processing */
$nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
$this->mTitleError = self::ILLEGAL_FILENAME;
return $this->mTitle = null;
}
@@ -741,7 +745,7 @@ abstract class UploadBase {
*/
list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName );
- if( count( $ext ) ) {
+ if ( count( $ext ) ) {
$this->mFinalExtension = trim( $ext[count( $ext ) - 1] );
} else {
$this->mFinalExtension = '';
@@ -789,13 +793,13 @@ abstract class UploadBase {
# If there was more than one "extension", reassemble the base
# filename to prevent bogus complaints about length
- if( count( $ext ) > 1 ) {
- for( $i = 0; $i < count( $ext ) - 1; $i++ ) {
+ if ( count( $ext ) > 1 ) {
+ for ( $i = 0; $i < count( $ext ) - 1; $i++ ) {
$partname .= '.' . $ext[$i];
}
}
- if( strlen( $partname ) < 1 ) {
+ if ( strlen( $partname ) < 1 ) {
$this->mTitleError = self::MIN_LENGTH_PARTNAME;
return $this->mTitle = null;
}
@@ -809,7 +813,7 @@ abstract class UploadBase {
* @return LocalFile|null
*/
public function getLocalFile() {
- if( is_null( $this->mLocalFile ) ) {
+ if ( is_null( $this->mLocalFile ) ) {
$nt = $this->getTitle();
$this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt );
}
@@ -922,23 +926,29 @@ abstract class UploadBase {
public static function verifyExtension( $mime, $extension ) {
$magic = MimeMagic::singleton();
- if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
+ if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) {
if ( !$magic->isRecognizableExtension( $extension ) ) {
wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
"unrecognized extension '$extension', can't verify\n" );
return true;
} else {
- wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; ".
+ wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
"recognized extension '$extension', so probably invalid file\n" );
return false;
}
+ }
$match = $magic->isMatchingExtension( $extension, $mime );
if ( $match === null ) {
- wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
- return true;
- } elseif( $match === true ) {
+ if ( $magic->getTypesForExtension( $extension ) !== null ) {
+ wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension\n" );
+ return false;
+ } else {
+ wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
+ return true;
+ }
+ } elseif ( $match === true ) {
wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file\n" );
#TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
@@ -968,7 +978,7 @@ abstract class UploadBase {
# ugly hack: for text files, always look at the entire file.
# For binary field, just check the first K.
- if( strpos( $mime, 'text/' ) === 0 ) {
+ if ( strpos( $mime, 'text/' ) === 0 ) {
$chunk = file_get_contents( $file );
} else {
$fp = fopen( $file, 'rb' );
@@ -978,21 +988,21 @@ abstract class UploadBase {
$chunk = strtolower( $chunk );
- if( !$chunk ) {
+ if ( !$chunk ) {
wfProfileOut( __METHOD__ );
return false;
}
# decode from UTF-16 if needed (could be used for obfuscation).
- if( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
+ if ( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
$enc = 'UTF-16BE';
- } elseif( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
+ } elseif ( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
$enc = 'UTF-16LE';
} else {
$enc = null;
}
- if( $enc ) {
+ if ( $enc ) {
$chunk = iconv( $enc, "ASCII//IGNORE", $chunk );
}
@@ -1042,12 +1052,12 @@ abstract class UploadBase {
'<table'
);
- if( !$wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
+ if ( !$wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
$tags[] = '<title';
}
- foreach( $tags as $tag ) {
- if( false !== strpos( $chunk, $tag ) ) {
+ 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;
@@ -1062,21 +1072,21 @@ abstract class UploadBase {
$chunk = Sanitizer::decodeCharReferences( $chunk );
# look for script-types
- if( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
+ 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 ) ) {
+ 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 ) ) {
+ if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found css-style script urls\n" );
wfProfileOut( __METHOD__ );
return true;
@@ -1112,7 +1122,7 @@ abstract class UploadBase {
// bytes. There shouldn't be a legitimate reason for this to happen.
wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
return true;
- } elseif ( substr( $contents, 0, 4) == "\x4C\x6F\xA7\x94" ) {
+ } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
// EBCDIC encoded XML
wfDebug( __METHOD__ . ": EBCDIC Encoded XML\n" );
return true;
@@ -1164,77 +1174,77 @@ abstract class UploadBase {
/*
* check for elements that can contain javascript
*/
- if( $strippedElement == 'script' ) {
+ if ( $strippedElement == 'script' ) {
wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
return true;
}
# e.g., <svg xmlns="http://www.w3.org/2000/svg"> <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
- if( $strippedElement == 'handler' ) {
+ if ( $strippedElement == 'handler' ) {
wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
return true;
}
# SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
- if( $strippedElement == 'stylesheet' ) {
+ if ( $strippedElement == 'stylesheet' ) {
wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
return true;
}
- foreach( $attribs as $attrib => $value ) {
+ foreach ( $attribs as $attrib => $value ) {
$stripped = $this->stripXmlNamespace( $attrib );
$value = strtolower( $value );
- if( substr( $stripped, 0, 2 ) == 'on' ) {
+ if ( substr( $stripped, 0, 2 ) == 'on' ) {
wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
return true;
}
# href with javascript target
- if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
+ if ( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
wfDebug( __METHOD__ . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" );
return true;
}
# href with embedded svg as target
- if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) {
+ if ( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
return true;
}
# href with embedded (text/xml) svg as target
- if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) {
+ if ( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
return true;
}
# use set/animate to add event-handler attribute to parent
- if( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) {
+ if ( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) {
wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
return true;
}
# use set to add href attribute to parent element
- if( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
+ if ( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" );
return true;
}
# use set to add a remote / data / script target to an element
- if( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
+ if ( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" );
return true;
}
# use handler attribute with remote / data / script
- if( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
+ 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;
}
# use CSS styles to bring in remote code
# catch url("http:..., url('http:..., url(http:..., but not url("#..., url('#..., url(#....
- if( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) {
+ if ( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) {
foreach ( $matches[1] as $match ) {
if ( !preg_match( '!(?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) {
wfDebug( __METHOD__ . ": Found svg setting a style with remote url '$attrib'='$value' in uploaded file.\n" );
@@ -1244,7 +1254,7 @@ abstract class UploadBase {
}
# image filters can pull in url, which could be svg that executes scripts
- if( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) {
+ if ( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found image filter with url: \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
return true;
}
@@ -1314,7 +1324,7 @@ abstract class UploadBase {
# NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
# that does not seem to be worth the pain.
# Ask me (Duesentrieb) about it if it's ever needed.
- $output = wfShellExec( "$command 2>&1", $exitCode );
+ $output = wfShellExecWithStderr( $command, $exitCode );
# map exit code to AV_xxx constants.
$mappedCode = $exitCode;
@@ -1374,8 +1384,8 @@ abstract class UploadBase {
private function checkOverwrite( $user ) {
// First check whether the local file can be overwritten
$file = $this->getLocalFile();
- if( $file->exists() ) {
- if( !self::userCanReUpload( $user, $file ) ) {
+ if ( $file->exists() ) {
+ if ( !self::userCanReUpload( $user, $file ) ) {
return array( 'fileexists-forbidden', $file->getName() );
} else {
return true;
@@ -1401,13 +1411,13 @@ abstract class UploadBase {
* @return Boolean
*/
public static function userCanReUpload( User $user, $img ) {
- if( $user->isAllowed( 'reupload' ) ) {
+ if ( $user->isAllowed( 'reupload' ) ) {
return true; // non-conditional
}
- if( !$user->isAllowed( 'reupload-own' ) ) {
+ if ( !$user->isAllowed( 'reupload-own' ) ) {
return false;
}
- if( is_string( $img ) ) {
+ if ( is_string( $img ) ) {
$img = wfLocalFile( $img );
}
if ( !( $img instanceof LocalFile ) ) {
@@ -1429,11 +1439,11 @@ abstract class UploadBase {
* @return mixed False if the file does not exists, else an array
*/
public static function getExistsWarning( $file ) {
- if( $file->exists() ) {
+ if ( $file->exists() ) {
return array( 'warning' => 'exists', 'file' => $file );
}
- if( $file->getTitle()->getArticleID() ) {
+ if ( $file->getTitle()->getArticleID() ) {
return array( 'warning' => 'page-exists', 'file' => $file );
}
@@ -1441,7 +1451,7 @@ abstract class UploadBase {
return array( 'warning' => 'was-deleted', 'file' => $file );
}
- if( strpos( $file->getName(), '.' ) == false ) {
+ if ( strpos( $file->getName(), '.' ) == false ) {
$partname = $file->getName();
$extension = '';
} else {
@@ -1460,7 +1470,7 @@ abstract class UploadBase {
$nt_lc = Title::makeTitle( NS_FILE, "{$partname}.{$normalizedExtension}" );
$file_lc = wfLocalFile( $nt_lc );
- if( $file_lc->exists() ) {
+ if ( $file_lc->exists() ) {
return array(
'warning' => 'exists-normalized',
'file' => $file,
@@ -1484,7 +1494,7 @@ abstract class UploadBase {
# Check for filenames like 50px- or 180px-, these are mostly thumbnails
$nt_thb = Title::newFromText( substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension, NS_FILE );
$file_thb = wfLocalFile( $nt_thb );
- if( $file_thb->exists() ) {
+ if ( $file_thb->exists() ) {
return array(
'warning' => 'thumb',
'file' => $file,
@@ -1500,7 +1510,7 @@ abstract class UploadBase {
}
}
- foreach( self::getFilenamePrefixBlacklist() as $prefix ) {
+ foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
return array(
'warning' => 'bad-prefix',
@@ -1536,9 +1546,9 @@ abstract class UploadBase {
public static function getFilenamePrefixBlacklist() {
$blacklist = array();
$message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage();
- if( !$message->isDisabled() ) {
+ if ( !$message->isDisabled() ) {
$lines = explode( "\n", $message->plain() );
- foreach( $lines as $line ) {
+ foreach ( $lines as $line ) {
// Remove comment lines
$comment = substr( trim( $line ), 0, 1 );
if ( $comment == '#' || $comment == '' ) {
@@ -1547,7 +1557,7 @@ abstract class UploadBase {
// Remove additional comments after a prefix
$comment = strpos( $line, '#' );
if ( $comment > 0 ) {
- $line = substr( $line, 0, $comment-1 );
+ $line = substr( $line, 0, $comment - 1 );
}
$blacklist[] = trim( $line );
}
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
index 4b331e98..2e0b9444 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -31,7 +31,7 @@ class UploadFromChunks extends UploadFromFile {
protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
/**
- * Setup local pointers to stash, repo and user ( similar to UploadFromStash )
+ * Setup local pointers to stash, repo and user (similar to UploadFromStash)
*
* @param $user User
* @param $stash UploadStash
@@ -41,16 +41,16 @@ class UploadFromChunks extends UploadFromFile {
// user object. sometimes this won't exist, as when running from cron.
$this->user = $user;
- if( $repo ) {
+ if ( $repo ) {
$this->repo = $repo;
} else {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
- if( $stash ) {
+ if ( $stash ) {
$this->stash = $stash;
} else {
- if( $user ) {
+ if ( $user ) {
wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" );
} else {
wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" );
@@ -74,7 +74,7 @@ class UploadFromChunks extends UploadFromFile {
$this->verifyChunk();
// 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();
@@ -114,7 +114,7 @@ class UploadFromChunks extends UploadFromFile {
// Concatenate all the chunks to mVirtualTempPath
$fileList = Array();
// The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1"
- for( $i = 0; $i <= $this->getChunkIndex(); $i++ ) {
+ for ( $i = 0; $i <= $this->getChunkIndex(); $i++ ) {
$fileList[] = $this->getVirtualChunkLocation( $i );
}
@@ -129,7 +129,7 @@ class UploadFromChunks extends UploadFromFile {
$tStart = microtime( true );
$status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE );
$tAmount = microtime( true ) - $tStart;
- if( !$status->isOk() ) {
+ if ( !$status->isOk() ) {
return $status;
}
wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds.\n" );
@@ -144,7 +144,7 @@ class UploadFromChunks extends UploadFromFile {
}
// Update the mTempPath and mLocalFile
- // ( for FileUpload or normal Stash to take over )
+ // (for FileUpload or normal Stash to take over)
$tStart = microtime( true );
$this->mLocalFile = parent::stashFile( $this->user );
$tAmount = microtime( true ) - $tStart;
@@ -193,7 +193,7 @@ class UploadFromChunks extends UploadFromFile {
// Get the offset before we add the chunk to the file system
$preAppendOffset = $this->getOffset();
- if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) {
+ 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.
@@ -210,7 +210,7 @@ class UploadFromChunks extends UploadFromFile {
return Status::newFatal( $e->getMessage() );
}
$status = $this->outputChunk( $chunkPath );
- if( $status->isGood() ) {
+ if ( $status->isGood() ) {
// Update local offset:
$this->mOffset = $preAppendOffset + $chunkSize;
// Update chunk table status db
@@ -277,7 +277,7 @@ class UploadFromChunks extends UploadFromFile {
* @return Integer index of the current chunk
*/
private function getChunkIndex() {
- if( $this->mChunkIndex !== null ) {
+ if ( $this->mChunkIndex !== null ) {
return $this->mChunkIndex;
}
return 0;
@@ -327,7 +327,7 @@ class UploadFromChunks extends UploadFromFile {
}
private function getChunkFileKey( $index = null ) {
- if( $index === null ) {
+ if ( $index === null ) {
$index = $this->getChunkIndex();
}
return $this->mFileKey . '.' . $index;
@@ -346,7 +346,7 @@ class UploadFromChunks extends UploadFromFile {
$res = $this->verifyPartialFile();
$this->mDesiredDestName = $oldDesiredDestName;
$this->mTitle = false;
- if( is_array( $res ) ) {
+ if ( is_array( $res ) ) {
throw new UploadChunkVerificationException( $res[0] );
}
}
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index ab2a7a39..a00ed327 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -40,7 +40,7 @@ class UploadFromFile extends UploadBase {
function initializeFromRequest( &$request ) {
$upload = $request->getUpload( 'wpUploadFile' );
$desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName ) {
+ if ( !$desiredDestName ) {
$desiredDestName = $upload->getName();
}
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index c82103cb..cb85fc63 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -45,16 +45,16 @@ class UploadFromStash extends UploadBase {
// user object. sometimes this won't exist, as when running from cron.
$this->user = $user;
- if( $repo ) {
+ if ( $repo ) {
$this->repo = $repo;
} else {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
- if( $stash ) {
+ if ( $stash ) {
$this->stash = $stash;
} else {
- if( $user ) {
+ if ( $user ) {
wfDebug( __METHOD__ . " creating new UploadStash instance for " . $user->getId() . "\n" );
} else {
wfDebug( __METHOD__ . " creating new UploadStash instance with no user\n" );
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index 70b69034..0201d5f4 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -34,6 +34,8 @@ class UploadFromUrl extends UploadBase {
protected $mTempPath, $mTmpHandle;
+ protected static $allowedUrls = array();
+
/**
* Checks if the user is allowed to use the upload-by-URL feature. If the
* user is not allowed, return the name of the user right as a string. If
@@ -77,7 +79,7 @@ class UploadFromUrl extends UploadBase {
return false;
}
$valid = false;
- foreach( $wgCopyUploadsDomains as $domain ) {
+ foreach ( $wgCopyUploadsDomains as $domain ) {
// See if the domain for the upload matches this whitelisted domain
$whitelistedDomainPieces = explode( '.', $domain );
$uploadDomainPieces = explode( '.', $parsedUrl['host'] );
@@ -105,6 +107,21 @@ class UploadFromUrl extends UploadBase {
}
/**
+ * Checks whether the URL is not allowed.
+ *
+ * @param $url string
+ * @return bool
+ */
+ public static function isAllowedUrl( $url ) {
+ if ( !isset( self::$allowedUrls[$url] ) ) {
+ $allowed = true;
+ wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
+ self::$allowedUrls[$url] = $allowed;
+ }
+ return self::$allowedUrls[$url];
+ }
+
+ /**
* Entry point for API upload
*
* @param $name string
@@ -160,21 +177,30 @@ class UploadFromUrl extends UploadBase {
/**
* @return string
*/
- public function getSourceType() { return 'url'; }
+ public function getSourceType() {
+ return 'url';
+ }
/**
+ * Download the file (if not async)
+ *
+ * @param Array $httpOptions Array of options for MWHttpRequest. Ignored if async.
+ * This could be used to override the timeout on the http request.
* @return Status
*/
- public function fetchFile() {
+ public function fetchFile( $httpOptions = array() ) {
if ( !Http::isValidURI( $this->mUrl ) ) {
return Status::newFatal( 'http-invalid-url' );
}
- if( !self::isAllowedHost( $this->mUrl ) ) {
+ if ( !self::isAllowedHost( $this->mUrl ) ) {
return Status::newFatal( 'upload-copy-upload-invalid-domain' );
}
+ if ( !self::isAllowedUrl( $this->mUrl ) ) {
+ return Status::newFatal( 'upload-copy-upload-invalid-url' );
+ }
if ( !$this->mAsync ) {
- return $this->reallyFetchFile();
+ return $this->reallyFetchFile( $httpOptions );
}
return Status::newGood();
}
@@ -211,9 +237,12 @@ class UploadFromUrl extends UploadBase {
/**
* Download the file, save it to the temporary file and update the file
* size and set $mRemoveTempFile to true.
+ *
+ * @param Array $httpOptions Array of options for MWHttpRequest
* @return Status
*/
- protected function reallyFetchFile() {
+ protected function reallyFetchFile( $httpOptions = array() ) {
+ global $wgCopyUploadProxy, $wgCopyUploadTimeout;
if ( $this->mTempPath === false ) {
return Status::newFatal( 'tmp-create-error' );
}
@@ -227,13 +256,15 @@ class UploadFromUrl extends UploadBase {
$this->mRemoveTempFile = true;
$this->mFileSize = 0;
- $options = array(
- 'followRedirects' => true
+ $options = $httpOptions + array(
+ 'followRedirects' => true,
);
- global $wgCopyUploadProxy;
if ( $wgCopyUploadProxy !== false ) {
$options['proxy'] = $wgCopyUploadProxy;
}
+ if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) {
+ $options['timeout'] = $wgCopyUploadTimeout;
+ }
$req = MWHttpRequest::factory( $this->mUrl, $options );
$req->setCallback( array( $this, 'saveTempFileChunk' ) );
$status = $req->execute();
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 089bd8b7..7db6c64b 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -158,7 +158,7 @@ class UploadStash {
* @param string $key key under which file information is stored
* @return Array
*/
- public function getMetadata ( $key ) {
+ public function getMetadata( $key ) {
$this->getFile( $key );
return $this->fileMetadata[$key];
}
@@ -169,7 +169,7 @@ class UploadStash {
* @param string $key key under which file information is stored
* @return Array
*/
- public function getFileProps ( $key ) {
+ public function getFileProps( $key ) {
$this->getFile( $key );
return $this->fileProps[$key];
}
@@ -209,7 +209,7 @@ class UploadStash {
list( $usec, $sec ) = explode( ' ', microtime() );
$usec = substr( $usec, 2 );
$key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' .
- wfBaseConvert( mt_rand(), 10, 36 ) . '.'.
+ wfBaseConvert( mt_rand(), 10, 36 ) . '.' .
$this->userId . '.' .
$extension;
@@ -338,7 +338,7 @@ class UploadStash {
__METHOD__
);
- if( !$row ) {
+ if ( !$row ) {
throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" );
}
@@ -358,7 +358,7 @@ class UploadStash {
wfDebug( __METHOD__ . " clearing row $key\n" );
// Ensure we have the UploadStashFile loaded for this key
- $this->getFile( $key );
+ $this->getFile( $key, true );
$dbw = $this->repo->getMasterDb();
@@ -463,7 +463,7 @@ class UploadStash {
protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) {
// populate $fileMetadata[$key]
$dbr = null;
- if( $readFromDB === DB_MASTER ) {
+ if ( $readFromDB === DB_MASTER ) {
// sometimes reading from the master is necessary, if there's replication lag.
$dbr = $this->repo->getMasterDb();
} else {
@@ -675,11 +675,12 @@ class UploadStashFile extends UnregisteredLocalFile {
}
-class UploadStashNotAvailableException extends MWException {};
-class UploadStashFileNotFoundException extends MWException {};
-class UploadStashBadPathException extends MWException {};
-class UploadStashFileException extends MWException {};
-class UploadStashZeroLengthFileException extends MWException {};
-class UploadStashNotLoggedInException extends MWException {};
-class UploadStashWrongOwnerException extends MWException {};
-class UploadStashNoSuchKeyException extends MWException {};
+class UploadStashException extends MWException {};
+class UploadStashNotAvailableException extends UploadStashException {};
+class UploadStashFileNotFoundException extends UploadStashException {};
+class UploadStashBadPathException extends UploadStashException {};
+class UploadStashFileException extends UploadStashException {};
+class UploadStashZeroLengthFileException extends UploadStashException {};
+class UploadStashNotLoggedInException extends UploadStashException {};
+class UploadStashWrongOwnerException extends UploadStashException {};
+class UploadStashNoSuchKeyException extends UploadStashException {};