26use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
40use Wikimedia\AtEase\AtEase;
61 use ProtectedHookAccessorTrait;
78 protected $mTitleError = 0;
122 public const FILETYPE_MISSING = 8;
123 public const FILETYPE_BADTYPE = 9;
124 public const VERIFICATION_ERROR = 10;
125 public const HOOK_ABORTED = 11;
126 public const FILE_TOO_LARGE = 12;
127 public const WINDOWS_NONASCII_FILENAME = 13;
128 public const FILENAME_TOO_LONG = 14;
130 private const CODE_TO_STATUS = [
131 self::EMPTY_FILE => 'empty-file',
132 self::FILE_TOO_LARGE => 'file-too-large',
133 self::FILETYPE_MISSING => 'filetype-missing',
134 self::FILETYPE_BADTYPE => 'filetype-banned',
135 self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
136 self::ILLEGAL_FILENAME => 'illegal-filename',
137 self::OVERWRITE_EXISTING_FILE => 'overwrite',
138 self::VERIFICATION_ERROR => 'verification-error',
139 self::HOOK_ABORTED => 'hookaborted',
140 self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
141 self::FILENAME_TOO_LONG => 'filename-toolong',
148 public function getVerificationErrorCode( $error ) {
149 return self::CODE_TO_STATUS[$error] ??
'unknown-error';
159 $enableUploads = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnableUploads );
161 return $enableUploads &&
wfIniGetBool(
'file_uploads' );
173 foreach ( [
'upload',
'edit' ] as $permission ) {
174 if ( !$performer->
isAllowed( $permission ) ) {
193 return $user->pingLimiter(
'upload' );
197 private static $uploadHandlers = [
'Stash',
'File',
'Url' ];
207 $type = $type ?: $request->getVal(
'wpSourceType',
'File' );
214 $type = ucfirst( $type );
219 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
221 ->onUploadCreateFromRequest( $type, $className );
222 if ( $className ===
null ) {
223 $className =
'UploadFrom' . $type;
224 wfDebug( __METHOD__ .
": class name: $className" );
225 if ( !in_array( $type, self::$uploadHandlers ) ) {
230 if ( !$className::isEnabled() || !$className::isValidRequest( $request ) ) {
235 $handler =
new $className;
237 $handler->initializeFromRequest( $request );
256 return $this->mDesiredDestName;
283 $this->mDesiredDestName = $name;
284 if ( FileBackend::isStoragePath( $tempPath ) ) {
285 throw new InvalidArgumentException( __METHOD__ .
" given storage path `$tempPath`." );
289 $this->mRemoveTempFile = $removeTempFile;
304 $this->mTempPath = $tempPath ??
'';
305 $this->mFileSize = $fileSize ?:
null;
306 $this->mFileProps =
null;
307 if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
308 $this->tempFileObj =
new TempFSFile( $this->mTempPath );
310 $this->mFileSize = filesize( $this->mTempPath );
313 $this->tempFileObj =
null;
323 return Status::newGood();
332 return Status::newGood();
340 return !$this->mFileSize;
348 return $this->mFileSize;
358 if ( $this->mFileProps && is_string( $this->mFileProps[
'sha1'] ) ) {
359 return $this->mFileProps[
'sha1'];
361 return FSFile::getSha1Base36FromPath( $this->mTempPath );
369 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
374 $tmpFile = $repo->getLocalCopy( $srcPath );
376 $tmpFile->bind( $this );
378 $path = $tmpFile ? $tmpFile->getPath() :
false;
408 return [
'status' => self::EMPTY_FILE ];
415 if ( $this->mFileSize > $maxSize ) {
417 'status' => self::FILE_TOO_LARGE,
428 if ( $verification !==
true ) {
430 'status' => self::VERIFICATION_ERROR,
431 'details' => $verification
439 if ( $result !==
true ) {
443 return [
'status' => self::OK ];
454 if ( $nt ===
null ) {
455 $result = [
'status' => $this->mTitleError ];
456 if ( $this->mTitleError === self::ILLEGAL_FILENAME ) {
457 $result[
'filtered'] = $this->mFilteredName;
459 if ( $this->mTitleError === self::FILETYPE_BADTYPE ) {
460 $result[
'finalExt'] = $this->mFinalExtension;
461 if ( count( $this->mBlackListedExtensions ) ) {
462 $result[
'blacklistedExt'] = $this->mBlackListedExtensions;
482 $verifyMimeType = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeType );
483 if ( $verifyMimeType ) {
484 wfDebug(
"mime: <$mime> extension: <{$this->mFinalExtension}>" );
485 $mimeTypeExclusions = MediaWikiServices::getInstance()->getMainConfig()
486 ->get( MainConfigNames::MimeTypeExclusions );
487 if ( self::checkFileExtension( $mime, $mimeTypeExclusions ) ) {
488 return [
'filetype-badmime', $mime ];
501 $config = MediaWikiServices::getInstance()->getMainConfig();
502 $verifyMimeType = $config->get( MainConfigNames::VerifyMimeType );
503 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
505 if ( $status !==
true ) {
511 if ( !is_array( $this->mFileProps ) ) {
512 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
513 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
515 $mime = $this->mFileProps[
'mime'];
517 if ( $verifyMimeType ) {
518 # XXX: Missing extension will be caught by validateName() via getTitle()
519 if ( (
string)$this->mFinalExtension !==
'' &&
520 !self::verifyExtension( $mime, $this->mFinalExtension )
522 return [
'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
526 # check for htmlish code and javascript
527 if ( !$disableUploadScriptChecks ) {
528 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
530 if ( $svgStatus !==
false ) {
536 $handler = MediaHandler::getHandler( $mime );
538 $handlerStatus = $handler->verifyUpload( $this->mTempPath );
539 if ( !$handlerStatus->isOK() ) {
540 $errors = $handlerStatus->getErrorsArray();
542 return reset( $errors );
547 $this->getHookRunner()->onUploadVerifyFile( $this, $mime, $error );
548 if ( $error !==
true ) {
549 if ( !is_array( $error ) ) {
555 wfDebug( __METHOD__ .
": all clear; passing." );
570 $config = MediaWikiServices::getInstance()->getMainConfig();
571 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
572 # getTitle() sets some internal parameters like $this->mFinalExtension
577 if ( !is_array( $this->mFileProps ) ) {
578 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
579 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
582 # check MIME type, if desired
583 $mime = $this->mFileProps[
'file-mime'];
585 if ( $status !==
true ) {
589 # check for htmlish code and javascript
590 if ( !$disableUploadScriptChecks ) {
591 if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
592 return [
'uploadscripted' ];
594 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
596 if ( $svgStatus !==
false ) {
602 # Scan the uploaded file for viruses
603 $virus = self::detectVirus( $this->mTempPath );
605 return [
'uploadvirus', $virus ];
617 $names = [ $entry[
'name'] ];
624 $nullPos = strpos( $entry[
'name'],
"\000" );
625 if ( $nullPos !==
false ) {
626 $names[] = substr( $entry[
'name'], 0, $nullPos );
631 if ( preg_grep(
'!\.class/?$!', $names ) ) {
632 $this->mJavaDetected =
true;
666 if ( $nt ===
null ) {
670 $status = PermissionStatus::newEmpty();
673 if ( !$status->isGood() ) {
674 return $status->toLegacyErrorArray();
677 $overwriteError = $this->checkOverwrite( $performer );
678 if ( $overwriteError !==
true ) {
679 return [ $overwriteError ];
695 if ( $user ===
null ) {
697 $user = RequestContext::getMain()->getUser();
703 $localFile->
load( IDBAccessObject::READ_LATEST );
704 $filename = $localFile->
getName();
707 $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
708 if ( $badFileName !==
null ) {
709 $warnings[
'badfilename'] = $badFileName;
712 $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (
string)$this->mFinalExtension );
713 if ( $unwantedFileExtensionDetails !==
null ) {
714 $warnings[
'filetype-unwanted-type'] = $unwantedFileExtensionDetails;
717 $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
718 if ( $fileSizeWarnings ) {
719 $warnings = array_merge( $warnings, $fileSizeWarnings );
722 $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
723 if ( $localFileExistsWarnings ) {
724 $warnings = array_merge( $warnings, $localFileExistsWarnings );
727 if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
728 $warnings[
'was-deleted'] = $filename;
733 $ignoreLocalDupes = isset( $warnings[
'exists'] );
734 $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
736 $warnings[
'duplicate'] = $dupes;
739 $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
740 if ( $archivedDupes !==
null ) {
741 $warnings[
'duplicate-archive'] = $archivedDupes;
759 array_walk_recursive( $warnings,
static function ( &$param, $key ) {
760 if ( $param instanceof
File ) {
762 'fileName' => $param->getName(),
763 'timestamp' => $param->getTimestamp()
765 } elseif ( is_object( $param ) ) {
766 throw new InvalidArgumentException(
767 'UploadBase::makeWarningsSerializable: ' .
768 'Unexpected object of class ' . get_class( $param ) );
782 foreach ( $warnings as $key => $value ) {
783 if ( is_array( $value ) ) {
784 if ( isset( $value[
'fileName'] ) && isset( $value[
'timestamp'] ) ) {
785 $warnings[$key] = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
787 [
'time' => $value[
'timestamp'] ]
790 $warnings[$key] = self::unserializeWarnings( $value );
806 private function checkBadFileName( $filename, $desiredFileName ) {
807 $comparableName = str_replace(
' ',
'_', $desiredFileName );
808 $comparableName = Title::capitalize( $comparableName,
NS_FILE );
810 if ( $desiredFileName != $filename && $comparableName != $filename ) {
825 private function checkUnwantedFileExtensions( $fileExtension ) {
826 $checkFileExtensions = MediaWikiServices::getInstance()->getMainConfig()
827 ->get( MainConfigNames::CheckFileExtensions );
828 $fileExtensions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FileExtensions );
829 if ( $checkFileExtensions ) {
830 $extensions = array_unique( $fileExtensions );
831 if ( !self::checkFileExtension( $fileExtension, $extensions ) ) {
834 Message::listParam( $extensions,
'comma' ),
848 private function checkFileSize( $fileSize ) {
849 $uploadSizeWarning = MediaWikiServices::getInstance()->getMainConfig()
850 ->get( MainConfigNames::UploadSizeWarning );
854 if ( $uploadSizeWarning && ( $fileSize > $uploadSizeWarning ) ) {
855 $warnings[
'large-file'] = [
856 Message::sizeParam( $uploadSizeWarning ),
857 Message::sizeParam( $fileSize ),
861 if ( $fileSize == 0 ) {
862 $warnings[
'empty-file'] =
true;
874 private function checkLocalFileExists(
LocalFile $localFile, $hash ) {
877 $exists = self::getExistsWarning( $localFile );
878 if ( $exists !==
false ) {
879 $warnings[
'exists'] = $exists;
882 if ( $hash !==
false && $hash === $localFile->
getSha1() ) {
883 $warnings[
'no-change'] = $localFile;
888 foreach ( $history as $oldFile ) {
889 if ( $hash === $oldFile->getSha1() ) {
890 $warnings[
'duplicate-version'][] = $oldFile;
898 private function checkLocalFileWasDeleted(
LocalFile $localFile ) {
908 private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
909 if ( $hash ===
false ) {
912 $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
914 foreach ( $dupes as $key => $dupe ) {
918 $title->equals( $dupe->getTitle() )
920 unset( $dupes[$key] );
934 private function checkAgainstArchiveDupes( $hash,
Authority $performer ) {
935 if ( $hash ===
false ) {
939 if ( $archivedFile->getID() > 0 ) {
940 if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
941 return $archivedFile->getName();
967 $comment, $pageText, $watch, $user, $tags = [], ?
string $watchlistExpiry =
null
969 $this->
getLocalFile()->load( IDBAccessObject::READ_LATEST );
970 $props = $this->mFileProps;
973 $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
975 if ( !is_array( $error ) ) {
978 return Status::newFatal( ...$error );
984 $pageText !==
false ? $pageText :
'',
992 if ( $status->isGood() ) {
994 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
1000 $this->getHookRunner()->onUploadComplete( $this );
1024 if ( $this->mTitle !==
false ) {
1027 if ( !is_string( $this->mDesiredDestName ) ) {
1028 $this->mTitleError = self::ILLEGAL_FILENAME;
1029 $this->mTitle =
null;
1036 $title = Title::newFromText( $this->mDesiredDestName );
1037 if ( $title && $title->getNamespace() ===
NS_FILE ) {
1038 $this->mFilteredName = $title->getDBkey();
1040 $this->mFilteredName = $this->mDesiredDestName;
1043 # oi_archive_name is max 255 bytes, which include a timestamp and an
1044 # exclamation mark, so restrict file name to 240 bytes.
1045 if ( strlen( $this->mFilteredName ) > 240 ) {
1046 $this->mTitleError = self::FILENAME_TOO_LONG;
1047 $this->mTitle =
null;
1059 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1060 if ( $nt ===
null ) {
1061 $this->mTitleError = self::ILLEGAL_FILENAME;
1062 $this->mTitle =
null;
1066 $this->mFilteredName = $nt->
getDBkey();
1072 [ $partname, $ext ] = self::splitExtensions( $this->mFilteredName );
1074 if ( $ext !== [] ) {
1075 $this->mFinalExtension = trim( end( $ext ) );
1077 $this->mFinalExtension =
'';
1082 if ( $this->mTempPath !==
null ) {
1083 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1084 $mime = $magic->guessMimeType( $this->mTempPath );
1085 if ( $mime !==
'unknown/unknown' ) {
1086 # Get a space separated list of extensions
1087 $mimeExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
1088 if ( $mimeExt !==
null ) {
1089 # Set the extension to the canonical extension
1090 $this->mFinalExtension = $mimeExt;
1092 # Fix up the other variables
1093 $this->mFilteredName .=
".{$this->mFinalExtension}";
1094 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1095 $ext = [ $this->mFinalExtension ];
1102 $config = MediaWikiServices::getInstance()->getMainConfig();
1103 $checkFileExtensions = $config->get( MainConfigNames::CheckFileExtensions );
1104 $strictFileExtensions = $config->get( MainConfigNames::StrictFileExtensions );
1105 $fileExtensions = $config->get( MainConfigNames::FileExtensions );
1106 $prohibitedFileExtensions = $config->get( MainConfigNames::ProhibitedFileExtensions );
1108 $badList = self::checkFileExtensionList( $ext, $prohibitedFileExtensions );
1110 if ( $this->mFinalExtension ==
'' ) {
1111 $this->mTitleError = self::FILETYPE_MISSING;
1112 $this->mTitle =
null;
1118 ( $checkFileExtensions && $strictFileExtensions &&
1119 !self::checkFileExtension( $this->mFinalExtension, $fileExtensions ) )
1121 $this->mBlackListedExtensions = $badList;
1122 $this->mTitleError = self::FILETYPE_BADTYPE;
1123 $this->mTitle =
null;
1129 if ( !preg_match(
'/^[\x0-\x7f]*$/', $nt->getText() )
1130 && !MediaWikiServices::getInstance()->getRepoGroup()
1131 ->getLocalRepo()->backendSupportsUnicodePaths()
1133 $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1134 $this->mTitle =
null;
1139 # If there was more than one file "extension", reassemble the base
1140 # filename to prevent bogus complaints about length
1141 if ( count( $ext ) > 1 ) {
1142 $iterations = count( $ext ) - 1;
1143 for ( $i = 0; $i < $iterations; $i++ ) {
1144 $partname .=
'.' . $ext[$i];
1148 if ( strlen( $partname ) < 1 ) {
1149 $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1150 $this->mTitle =
null;
1155 $this->mTitle = $nt;
1167 if ( $this->mLocalFile ===
null ) {
1169 $this->mLocalFile = $nt ===
null
1171 : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1174 return $this->mLocalFile;
1181 return $this->mStashFile;
1197 if ( !$isPartial ) {
1200 return Status::newFatal( ...$error );
1205 return Status::newGood( $file );
1207 return Status::newFatal(
'uploadstash-exception', get_class( $e ), $e->getMessage() );
1216 $props = $this->mFileProps;
1218 $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1219 if ( $error && !is_array( $error ) ) {
1220 $error = [ $error ];
1233 $stash = MediaWikiServices::getInstance()->getRepoGroup()
1234 ->getLocalRepo()->getUploadStash( $user );
1235 $file = $stash->stashFile( $this->mTempPath, $this->
getSourceType(), $this->mFileProps );
1236 $this->mStashFile = $file;
1246 if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1248 wfDebug( __METHOD__ .
": Marked temporary file '{$this->mTempPath}' for removal" );
1249 $this->tempFileObj->autocollect();
1257 return $this->mTempPath;
1270 $bits = explode(
'.', $filename );
1271 $basename = array_shift( $bits );
1273 return [ $basename, $bits ];
1284 return in_array( strtolower( $ext ??
'' ), $list,
true );
1296 return array_intersect( array_map(
'strtolower', $ext ), $list );
1307 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1309 if ( !$mime || $mime ===
'unknown' || $mime ===
'unknown/unknown' ) {
1310 if ( !$magic->isRecognizableExtension( $extension ) ) {
1311 wfDebug( __METHOD__ .
": passing file with unknown detected mime type; " .
1312 "unrecognized extension '$extension', can't verify" );
1317 wfDebug( __METHOD__ .
": rejecting file with unknown detected mime type; " .
1318 "recognized extension '$extension', so probably invalid file" );
1322 $match = $magic->isMatchingExtension( $extension, $mime );
1324 if ( $match ===
null ) {
1325 if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1326 wfDebug( __METHOD__ .
": No extension known for $mime, but we know a mime for $extension" );
1331 wfDebug( __METHOD__ .
": no file extension known for mime type $mime, passing file" );
1336 wfDebug( __METHOD__ .
": mime type $mime matches extension $extension, passing file" );
1343 .
": mime type $mime mismatches file extension $extension, rejecting file" );
1360 # ugly hack: for text files, always look at the entire file.
1361 # For binary field, just check the first K.
1363 if ( str_starts_with( $mime ??
'',
'text/' ) ) {
1364 $chunk = file_get_contents( $file );
1366 $fp = fopen( $file,
'rb' );
1370 $chunk = fread( $fp, 1024 );
1374 $chunk = strtolower( $chunk );
1380 # decode from UTF-16 if needed (could be used for obfuscation).
1381 if ( str_starts_with( $chunk,
"\xfe\xff" ) ) {
1383 } elseif ( str_starts_with( $chunk,
"\xff\xfe" ) ) {
1389 if ( $enc !==
null ) {
1390 $chunk = iconv( $enc,
"ASCII//IGNORE", $chunk );
1393 $chunk = trim( $chunk );
1396 wfDebug( __METHOD__ .
": checking for embedded scripts and HTML stuff" );
1398 # check for HTML doctype
1399 if ( preg_match(
"/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1405 if ( $extension ===
'svg' || str_starts_with( $mime ??
'',
'image/svg' ) ) {
1406 if ( self::checkXMLEncodingMissmatch( $file ) ) {
1420 '<html', # also in safari
1421 '<script', # also in safari
1424 foreach ( $tags as $tag ) {
1425 if ( strpos( $chunk, $tag ) !==
false ) {
1426 wfDebug( __METHOD__ .
": found something that may make it be mistaken for html: $tag" );
1436 # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1437 $chunk = Sanitizer::decodeCharReferences( $chunk );
1439 # look for script-types
1440 if ( preg_match(
'!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!im', $chunk ) ) {
1441 wfDebug( __METHOD__ .
": found script types" );
1446 # look for html-style script-urls
1447 if ( preg_match(
'!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1448 wfDebug( __METHOD__ .
": found html-style script urls" );
1453 # look for css-style script-urls
1454 if ( preg_match(
'!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1455 wfDebug( __METHOD__ .
": found css-style script urls" );
1460 wfDebug( __METHOD__ .
": no scripts found" );
1477 $contents = file_get_contents( $file,
false,
null, 0, 4096 );
1478 $encodingRegex =
'!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1480 if ( preg_match(
"!<\?xml\b(.*?)\?>!si", $contents,
$matches ) ) {
1481 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1482 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1484 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1488 } elseif ( preg_match(
"!<\?xml\b!i", $contents ) ) {
1491 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1494 } elseif ( str_starts_with( $contents,
"\x4C\x6F\xA7\x94" ) ) {
1496 wfDebug( __METHOD__ .
": EBCDIC Encoded XML" );
1503 $attemptEncodings = [
'UTF-16',
'UTF-16BE',
'UTF-32',
'UTF-32BE' ];
1504 foreach ( $attemptEncodings as $encoding ) {
1505 AtEase::suppressWarnings();
1506 $str = iconv( $encoding,
'UTF-8', $contents );
1507 AtEase::restoreWarnings();
1508 if ( $str !=
'' && preg_match(
"!<\?xml\b(.*?)\?>!si", $str,
$matches ) ) {
1509 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1510 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1512 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1516 } elseif ( $str !=
'' && preg_match(
"!<\?xml\b!i", $str ) ) {
1519 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1534 $this->mSVGNSError =
false;
1537 [ $this,
'checkSvgScriptCallback' ],
1540 'processing_instruction_handler' => [ __CLASS__,
'checkSvgPICallback' ],
1541 'external_dtd_handler' => [ __CLASS__,
'checkSvgExternalDTD' ],
1544 if ( $check->wellFormed !==
true ) {
1547 return $partial ? false : [
'uploadinvalidxml' ];
1550 if ( $check->filterMatch ) {
1551 if ( $this->mSVGNSError ) {
1552 return [
'uploadscriptednamespace', $this->mSVGNSError ];
1554 return $check->filterMatchType;
1569 if ( preg_match(
'/xml-stylesheet/i', $target ) ) {
1570 return [
'upload-scripted-pi-callback' ];
1591 static $allowedDTDs = [
1592 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1593 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1594 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1595 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1597 'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1599 if ( $type !==
'PUBLIC'
1600 || !in_array( $systemId, $allowedDTDs )
1601 || !str_starts_with( $publicId,
"-//W3C//" )
1603 return [
'upload-scripted-dtd' ];
1616 [ $namespace, $strippedElement ] = self::splitXmlNamespace( $element );
1620 static $validNamespaces = [
1623 'http://creativecommons.org/ns#',
1624 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1625 'http://ns.adobe.com/adobeillustrator/10.0/',
1626 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1627 'http://ns.adobe.com/extensibility/1.0/',
1628 'http://ns.adobe.com/flows/1.0/',
1629 'http://ns.adobe.com/illustrator/1.0/',
1630 'http://ns.adobe.com/imagereplacement/1.0/',
1631 'http://ns.adobe.com/pdf/1.3/',
1632 'http://ns.adobe.com/photoshop/1.0/',
1633 'http://ns.adobe.com/saveforweb/1.0/',
1634 'http://ns.adobe.com/variables/1.0/',
1635 'http://ns.adobe.com/xap/1.0/',
1636 'http://ns.adobe.com/xap/1.0/g/',
1637 'http://ns.adobe.com/xap/1.0/g/img/',
1638 'http://ns.adobe.com/xap/1.0/mm/',
1639 'http://ns.adobe.com/xap/1.0/rights/',
1640 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1641 'http://ns.adobe.com/xap/1.0/stype/font#',
1642 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1643 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1644 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1645 'http://ns.adobe.com/xap/1.0/t/pg/',
1646 'http://purl.org/dc/elements/1.1/',
1647 'http://purl.org/dc/elements/1.1',
1648 'http://schemas.microsoft.com/visio/2003/svgextensions/',
1649 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1650 'http://taptrix.com/inkpad/svg_extensions',
1651 'http://web.resource.org/cc/',
1652 'http://www.freesoftware.fsf.org/bkchem/cdml',
1653 'http://www.inkscape.org/namespaces/inkscape',
1654 'http://www.opengis.net/gml',
1655 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1656 'http://www.w3.org/2000/svg',
1657 'http://www.w3.org/tr/rec-rdf-syntax/',
1658 'http://www.w3.org/2000/01/rdf-schema#',
1659 'http://www.w3.org/2000/02/svg/testsuite/description/',
1664 $isBuggyInkscape = preg_match(
'/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1666 if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1667 wfDebug( __METHOD__ .
": Non-svg namespace '$namespace' in uploaded file." );
1669 $this->mSVGNSError = $namespace;
1675 if ( $strippedElement ===
'script' ) {
1676 wfDebug( __METHOD__ .
": Found script element '$element' in uploaded file." );
1678 return [
'uploaded-script-svg', $strippedElement ];
1683 if ( $strippedElement ===
'handler' ) {
1684 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1686 return [
'uploaded-script-svg', $strippedElement ];
1690 if ( $strippedElement ===
'stylesheet' ) {
1691 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1693 return [
'uploaded-script-svg', $strippedElement ];
1697 if ( $strippedElement ===
'iframe' ) {
1698 wfDebug( __METHOD__ .
": iframe in uploaded file." );
1700 return [
'uploaded-script-svg', $strippedElement ];
1704 if ( $strippedElement ===
'style'
1705 && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1707 wfDebug( __METHOD__ .
": hostile css in style element." );
1709 return [
'uploaded-hostile-svg' ];
1712 static $cssAttrs = [
'font',
'clip-path',
'fill',
'filter',
'marker',
1713 'marker-end',
'marker-mid',
'marker-start',
'mask',
'stroke' ];
1715 foreach ( $attribs as $attrib => $value ) {
1717 [ $attributeNamespace, $stripped ] = self::splitXmlNamespace( $attrib );
1718 $value = strtolower( $value );
1722 $namespace ===
'http://www.inkscape.org/namespaces/inkscape' &&
1723 $attributeNamespace ===
''
1724 ) && str_starts_with( $stripped,
'on' )
1727 .
": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1729 return [
'uploaded-event-handler-on-svg', $attrib, $value ];
1737 $stripped ===
'href'
1739 && !str_starts_with( $value,
'data:' )
1740 && !str_starts_with( $value,
'#' )
1741 && !( $strippedElement ===
'a' && preg_match(
'!^https?://!i', $value ) )
1743 wfDebug( __METHOD__ .
": Found href attribute <$strippedElement "
1744 .
"'$attrib'='$value' in uploaded file." );
1746 return [
'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1751 if ( $stripped ===
'href' && strncasecmp(
'data:', $value, 5 ) === 0 ) {
1755 $parameters =
'(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1757 if ( !preg_match(
"!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1758 wfDebug( __METHOD__ .
": Found href to allow listed data: uri "
1759 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1760 return [
'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1765 if ( $stripped ===
'attributename'
1766 && $strippedElement ===
'animate'
1767 && $this->stripXmlNamespace( $value ) ===
'href'
1769 wfDebug( __METHOD__ .
": Found animate that might be changing href using from "
1770 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1772 return [
'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1776 if ( ( $strippedElement ===
'set' || $strippedElement ===
'animate' )
1777 && $stripped ===
'attributename'
1778 && str_starts_with( $value,
'on' )
1780 wfDebug( __METHOD__ .
": Found svg setting event-handler attribute with "
1781 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1783 return [
'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1787 if ( $strippedElement ===
'set'
1788 && $stripped ===
'attributename'
1789 && str_contains( $value,
'href' )
1791 wfDebug( __METHOD__ .
": Found svg setting href attribute '$value' in uploaded file." );
1793 return [
'uploaded-setting-href-svg' ];
1797 if ( $strippedElement ===
'set'
1798 && $stripped ===
'to'
1799 && preg_match(
'!(http|https|data|script):!im', $value )
1801 wfDebug( __METHOD__ .
": Found svg setting attribute to '$value' in uploaded file." );
1803 return [
'uploaded-wrong-setting-svg', $value ];
1807 if ( $stripped ===
'handler' && preg_match(
'!(http|https|data|script):!im', $value ) ) {
1808 wfDebug( __METHOD__ .
": Found svg setting handler with remote/data/script "
1809 .
"'$attrib'='$value' in uploaded file." );
1811 return [
'uploaded-setting-handler-svg', $attrib, $value ];
1815 if ( $stripped ===
'style'
1816 && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1818 wfDebug( __METHOD__ .
": Found svg setting a style with "
1819 .
"remote url '$attrib'='$value' in uploaded file." );
1820 return [
'uploaded-remote-url-svg', $attrib, $value ];
1824 if ( in_array( $stripped, $cssAttrs,
true )
1825 && self::checkCssFragment( $value )
1827 wfDebug( __METHOD__ .
": Found svg setting a style with "
1828 .
"remote url '$attrib'='$value' in uploaded file." );
1829 return [
'uploaded-remote-url-svg', $attrib, $value ];
1835 if ( $strippedElement ===
'image'
1836 && $stripped ===
'filter'
1837 && preg_match(
'!url\s*\(\s*["\']?[^#]!im', $value )
1839 wfDebug( __METHOD__ .
": Found image filter with url: "
1840 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1842 return [
'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1855 private static function checkCssFragment( $value ) {
1856 # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1857 if ( stripos( $value,
'@import' ) !==
false ) {
1861 # We allow @font-face to embed fonts with data: urls, so we snip the string
1862 # 'url' out so that this case won't match when we check for urls below
1863 $pattern =
'!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1864 $value = preg_replace( $pattern,
'$1$2', $value );
1866 # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1867 # properties filter and accelerator don't seem to be useful for xss in SVG files.
1868 # Expression and -o-link don't seem to work either, but filtering them here in case.
1869 # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1870 # but not local ones such as url("#..., url('#..., url(#....
1871 if ( preg_match(
'!expression
1873 | -o-link-source\s*:
1874 | -o-replace\s*:!imx', $value ) ) {
1878 if ( preg_match_all(
1879 "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1884 # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1885 foreach (
$matches[1] as $match ) {
1886 if ( !preg_match(
"!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1892 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
1904 private static function splitXmlNamespace( $element ) {
1906 $parts = explode(
':', strtolower( $element ) );
1907 $name = array_pop( $parts );
1908 $ns = implode(
':', $parts );
1910 return [ $ns, $name ];
1917 private function stripXmlNamespace( $element ) {
1919 return self::splitXmlNamespace( $element )[1];
1934 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1935 $antivirus = $mainConfig->get( MainConfigNames::Antivirus );
1936 $antivirusSetup = $mainConfig->get( MainConfigNames::AntivirusSetup );
1937 $antivirusRequired = $mainConfig->get( MainConfigNames::AntivirusRequired );
1938 if ( !$antivirus ) {
1939 wfDebug( __METHOD__ .
": virus scanner disabled" );
1944 if ( !$antivirusSetup[$antivirus] ) {
1945 wfDebug( __METHOD__ .
": unknown virus scanner: {$antivirus}" );
1946 $wgOut->wrapWikiMsg(
"<div class=\"error\">\n$1\n</div>",
1947 [
'virus-badscanner', $antivirus ] );
1949 return wfMessage(
'virus-unknownscanner' )->text() .
" {$antivirus}";
1952 # look up scanner configuration
1953 $command = $antivirusSetup[$antivirus][
'command'];
1954 $exitCodeMap = $antivirusSetup[$antivirus][
'codemap'];
1955 $msgPattern = $antivirusSetup[$antivirus][
'messagepattern'] ??
null;
1957 if ( !str_contains( $command,
"%f" ) ) {
1958 # simple pattern: append file to scan
1959 $command .=
" " . Shell::escape( $file );
1961 # complex pattern: replace "%f" with file to scan
1962 $command = str_replace(
"%f", Shell::escape( $file ), $command );
1965 wfDebug( __METHOD__ .
": running virus scan: $command " );
1967 # execute virus scanner
1970 # NOTE: there's a 50-line workaround to make stderr redirection work on windows, too.
1971 # that does not seem to be worth the pain.
1972 # Ask me (Duesentrieb) about it if it's ever needed.
1975 # map exit code to AV_xxx constants.
1976 $mappedCode = $exitCode;
1977 if ( $exitCodeMap ) {
1978 if ( isset( $exitCodeMap[$exitCode] ) ) {
1979 $mappedCode = $exitCodeMap[$exitCode];
1980 } elseif ( isset( $exitCodeMap[
"*"] ) ) {
1981 $mappedCode = $exitCodeMap[
"*"];
1985 # NB: AV_NO_VIRUS is 0, but AV_SCAN_FAILED is false,
1986 # so we need the strict equalities === and thus can't use a switch here
1988 # scan failed (code was mapped to false by $exitCodeMap)
1989 wfDebug( __METHOD__ .
": failed to scan $file (code $exitCode)." );
1991 $output = $antivirusRequired
1992 ?
wfMessage(
'virus-scanfailed', [ $exitCode ] )->text()
1995 # scan failed because filetype is unknown (probably immune)
1996 wfDebug( __METHOD__ .
": unsupported file type $file (code $exitCode)." );
2000 wfDebug( __METHOD__ .
": file passed virus scan." );
2003 $output = trim( $output );
2006 $output =
true; #
if there
's no output, return true
2007 } elseif ( $msgPattern ) {
2009 if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
2010 $output = $groups[1];
2014 wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
2028 private function checkOverwrite( Authority $performer ) {
2029 // First check whether the local file can be overwritten
2030 $file = $this->getLocalFile();
2031 $file->load( IDBAccessObject::READ_LATEST );
2032 if ( $file->exists() ) {
2033 if ( !self::userCanReUpload( $performer, $file ) ) {
2034 return [ 'fileexists-forbidden
', $file->getName() ];
2040 $services = MediaWikiServices::getInstance();
2042 /* Check shared conflicts: if the local file does not exist, but
2043 * RepoGroup::findFile finds a file, it exists in a shared repository.
2045 $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest
' => true ] );
2046 if ( $file && !$performer->isAllowed( 'reupload-shared
' ) ) {
2047 return [ 'fileexists-shared-forbidden
', $file->getName() ];
2060 public static function userCanReUpload( Authority $performer, File $img ) {
2061 if ( $performer->isAllowed( 'reupload
' ) ) {
2062 return true; // non-conditional
2065 if ( !$performer->isAllowed( 'reupload-own
' ) ) {
2069 if ( !( $img instanceof LocalFile ) ) {
2073 return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2087 public static function getExistsWarning( $file ) {
2088 if ( $file->exists() ) {
2089 return [ 'warning
' => 'exists
', 'file
' => $file ];
2092 if ( $file->getTitle()->getArticleID() ) {
2093 return [ 'warning
' => 'page-exists
', 'file
' => $file ];
2096 $n = strrpos( $file->getName(), '.
' );
2098 $partname = substr( $file->getName(), 0, $n );
2099 $extension = substr( $file->getName(), $n + 1 );
2101 $partname = $file->getName();
2104 $normalizedExtension = File::normalizeExtension( $extension );
2105 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2107 if ( $normalizedExtension != $extension ) {
2108 // We're not
using the normalized form of the extension.
2113 $nt_lc = Title::makeTitle(
NS_FILE,
"{$partname}.{$normalizedExtension}" );
2114 $file_lc = $localRepo->newFile( $nt_lc );
2116 if ( $file_lc->exists() ) {
2118 'warning' =>
'exists-normalized',
2120 'normalizedFile' => $file_lc
2126 $similarFiles = $localRepo->findFilesByPrefix(
"{$partname}.", 1 );
2127 if ( count( $similarFiles ) ) {
2129 'warning' =>
'exists-normalized',
2131 'normalizedFile' => $similarFiles[0],
2135 if ( self::isThumbName( $file->getName() ) ) {
2137 $nt_thb = Title::newFromText(
2138 substr( $partname, strpos( $partname,
'-' ) + 1 ) .
'.' . $extension,
2141 $file_thb = $localRepo->newFile( $nt_thb );
2142 if ( $file_thb->exists() ) {
2144 'warning' =>
'thumb',
2146 'thumbFile' => $file_thb
2152 'warning' =>
'thumb-name',
2154 'thumbFile' => $file_thb
2158 foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2159 if ( str_starts_with( $partname, $prefix ) ) {
2161 'warning' =>
'bad-prefix',
2177 $n = strrpos( $filename,
'.' );
2178 $partname = $n ? substr( $filename, 0, $n ) : $filename;
2181 substr( $partname, 3, 3 ) ===
'px-' ||
2182 substr( $partname, 2, 3 ) ===
'px-'
2183 ) && preg_match(
"/[0-9]{2}/", substr( $partname, 0, 2 ) );
2193 $message =
wfMessage(
'filename-prefix-blacklist' )->inContentLanguage();
2194 if ( !$message->isDisabled() ) {
2195 $lines = explode(
"\n", $message->plain() );
2196 foreach (
$lines as $line ) {
2198 $comment = substr( trim( $line ), 0, 1 );
2199 if ( $comment ===
'#' || $comment ==
'' ) {
2203 $comment = strpos( $line,
'#' );
2204 if ( $comment > 0 ) {
2205 $line = substr( $line, 0, $comment - 1 );
2207 $list[] = trim( $line );
2225 return $apiUpload->getUploadImageInfo( $this );
2233 $code = $error[
'status'];
2234 unset( $code[
'status'] );
2247 $maxUploadSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxUploadSize );
2249 if ( is_array( $maxUploadSize ) ) {
2250 if ( $forType !==
null && isset( $maxUploadSize[$forType] ) ) {
2251 return $maxUploadSize[$forType];
2253 return $maxUploadSize[
'*'];
2255 return intval( $maxUploadSize );
2267 ini_get(
'upload_max_filesize' ),
2271 ini_get(
'post_max_size' ),
2274 return min( $phpMaxFileSize, $phpMaxPostSize );
2289 $store = self::getUploadSessionStore();
2290 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2292 return $store->
get( $key );
2308 $store = self::getUploadSessionStore();
2309 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2310 $logger = LoggerFactory::getInstance(
'upload' );
2312 if ( is_array( $value ) && ( $value[
'result'] ??
'' ) ===
'Failure' ) {
2313 $logger->info(
'Upload session {key} for {user} set to failure {status} at {stage}',
2315 'result' => $value[
'result'] ??
'',
2316 'stage' => $value[
'stage'] ??
'unknown',
2318 'status' => (
string)( $value[
'status'] ??
'-' ),
2319 'filekey' => $value[
'filekey'] ??
'',
2323 } elseif ( is_array( $value ) ) {
2324 $logger->debug(
'Upload session {key} for {user} changed {status} at {stage}',
2326 'result' => $value[
'result'] ??
'',
2327 'stage' => $value[
'stage'] ??
'unknown',
2329 'status' => (
string)( $value[
'status'] ??
'-' ),
2330 'filekey' => $value[
'filekey'] ??
'',
2335 $logger->debug(
"Upload session {key} deleted for {user}",
2338 'key' => $statusKey,
2344 if ( $value ===
false ) {
2347 $store->
set( $key, $value, $store::TTL_DAY );
2368 private static function getUploadSessionStore() {
2369 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
static getDummyInstance()
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.
This class is used to hold the location and do limited manipulation of files stored temporarily (this...
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
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...
doStashFile(User $user=null)
Implementation for stashFile() and tryStashFile().
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
XML syntax and type checker.
if(!file_exists( $CREDITS)) $lines