<?php
/**
+ * 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
+ * @ingroup Upload
+ */
+
+/**
+ * @defgroup Upload Upload related
+ */
+
+/**
+ * @ingroup Upload
*
* UploadBase and subclasses are the backend of MediaWiki's file uploads.
* The frontends are formed by ApiUpload and SpecialUpload.
* @author Bryan Tong Minh
* @author Michael Dale
*/
-
abstract class UploadBase {
protected $mTempPath;
protected $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
const HOOK_ABORTED = 11;
const FILE_TOO_LARGE = 12;
const WINDOWS_NONASCII_FILENAME = 13;
+ const FILENAME_TOO_LONG = 14;
- const SESSION_VERSION = 2;
- const SESSION_KEYNAME = 'wsUploadData';
-
- static public function getSessionKeyname() {
- return self::SESSION_KEYNAME;
- }
-
+ /**
+ * @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',
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] ) ) {
return $code_to_status[$error];
/**
* Returns true if uploads are enabled.
* Can be override by subclasses.
+ * @return bool
*/
public static function isEnabled() {
global $wgEnableUploads;
+
if ( !$wgEnableUploads ) {
return false;
}
# Check php's file_uploads setting
- if( !wfIniGetBool( 'file_uploads' ) ) {
- return false;
- }
- return true;
+ return wfIsHipHop() || wfIniGetBool( 'file_uploads' );
}
/**
* Can be overriden by subclasses.
*
* @param $user User
+ * @return bool
*/
public static function isAllowed( $user ) {
foreach ( array( 'upload', 'edit' ) as $permission ) {
* Create a form of UploadBase depending on wpSourceType and initializes it
*
* @param $request WebRequest
+ * @param $type
+ * @return null
*/
public static function createFromRequest( &$request, $type = null ) {
$type = $type ? $type : $request->getVal( 'wpSourceType', 'File' );
/**
* Check whether a request if valid for this handler
+ * @param $request
+ * @return bool
*/
public static function isValidRequest( $request ) {
return false;
* @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;
+ if ( FileBackend::isStoragePath( $tempPath ) ) {
+ throw new MWException( __METHOD__ . " given storage path `$tempPath`." );
+ }
$this->mTempPath = $tempPath;
$this->mFileSize = $fileSize;
$this->mRemoveTempFile = $removeTempFile;
/**
* Fetch the file. Usually a no-op
+ * @return Status
*/
public function fetchFile() {
return Status::newGood();
return $this->mFileSize;
}
- /**
- * Append a file to the Repo file
- *
- * @param $srcPath String: path to source file
- * @param $toAppendPath String: path to the Repo file that will be appended to.
- * @return Status Status
- */
- protected function appendToUploadFile( $srcPath, $toAppendPath ) {
- $repo = RepoGroup::singleton()->getLocalRepo();
- $status = $repo->append( $srcPath, $toAppendPath );
- return $status;
- }
-
/**
* @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 ) ) {
- return $repo->resolveVirtualUrl( $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;
}
* @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 );
}
*/
$maxSize = self::getMaxUploadSize( $this->getSourceType() );
if( $this->mFileSize > $maxSize ) {
+ wfProfileOut( __METHOD__ );
return array(
'status' => self::FILE_TOO_LARGE,
'max' => $maxSize,
*/
$verification = $this->verifyFile();
if( $verification !== true ) {
+ wfProfileOut( __METHOD__ );
return array(
'status' => self::VERIFICATION_ERROR,
'details' => $verification
*/
$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 );
}
*/
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 );
}
$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;
}
* @return mixed true of the file is verified, array otherwise.
*/
protected function verifyFile() {
- global $wgAllowJavaUploads;
+ global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
+ wfProfileIn( __METHOD__ );
+
# get the title, even though we are doing nothing with it, because
# we need to populate mFinalExtension
$this->getTitle();
- $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
- $this->checkMacBinary();
+ $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
# check mime type, if desired
$mime = $this->mFileProps[ 'file-mime' ];
$status = $this->verifyMimeType( $mime );
if ( $status !== true ) {
+ wfProfileOut( __METHOD__ );
return $status;
}
# check for htmlish code and javascript
- if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
- return array( 'uploadscripted' );
- }
- if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
- if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+ 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' );
+ }
+ }
}
# Check for Java applets, which if uploaded can bypass cross-site
$errors = $zipStatus->getErrorsArray();
$error = reset( $errors );
if ( $error[0] !== 'zip-wrong-format' ) {
+ wfProfileOut( __METHOD__ );
return $error;
}
}
if ( $this->mJavaDetected ) {
+ wfProfileOut( __METHOD__ );
return array( 'uploadjava' );
}
}
# Scan the uploaded file for viruses
$virus = $this->detectVirus( $this->mTempPath );
if ( $virus ) {
+ wfProfileOut( __METHOD__ );
return array( 'uploadvirus', $virus );
}
$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;
}
}
/**
- * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions'
+ * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions'
* but that suggests it's checking the user, when it's really checking the title + user combination.
* @param $user User object to verify the permissions against
* @return mixed An array as returned by getUserPermissionsErrors or true
$permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
$permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user );
if ( !$nt->exists() ) {
- $permErrorsCreate = $nt->getUserPermissionsErrors( 'createpage', $user );
+ $permErrorsCreate = $nt->getUserPermissionsErrors( 'create', $user );
} else {
$permErrorsCreate = array();
}
* @return Array of warnings
*/
public function checkWarnings() {
+ global $wgLang;
+ wfProfileIn( __METHOD__ );
+
$warnings = array();
$localFile = $this->getLocalFile();
global $wgCheckFileExtensions, $wgFileExtensions;
if ( $wgCheckFileExtensions ) {
if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) {
- $warnings['filetype-unwanted-type'] = $this->mFinalExtension;
+ $warnings['filetype-unwanted-type'] = array( $this->mFinalExtension,
+ $wgLang->commaList( $wgFileExtensions ), count( $wgFileExtensions ) );
}
}
}
// Check dupes against existing files
- $hash = File::sha1Base36( $this->mTempPath );
+ $hash = FSFile::getSha1Base36FromPath( $this->mTempPath );
$dupes = RepoGroup::singleton()->findBySha1( $hash );
$title = $this->getTitle();
// Remove all matches against self
$warnings['duplicate-archive'] = $archivedImage->getName();
}
+ wfProfileOut( __METHOD__ );
return $warnings;
}
* 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,
if ( $watch ) {
$user->addWatch( $this->getLocalFile()->getTitle() );
}
-
wfRunHooks( 'UploadComplete', array( &$this ) );
}
+ wfProfileOut( __METHOD__ );
return $status;
}
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.
*/
$title = Title::newFromText( $this->mDesiredDestName );
- if ( $title->getNamespace() == NS_FILE ) {
+ if ( $title && $title->getNamespace() == NS_FILE ) {
$this->mFilteredName = $title->getDBkey();
} else {
$this->mFilteredName = $this->mDesiredDestName;
}
+ # oi_archive_name is max 255 bytes, which include a timestamp and an
+ # exclamation mark, so restrict file name to 240 bytes.
+ if ( strlen( $this->mFilteredName ) > 240 ) {
+ $this->mTitleError = self::FILENAME_TOO_LONG;
+ return $this->mTitle = null;
+ }
+
/**
* Chop off any directories in the given filename. Then
* filter out illegal characters, and try to make a legible name
}
$this->mFilteredName = $nt->getDBkey();
+
+
/**
* We'll want to blacklist against *any* 'extension', and use
* only the final one for the whitelist.
return $this->mTitle = null;
} elseif ( $blackListedExtensions ||
( $wgCheckFileExtensions && $wgStrictFileExtensions &&
- !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) {
+ !$this->checkFileExtensionList( $ext, $wgFileExtensions ) ) ) {
$this->mBlackListedExtensions = $blackListedExtensions;
$this->mTitleError = self::FILETYPE_BADTYPE;
return $this->mTitle = null;
}
-
+
// Windows may be broken with special characters, see bug XXX
if ( wfIsWindows() && !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() ) ) {
$this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
/**
* Return the local file and initializes if necessary.
*
- * @return LocalFile
+ * @return LocalFile|null
*/
public function getLocalFile() {
if( is_null( $this->mLocalFile ) ) {
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.
*
* This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
- * into the user's session.
- * This method returns the file object, which also has a 'sessionKey' property which can be passed through a form or
+ * into the database.
+ * This method returns the file object, which also has a 'fileKey' property which can be passed through a form or
* API request to find this stashed file again.
*
- * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
* @return UploadStashFile stashed file
*/
- public function stashSessionFile( $key = null ) {
+ public function stashFile() {
+ // was stashSessionFile
+ wfProfileIn( __METHOD__ );
+
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
- $data = array(
- 'mFileProps' => $this->mFileProps,
- 'mSourceType' => $this->getSourceType(),
- );
- $file = $stash->stashFile( $this->mTempPath, $data, $key );
+ $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
$this->mLocalFile = $file;
+
+ wfProfileOut( __METHOD__ );
return $file;
}
/**
- * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashSessionFile().
+ * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashFile().
+ *
+ * @return String: file key
+ */
+ public function stashFileGetKey() {
+ return $this->stashFile()->getFileKey();
+ }
+
+ /**
+ * alias for stashFileGetKey, for backwards compatibility
*
- * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
- * @return String: session key
+ * @return String: file key
*/
- public function stashSession( $key = null ) {
- return $this->stashSessionFile( $key )->getSessionKey();
+ public function stashSession() {
+ return $this->stashFileGetKey();
}
/**
* 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 ) {
*/
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.
$chunk = strtolower( $chunk );
if( !$chunk ) {
+ wfProfileOut( __METHOD__ );
return false;
}
$chunk = trim( $chunk );
- # FIXME: convert from UTF-16 if necessarry!
+ # @todo FIXME: Convert from UTF-16 if necessarry!
wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff\n" );
# check for HTML doctype
if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
+ wfProfileOut( __METHOD__ );
return true;
}
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;
}
}
# 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;
/**
* @todo Replace this with a whitelist filter!
+ * @param $element string
+ * @param $attribs array
+ * @return bool
*/
public function checkSvgScriptCallback( $element, $attribs ) {
- $stripped = $this->stripXmlNamespace( $element );
+ $strippedElement = $this->stripXmlNamespace( $element );
- if( $stripped == 'script' ) {
+ /*
+ * check for elements that can contain javascript
+ */
+ 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' ) {
+ 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' ) {
+ wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
+ return true;
+ }
+
foreach( $attribs as $attrib => $value ) {
$stripped = $this->stripXmlNamespace( $attrib );
+ $value = strtolower($value);
+
if( substr( $stripped, 0, 2 ) == 'on' ) {
- wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" );
+ 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 ) {
- wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" );
+ wfDebug( __METHOD__ . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" );
+ return true;
+ }
+
+ # href with embeded svg as target
+ 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 embeded (text/xml) svg as target
+ 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' ) {
+ 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 ) {
+ wfDebug( __METHOD__ . ": Found svg setting href attibute '$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 ) ) {
+ wfDebug( __METHOD__ . ": Found svg setting attibute 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 ) ) {
+ 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 ) ) {
+ 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" );
+ return true;
+ }
+ }
+ }
+
+ # image filters can pull in url, which could be svg that executes scripts
+ 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;
+ }
+
}
+
+ 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 ) );
*/
public static function detectVirus( $file ) {
global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
+ wfProfileIn( __METHOD__ );
if ( !$wgAntivirus ) {
wfDebug( __METHOD__ . ": virus scanner disabled\n" );
+ wfProfileOut( __METHOD__ );
return null;
}
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
wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
if ( $wgAntivirusRequired ) {
+ wfProfileOut( __METHOD__ );
return wfMsg( 'virus-scanfailed', array( $exitCode ) );
} 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 );
}
wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" );
+ wfProfileOut( __METHOD__ );
return $output;
}
}
- /**
- * Check if the temporary file is MacBinary-encoded, as some uploads
- * from Internet Explorer on Mac OS Classic and Mac OS X will be.
- * If so, the data fork will be extracted to a second temporary file,
- * which will then be checked for validity and either kept or discarded.
- */
- private function checkMacBinary() {
- $macbin = new MacBinary( $this->mTempPath );
- if( $macbin->isValid() ) {
- $dataFile = tempnam( wfTempDir(), 'WikiMacBinary' );
- $dataHandle = fopen( $dataFile, 'wb' );
-
- wfDebug( __METHOD__ . ": Extracting MacBinary data fork to $dataFile\n" );
- $macbin->extractData( $dataHandle );
-
- $this->mTempPath = $dataFile;
- $this->mFileSize = $macbin->dataForkLength();
-
- // We'll have to manually remove the new file if it's not kept.
- $this->mRemoveTempFile = true;
- }
- $macbin->close();
- }
-
/**
* Check if there's an overwrite conflict and, if so, if restrictions
* forbid this user from performing the upload.
/**
* Helper function that checks whether the filename looks like a thumbnail
+ * @param $filename string
+ * @return bool
*/
public static function isThumbName( $filename ) {
$n = strrpos( $filename, '.' );
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;