28use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
42use Wikimedia\AtEase\AtEase;
67 use ProtectedHookAccessorTrait;
84 protected $mTitleError = 0;
104 private const SAFE_XML_ENCONDINGS = [
128 public const FILETYPE_MISSING = 8;
129 public const FILETYPE_BADTYPE = 9;
130 public const VERIFICATION_ERROR = 10;
131 public const HOOK_ABORTED = 11;
132 public const FILE_TOO_LARGE = 12;
133 public const WINDOWS_NONASCII_FILENAME = 13;
134 public const FILENAME_TOO_LONG = 14;
136 private const CODE_TO_STATUS = [
137 self::EMPTY_FILE => 'empty-file',
138 self::FILE_TOO_LARGE => 'file-too-large',
139 self::FILETYPE_MISSING => 'filetype-missing',
140 self::FILETYPE_BADTYPE => 'filetype-banned',
141 self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
142 self::ILLEGAL_FILENAME => 'illegal-filename',
143 self::OVERWRITE_EXISTING_FILE => 'overwrite',
144 self::VERIFICATION_ERROR => 'verification-error',
145 self::HOOK_ABORTED => 'hookaborted',
146 self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
147 self::FILENAME_TOO_LONG => 'filename-toolong',
154 public function getVerificationErrorCode( $error ) {
155 return self::CODE_TO_STATUS[$error] ??
'unknown-error';
165 $enableUploads = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnableUploads );
167 return $enableUploads &&
wfIniGetBool(
'file_uploads' );
179 foreach ( [
'upload',
'edit' ] as $permission ) {
180 if ( !$performer->
isAllowed( $permission ) ) {
199 return $user->pingLimiter(
'upload' );
203 private static $uploadHandlers = [
'Stash',
'File',
'Url' ];
213 $type = $type ?: $request->getVal(
'wpSourceType',
'File' );
220 $type = ucfirst( $type );
225 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
227 ->onUploadCreateFromRequest( $type, $className );
228 if ( $className ===
null ) {
229 $className =
'UploadFrom' . $type;
230 wfDebug( __METHOD__ .
": class name: $className" );
231 if ( !in_array( $type, self::$uploadHandlers ) ) {
236 if ( !$className::isEnabled() || !$className::isValidRequest( $request ) ) {
241 $handler =
new $className;
243 $handler->initializeFromRequest( $request );
262 return $this->mDesiredDestName;
289 $this->mDesiredDestName = $name;
290 if ( FileBackend::isStoragePath( $tempPath ) ) {
291 throw new InvalidArgumentException( __METHOD__ .
" given storage path `$tempPath`." );
295 $this->mRemoveTempFile = $removeTempFile;
310 $this->mTempPath = $tempPath ??
'';
311 $this->mFileSize = $fileSize ?:
null;
312 $this->mFileProps =
null;
313 if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
314 $this->tempFileObj =
new TempFSFile( $this->mTempPath );
316 $this->mFileSize = filesize( $this->mTempPath );
319 $this->tempFileObj =
null;
329 return Status::newGood();
338 return Status::newGood();
346 return !$this->mFileSize;
354 return $this->mFileSize;
364 if ( $this->mFileProps && is_string( $this->mFileProps[
'sha1'] ) ) {
365 return $this->mFileProps[
'sha1'];
367 return FSFile::getSha1Base36FromPath( $this->mTempPath );
375 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
380 $tmpFile = $repo->getLocalCopy( $srcPath );
382 $tmpFile->bind( $this );
384 $path = $tmpFile ? $tmpFile->getPath() :
false;
414 return [
'status' => self::EMPTY_FILE ];
421 if ( $this->mFileSize > $maxSize ) {
423 'status' => self::FILE_TOO_LARGE,
434 if ( $verification !==
true ) {
436 'status' => self::VERIFICATION_ERROR,
437 'details' => $verification
445 if ( $result !==
true ) {
449 return [
'status' => self::OK ];
460 if ( $nt ===
null ) {
461 $result = [
'status' => $this->mTitleError ];
462 if ( $this->mTitleError === self::ILLEGAL_FILENAME ) {
463 $result[
'filtered'] = $this->mFilteredName;
465 if ( $this->mTitleError === self::FILETYPE_BADTYPE ) {
466 $result[
'finalExt'] = $this->mFinalExtension;
467 if ( count( $this->mBlackListedExtensions ) ) {
468 $result[
'blacklistedExt'] = $this->mBlackListedExtensions;
488 $verifyMimeType = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeType );
489 if ( $verifyMimeType ) {
490 wfDebug(
"mime: <$mime> extension: <{$this->mFinalExtension}>" );
491 $mimeTypeExclusions = MediaWikiServices::getInstance()->getMainConfig()
492 ->get( MainConfigNames::MimeTypeExclusions );
493 if ( self::checkFileExtension( $mime, $mimeTypeExclusions ) ) {
494 return [
'filetype-badmime', $mime ];
507 $config = MediaWikiServices::getInstance()->getMainConfig();
508 $verifyMimeType = $config->get( MainConfigNames::VerifyMimeType );
509 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
511 if ( $status !==
true ) {
517 if ( !is_array( $this->mFileProps ) ) {
518 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
519 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
521 $mime = $this->mFileProps[
'mime'];
523 if ( $verifyMimeType ) {
524 # XXX: Missing extension will be caught by validateName() via getTitle()
525 if ( (
string)$this->mFinalExtension !==
'' &&
526 !self::verifyExtension( $mime, $this->mFinalExtension )
528 return [
'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
532 # check for htmlish code and javascript
533 if ( !$disableUploadScriptChecks ) {
534 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
536 if ( $svgStatus !==
false ) {
542 $handler = MediaHandler::getHandler( $mime );
544 $handlerStatus = $handler->verifyUpload( $this->mTempPath );
545 if ( !$handlerStatus->isOK() ) {
546 $errors = $handlerStatus->getErrorsArray();
548 return reset( $errors );
553 $this->getHookRunner()->onUploadVerifyFile( $this, $mime, $error );
554 if ( $error !==
true ) {
555 if ( !is_array( $error ) ) {
561 wfDebug( __METHOD__ .
": all clear; passing." );
576 $config = MediaWikiServices::getInstance()->getMainConfig();
577 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
578 # getTitle() sets some internal parameters like $this->mFinalExtension
583 if ( !is_array( $this->mFileProps ) ) {
584 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
585 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
588 # check MIME type, if desired
589 $mime = $this->mFileProps[
'file-mime'];
591 if ( $status !==
true ) {
595 # check for htmlish code and javascript
596 if ( !$disableUploadScriptChecks ) {
597 if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
598 return [
'uploadscripted' ];
600 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
602 if ( $svgStatus !==
false ) {
608 # Scan the uploaded file for viruses
609 $virus = self::detectVirus( $this->mTempPath );
611 return [
'uploadvirus', $virus ];
623 $names = [ $entry[
'name'] ];
630 $nullPos = strpos( $entry[
'name'],
"\000" );
631 if ( $nullPos !==
false ) {
632 $names[] = substr( $entry[
'name'], 0, $nullPos );
637 if ( preg_grep(
'!\.class/?$!', $names ) ) {
638 $this->mJavaDetected =
true;
672 if ( $nt ===
null ) {
676 $status = PermissionStatus::newEmpty();
679 if ( !$status->isGood() ) {
680 return $status->toLegacyErrorArray();
683 $overwriteError = $this->checkOverwrite( $performer );
684 if ( $overwriteError !==
true ) {
685 return [ $overwriteError ];
701 if ( $user ===
null ) {
703 $user = RequestContext::getMain()->getUser();
709 $localFile->
load( IDBAccessObject::READ_LATEST );
710 $filename = $localFile->
getName();
713 $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
714 if ( $badFileName !==
null ) {
715 $warnings[
'badfilename'] = $badFileName;
718 $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (
string)$this->mFinalExtension );
719 if ( $unwantedFileExtensionDetails !==
null ) {
720 $warnings[
'filetype-unwanted-type'] = $unwantedFileExtensionDetails;
723 $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
724 if ( $fileSizeWarnings ) {
725 $warnings = array_merge( $warnings, $fileSizeWarnings );
728 $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
729 if ( $localFileExistsWarnings ) {
730 $warnings = array_merge( $warnings, $localFileExistsWarnings );
733 if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
734 $warnings[
'was-deleted'] = $filename;
739 $ignoreLocalDupes = isset( $warnings[
'exists'] );
740 $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
742 $warnings[
'duplicate'] = $dupes;
745 $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
746 if ( $archivedDupes !==
null ) {
747 $warnings[
'duplicate-archive'] = $archivedDupes;
765 array_walk_recursive( $warnings,
static function ( &$param, $key ) {
766 if ( $param instanceof
File ) {
768 'fileName' => $param->getName(),
769 'timestamp' => $param->getTimestamp()
771 } elseif ( is_object( $param ) ) {
772 throw new InvalidArgumentException(
773 'UploadBase::makeWarningsSerializable: ' .
774 'Unexpected object of class ' . get_class( $param ) );
788 foreach ( $warnings as $key => $value ) {
789 if ( is_array( $value ) ) {
790 if ( isset( $value[
'fileName'] ) && isset( $value[
'timestamp'] ) ) {
791 $warnings[$key] = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
793 [
'time' => $value[
'timestamp'] ]
796 $warnings[$key] = self::unserializeWarnings( $value );
812 private function checkBadFileName( $filename, $desiredFileName ) {
813 $comparableName = str_replace(
' ',
'_', $desiredFileName );
814 $comparableName = Title::capitalize( $comparableName,
NS_FILE );
816 if ( $desiredFileName != $filename && $comparableName != $filename ) {
831 private function checkUnwantedFileExtensions( $fileExtension ) {
832 $checkFileExtensions = MediaWikiServices::getInstance()->getMainConfig()
833 ->get( MainConfigNames::CheckFileExtensions );
834 $fileExtensions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FileExtensions );
835 if ( $checkFileExtensions ) {
836 $extensions = array_unique( $fileExtensions );
837 if ( !self::checkFileExtension( $fileExtension, $extensions ) ) {
840 Message::listParam( $extensions,
'comma' ),
854 private function checkFileSize( $fileSize ) {
855 $uploadSizeWarning = MediaWikiServices::getInstance()->getMainConfig()
856 ->get( MainConfigNames::UploadSizeWarning );
860 if ( $uploadSizeWarning && ( $fileSize > $uploadSizeWarning ) ) {
861 $warnings[
'large-file'] = [
862 Message::sizeParam( $uploadSizeWarning ),
863 Message::sizeParam( $fileSize ),
867 if ( $fileSize == 0 ) {
868 $warnings[
'empty-file'] =
true;
880 private function checkLocalFileExists(
LocalFile $localFile, $hash ) {
883 $exists = self::getExistsWarning( $localFile );
884 if ( $exists !==
false ) {
885 $warnings[
'exists'] = $exists;
888 if ( $hash !==
false && $hash === $localFile->
getSha1() ) {
889 $warnings[
'no-change'] = $localFile;
894 foreach ( $history as $oldFile ) {
895 if ( $hash === $oldFile->getSha1() ) {
896 $warnings[
'duplicate-version'][] = $oldFile;
904 private function checkLocalFileWasDeleted(
LocalFile $localFile ) {
914 private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
915 if ( $hash ===
false ) {
918 $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
920 foreach ( $dupes as $key => $dupe ) {
924 $title->equals( $dupe->getTitle() )
926 unset( $dupes[$key] );
940 private function checkAgainstArchiveDupes( $hash,
Authority $performer ) {
941 if ( $hash ===
false ) {
945 if ( $archivedFile->getID() > 0 ) {
946 if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
947 return $archivedFile->getName();
973 $comment, $pageText, $watch, $user, $tags = [], ?
string $watchlistExpiry =
null
975 $this->
getLocalFile()->load( IDBAccessObject::READ_LATEST );
976 $props = $this->mFileProps;
979 $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
981 if ( !is_array( $error ) ) {
984 return Status::newFatal( ...$error );
990 $pageText !==
false ? $pageText :
'',
998 if ( $status->isGood() ) {
1000 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
1006 $this->getHookRunner()->onUploadComplete( $this );
1030 if ( $this->mTitle !==
false ) {
1033 if ( !is_string( $this->mDesiredDestName ) ) {
1034 $this->mTitleError = self::ILLEGAL_FILENAME;
1035 $this->mTitle =
null;
1042 $title = Title::newFromText( $this->mDesiredDestName );
1043 if ( $title && $title->getNamespace() ===
NS_FILE ) {
1044 $this->mFilteredName = $title->getDBkey();
1046 $this->mFilteredName = $this->mDesiredDestName;
1049 # oi_archive_name is max 255 bytes, which include a timestamp and an
1050 # exclamation mark, so restrict file name to 240 bytes.
1051 if ( strlen( $this->mFilteredName ) > 240 ) {
1052 $this->mTitleError = self::FILENAME_TOO_LONG;
1053 $this->mTitle =
null;
1065 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1066 if ( $nt ===
null ) {
1067 $this->mTitleError = self::ILLEGAL_FILENAME;
1068 $this->mTitle =
null;
1072 $this->mFilteredName = $nt->
getDBkey();
1078 [ $partname, $ext ] = self::splitExtensions( $this->mFilteredName );
1080 if ( $ext !== [] ) {
1081 $this->mFinalExtension = trim( end( $ext ) );
1083 $this->mFinalExtension =
'';
1088 if ( $this->mTempPath !==
null ) {
1089 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1090 $mime = $magic->guessMimeType( $this->mTempPath );
1091 if ( $mime !==
'unknown/unknown' ) {
1092 # Get a space separated list of extensions
1093 $mimeExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
1094 if ( $mimeExt !==
null ) {
1095 # Set the extension to the canonical extension
1096 $this->mFinalExtension = $mimeExt;
1098 # Fix up the other variables
1099 $this->mFilteredName .=
".{$this->mFinalExtension}";
1100 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1101 $ext = [ $this->mFinalExtension ];
1108 $config = MediaWikiServices::getInstance()->getMainConfig();
1109 $checkFileExtensions = $config->get( MainConfigNames::CheckFileExtensions );
1110 $strictFileExtensions = $config->get( MainConfigNames::StrictFileExtensions );
1111 $fileExtensions = $config->get( MainConfigNames::FileExtensions );
1112 $prohibitedFileExtensions = $config->get( MainConfigNames::ProhibitedFileExtensions );
1114 $badList = self::checkFileExtensionList( $ext, $prohibitedFileExtensions );
1116 if ( $this->mFinalExtension ==
'' ) {
1117 $this->mTitleError = self::FILETYPE_MISSING;
1118 $this->mTitle =
null;
1124 ( $checkFileExtensions && $strictFileExtensions &&
1125 !self::checkFileExtension( $this->mFinalExtension, $fileExtensions ) )
1127 $this->mBlackListedExtensions = $badList;
1128 $this->mTitleError = self::FILETYPE_BADTYPE;
1129 $this->mTitle =
null;
1135 if ( !preg_match(
'/^[\x0-\x7f]*$/', $nt->getText() )
1136 && !MediaWikiServices::getInstance()->getRepoGroup()
1137 ->getLocalRepo()->backendSupportsUnicodePaths()
1139 $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1140 $this->mTitle =
null;
1145 # If there was more than one file "extension", reassemble the base
1146 # filename to prevent bogus complaints about length
1147 if ( count( $ext ) > 1 ) {
1148 $iterations = count( $ext ) - 1;
1149 for ( $i = 0; $i < $iterations; $i++ ) {
1150 $partname .=
'.' . $ext[$i];
1154 if ( strlen( $partname ) < 1 ) {
1155 $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1156 $this->mTitle =
null;
1161 $this->mTitle = $nt;
1173 if ( $this->mLocalFile ===
null ) {
1175 $this->mLocalFile = $nt ===
null
1177 : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1180 return $this->mLocalFile;
1187 return $this->mStashFile;
1203 if ( !$isPartial ) {
1206 return Status::newFatal( ...$error );
1211 return Status::newGood( $file );
1213 return Status::newFatal(
'uploadstash-exception', get_class( $e ), $e->getMessage() );
1222 $props = $this->mFileProps;
1224 $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1225 if ( $error && !is_array( $error ) ) {
1226 $error = [ $error ];
1239 $stash = MediaWikiServices::getInstance()->getRepoGroup()
1240 ->getLocalRepo()->getUploadStash( $user );
1241 $file = $stash->stashFile( $this->mTempPath, $this->
getSourceType(), $this->mFileProps );
1242 $this->mStashFile = $file;
1252 if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1254 wfDebug( __METHOD__ .
": Marked temporary file '{$this->mTempPath}' for removal" );
1255 $this->tempFileObj->autocollect();
1263 return $this->mTempPath;
1276 $bits = explode(
'.', $filename );
1277 $basename = array_shift( $bits );
1279 return [ $basename, $bits ];
1290 return in_array( strtolower( $ext ??
'' ), $list,
true );
1302 return array_intersect( array_map(
'strtolower', $ext ), $list );
1313 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1315 if ( !$mime || $mime ===
'unknown' || $mime ===
'unknown/unknown' ) {
1316 if ( !$magic->isRecognizableExtension( $extension ) ) {
1317 wfDebug( __METHOD__ .
": passing file with unknown detected mime type; " .
1318 "unrecognized extension '$extension', can't verify" );
1323 wfDebug( __METHOD__ .
": rejecting file with unknown detected mime type; " .
1324 "recognized extension '$extension', so probably invalid file" );
1328 $match = $magic->isMatchingExtension( $extension, $mime );
1330 if ( $match ===
null ) {
1331 if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1332 wfDebug( __METHOD__ .
": No extension known for $mime, but we know a mime for $extension" );
1337 wfDebug( __METHOD__ .
": no file extension known for mime type $mime, passing file" );
1342 wfDebug( __METHOD__ .
": mime type $mime matches extension $extension, passing file" );
1349 .
": mime type $mime mismatches file extension $extension, rejecting file" );
1366 # ugly hack: for text files, always look at the entire file.
1367 # For binary field, just check the first K.
1369 if ( str_starts_with( $mime ??
'',
'text/' ) ) {
1370 $chunk = file_get_contents( $file );
1372 $fp = fopen( $file,
'rb' );
1376 $chunk = fread( $fp, 1024 );
1380 $chunk = strtolower( $chunk );
1386 # decode from UTF-16 if needed (could be used for obfuscation).
1387 if ( str_starts_with( $chunk,
"\xfe\xff" ) ) {
1389 } elseif ( str_starts_with( $chunk,
"\xff\xfe" ) ) {
1395 if ( $enc !==
null ) {
1396 $chunk = iconv( $enc,
"ASCII//IGNORE", $chunk );
1399 $chunk = trim( $chunk );
1402 wfDebug( __METHOD__ .
": checking for embedded scripts and HTML stuff" );
1404 # check for HTML doctype
1405 if ( preg_match(
"/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1411 if ( $extension ===
'svg' || str_starts_with( $mime ??
'',
'image/svg' ) ) {
1412 if ( self::checkXMLEncodingMissmatch( $file ) ) {
1426 '<html', # also in safari
1427 '<script', # also in safari
1430 foreach ( $tags as $tag ) {
1431 if ( strpos( $chunk, $tag ) !==
false ) {
1432 wfDebug( __METHOD__ .
": found something that may make it be mistaken for html: $tag" );
1442 # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1443 $chunk = Sanitizer::decodeCharReferences( $chunk );
1445 # look for script-types
1446 if ( preg_match(
'!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!im', $chunk ) ) {
1447 wfDebug( __METHOD__ .
": found script types" );
1452 # look for html-style script-urls
1453 if ( preg_match(
'!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1454 wfDebug( __METHOD__ .
": found html-style script urls" );
1459 # look for css-style script-urls
1460 if ( preg_match(
'!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1461 wfDebug( __METHOD__ .
": found css-style script urls" );
1466 wfDebug( __METHOD__ .
": no scripts found" );
1483 $contents = file_get_contents( $file,
false,
null, 0, 4096 );
1484 $encodingRegex =
'!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1486 if ( preg_match(
"!<\?xml\b(.*?)\?>!si", $contents,
$matches ) ) {
1487 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1488 && !in_array( strtoupper( $encMatch[1] ), self::SAFE_XML_ENCONDINGS )
1490 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1494 } elseif ( preg_match(
"!<\?xml\b!i", $contents ) ) {
1497 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1500 } elseif ( str_starts_with( $contents,
"\x4C\x6F\xA7\x94" ) ) {
1502 wfDebug( __METHOD__ .
": EBCDIC Encoded XML" );
1509 $attemptEncodings = [
'UTF-16',
'UTF-16BE',
'UTF-32',
'UTF-32BE' ];
1510 foreach ( $attemptEncodings as $encoding ) {
1511 AtEase::suppressWarnings();
1512 $str = iconv( $encoding,
'UTF-8', $contents );
1513 AtEase::restoreWarnings();
1514 if ( $str !=
'' && preg_match(
"!<\?xml\b(.*?)\?>!si", $str,
$matches ) ) {
1515 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1516 && !in_array( strtoupper( $encMatch[1] ), self::SAFE_XML_ENCONDINGS )
1518 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1522 } elseif ( $str !=
'' && preg_match(
"!<\?xml\b!i", $str ) ) {
1525 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1540 $this->mSVGNSError =
false;
1543 [ $this,
'checkSvgScriptCallback' ],
1546 'processing_instruction_handler' => [ __CLASS__,
'checkSvgPICallback' ],
1547 'external_dtd_handler' => [ __CLASS__,
'checkSvgExternalDTD' ],
1550 if ( $check->wellFormed !==
true ) {
1553 return $partial ? false : [
'uploadinvalidxml' ];
1556 if ( $check->filterMatch ) {
1557 if ( $this->mSVGNSError ) {
1558 return [
'uploadscriptednamespace', $this->mSVGNSError ];
1560 return $check->filterMatchType;
1575 if ( preg_match(
'/xml-stylesheet/i', $target ) ) {
1576 return [
'upload-scripted-pi-callback' ];
1597 static $allowedDTDs = [
1598 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1599 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1600 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1601 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1603 'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1605 if ( $type !==
'PUBLIC'
1606 || !in_array( $systemId, $allowedDTDs )
1607 || !str_starts_with( $publicId,
"-//W3C//" )
1609 return [
'upload-scripted-dtd' ];
1622 [ $namespace, $strippedElement ] = self::splitXmlNamespace( $element );
1626 static $validNamespaces = [
1629 'http://creativecommons.org/ns#',
1630 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1631 'http://ns.adobe.com/adobeillustrator/10.0/',
1632 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1633 'http://ns.adobe.com/extensibility/1.0/',
1634 'http://ns.adobe.com/flows/1.0/',
1635 'http://ns.adobe.com/illustrator/1.0/',
1636 'http://ns.adobe.com/imagereplacement/1.0/',
1637 'http://ns.adobe.com/pdf/1.3/',
1638 'http://ns.adobe.com/photoshop/1.0/',
1639 'http://ns.adobe.com/saveforweb/1.0/',
1640 'http://ns.adobe.com/variables/1.0/',
1641 'http://ns.adobe.com/xap/1.0/',
1642 'http://ns.adobe.com/xap/1.0/g/',
1643 'http://ns.adobe.com/xap/1.0/g/img/',
1644 'http://ns.adobe.com/xap/1.0/mm/',
1645 'http://ns.adobe.com/xap/1.0/rights/',
1646 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1647 'http://ns.adobe.com/xap/1.0/stype/font#',
1648 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1649 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1650 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1651 'http://ns.adobe.com/xap/1.0/t/pg/',
1652 'http://purl.org/dc/elements/1.1/',
1653 'http://purl.org/dc/elements/1.1',
1654 'http://schemas.microsoft.com/visio/2003/svgextensions/',
1655 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1656 'http://taptrix.com/inkpad/svg_extensions',
1657 'http://web.resource.org/cc/',
1658 'http://www.freesoftware.fsf.org/bkchem/cdml',
1659 'http://www.inkscape.org/namespaces/inkscape',
1660 'http://www.opengis.net/gml',
1661 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1662 'http://www.w3.org/2000/svg',
1663 'http://www.w3.org/tr/rec-rdf-syntax/',
1664 'http://www.w3.org/2000/01/rdf-schema#',
1665 'http://www.w3.org/2000/02/svg/testsuite/description/',
1670 $isBuggyInkscape = preg_match(
'/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1672 if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1673 wfDebug( __METHOD__ .
": Non-svg namespace '$namespace' in uploaded file." );
1675 $this->mSVGNSError = $namespace;
1681 if ( $strippedElement ===
'script' ) {
1682 wfDebug( __METHOD__ .
": Found script element '$element' in uploaded file." );
1684 return [
'uploaded-script-svg', $strippedElement ];
1689 if ( $strippedElement ===
'handler' ) {
1690 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1692 return [
'uploaded-script-svg', $strippedElement ];
1696 if ( $strippedElement ===
'stylesheet' ) {
1697 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1699 return [
'uploaded-script-svg', $strippedElement ];
1703 if ( $strippedElement ===
'iframe' ) {
1704 wfDebug( __METHOD__ .
": iframe in uploaded file." );
1706 return [
'uploaded-script-svg', $strippedElement ];
1710 if ( $strippedElement ===
'style'
1711 && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1713 wfDebug( __METHOD__ .
": hostile css in style element." );
1715 return [
'uploaded-hostile-svg' ];
1718 static $cssAttrs = [
'font',
'clip-path',
'fill',
'filter',
'marker',
1719 'marker-end',
'marker-mid',
'marker-start',
'mask',
'stroke' ];
1721 foreach ( $attribs as $attrib => $value ) {
1723 [ $attributeNamespace, $stripped ] = self::splitXmlNamespace( $attrib );
1724 $value = strtolower( $value );
1728 $namespace ===
'http://www.inkscape.org/namespaces/inkscape' &&
1729 $attributeNamespace ===
''
1730 ) && str_starts_with( $stripped,
'on' )
1733 .
": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1735 return [
'uploaded-event-handler-on-svg', $attrib, $value ];
1743 $stripped ===
'href'
1745 && !str_starts_with( $value,
'data:' )
1746 && !str_starts_with( $value,
'#' )
1747 && !( $strippedElement ===
'a' && preg_match(
'!^https?://!i', $value ) )
1749 wfDebug( __METHOD__ .
": Found href attribute <$strippedElement "
1750 .
"'$attrib'='$value' in uploaded file." );
1752 return [
'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1757 if ( $stripped ===
'href' && strncasecmp(
'data:', $value, 5 ) === 0 ) {
1761 $parameters =
'(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1763 if ( !preg_match(
"!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1764 wfDebug( __METHOD__ .
": Found href to allow listed data: uri "
1765 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1766 return [
'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1771 if ( $stripped ===
'attributename'
1772 && $strippedElement ===
'animate'
1773 && $this->stripXmlNamespace( $value ) ===
'href'
1775 wfDebug( __METHOD__ .
": Found animate that might be changing href using from "
1776 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1778 return [
'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1782 if ( ( $strippedElement ===
'set' || $strippedElement ===
'animate' )
1783 && $stripped ===
'attributename'
1784 && str_starts_with( $value,
'on' )
1786 wfDebug( __METHOD__ .
": Found svg setting event-handler attribute with "
1787 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1789 return [
'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1793 if ( $strippedElement ===
'set'
1794 && $stripped ===
'attributename'
1795 && str_contains( $value,
'href' )
1797 wfDebug( __METHOD__ .
": Found svg setting href attribute '$value' in uploaded file." );
1799 return [
'uploaded-setting-href-svg' ];
1803 if ( $strippedElement ===
'set'
1804 && $stripped ===
'to'
1805 && preg_match(
'!(http|https|data|script):!im', $value )
1807 wfDebug( __METHOD__ .
": Found svg setting attribute to '$value' in uploaded file." );
1809 return [
'uploaded-wrong-setting-svg', $value ];
1813 if ( $stripped ===
'handler' && preg_match(
'!(http|https|data|script):!im', $value ) ) {
1814 wfDebug( __METHOD__ .
": Found svg setting handler with remote/data/script "
1815 .
"'$attrib'='$value' in uploaded file." );
1817 return [
'uploaded-setting-handler-svg', $attrib, $value ];
1821 if ( $stripped ===
'style'
1822 && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1824 wfDebug( __METHOD__ .
": Found svg setting a style with "
1825 .
"remote url '$attrib'='$value' in uploaded file." );
1826 return [
'uploaded-remote-url-svg', $attrib, $value ];
1830 if ( in_array( $stripped, $cssAttrs,
true )
1831 && self::checkCssFragment( $value )
1833 wfDebug( __METHOD__ .
": Found svg setting a style with "
1834 .
"remote url '$attrib'='$value' in uploaded file." );
1835 return [
'uploaded-remote-url-svg', $attrib, $value ];
1841 if ( $strippedElement ===
'image'
1842 && $stripped ===
'filter'
1843 && preg_match(
'!url\s*\(\s*["\']?[^#]!im', $value )
1845 wfDebug( __METHOD__ .
": Found image filter with url: "
1846 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1848 return [
'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1861 private static function checkCssFragment( $value ) {
1862 # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1863 if ( stripos( $value,
'@import' ) !==
false ) {
1867 # We allow @font-face to embed fonts with data: urls, so we snip the string
1868 # 'url' out so that this case won't match when we check for urls below
1869 $pattern =
'!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1870 $value = preg_replace( $pattern,
'$1$2', $value );
1872 # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1873 # properties filter and accelerator don't seem to be useful for xss in SVG files.
1874 # Expression and -o-link don't seem to work either, but filtering them here in case.
1875 # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1876 # but not local ones such as url("#..., url('#..., url(#....
1877 if ( preg_match(
'!expression
1879 | -o-link-source\s*:
1880 | -o-replace\s*:!imx', $value ) ) {
1884 if ( preg_match_all(
1885 "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1890 # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1891 foreach (
$matches[1] as $match ) {
1892 if ( !preg_match(
"!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1898 return (
bool)preg_match(
'/[\000-\010\013\016-\037\177]/', $value );
1906 private static function splitXmlNamespace( $element ) {
1908 $parts = explode(
':', strtolower( $element ) );
1909 $name = array_pop( $parts );
1910 $ns = implode(
':', $parts );
1912 return [ $ns, $name ];
1919 private function stripXmlNamespace( $element ) {
1921 return self::splitXmlNamespace( $element )[1];
1936 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1937 $antivirus = $mainConfig->get( MainConfigNames::Antivirus );
1938 $antivirusSetup = $mainConfig->get( MainConfigNames::AntivirusSetup );
1939 $antivirusRequired = $mainConfig->get( MainConfigNames::AntivirusRequired );
1940 if ( !$antivirus ) {
1941 wfDebug( __METHOD__ .
": virus scanner disabled" );
1946 if ( !$antivirusSetup[$antivirus] ) {
1947 wfDebug( __METHOD__ .
": unknown virus scanner: {$antivirus}" );
1948 $wgOut->wrapWikiMsg(
"<div class=\"error\">\n$1\n</div>",
1949 [
'virus-badscanner', $antivirus ] );
1951 return wfMessage(
'virus-unknownscanner' )->text() .
" {$antivirus}";
1954 # look up scanner configuration
1955 $command = $antivirusSetup[$antivirus][
'command'];
1956 $exitCodeMap = $antivirusSetup[$antivirus][
'codemap'];
1957 $msgPattern = $antivirusSetup[$antivirus][
'messagepattern'] ??
null;
1959 if ( !str_contains( $command,
"%f" ) ) {
1960 # simple pattern: append file to scan
1961 $command .=
" " . Shell::escape( $file );
1963 # complex pattern: replace "%f" with file to scan
1964 $command = str_replace(
"%f", Shell::escape( $file ), $command );
1967 wfDebug( __METHOD__ .
": running virus scan: $command " );
1969 # execute virus scanner
1972 # NOTE: there's a 50-line workaround to make stderr redirection work on windows, too.
1973 # that does not seem to be worth the pain.
1974 # Ask me (Duesentrieb) about it if it's ever needed.
1977 # map exit code to AV_xxx constants.
1978 $mappedCode = $exitCode;
1979 if ( $exitCodeMap ) {
1980 if ( isset( $exitCodeMap[$exitCode] ) ) {
1981 $mappedCode = $exitCodeMap[$exitCode];
1982 } elseif ( isset( $exitCodeMap[
"*"] ) ) {
1983 $mappedCode = $exitCodeMap[
"*"];
1987 # NB: AV_NO_VIRUS is 0, but AV_SCAN_FAILED is false,
1988 # so we need the strict equalities === and thus can't use a switch here
1990 # scan failed (code was mapped to false by $exitCodeMap)
1991 wfDebug( __METHOD__ .
": failed to scan $file (code $exitCode)." );
1993 $output = $antivirusRequired
1994 ?
wfMessage(
'virus-scanfailed', [ $exitCode ] )->text()
1997 # scan failed because filetype is unknown (probably immune)
1998 wfDebug( __METHOD__ .
": unsupported file type $file (code $exitCode)." );
2002 wfDebug( __METHOD__ .
": file passed virus scan." );
2005 $output = trim( $output );
2008 $output =
true; #
if there
's no output, return true
2009 } elseif ( $msgPattern ) {
2011 if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
2012 $output = $groups[1];
2016 wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
2030 private function checkOverwrite( Authority $performer ) {
2031 // First check whether the local file can be overwritten
2032 $file = $this->getLocalFile();
2033 $file->load( IDBAccessObject::READ_LATEST );
2034 if ( $file->exists() ) {
2035 if ( !self::userCanReUpload( $performer, $file ) ) {
2036 return [ 'fileexists-forbidden
', $file->getName() ];
2042 $services = MediaWikiServices::getInstance();
2044 /* Check shared conflicts: if the local file does not exist, but
2045 * RepoGroup::findFile finds a file, it exists in a shared repository.
2047 $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest
' => true ] );
2048 if ( $file && !$performer->isAllowed( 'reupload-shared
' ) ) {
2049 return [ 'fileexists-shared-forbidden
', $file->getName() ];
2062 public static function userCanReUpload( Authority $performer, File $img ) {
2063 if ( $performer->isAllowed( 'reupload
' ) ) {
2064 return true; // non-conditional
2067 if ( !$performer->isAllowed( 'reupload-own
' ) ) {
2071 if ( !( $img instanceof LocalFile ) ) {
2075 return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2089 public static function getExistsWarning( $file ) {
2090 if ( $file->exists() ) {
2091 return [ 'warning
' => 'exists
', 'file
' => $file ];
2094 if ( $file->getTitle()->getArticleID() ) {
2095 return [ 'warning
' => 'page-exists
', 'file
' => $file ];
2098 $n = strrpos( $file->getName(), '.
' );
2100 $partname = substr( $file->getName(), 0, $n );
2101 $extension = substr( $file->getName(), $n + 1 );
2103 $partname = $file->getName();
2106 $normalizedExtension = File::normalizeExtension( $extension );
2107 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2109 if ( $normalizedExtension != $extension ) {
2110 // We're not
using the normalized form of the extension.
2115 $nt_lc = Title::makeTitle(
NS_FILE,
"{$partname}.{$normalizedExtension}" );
2116 $file_lc = $localRepo->newFile( $nt_lc );
2118 if ( $file_lc->exists() ) {
2120 'warning' =>
'exists-normalized',
2122 'normalizedFile' => $file_lc
2128 $similarFiles = $localRepo->findFilesByPrefix(
"{$partname}.", 1 );
2129 if ( count( $similarFiles ) ) {
2131 'warning' =>
'exists-normalized',
2133 'normalizedFile' => $similarFiles[0],
2137 if ( self::isThumbName( $file->getName() ) ) {
2139 $nt_thb = Title::newFromText(
2140 substr( $partname, strpos( $partname,
'-' ) + 1 ) .
'.' . $extension,
2143 $file_thb = $localRepo->newFile( $nt_thb );
2144 if ( $file_thb->exists() ) {
2146 'warning' =>
'thumb',
2148 'thumbFile' => $file_thb
2154 'warning' =>
'thumb-name',
2156 'thumbFile' => $file_thb
2160 foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2161 if ( str_starts_with( $partname, $prefix ) ) {
2163 'warning' =>
'bad-prefix',
2179 $n = strrpos( $filename,
'.' );
2180 $partname = $n ? substr( $filename, 0, $n ) : $filename;
2183 substr( $partname, 3, 3 ) ===
'px-' ||
2184 substr( $partname, 2, 3 ) ===
'px-'
2185 ) && preg_match(
"/[0-9]{2}/", substr( $partname, 0, 2 ) );
2195 $message =
wfMessage(
'filename-prefix-blacklist' )->inContentLanguage();
2196 if ( !$message->isDisabled() ) {
2197 $lines = explode(
"\n", $message->plain() );
2198 foreach (
$lines as $line ) {
2200 $comment = substr( trim( $line ), 0, 1 );
2201 if ( $comment ===
'#' || $comment ==
'' ) {
2205 $comment = strpos( $line,
'#' );
2206 if ( $comment > 0 ) {
2207 $line = substr( $line, 0, $comment - 1 );
2209 $list[] = trim( $line );
2226 $apiUpload = ApiUpload::getDummyInstance();
2227 return $apiUpload->getUploadImageInfo( $this );
2235 $code = $error[
'status'];
2236 unset( $code[
'status'] );
2249 $maxUploadSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxUploadSize );
2251 if ( is_array( $maxUploadSize ) ) {
2252 return $maxUploadSize[$forType] ?? $maxUploadSize[
'*'];
2254 return intval( $maxUploadSize );
2266 ini_get(
'upload_max_filesize' ),
2270 ini_get(
'post_max_size' ),
2273 return min( $phpMaxFileSize, $phpMaxPostSize );
2288 $store = self::getUploadSessionStore();
2289 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2291 return $store->
get( $key );
2307 $store = self::getUploadSessionStore();
2308 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2309 $logger = LoggerFactory::getInstance(
'upload' );
2311 if ( is_array( $value ) && ( $value[
'result'] ??
'' ) ===
'Failure' ) {
2312 $logger->info(
'Upload session {key} for {user} set to failure {status} at {stage}',
2314 'result' => $value[
'result'] ??
'',
2315 'stage' => $value[
'stage'] ??
'unknown',
2317 'status' => (
string)( $value[
'status'] ??
'-' ),
2318 'filekey' => $value[
'filekey'] ??
'',
2322 } elseif ( is_array( $value ) ) {
2323 $logger->debug(
'Upload session {key} for {user} changed {status} at {stage}',
2325 'result' => $value[
'result'] ??
'',
2326 'stage' => $value[
'stage'] ??
'unknown',
2328 'status' => (
string)( $value[
'status'] ??
'-' ),
2329 'filekey' => $value[
'filekey'] ??
'',
2334 $logger->debug(
"Upload session {key} deleted for {user}",
2337 'key' => $statusKey,
2343 if ( $value ===
false ) {
2346 $store->
set( $key, $value, $store::TTL_DAY );
2367 private static function getUploadSessionStore() {
2368 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
Deleted file in the 'filearchive' table.
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Implements some public methods and some protected utility functions which are required by multiple ch...
getName()
Return the name of this file.
wasDeleted()
Was this file ever deleted from the wiki?
Local file in the wiki's own database.
exists()
canRender inherited
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
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]].
const OVERWRITE_EXISTING_FILE
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...
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