32use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
46use Wikimedia\AtEase\AtEase;
71 use ProtectedHookAccessorTrait;
88 protected $mTitleError = 0;
108 private const SAFE_XML_ENCODINGS = [
139 private const CODE_TO_STATUS = [
140 self::EMPTY_FILE =>
'empty-file',
141 self::FILE_TOO_LARGE =>
'file-too-large',
142 self::FILETYPE_MISSING =>
'filetype-missing',
143 self::FILETYPE_BADTYPE =>
'filetype-banned',
144 self::MIN_LENGTH_PARTNAME =>
'filename-tooshort',
145 self::ILLEGAL_FILENAME =>
'illegal-filename',
146 self::VERIFICATION_ERROR =>
'verification-error',
147 self::HOOK_ABORTED =>
'hookaborted',
148 self::WINDOWS_NONASCII_FILENAME =>
'windows-nonascii-filename',
149 self::FILENAME_TOO_LONG =>
'filename-toolong',
157 return self::CODE_TO_STATUS[$error] ??
'unknown-error';
167 $enableUploads = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnableUploads );
169 return $enableUploads &&
wfIniGetBool(
'file_uploads' );
181 foreach ( [
'upload',
'edit' ] as $permission ) {
182 if ( !$performer->
isAllowed( $permission ) ) {
201 return $user->pingLimiter(
'upload' );
205 private static $uploadHandlers = [
'Stash',
'File',
'Url' ];
215 $type = $type ?: $request->getVal(
'wpSourceType',
'File' );
222 $type = ucfirst( $type );
227 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
229 ->onUploadCreateFromRequest( $type, $className );
230 if ( $className ===
null ) {
231 $className =
'UploadFrom' . $type;
232 wfDebug( __METHOD__ .
": class name: $className" );
233 if ( !in_array( $type, self::$uploadHandlers ) ) {
238 if ( !$className::isEnabled() || !$className::isValidRequest( $request ) ) {
243 $handler =
new $className;
245 $handler->initializeFromRequest( $request );
264 return $this->mDesiredDestName;
291 $this->mDesiredDestName = $name;
292 if ( FileBackend::isStoragePath( $tempPath ) ) {
293 throw new InvalidArgumentException( __METHOD__ .
" given storage path `$tempPath`." );
297 $this->mRemoveTempFile = $removeTempFile;
312 $this->mTempPath = $tempPath ??
'';
313 $this->mFileSize = $fileSize ?:
null;
314 $this->mFileProps =
null;
315 if ( $this->mTempPath !==
'' && file_exists( $this->mTempPath ) ) {
316 $this->tempFileObj =
new TempFSFile( $this->mTempPath );
318 $this->mFileSize = filesize( $this->mTempPath );
321 $this->tempFileObj =
null;
331 return Status::newGood();
340 return Status::newGood();
348 return !$this->mFileSize;
356 return $this->mFileSize;
366 if ( $this->mFileProps && is_string( $this->mFileProps[
'sha1'] ) ) {
367 return $this->mFileProps[
'sha1'];
369 return FSFile::getSha1Base36FromPath( $this->mTempPath );
377 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
378 if ( FileRepo::isVirtualUrl( $srcPath ) ) {
382 $tmpFile = $repo->getLocalCopy( $srcPath );
384 $tmpFile->bind( $this );
386 $path = $tmpFile ? $tmpFile->getPath() :
false;
416 return [
'status' => self::EMPTY_FILE ];
423 if ( $this->mFileSize > $maxSize ) {
425 'status' => self::FILE_TOO_LARGE,
436 if ( $verification !==
true ) {
438 'status' => self::VERIFICATION_ERROR,
439 'details' => $verification
447 if ( $result !==
true ) {
451 return [
'status' => self::OK ];
462 if ( $nt ===
null ) {
463 $result = [
'status' => $this->mTitleError ];
464 if ( $this->mTitleError === self::ILLEGAL_FILENAME ) {
465 $result[
'filtered'] = $this->mFilteredName;
467 if ( $this->mTitleError === self::FILETYPE_BADTYPE ) {
468 $result[
'finalExt'] = $this->mFinalExtension;
469 if ( count( $this->mBlackListedExtensions ) ) {
470 $result[
'blacklistedExt'] = $this->mBlackListedExtensions;
490 $verifyMimeType = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeType );
491 if ( $verifyMimeType ) {
492 wfDebug(
"mime: <$mime> extension: <{$this->mFinalExtension}>" );
493 $mimeTypeExclusions = MediaWikiServices::getInstance()->getMainConfig()
494 ->get( MainConfigNames::MimeTypeExclusions );
495 if ( self::checkFileExtension( $mime, $mimeTypeExclusions ) ) {
496 return [
'filetype-badmime', $mime ];
509 $config = MediaWikiServices::getInstance()->getMainConfig();
510 $verifyMimeType = $config->get( MainConfigNames::VerifyMimeType );
511 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
513 if ( $status !==
true ) {
519 if ( !is_array( $this->mFileProps ) ) {
520 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
521 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
523 $mime = $this->mFileProps[
'mime'];
525 if ( $verifyMimeType ) {
526 # XXX: Missing extension will be caught by validateName() via getTitle()
527 if ( (
string)$this->mFinalExtension !==
'' &&
528 !self::verifyExtension( $mime, $this->mFinalExtension )
530 return [
'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
534 # check for htmlish code and javascript
535 if ( !$disableUploadScriptChecks ) {
536 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
538 if ( $svgStatus !==
false ) {
544 $handler = MediaHandler::getHandler( $mime );
546 $handlerStatus = $handler->verifyUpload( $this->mTempPath );
547 if ( !$handlerStatus->isOK() ) {
548 $errors = $handlerStatus->getErrorsArray();
550 return reset( $errors );
555 $this->getHookRunner()->onUploadVerifyFile( $this, $mime, $error );
556 if ( $error !==
true ) {
557 if ( !is_array( $error ) ) {
563 wfDebug( __METHOD__ .
": all clear; passing." );
578 $config = MediaWikiServices::getInstance()->getMainConfig();
579 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
580 # getTitle() sets some internal parameters like $this->mFinalExtension
585 if ( !is_array( $this->mFileProps ) ) {
586 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
587 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
590 # check MIME type, if desired
591 $mime = $this->mFileProps[
'file-mime'];
593 if ( $status !==
true ) {
597 # check for htmlish code and javascript
598 if ( !$disableUploadScriptChecks ) {
599 if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
600 return [
'uploadscripted' ];
602 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
604 if ( $svgStatus !==
false ) {
610 # Scan the uploaded file for viruses
611 $virus = self::detectVirus( $this->mTempPath );
613 return [
'uploadvirus', $virus ];
625 $names = [ $entry[
'name'] ];
632 $nullPos = strpos( $entry[
'name'],
"\000" );
633 if ( $nullPos !==
false ) {
634 $names[] = substr( $entry[
'name'], 0, $nullPos );
639 if ( preg_grep(
'!\.class/?$!', $names ) ) {
640 $this->mJavaDetected =
true;
674 if ( $nt ===
null ) {
678 $status = PermissionStatus::newEmpty();
681 if ( !$status->isGood() ) {
682 return $status->toLegacyErrorArray();
685 $overwriteError = $this->checkOverwrite( $performer );
686 if ( $overwriteError !==
true ) {
687 return [ $overwriteError ];
703 if ( $user ===
null ) {
705 $user = RequestContext::getMain()->getUser();
711 $localFile->
load( IDBAccessObject::READ_LATEST );
712 $filename = $localFile->
getName();
715 $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
716 if ( $badFileName !==
null ) {
717 $warnings[
'badfilename'] = $badFileName;
720 $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (
string)$this->mFinalExtension );
721 if ( $unwantedFileExtensionDetails !==
null ) {
722 $warnings[
'filetype-unwanted-type'] = $unwantedFileExtensionDetails;
725 $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
726 if ( $fileSizeWarnings ) {
727 $warnings = array_merge( $warnings, $fileSizeWarnings );
730 $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
731 if ( $localFileExistsWarnings ) {
732 $warnings = array_merge( $warnings, $localFileExistsWarnings );
735 if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
736 $warnings[
'was-deleted'] = $filename;
741 $ignoreLocalDupes = isset( $warnings[
'exists'] );
742 $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
744 $warnings[
'duplicate'] = $dupes;
747 $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
748 if ( $archivedDupes !==
null ) {
749 $warnings[
'duplicate-archive'] = $archivedDupes;
767 array_walk_recursive( $warnings,
static function ( &$param, $key ) {
768 if ( $param instanceof
File ) {
770 'fileName' => $param->getName(),
771 'timestamp' => $param->getTimestamp()
773 } elseif ( is_object( $param ) ) {
774 throw new InvalidArgumentException(
775 'UploadBase::makeWarningsSerializable: ' .
776 'Unexpected object of class ' . get_class( $param ) );
790 foreach ( $warnings as $key => $value ) {
791 if ( is_array( $value ) ) {
792 if ( isset( $value[
'fileName'] ) && isset( $value[
'timestamp'] ) ) {
793 $warnings[$key] = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
795 [
'time' => $value[
'timestamp'] ]
798 $warnings[$key] = self::unserializeWarnings( $value );
814 private function checkBadFileName( $filename, $desiredFileName ) {
815 $comparableName = str_replace(
' ',
'_', $desiredFileName );
816 $comparableName = Title::capitalize( $comparableName,
NS_FILE );
818 if ( $desiredFileName != $filename && $comparableName != $filename ) {
833 private function checkUnwantedFileExtensions( $fileExtension ) {
834 $checkFileExtensions = MediaWikiServices::getInstance()->getMainConfig()
835 ->get( MainConfigNames::CheckFileExtensions );
836 $fileExtensions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FileExtensions );
837 if ( $checkFileExtensions ) {
838 $extensions = array_unique( $fileExtensions );
839 if ( !self::checkFileExtension( $fileExtension, $extensions ) ) {
842 Message::listParam( $extensions,
'comma' ),
856 private function checkFileSize( $fileSize ) {
857 $uploadSizeWarning = MediaWikiServices::getInstance()->getMainConfig()
858 ->get( MainConfigNames::UploadSizeWarning );
862 if ( $uploadSizeWarning && ( $fileSize > $uploadSizeWarning ) ) {
863 $warnings[
'large-file'] = [
864 Message::sizeParam( $uploadSizeWarning ),
865 Message::sizeParam( $fileSize ),
869 if ( $fileSize == 0 ) {
870 $warnings[
'empty-file'] =
true;
882 private function checkLocalFileExists(
LocalFile $localFile, $hash ) {
885 $exists = self::getExistsWarning( $localFile );
886 if ( $exists !==
false ) {
887 $warnings[
'exists'] = $exists;
890 if ( $hash !==
false && $hash === $localFile->
getSha1() ) {
891 $warnings[
'no-change'] = $localFile;
896 foreach ( $history as $oldFile ) {
897 if ( $hash === $oldFile->getSha1() ) {
898 $warnings[
'duplicate-version'][] = $oldFile;
906 private function checkLocalFileWasDeleted(
LocalFile $localFile ) {
916 private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
917 if ( $hash ===
false ) {
920 $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
922 foreach ( $dupes as $key => $dupe ) {
926 $title->equals( $dupe->getTitle() )
928 unset( $dupes[$key] );
942 private function checkAgainstArchiveDupes( $hash,
Authority $performer ) {
943 if ( $hash ===
false ) {
947 if ( $archivedFile->getID() > 0 ) {
948 if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
949 return $archivedFile->getName();
975 $comment, $pageText, $watch, $user, $tags = [], ?
string $watchlistExpiry =
null
977 $this->
getLocalFile()->load( IDBAccessObject::READ_LATEST );
978 $props = $this->mFileProps;
981 $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
983 if ( !is_array( $error ) ) {
986 return Status::newFatal( ...$error );
992 $pageText !==
false ? $pageText :
'',
1000 if ( $status->isGood() ) {
1002 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
1008 $this->getHookRunner()->onUploadComplete( $this );
1032 if ( $this->mTitle !==
false ) {
1035 if ( !is_string( $this->mDesiredDestName ) ) {
1036 $this->mTitleError = self::ILLEGAL_FILENAME;
1037 $this->mTitle =
null;
1044 $title = Title::newFromText( $this->mDesiredDestName );
1045 if ( $title && $title->getNamespace() ===
NS_FILE ) {
1046 $this->mFilteredName = $title->getDBkey();
1048 $this->mFilteredName = $this->mDesiredDestName;
1051 # oi_archive_name is max 255 bytes, which include a timestamp and an
1052 # exclamation mark, so restrict file name to 240 bytes.
1053 if ( strlen( $this->mFilteredName ) > 240 ) {
1054 $this->mTitleError = self::FILENAME_TOO_LONG;
1055 $this->mTitle =
null;
1067 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1068 if ( $nt ===
null ) {
1069 $this->mTitleError = self::ILLEGAL_FILENAME;
1070 $this->mTitle =
null;
1074 $this->mFilteredName = $nt->
getDBkey();
1080 [ $partname, $ext ] = self::splitExtensions( $this->mFilteredName );
1082 if ( $ext !== [] ) {
1083 $this->mFinalExtension = trim( end( $ext ) );
1085 $this->mFinalExtension =
'';
1090 if ( $this->mTempPath !==
null ) {
1091 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1092 $mime = $magic->guessMimeType( $this->mTempPath );
1093 if ( $mime !==
'unknown/unknown' ) {
1094 # Get a space separated list of extensions
1095 $mimeExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
1096 if ( $mimeExt !==
null ) {
1097 # Set the extension to the canonical extension
1098 $this->mFinalExtension = $mimeExt;
1100 # Fix up the other variables
1101 $this->mFilteredName .=
".{$this->mFinalExtension}";
1102 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1103 $ext = [ $this->mFinalExtension ];
1110 $config = MediaWikiServices::getInstance()->getMainConfig();
1111 $checkFileExtensions = $config->get( MainConfigNames::CheckFileExtensions );
1112 $strictFileExtensions = $config->get( MainConfigNames::StrictFileExtensions );
1113 $fileExtensions = $config->get( MainConfigNames::FileExtensions );
1114 $prohibitedFileExtensions = $config->get( MainConfigNames::ProhibitedFileExtensions );
1116 $badList = self::checkFileExtensionList( $ext, $prohibitedFileExtensions );
1118 if ( $this->mFinalExtension ==
'' ) {
1119 $this->mTitleError = self::FILETYPE_MISSING;
1120 $this->mTitle =
null;
1126 ( $checkFileExtensions && $strictFileExtensions &&
1127 !self::checkFileExtension( $this->mFinalExtension, $fileExtensions ) )
1129 $this->mBlackListedExtensions = $badList;
1130 $this->mTitleError = self::FILETYPE_BADTYPE;
1131 $this->mTitle =
null;
1137 if ( !preg_match(
'/^[\x0-\x7f]*$/', $nt->getText() )
1138 && !MediaWikiServices::getInstance()->getRepoGroup()
1139 ->getLocalRepo()->backendSupportsUnicodePaths()
1141 $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1142 $this->mTitle =
null;
1147 # If there was more than one file "extension", reassemble the base
1148 # filename to prevent bogus complaints about length
1149 if ( count( $ext ) > 1 ) {
1150 $iterations = count( $ext ) - 1;
1151 for ( $i = 0; $i < $iterations; $i++ ) {
1152 $partname .=
'.' . $ext[$i];
1156 if ( strlen( $partname ) < 1 ) {
1157 $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1158 $this->mTitle =
null;
1163 $this->mTitle = $nt;
1175 if ( $this->mLocalFile ===
null ) {
1177 $this->mLocalFile = $nt ===
null
1179 : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1182 return $this->mLocalFile;
1189 return $this->mStashFile;
1205 if ( !$isPartial ) {
1208 return Status::newFatal( ...$error );
1213 return Status::newGood( $file );
1215 return Status::newFatal(
'uploadstash-exception', get_class( $e ), $e->getMessage() );
1224 $props = $this->mFileProps;
1226 $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1227 if ( $error && !is_array( $error ) ) {
1228 $error = [ $error ];
1241 $stash = MediaWikiServices::getInstance()->getRepoGroup()
1242 ->getLocalRepo()->getUploadStash( $user );
1243 $file = $stash->stashFile( $this->mTempPath, $this->
getSourceType(), $this->mFileProps );
1244 $this->mStashFile = $file;
1254 if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1256 wfDebug( __METHOD__ .
": Marked temporary file '{$this->mTempPath}' for removal" );
1257 $this->tempFileObj->autocollect();
1265 return $this->mTempPath;
1278 $bits = explode(
'.', $filename );
1279 $basename = array_shift( $bits );
1281 return [ $basename, $bits ];
1292 return in_array( strtolower( $ext ??
'' ), $list,
true );
1304 return array_intersect( array_map(
'strtolower', $ext ), $list );
1315 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1317 if ( !$mime || $mime ===
'unknown' || $mime ===
'unknown/unknown' ) {
1318 if ( !$magic->isRecognizableExtension( $extension ) ) {
1319 wfDebug( __METHOD__ .
": passing file with unknown detected mime type; " .
1320 "unrecognized extension '$extension', can't verify" );
1325 wfDebug( __METHOD__ .
": rejecting file with unknown detected mime type; " .
1326 "recognized extension '$extension', so probably invalid file" );
1330 $match = $magic->isMatchingExtension( $extension, $mime );
1332 if ( $match ===
null ) {
1333 if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1334 wfDebug( __METHOD__ .
": No extension known for $mime, but we know a mime for $extension" );
1339 wfDebug( __METHOD__ .
": no file extension known for mime type $mime, passing file" );
1344 wfDebug( __METHOD__ .
": mime type $mime matches extension $extension, passing file" );
1351 .
": mime type $mime mismatches file extension $extension, rejecting file" );
1368 # ugly hack: for text files, always look at the entire file.
1369 # For binary field, just check the first K.
1371 if ( str_starts_with( $mime ??
'',
'text/' ) ) {
1372 $chunk = file_get_contents( $file );
1374 $fp = fopen( $file,
'rb' );
1378 $chunk = fread( $fp, 1024 );
1382 $chunk = strtolower( $chunk );
1388 # decode from UTF-16 if needed (could be used for obfuscation).
1389 if ( str_starts_with( $chunk,
"\xfe\xff" ) ) {
1391 } elseif ( str_starts_with( $chunk,
"\xff\xfe" ) ) {
1397 if ( $enc !==
null ) {
1398 AtEase::suppressWarnings();
1399 $chunk = iconv( $enc,
"ASCII//IGNORE", $chunk );
1400 AtEase::restoreWarnings();
1403 $chunk = trim( $chunk );
1406 wfDebug( __METHOD__ .
": checking for embedded scripts and HTML stuff" );
1408 # check for HTML doctype
1409 if ( preg_match(
"/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1415 if ( $extension ===
'svg' || str_starts_with( $mime ??
'',
'image/svg' ) ) {
1416 if ( self::checkXMLEncodingMissmatch( $file ) ) {
1430 '<html', # also in safari
1431 '<script', # also in safari
1434 foreach ( $tags as $tag ) {
1435 if ( strpos( $chunk, $tag ) !==
false ) {
1436 wfDebug( __METHOD__ .
": found something that may make it be mistaken for html: $tag" );
1446 # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1447 $chunk = Sanitizer::decodeCharReferences( $chunk );
1449 # look for script-types
1450 if ( preg_match(
'!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!im', $chunk ) ) {
1451 wfDebug( __METHOD__ .
": found script types" );
1456 # look for html-style script-urls
1457 if ( preg_match(
'!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1458 wfDebug( __METHOD__ .
": found html-style script urls" );
1463 # look for css-style script-urls
1464 if ( preg_match(
'!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1465 wfDebug( __METHOD__ .
": found css-style script urls" );
1470 wfDebug( __METHOD__ .
": no scripts found" );
1487 $contents = file_get_contents( $file,
false,
null, 0, 4096 );
1488 $encodingRegex =
'!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1490 if ( preg_match(
"!<\?xml\b(.*?)\?>!si", $contents,
$matches ) ) {
1491 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1492 && !in_array( strtoupper( $encMatch[1] ), self::SAFE_XML_ENCODINGS )
1494 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1498 } elseif ( preg_match(
"!<\?xml\b!i", $contents ) ) {
1501 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1504 } elseif ( str_starts_with( $contents,
"\x4C\x6F\xA7\x94" ) ) {
1506 wfDebug( __METHOD__ .
": EBCDIC Encoded XML" );
1513 $attemptEncodings = [
'UTF-16',
'UTF-16BE',
'UTF-32',
'UTF-32BE' ];
1514 foreach ( $attemptEncodings as $encoding ) {
1515 AtEase::suppressWarnings();
1516 $str = iconv( $encoding,
'UTF-8', $contents );
1517 AtEase::restoreWarnings();
1518 if ( $str !=
'' && preg_match(
"!<\?xml\b(.*?)\?>!si", $str,
$matches ) ) {
1519 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1520 && !in_array( strtoupper( $encMatch[1] ), self::SAFE_XML_ENCODINGS )
1522 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1526 } elseif ( $str !=
'' && preg_match(
"!<\?xml\b!i", $str ) ) {
1529 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1544 $this->mSVGNSError =
false;
1547 [ $this,
'checkSvgScriptCallback' ],
1550 'processing_instruction_handler' => [ __CLASS__,
'checkSvgPICallback' ],
1551 'external_dtd_handler' => [ __CLASS__,
'checkSvgExternalDTD' ],
1554 if ( $check->wellFormed !==
true ) {
1557 return $partial ? false : [
'uploadinvalidxml' ];
1560 if ( $check->filterMatch ) {
1561 if ( $this->mSVGNSError ) {
1562 return [
'uploadscriptednamespace', $this->mSVGNSError ];
1564 return $check->filterMatchType;
1579 if ( preg_match(
'/xml-stylesheet/i', $target ) ) {
1580 return [
'upload-scripted-pi-callback' ];
1601 static $allowedDTDs = [
1602 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1603 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1604 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1605 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1607 'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1609 if ( $type !==
'PUBLIC'
1610 || !in_array( $systemId, $allowedDTDs )
1611 || !str_starts_with( $publicId,
"-//W3C//" )
1613 return [
'upload-scripted-dtd' ];
1626 [ $namespace, $strippedElement ] = self::splitXmlNamespace( $element );
1630 static $validNamespaces = [
1633 'http://creativecommons.org/ns#',
1634 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1635 'http://ns.adobe.com/adobeillustrator/10.0/',
1636 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1637 'http://ns.adobe.com/extensibility/1.0/',
1638 'http://ns.adobe.com/flows/1.0/',
1639 'http://ns.adobe.com/illustrator/1.0/',
1640 'http://ns.adobe.com/imagereplacement/1.0/',
1641 'http://ns.adobe.com/pdf/1.3/',
1642 'http://ns.adobe.com/photoshop/1.0/',
1643 'http://ns.adobe.com/saveforweb/1.0/',
1644 'http://ns.adobe.com/variables/1.0/',
1645 'http://ns.adobe.com/xap/1.0/',
1646 'http://ns.adobe.com/xap/1.0/g/',
1647 'http://ns.adobe.com/xap/1.0/g/img/',
1648 'http://ns.adobe.com/xap/1.0/mm/',
1649 'http://ns.adobe.com/xap/1.0/rights/',
1650 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1651 'http://ns.adobe.com/xap/1.0/stype/font#',
1652 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1653 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1654 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1655 'http://ns.adobe.com/xap/1.0/t/pg/',
1656 'http://purl.org/dc/elements/1.1/',
1657 'http://purl.org/dc/elements/1.1',
1658 'http://schemas.microsoft.com/visio/2003/svgextensions/',
1659 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1660 'http://taptrix.com/inkpad/svg_extensions',
1661 'http://web.resource.org/cc/',
1662 'http://www.freesoftware.fsf.org/bkchem/cdml',
1663 'http://www.inkscape.org/namespaces/inkscape',
1664 'http://www.opengis.net/gml',
1665 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1666 'http://www.w3.org/2000/svg',
1667 'http://www.w3.org/tr/rec-rdf-syntax/',
1668 'http://www.w3.org/2000/01/rdf-schema#',
1669 'http://www.w3.org/2000/02/svg/testsuite/description/',
1674 $isBuggyInkscape = preg_match(
'/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1676 if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1677 wfDebug( __METHOD__ .
": Non-svg namespace '$namespace' in uploaded file." );
1679 $this->mSVGNSError = $namespace;
1685 if ( $strippedElement ===
'script' ) {
1686 wfDebug( __METHOD__ .
": Found script element '$element' in uploaded file." );
1688 return [
'uploaded-script-svg', $strippedElement ];
1693 if ( $strippedElement ===
'handler' ) {
1694 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1696 return [
'uploaded-script-svg', $strippedElement ];
1700 if ( $strippedElement ===
'stylesheet' ) {
1701 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1703 return [
'uploaded-script-svg', $strippedElement ];
1707 if ( $strippedElement ===
'iframe' ) {
1708 wfDebug( __METHOD__ .
": iframe in uploaded file." );
1710 return [
'uploaded-script-svg', $strippedElement ];
1714 if ( $strippedElement ===
'style'
1715 && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1717 wfDebug( __METHOD__ .
": hostile css in style element." );
1719 return [
'uploaded-hostile-svg' ];
1722 static $cssAttrs = [
'font',
'clip-path',
'fill',
'filter',
'marker',
1723 'marker-end',
'marker-mid',
'marker-start',
'mask',
'stroke' ];
1725 foreach ( $attribs as $attrib => $value ) {
1727 [ $attributeNamespace, $stripped ] = self::splitXmlNamespace( $attrib );
1728 $value = strtolower( $value );
1732 $namespace ===
'http://www.inkscape.org/namespaces/inkscape' &&
1733 $attributeNamespace ===
''
1734 ) && str_starts_with( $stripped,
'on' )
1737 .
": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1739 return [
'uploaded-event-handler-on-svg', $attrib, $value ];
1747 $stripped ===
'href'
1749 && !str_starts_with( $value,
'data:' )
1750 && !str_starts_with( $value,
'#' )
1751 && !( $strippedElement ===
'a' && preg_match(
'!^https?://!i', $value ) )
1753 wfDebug( __METHOD__ .
": Found href attribute <$strippedElement "
1754 .
"'$attrib'='$value' in uploaded file." );
1756 return [
'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1761 if ( $stripped ===
'href' && strncasecmp(
'data:', $value, 5 ) === 0 ) {
1765 $parameters =
'(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1767 if ( !preg_match(
"!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1768 wfDebug( __METHOD__ .
": Found href to allow listed data: uri "
1769 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1770 return [
'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1775 if ( $stripped ===
'attributename'
1776 && $strippedElement ===
'animate'
1777 && $this->stripXmlNamespace( $value ) ===
'href'
1779 wfDebug( __METHOD__ .
": Found animate that might be changing href using from "
1780 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1782 return [
'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1786 if ( ( $strippedElement ===
'set' || $strippedElement ===
'animate' )
1787 && $stripped ===
'attributename'
1788 && str_starts_with( $value,
'on' )
1790 wfDebug( __METHOD__ .
": Found svg setting event-handler attribute with "
1791 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1793 return [
'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1797 if ( $strippedElement ===
'set'
1798 && $stripped ===
'attributename'
1799 && str_contains( $value,
'href' )
1801 wfDebug( __METHOD__ .
": Found svg setting href attribute '$value' in uploaded file." );
1803 return [
'uploaded-setting-href-svg' ];
1807 if ( $strippedElement ===
'set'
1808 && $stripped ===
'to'
1809 && preg_match(
'!(http|https|data|script):!im', $value )
1811 wfDebug( __METHOD__ .
": Found svg setting attribute to '$value' in uploaded file." );
1813 return [
'uploaded-wrong-setting-svg', $value ];
1817 if ( $stripped ===
'handler' && preg_match(
'!(http|https|data|script):!im', $value ) ) {
1818 wfDebug( __METHOD__ .
": Found svg setting handler with remote/data/script "
1819 .
"'$attrib'='$value' in uploaded file." );
1821 return [
'uploaded-setting-handler-svg', $attrib, $value ];
1825 if ( $stripped ===
'style'
1826 && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1828 wfDebug( __METHOD__ .
": Found svg setting a style with "
1829 .
"remote url '$attrib'='$value' in uploaded file." );
1830 return [
'uploaded-remote-url-svg', $attrib, $value ];
1834 if ( in_array( $stripped, $cssAttrs,
true )
1835 && self::checkCssFragment( $value )
1837 wfDebug( __METHOD__ .
": Found svg setting a style with "
1838 .
"remote url '$attrib'='$value' in uploaded file." );
1839 return [
'uploaded-remote-url-svg', $attrib, $value ];
1845 if ( $strippedElement ===
'image'
1846 && $stripped ===
'filter'
1847 && preg_match(
'!url\s*\(\s*["\']?[^#]!im', $value )
1849 wfDebug( __METHOD__ .
": Found image filter with url: "
1850 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1852 return [
'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1865 private static function checkCssFragment( $value ) {
1866 # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1867 if ( stripos( $value,
'@import' ) !==
false ) {
1871 # We allow @font-face to embed fonts with data: urls, so we snip the string
1872 # 'url' out so that this case won't match when we check for urls below
1873 $pattern =
'!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1874 $value = preg_replace( $pattern,
'$1$2', $value );
1876 # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1877 # properties filter and accelerator don't seem to be useful for xss in SVG files.
1878 # Expression and -o-link don't seem to work either, but filtering them here in case.
1879 # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1880 # but not local ones such as url("#..., url('#..., url(#....
1881 if ( preg_match(
'!expression
1883 | -o-link-source\s*:
1884 | -o-replace\s*:!imx', $value ) ) {
1888 if ( preg_match_all(
1889 "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1894 # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1895 foreach (
$matches[1] as $match ) {
1896 if ( !preg_match(
"!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1902 return (
bool)preg_match(
'/[\000-\010\013\016-\037\177]/', $value );
1910 private static function splitXmlNamespace( $element ) {
1912 $parts = explode(
':', strtolower( $element ) );
1913 $name = array_pop( $parts );
1914 $ns = implode(
':', $parts );
1916 return [ $ns, $name ];
1923 private function stripXmlNamespace( $element ) {
1925 return self::splitXmlNamespace( $element )[1];
1940 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1941 $antivirus = $mainConfig->get( MainConfigNames::Antivirus );
1942 $antivirusSetup = $mainConfig->get( MainConfigNames::AntivirusSetup );
1943 $antivirusRequired = $mainConfig->get( MainConfigNames::AntivirusRequired );
1944 if ( !$antivirus ) {
1945 wfDebug( __METHOD__ .
": virus scanner disabled" );
1950 if ( !$antivirusSetup[$antivirus] ) {
1951 wfDebug( __METHOD__ .
": unknown virus scanner: {$antivirus}" );
1952 $wgOut->wrapWikiMsg(
"<div class=\"error\">\n$1\n</div>",
1953 [
'virus-badscanner', $antivirus ] );
1955 return wfMessage(
'virus-unknownscanner' )->text() .
" {$antivirus}";
1958 # look up scanner configuration
1959 $command = $antivirusSetup[$antivirus][
'command'];
1960 $exitCodeMap = $antivirusSetup[$antivirus][
'codemap'];
1961 $msgPattern = $antivirusSetup[$antivirus][
'messagepattern'] ??
null;
1963 if ( !str_contains( $command,
"%f" ) ) {
1964 # simple pattern: append file to scan
1965 $command .=
" " . Shell::escape( $file );
1967 # complex pattern: replace "%f" with file to scan
1968 $command = str_replace(
"%f", Shell::escape( $file ), $command );
1971 wfDebug( __METHOD__ .
": running virus scan: $command " );
1973 # execute virus scanner
1976 # NOTE: there's a 50-line workaround to make stderr redirection work on windows, too.
1977 # that does not seem to be worth the pain.
1978 # Ask me (Duesentrieb) about it if it's ever needed.
1981 # map exit code to AV_xxx constants.
1982 $mappedCode = $exitCode;
1983 if ( $exitCodeMap ) {
1984 if ( isset( $exitCodeMap[$exitCode] ) ) {
1985 $mappedCode = $exitCodeMap[$exitCode];
1986 } elseif ( isset( $exitCodeMap[
"*"] ) ) {
1987 $mappedCode = $exitCodeMap[
"*"];
1991 # NB: AV_NO_VIRUS is 0, but AV_SCAN_FAILED is false,
1992 # so we need the strict equalities === and thus can't use a switch here
1994 # scan failed (code was mapped to false by $exitCodeMap)
1995 wfDebug( __METHOD__ .
": failed to scan $file (code $exitCode)." );
1997 $output = $antivirusRequired
1998 ?
wfMessage(
'virus-scanfailed', [ $exitCode ] )->text()
2001 # scan failed because filetype is unknown (probably immune)
2002 wfDebug( __METHOD__ .
": unsupported file type $file (code $exitCode)." );
2006 wfDebug( __METHOD__ .
": file passed virus scan." );
2009 $output = trim( $output );
2012 $output =
true; #
if there
's no output, return true
2013 } elseif ( $msgPattern ) {
2015 if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
2016 $output = $groups[1];
2020 wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
2034 private function checkOverwrite( Authority $performer ) {
2035 // First check whether the local file can be overwritten
2036 $file = $this->getLocalFile();
2037 $file->load( IDBAccessObject::READ_LATEST );
2038 if ( $file->exists() ) {
2039 if ( !self::userCanReUpload( $performer, $file ) ) {
2040 return [ 'fileexists-forbidden
', $file->getName() ];
2046 $services = MediaWikiServices::getInstance();
2048 /* Check shared conflicts: if the local file does not exist, but
2049 * RepoGroup::findFile finds a file, it exists in a shared repository.
2051 $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest
' => true ] );
2052 if ( $file && !$performer->isAllowed( 'reupload-shared
' ) ) {
2053 return [ 'fileexists-shared-forbidden
', $file->getName() ];
2066 public static function userCanReUpload( Authority $performer, File $img ) {
2067 if ( $performer->isAllowed( 'reupload
' ) ) {
2068 return true; // non-conditional
2071 if ( !$performer->isAllowed( 'reupload-own
' ) ) {
2075 if ( !( $img instanceof LocalFile ) ) {
2079 return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2093 public static function getExistsWarning( $file ) {
2094 if ( $file->exists() ) {
2095 return [ 'warning
' => 'exists
', 'file
' => $file ];
2098 if ( $file->getTitle()->getArticleID() ) {
2099 return [ 'warning
' => 'page-exists
', 'file
' => $file ];
2102 $n = strrpos( $file->getName(), '.
' );
2104 $partname = substr( $file->getName(), 0, $n );
2105 $extension = substr( $file->getName(), $n + 1 );
2107 $partname = $file->getName();
2110 $normalizedExtension = File::normalizeExtension( $extension );
2111 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2113 if ( $normalizedExtension != $extension ) {
2114 // We're not
using the normalized form of the extension.
2119 $nt_lc = Title::makeTitle(
NS_FILE,
"{$partname}.{$normalizedExtension}" );
2120 $file_lc = $localRepo->newFile( $nt_lc );
2122 if ( $file_lc->exists() ) {
2124 'warning' =>
'exists-normalized',
2126 'normalizedFile' => $file_lc
2132 $similarFiles = $localRepo->findFilesByPrefix(
"{$partname}.", 1 );
2133 if ( count( $similarFiles ) ) {
2135 'warning' =>
'exists-normalized',
2137 'normalizedFile' => $similarFiles[0],
2141 if ( self::isThumbName( $file->getName() ) ) {
2143 $nt_thb = Title::newFromText(
2144 substr( $partname, strpos( $partname,
'-' ) + 1 ) .
'.' . $extension,
2147 $file_thb = $localRepo->newFile( $nt_thb );
2148 if ( $file_thb->exists() ) {
2150 'warning' =>
'thumb',
2152 'thumbFile' => $file_thb
2158 'warning' =>
'thumb-name',
2160 'thumbFile' => $file_thb
2164 foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2165 if ( str_starts_with( $partname, $prefix ) ) {
2167 'warning' =>
'bad-prefix',
2183 $n = strrpos( $filename,
'.' );
2184 $partname = $n ? substr( $filename, 0, $n ) : $filename;
2187 substr( $partname, 3, 3 ) ===
'px-' ||
2188 substr( $partname, 2, 3 ) ===
'px-'
2189 ) && preg_match(
"/[0-9]{2}/", substr( $partname, 0, 2 ) );
2199 $message =
wfMessage(
'filename-prefix-blacklist' )->inContentLanguage();
2200 if ( !$message->isDisabled() ) {
2201 $lines = explode(
"\n", $message->plain() );
2202 foreach (
$lines as $line ) {
2204 $comment = substr( trim( $line ), 0, 1 );
2205 if ( $comment ===
'#' || $comment ==
'' ) {
2209 $comment = strpos( $line,
'#' );
2210 if ( $comment > 0 ) {
2211 $line = substr( $line, 0, $comment - 1 );
2213 $list[] = trim( $line );
2230 $apiUpload = ApiUpload::getDummyInstance();
2231 return $apiUpload->getUploadImageInfo( $this );
2239 $code = $error[
'status'];
2240 unset( $code[
'status'] );
2253 $maxUploadSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxUploadSize );
2255 if ( is_array( $maxUploadSize ) ) {
2256 return $maxUploadSize[$forType] ?? $maxUploadSize[
'*'];
2258 return intval( $maxUploadSize );
2270 ini_get(
'upload_max_filesize' ),
2274 ini_get(
'post_max_size' ),
2277 return min( $phpMaxFileSize, $phpMaxPostSize );
2292 $store = self::getUploadSessionStore();
2293 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2295 return $store->
get( $key );
2311 $store = self::getUploadSessionStore();
2312 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2313 $logger = LoggerFactory::getInstance(
'upload' );
2315 if ( is_array( $value ) && ( $value[
'result'] ??
'' ) ===
'Failure' ) {
2316 $logger->info(
'Upload session {key} for {user} set to failure {status} at {stage}',
2318 'result' => $value[
'result'] ??
'',
2319 'stage' => $value[
'stage'] ??
'unknown',
2321 'status' => (
string)( $value[
'status'] ??
'-' ),
2322 'filekey' => $value[
'filekey'] ??
'',
2326 } elseif ( is_array( $value ) ) {
2327 $logger->debug(
'Upload session {key} for {user} changed {status} at {stage}',
2329 'result' => $value[
'result'] ??
'',
2330 'stage' => $value[
'stage'] ??
'unknown',
2332 'status' => (
string)( $value[
'status'] ??
'-' ),
2333 'filekey' => $value[
'filekey'] ??
'',
2338 $logger->debug(
"Upload session {key} deleted for {user}",
2341 'key' => $statusKey,
2347 if ( $value ===
false ) {
2350 $store->
set( $key, $value, $store::TTL_DAY );
2371 private static function getUploadSessionStore() {
2372 return MediaWikiServices::getInstance()->getMainObjectStash();
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
wfShorthandToInteger(?string $string='', int $default=-1)
Converts shorthand byte notation to integer form.
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
wfStripIllegalFilenameChars( $name)
Replace all invalid characters with '-'.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgOut
MimeMagic helper wrapper.
Group all the pieces relevant to the context of a request into one instance.
A class containing constants representing the names of configuration variables.
UploadBase and subclasses are the backend of MediaWiki's file uploads.
getSourceType()
Returns the upload type.
getDesiredDestName()
Get the desired destination name.
static makeWarningsSerializable( $warnings)
Convert the warnings array returned by checkWarnings() to something that can be serialized.
static setSessionStatus(UserIdentity $user, $statusKey, $value)
Set the current status of a chunked upload (used for polling).
UploadStashFile null $mStashFile
static verifyExtension( $mime, $extension)
Checks if the MIME type of the uploaded file matches the file extension.
postProcessUpload()
Perform extra steps after a successful upload.
checkSvgScriptCallback( $element, $attribs, $data=null)
verifyPermissions(Authority $performer)
Alias for verifyTitlePermissions.
getLocalFile()
Return the local file and initializes if necessary.
string null $mFilteredName
doStashFile(?User $user=null)
Implementation for stashFile() and tryStashFile().
static createFromRequest(&$request, $type=null)
Create a form of UploadBase depending on wpSourceType and initializes it.
runUploadStashFileHook(User $user)
zipEntryCallback( $entry)
Callback for ZipDirectoryReader to detect Java class files.
static checkSvgPICallback( $target, $data)
Callback to filter SVG Processing Instructions.
static isValidRequest( $request)
Check whether a request if valid for this handler.
convertVerifyErrorToStatus( $error)
string null $mFinalExtension
verifyPartialFile()
A verification routine suitable for partial files.
static detectScript( $file, $mime, $extension)
Heuristic for detecting files that could contain JavaScript instructions or things that may look like...
verifyFile()
Verifies that it's ok to include the uploaded file.
static isEnabled()
Returns true if uploads are enabled.
static isThumbName( $filename)
Helper function that checks whether the filename looks like a thumbnail.
getVerificationErrorCode( $error)
performUpload( $comment, $pageText, $watch, $user, $tags=[], ?string $watchlistExpiry=null)
Really perform the upload.
string null $mDesiredDestName
verifyTitlePermissions(Authority $performer)
Check whether the user can edit, upload and create the image.
static getFilenamePrefixBlacklist()
Get a list of disallowed filename prefixes from [[MediaWiki:Filename-prefix-blacklist]].
setTempFile( $tempPath, $fileSize=null)
static getSessionStatus(UserIdentity $user, $statusKey)
Get the current status of a chunked upload (used for polling).
static checkXMLEncodingMissmatch( $file)
Check an allowed list of xml encodings that are known not to be interpreted differently by the server...
string[] $mBlackListedExtensions
static isAllowed(Authority $performer)
Returns true if the user can use this upload module or else a string identifying the missing permissi...
const WINDOWS_NONASCII_FILENAME
cleanupTempFile()
If we've modified the upload file, then we need to manually remove it on exit to clean up.
getImageInfo( $result=null)
Gets image info about the file just uploaded.
validateName()
Verify that the name is valid and, if necessary, that we can overwrite.
isEmptyFile()
Return true if the file is empty.
static checkFileExtension( $ext, $list)
Perform case-insensitive match against a list of file extensions.
tryStashFile(User $user, $isPartial=false)
Like stashFile(), but respects extensions' wishes to prevent the stashing.
getTitle()
Returns the title of the file to be uploaded.
initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile=false)
static getMaxUploadSize( $forType=null)
Get MediaWiki's maximum uploaded file size for a given type of upload, based on $wgMaxUploadSize.
bool null $mRemoveTempFile
static checkSvgExternalDTD( $type, $publicId, $systemId)
Verify that DTD URLs referenced are only the standard DTDs.
getTempFileSha1Base36()
Get the base 36 SHA1 of the file.
detectScriptInSvg( $filename, $partial)
static splitExtensions( $filename)
Split a file into a base name and all dot-delimited 'extensions' on the end.
fetchFile()
Fetch the file.
checkWarnings( $user=null)
Check for non fatal problems with the file.
static isThrottled( $user)
Returns true if the user has surpassed the upload rate limit, false otherwise.
getFileSize()
Return the file size.
verifyUpload()
Verify whether the upload is sensible.
const MIN_LENGTH_PARTNAME
static checkFileExtensionList( $ext, $list)
Perform case-insensitive match against a list of file extensions.
static detectVirus( $file)
Generic wrapper function for a virus scanner program.
string null $mTempPath
Local file system path to the file to upload (or a local copy)
TempFSFile null $tempFileObj
Wrapper to handle deleting the temp file.
LocalFile null $mLocalFile
canFetchFile()
Perform checks to see if the file can be fetched.
static getMaxPhpUploadSize()
Get the PHP maximum uploaded file size, based on ini settings.
verifyMimeType( $mime)
Verify the MIME type.
static unserializeWarnings( $warnings)
Convert the serialized warnings array created by makeWarningsSerializable() back to the output of che...
initializeFromRequest(&$request)
Initialize from a WebRequest.
string false $mSVGNSError
if(!file_exists( $CREDITS)) $lines