26use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
39use Wikimedia\AtEase\AtEase;
58 use ProtectedHookAccessorTrait;
75 protected $mTitleError = 0;
119 public const FILETYPE_MISSING = 8;
120 public const FILETYPE_BADTYPE = 9;
121 public const VERIFICATION_ERROR = 10;
122 public const HOOK_ABORTED = 11;
123 public const FILE_TOO_LARGE = 12;
124 public const WINDOWS_NONASCII_FILENAME = 13;
125 public const FILENAME_TOO_LONG = 14;
127 private const CODE_TO_STATUS = [
128 self::EMPTY_FILE => 'empty-file',
129 self::FILE_TOO_LARGE => 'file-too-large',
130 self::FILETYPE_MISSING => 'filetype-missing',
131 self::FILETYPE_BADTYPE => 'filetype-banned',
132 self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
133 self::ILLEGAL_FILENAME => 'illegal-filename',
134 self::OVERWRITE_EXISTING_FILE => 'overwrite',
135 self::VERIFICATION_ERROR => 'verification-error',
136 self::HOOK_ABORTED => 'hookaborted',
137 self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
138 self::FILENAME_TOO_LONG => 'filename-toolong',
145 public function getVerificationErrorCode( $error ) {
146 return self::CODE_TO_STATUS[$error] ??
'unknown-error';
156 $enableUploads = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnableUploads );
158 return $enableUploads &&
wfIniGetBool(
'file_uploads' );
170 foreach ( [
'upload',
'edit' ] as $permission ) {
171 if ( !$performer->
isAllowed( $permission ) ) {
190 return $user->pingLimiter(
'upload' );
194 private static $uploadHandlers = [
'Stash',
'File',
'Url' ];
204 $type = $type ?: $request->getVal(
'wpSourceType',
'File' );
211 $type = ucfirst( $type );
216 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
218 ->onUploadCreateFromRequest( $type, $className );
219 if ( $className ===
null ) {
220 $className =
'UploadFrom' . $type;
221 wfDebug( __METHOD__ .
": class name: $className" );
222 if ( !in_array( $type, self::$uploadHandlers ) ) {
227 if ( !$className::isEnabled() || !$className::isValidRequest( $request ) ) {
232 $handler =
new $className;
234 $handler->initializeFromRequest( $request );
253 return $this->mDesiredDestName;
280 $this->mDesiredDestName = $name;
282 throw new InvalidArgumentException( __METHOD__ .
" given storage path `$tempPath`." );
286 $this->mRemoveTempFile = $removeTempFile;
301 $this->mTempPath = $tempPath ??
'';
302 $this->mFileSize = $fileSize ?:
null;
303 $this->mFileProps =
null;
304 if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
305 $this->tempFileObj =
new TempFSFile( $this->mTempPath );
307 $this->mFileSize = filesize( $this->mTempPath );
310 $this->tempFileObj =
null;
320 return Status::newGood();
329 return Status::newGood();
337 return !$this->mFileSize;
345 return $this->mFileSize;
355 if ( $this->mFileProps && is_string( $this->mFileProps[
'sha1'] ) ) {
356 return $this->mFileProps[
'sha1'];
366 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
371 $tmpFile = $repo->getLocalCopy( $srcPath );
373 $tmpFile->bind( $this );
375 $path = $tmpFile ? $tmpFile->getPath() :
false;
405 return [
'status' => self::EMPTY_FILE ];
412 if ( $this->mFileSize > $maxSize ) {
414 'status' => self::FILE_TOO_LARGE,
425 if ( $verification !==
true ) {
427 'status' => self::VERIFICATION_ERROR,
428 'details' => $verification
436 if ( $result !==
true ) {
440 return [
'status' => self::OK ];
451 if ( $nt ===
null ) {
452 $result = [
'status' => $this->mTitleError ];
453 if ( $this->mTitleError === self::ILLEGAL_FILENAME ) {
454 $result[
'filtered'] = $this->mFilteredName;
456 if ( $this->mTitleError === self::FILETYPE_BADTYPE ) {
457 $result[
'finalExt'] = $this->mFinalExtension;
458 if ( count( $this->mBlackListedExtensions ) ) {
459 $result[
'blacklistedExt'] = $this->mBlackListedExtensions;
479 $verifyMimeType = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeType );
480 if ( $verifyMimeType ) {
481 wfDebug(
"mime: <$mime> extension: <{$this->mFinalExtension}>" );
482 $mimeTypeExclusions = MediaWikiServices::getInstance()->getMainConfig()
483 ->get( MainConfigNames::MimeTypeExclusions );
484 if ( self::checkFileExtension( $mime, $mimeTypeExclusions ) ) {
485 return [
'filetype-badmime', $mime ];
498 $config = MediaWikiServices::getInstance()->getMainConfig();
499 $verifyMimeType = $config->get( MainConfigNames::VerifyMimeType );
500 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
502 if ( $status !==
true ) {
508 if ( !is_array( $this->mFileProps ) ) {
509 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
510 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
512 $mime = $this->mFileProps[
'mime'];
514 if ( $verifyMimeType ) {
515 # XXX: Missing extension will be caught by validateName() via getTitle()
516 if ( (
string)$this->mFinalExtension !==
'' &&
517 !self::verifyExtension( $mime, $this->mFinalExtension )
519 return [
'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
523 # check for htmlish code and javascript
524 if ( !$disableUploadScriptChecks ) {
525 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
527 if ( $svgStatus !==
false ) {
533 $handler = MediaHandler::getHandler( $mime );
535 $handlerStatus = $handler->verifyUpload( $this->mTempPath );
536 if ( !$handlerStatus->isOK() ) {
537 $errors = $handlerStatus->getErrorsArray();
539 return reset( $errors );
544 $this->getHookRunner()->onUploadVerifyFile( $this, $mime, $error );
545 if ( $error !==
true ) {
546 if ( !is_array( $error ) ) {
552 wfDebug( __METHOD__ .
": all clear; passing." );
567 $config = MediaWikiServices::getInstance()->getMainConfig();
568 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
569 # getTitle() sets some internal parameters like $this->mFinalExtension
574 if ( !is_array( $this->mFileProps ) ) {
575 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
576 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
579 # check MIME type, if desired
580 $mime = $this->mFileProps[
'file-mime'];
582 if ( $status !==
true ) {
586 # check for htmlish code and javascript
587 if ( !$disableUploadScriptChecks ) {
588 if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
589 return [
'uploadscripted' ];
591 if ( $this->mFinalExtension ===
'svg' || $mime ===
'image/svg+xml' ) {
593 if ( $svgStatus !==
false ) {
599 # Scan the uploaded file for viruses
600 $virus = self::detectVirus( $this->mTempPath );
602 return [
'uploadvirus', $virus ];
614 $names = [ $entry[
'name'] ];
621 $nullPos = strpos( $entry[
'name'],
"\000" );
622 if ( $nullPos !==
false ) {
623 $names[] = substr( $entry[
'name'], 0, $nullPos );
628 if ( preg_grep(
'!\.class/?$!', $names ) ) {
629 $this->mJavaDetected =
true;
663 if ( $nt ===
null ) {
667 $status = PermissionStatus::newEmpty();
670 if ( !$status->isGood() ) {
671 return $status->toLegacyErrorArray();
674 $overwriteError = $this->checkOverwrite( $performer );
675 if ( $overwriteError !==
true ) {
676 return [ $overwriteError ];
692 if ( $user ===
null ) {
694 $user = RequestContext::getMain()->getUser();
700 $localFile->
load( IDBAccessObject::READ_LATEST );
701 $filename = $localFile->
getName();
704 $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
705 if ( $badFileName !==
null ) {
706 $warnings[
'badfilename'] = $badFileName;
709 $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (
string)$this->mFinalExtension );
710 if ( $unwantedFileExtensionDetails !==
null ) {
711 $warnings[
'filetype-unwanted-type'] = $unwantedFileExtensionDetails;
714 $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
715 if ( $fileSizeWarnings ) {
716 $warnings = array_merge( $warnings, $fileSizeWarnings );
719 $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
720 if ( $localFileExistsWarnings ) {
721 $warnings = array_merge( $warnings, $localFileExistsWarnings );
724 if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
725 $warnings[
'was-deleted'] = $filename;
730 $ignoreLocalDupes = isset( $warnings[
'exists'] );
731 $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
733 $warnings[
'duplicate'] = $dupes;
736 $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
737 if ( $archivedDupes !==
null ) {
738 $warnings[
'duplicate-archive'] = $archivedDupes;
756 array_walk_recursive( $warnings,
static function ( &$param, $key ) {
757 if ( $param instanceof
File ) {
759 'fileName' => $param->getName(),
760 'timestamp' => $param->getTimestamp()
762 } elseif ( is_object( $param ) ) {
763 throw new InvalidArgumentException(
764 'UploadBase::makeWarningsSerializable: ' .
765 'Unexpected object of class ' . get_class( $param ) );
779 foreach ( $warnings as $key => $value ) {
780 if ( is_array( $value ) ) {
781 if ( isset( $value[
'fileName'] ) && isset( $value[
'timestamp'] ) ) {
782 $warnings[$key] = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
784 [
'time' => $value[
'timestamp'] ]
787 $warnings[$key] = self::unserializeWarnings( $value );
803 private function checkBadFileName( $filename, $desiredFileName ) {
804 $comparableName = str_replace(
' ',
'_', $desiredFileName );
805 $comparableName = Title::capitalize( $comparableName,
NS_FILE );
807 if ( $desiredFileName != $filename && $comparableName != $filename ) {
822 private function checkUnwantedFileExtensions( $fileExtension ) {
823 $checkFileExtensions = MediaWikiServices::getInstance()->getMainConfig()
824 ->get( MainConfigNames::CheckFileExtensions );
825 $fileExtensions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FileExtensions );
826 if ( $checkFileExtensions ) {
827 $extensions = array_unique( $fileExtensions );
828 if ( !self::checkFileExtension( $fileExtension, $extensions ) ) {
831 Message::listParam( $extensions,
'comma' ),
845 private function checkFileSize( $fileSize ) {
846 $uploadSizeWarning = MediaWikiServices::getInstance()->getMainConfig()
847 ->get( MainConfigNames::UploadSizeWarning );
851 if ( $uploadSizeWarning && ( $fileSize > $uploadSizeWarning ) ) {
852 $warnings[
'large-file'] = [
853 Message::sizeParam( $uploadSizeWarning ),
854 Message::sizeParam( $fileSize ),
858 if ( $fileSize == 0 ) {
859 $warnings[
'empty-file'] =
true;
871 private function checkLocalFileExists(
LocalFile $localFile, $hash ) {
874 $exists = self::getExistsWarning( $localFile );
875 if ( $exists !==
false ) {
876 $warnings[
'exists'] = $exists;
879 if ( $hash !==
false && $hash === $localFile->
getSha1() ) {
880 $warnings[
'no-change'] = $localFile;
885 foreach ( $history as $oldFile ) {
886 if ( $hash === $oldFile->getSha1() ) {
887 $warnings[
'duplicate-version'][] = $oldFile;
895 private function checkLocalFileWasDeleted(
LocalFile $localFile ) {
905 private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
906 if ( $hash ===
false ) {
909 $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
911 foreach ( $dupes as $key => $dupe ) {
915 $title->equals( $dupe->getTitle() )
917 unset( $dupes[$key] );
931 private function checkAgainstArchiveDupes( $hash,
Authority $performer ) {
932 if ( $hash ===
false ) {
936 if ( $archivedFile->getID() > 0 ) {
937 if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
938 return $archivedFile->getName();
964 $comment, $pageText, $watch, $user, $tags = [], ?
string $watchlistExpiry =
null
966 $this->
getLocalFile()->load( IDBAccessObject::READ_LATEST );
967 $props = $this->mFileProps;
970 $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
972 if ( !is_array( $error ) ) {
975 return Status::newFatal( ...$error );
981 $pageText !==
false ? $pageText :
'',
989 if ( $status->isGood() ) {
991 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
997 $this->getHookRunner()->onUploadComplete( $this );
1021 if ( $this->mTitle !==
false ) {
1024 if ( !is_string( $this->mDesiredDestName ) ) {
1025 $this->mTitleError = self::ILLEGAL_FILENAME;
1026 $this->mTitle =
null;
1033 $title = Title::newFromText( $this->mDesiredDestName );
1034 if ( $title && $title->getNamespace() ===
NS_FILE ) {
1035 $this->mFilteredName = $title->getDBkey();
1037 $this->mFilteredName = $this->mDesiredDestName;
1040 # oi_archive_name is max 255 bytes, which include a timestamp and an
1041 # exclamation mark, so restrict file name to 240 bytes.
1042 if ( strlen( $this->mFilteredName ) > 240 ) {
1043 $this->mTitleError = self::FILENAME_TOO_LONG;
1044 $this->mTitle =
null;
1056 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1057 if ( $nt ===
null ) {
1058 $this->mTitleError = self::ILLEGAL_FILENAME;
1059 $this->mTitle =
null;
1063 $this->mFilteredName = $nt->
getDBkey();
1069 [ $partname, $ext ] = self::splitExtensions( $this->mFilteredName );
1071 if ( $ext !== [] ) {
1072 $this->mFinalExtension = trim( end( $ext ) );
1074 $this->mFinalExtension =
'';
1079 if ( $this->mTempPath !==
null ) {
1080 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1081 $mime = $magic->guessMimeType( $this->mTempPath );
1082 if ( $mime !==
'unknown/unknown' ) {
1083 # Get a space separated list of extensions
1084 $mimeExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
1085 if ( $mimeExt !==
null ) {
1086 # Set the extension to the canonical extension
1087 $this->mFinalExtension = $mimeExt;
1089 # Fix up the other variables
1090 $this->mFilteredName .=
".{$this->mFinalExtension}";
1091 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1092 $ext = [ $this->mFinalExtension ];
1099 $config = MediaWikiServices::getInstance()->getMainConfig();
1100 $checkFileExtensions = $config->get( MainConfigNames::CheckFileExtensions );
1101 $strictFileExtensions = $config->get( MainConfigNames::StrictFileExtensions );
1102 $fileExtensions = $config->get( MainConfigNames::FileExtensions );
1103 $prohibitedFileExtensions = $config->get( MainConfigNames::ProhibitedFileExtensions );
1105 $badList = self::checkFileExtensionList( $ext, $prohibitedFileExtensions );
1107 if ( $this->mFinalExtension ==
'' ) {
1108 $this->mTitleError = self::FILETYPE_MISSING;
1109 $this->mTitle =
null;
1115 ( $checkFileExtensions && $strictFileExtensions &&
1116 !self::checkFileExtension( $this->mFinalExtension, $fileExtensions ) )
1118 $this->mBlackListedExtensions = $badList;
1119 $this->mTitleError = self::FILETYPE_BADTYPE;
1120 $this->mTitle =
null;
1126 if ( !preg_match(
'/^[\x0-\x7f]*$/', $nt->getText() )
1127 && !MediaWikiServices::getInstance()->getRepoGroup()
1128 ->getLocalRepo()->backendSupportsUnicodePaths()
1130 $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1131 $this->mTitle =
null;
1136 # If there was more than one file "extension", reassemble the base
1137 # filename to prevent bogus complaints about length
1138 if ( count( $ext ) > 1 ) {
1139 $iterations = count( $ext ) - 1;
1140 for ( $i = 0; $i < $iterations; $i++ ) {
1141 $partname .=
'.' . $ext[$i];
1145 if ( strlen( $partname ) < 1 ) {
1146 $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1147 $this->mTitle =
null;
1152 $this->mTitle = $nt;
1164 if ( $this->mLocalFile ===
null ) {
1166 $this->mLocalFile = $nt ===
null
1168 : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1171 return $this->mLocalFile;
1178 return $this->mStashFile;
1194 if ( !$isPartial ) {
1197 return Status::newFatal( ...$error );
1202 return Status::newGood( $file );
1204 return Status::newFatal(
'uploadstash-exception', get_class( $e ), $e->getMessage() );
1213 $props = $this->mFileProps;
1215 $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1216 if ( $error && !is_array( $error ) ) {
1217 $error = [ $error ];
1230 $stash = MediaWikiServices::getInstance()->getRepoGroup()
1231 ->getLocalRepo()->getUploadStash( $user );
1232 $file = $stash->stashFile( $this->mTempPath, $this->
getSourceType(), $this->mFileProps );
1233 $this->mStashFile = $file;
1243 if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1245 wfDebug( __METHOD__ .
": Marked temporary file '{$this->mTempPath}' for removal" );
1246 $this->tempFileObj->autocollect();
1254 return $this->mTempPath;
1267 $bits = explode(
'.', $filename );
1268 $basename = array_shift( $bits );
1270 return [ $basename, $bits ];
1281 return in_array( strtolower( $ext ??
'' ), $list,
true );
1293 return array_intersect( array_map(
'strtolower', $ext ), $list );
1304 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1306 if ( !$mime || $mime ===
'unknown' || $mime ===
'unknown/unknown' ) {
1307 if ( !$magic->isRecognizableExtension( $extension ) ) {
1308 wfDebug( __METHOD__ .
": passing file with unknown detected mime type; " .
1309 "unrecognized extension '$extension', can't verify" );
1314 wfDebug( __METHOD__ .
": rejecting file with unknown detected mime type; " .
1315 "recognized extension '$extension', so probably invalid file" );
1319 $match = $magic->isMatchingExtension( $extension, $mime );
1321 if ( $match ===
null ) {
1322 if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1323 wfDebug( __METHOD__ .
": No extension known for $mime, but we know a mime for $extension" );
1328 wfDebug( __METHOD__ .
": no file extension known for mime type $mime, passing file" );
1333 wfDebug( __METHOD__ .
": mime type $mime matches extension $extension, passing file" );
1340 .
": mime type $mime mismatches file extension $extension, rejecting file" );
1357 # ugly hack: for text files, always look at the entire file.
1358 # For binary field, just check the first K.
1360 if ( str_starts_with( $mime ??
'',
'text/' ) ) {
1361 $chunk = file_get_contents( $file );
1363 $fp = fopen( $file,
'rb' );
1367 $chunk = fread( $fp, 1024 );
1371 $chunk = strtolower( $chunk );
1377 # decode from UTF-16 if needed (could be used for obfuscation).
1378 if ( str_starts_with( $chunk,
"\xfe\xff" ) ) {
1380 } elseif ( str_starts_with( $chunk,
"\xff\xfe" ) ) {
1386 if ( $enc !==
null ) {
1387 $chunk = iconv( $enc,
"ASCII//IGNORE", $chunk );
1390 $chunk = trim( $chunk );
1393 wfDebug( __METHOD__ .
": checking for embedded scripts and HTML stuff" );
1395 # check for HTML doctype
1396 if ( preg_match(
"/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1402 if ( $extension ===
'svg' || str_starts_with( $mime ??
'',
'image/svg' ) ) {
1403 if ( self::checkXMLEncodingMissmatch( $file ) ) {
1417 '<html', # also in safari
1418 '<script', # also in safari
1421 foreach ( $tags as $tag ) {
1422 if ( strpos( $chunk, $tag ) !==
false ) {
1423 wfDebug( __METHOD__ .
": found something that may make it be mistaken for html: $tag" );
1433 # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1434 $chunk = Sanitizer::decodeCharReferences( $chunk );
1436 # look for script-types
1437 if ( preg_match(
'!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!im', $chunk ) ) {
1438 wfDebug( __METHOD__ .
": found script types" );
1443 # look for html-style script-urls
1444 if ( preg_match(
'!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1445 wfDebug( __METHOD__ .
": found html-style script urls" );
1450 # look for css-style script-urls
1451 if ( preg_match(
'!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!im', $chunk ) ) {
1452 wfDebug( __METHOD__ .
": found css-style script urls" );
1457 wfDebug( __METHOD__ .
": no scripts found" );
1474 $contents = file_get_contents( $file,
false,
null, 0, 4096 );
1475 $encodingRegex =
'!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1477 if ( preg_match(
"!<\?xml\b(.*?)\?>!si", $contents,
$matches ) ) {
1478 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1479 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1481 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1485 } elseif ( preg_match(
"!<\?xml\b!i", $contents ) ) {
1488 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1491 } elseif ( str_starts_with( $contents,
"\x4C\x6F\xA7\x94" ) ) {
1493 wfDebug( __METHOD__ .
": EBCDIC Encoded XML" );
1500 $attemptEncodings = [
'UTF-16',
'UTF-16BE',
'UTF-32',
'UTF-32BE' ];
1501 foreach ( $attemptEncodings as $encoding ) {
1502 AtEase::suppressWarnings();
1503 $str = iconv( $encoding,
'UTF-8', $contents );
1504 AtEase::restoreWarnings();
1505 if ( $str !=
'' && preg_match(
"!<\?xml\b(.*?)\?>!si", $str,
$matches ) ) {
1506 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1507 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1509 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1513 } elseif ( $str !=
'' && preg_match(
"!<\?xml\b!i", $str ) ) {
1516 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1531 $this->mSVGNSError =
false;
1534 [ $this,
'checkSvgScriptCallback' ],
1537 'processing_instruction_handler' => [ __CLASS__,
'checkSvgPICallback' ],
1538 'external_dtd_handler' => [ __CLASS__,
'checkSvgExternalDTD' ],
1541 if ( $check->wellFormed !==
true ) {
1544 return $partial ? false : [
'uploadinvalidxml' ];
1547 if ( $check->filterMatch ) {
1548 if ( $this->mSVGNSError ) {
1549 return [
'uploadscriptednamespace', $this->mSVGNSError ];
1551 return $check->filterMatchType;
1566 if ( preg_match(
'/xml-stylesheet/i', $target ) ) {
1567 return [
'upload-scripted-pi-callback' ];
1588 static $allowedDTDs = [
1589 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1590 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1591 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1592 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1594 'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1596 if ( $type !==
'PUBLIC'
1597 || !in_array( $systemId, $allowedDTDs )
1598 || !str_starts_with( $publicId,
"-//W3C//" )
1600 return [
'upload-scripted-dtd' ];
1613 [ $namespace, $strippedElement ] = self::splitXmlNamespace( $element );
1617 static $validNamespaces = [
1620 'http://creativecommons.org/ns#',
1621 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1622 'http://ns.adobe.com/adobeillustrator/10.0/',
1623 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1624 'http://ns.adobe.com/extensibility/1.0/',
1625 'http://ns.adobe.com/flows/1.0/',
1626 'http://ns.adobe.com/illustrator/1.0/',
1627 'http://ns.adobe.com/imagereplacement/1.0/',
1628 'http://ns.adobe.com/pdf/1.3/',
1629 'http://ns.adobe.com/photoshop/1.0/',
1630 'http://ns.adobe.com/saveforweb/1.0/',
1631 'http://ns.adobe.com/variables/1.0/',
1632 'http://ns.adobe.com/xap/1.0/',
1633 'http://ns.adobe.com/xap/1.0/g/',
1634 'http://ns.adobe.com/xap/1.0/g/img/',
1635 'http://ns.adobe.com/xap/1.0/mm/',
1636 'http://ns.adobe.com/xap/1.0/rights/',
1637 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1638 'http://ns.adobe.com/xap/1.0/stype/font#',
1639 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1640 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1641 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1642 'http://ns.adobe.com/xap/1.0/t/pg/',
1643 'http://purl.org/dc/elements/1.1/',
1644 'http://purl.org/dc/elements/1.1',
1645 'http://schemas.microsoft.com/visio/2003/svgextensions/',
1646 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1647 'http://taptrix.com/inkpad/svg_extensions',
1648 'http://web.resource.org/cc/',
1649 'http://www.freesoftware.fsf.org/bkchem/cdml',
1650 'http://www.inkscape.org/namespaces/inkscape',
1651 'http://www.opengis.net/gml',
1652 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1653 'http://www.w3.org/2000/svg',
1654 'http://www.w3.org/tr/rec-rdf-syntax/',
1655 'http://www.w3.org/2000/01/rdf-schema#',
1656 'http://www.w3.org/2000/02/svg/testsuite/description/',
1661 $isBuggyInkscape = preg_match(
'/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1663 if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1664 wfDebug( __METHOD__ .
": Non-svg namespace '$namespace' in uploaded file." );
1666 $this->mSVGNSError = $namespace;
1672 if ( $strippedElement ===
'script' ) {
1673 wfDebug( __METHOD__ .
": Found script element '$element' in uploaded file." );
1675 return [
'uploaded-script-svg', $strippedElement ];
1680 if ( $strippedElement ===
'handler' ) {
1681 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1683 return [
'uploaded-script-svg', $strippedElement ];
1687 if ( $strippedElement ===
'stylesheet' ) {
1688 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1690 return [
'uploaded-script-svg', $strippedElement ];
1694 if ( $strippedElement ===
'iframe' ) {
1695 wfDebug( __METHOD__ .
": iframe in uploaded file." );
1697 return [
'uploaded-script-svg', $strippedElement ];
1701 if ( $strippedElement ===
'style'
1702 && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1704 wfDebug( __METHOD__ .
": hostile css in style element." );
1706 return [
'uploaded-hostile-svg' ];
1709 static $cssAttrs = [
'font',
'clip-path',
'fill',
'filter',
'marker',
1710 'marker-end',
'marker-mid',
'marker-start',
'mask',
'stroke' ];
1712 foreach ( $attribs as $attrib => $value ) {
1714 [ $attributeNamespace, $stripped ] = self::splitXmlNamespace( $attrib );
1715 $value = strtolower( $value );
1719 $namespace ===
'http://www.inkscape.org/namespaces/inkscape' &&
1720 $attributeNamespace ===
''
1721 ) && str_starts_with( $stripped,
'on' )
1724 .
": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1726 return [
'uploaded-event-handler-on-svg', $attrib, $value ];
1734 $stripped ===
'href'
1736 && !str_starts_with( $value,
'data:' )
1737 && !str_starts_with( $value,
'#' )
1738 && !( $strippedElement ===
'a' && preg_match(
'!^https?://!i', $value ) )
1740 wfDebug( __METHOD__ .
": Found href attribute <$strippedElement "
1741 .
"'$attrib'='$value' in uploaded file." );
1743 return [
'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1748 if ( $stripped ===
'href' && strncasecmp(
'data:', $value, 5 ) === 0 ) {
1752 $parameters =
'(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1754 if ( !preg_match(
"!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1755 wfDebug( __METHOD__ .
": Found href to allow listed data: uri "
1756 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1757 return [
'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1762 if ( $stripped ===
'attributename'
1763 && $strippedElement ===
'animate'
1764 && $this->stripXmlNamespace( $value ) ===
'href'
1766 wfDebug( __METHOD__ .
": Found animate that might be changing href using from "
1767 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1769 return [
'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1773 if ( ( $strippedElement ===
'set' || $strippedElement ===
'animate' )
1774 && $stripped ===
'attributename'
1775 && str_starts_with( $value,
'on' )
1777 wfDebug( __METHOD__ .
": Found svg setting event-handler attribute with "
1778 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1780 return [
'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1784 if ( $strippedElement ===
'set'
1785 && $stripped ===
'attributename'
1786 && str_contains( $value,
'href' )
1788 wfDebug( __METHOD__ .
": Found svg setting href attribute '$value' in uploaded file." );
1790 return [
'uploaded-setting-href-svg' ];
1794 if ( $strippedElement ===
'set'
1795 && $stripped ===
'to'
1796 && preg_match(
'!(http|https|data|script):!im', $value )
1798 wfDebug( __METHOD__ .
": Found svg setting attribute to '$value' in uploaded file." );
1800 return [
'uploaded-wrong-setting-svg', $value ];
1804 if ( $stripped ===
'handler' && preg_match(
'!(http|https|data|script):!im', $value ) ) {
1805 wfDebug( __METHOD__ .
": Found svg setting handler with remote/data/script "
1806 .
"'$attrib'='$value' in uploaded file." );
1808 return [
'uploaded-setting-handler-svg', $attrib, $value ];
1812 if ( $stripped ===
'style'
1813 && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1815 wfDebug( __METHOD__ .
": Found svg setting a style with "
1816 .
"remote url '$attrib'='$value' in uploaded file." );
1817 return [
'uploaded-remote-url-svg', $attrib, $value ];
1821 if ( in_array( $stripped, $cssAttrs,
true )
1822 && self::checkCssFragment( $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 ];
1832 if ( $strippedElement ===
'image'
1833 && $stripped ===
'filter'
1834 && preg_match(
'!url\s*\(\s*["\']?[^#]!im', $value )
1836 wfDebug( __METHOD__ .
": Found image filter with url: "
1837 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1839 return [
'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1852 private static function checkCssFragment( $value ) {
1853 # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1854 if ( stripos( $value,
'@import' ) !==
false ) {
1858 # We allow @font-face to embed fonts with data: urls, so we snip the string
1859 # 'url' out so that this case won't match when we check for urls below
1860 $pattern =
'!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1861 $value = preg_replace( $pattern,
'$1$2', $value );
1863 # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1864 # properties filter and accelerator don't seem to be useful for xss in SVG files.
1865 # Expression and -o-link don't seem to work either, but filtering them here in case.
1866 # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1867 # but not local ones such as url("#..., url('#..., url(#....
1868 if ( preg_match(
'!expression
1870 | -o-link-source\s*:
1871 | -o-replace\s*:!imx', $value ) ) {
1875 if ( preg_match_all(
1876 "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1881 # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1882 foreach (
$matches[1] as $match ) {
1883 if ( !preg_match(
"!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1889 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
1901 private static function splitXmlNamespace( $element ) {
1903 $parts = explode(
':', strtolower( $element ) );
1904 $name = array_pop( $parts );
1905 $ns = implode(
':', $parts );
1907 return [ $ns, $name ];
1914 private function stripXmlNamespace( $element ) {
1916 return self::splitXmlNamespace( $element )[1];
1931 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1932 $antivirus = $mainConfig->get( MainConfigNames::Antivirus );
1933 $antivirusSetup = $mainConfig->get( MainConfigNames::AntivirusSetup );
1934 $antivirusRequired = $mainConfig->get( MainConfigNames::AntivirusRequired );
1935 if ( !$antivirus ) {
1936 wfDebug( __METHOD__ .
": virus scanner disabled" );
1941 if ( !$antivirusSetup[$antivirus] ) {
1942 wfDebug( __METHOD__ .
": unknown virus scanner: {$antivirus}" );
1943 $wgOut->wrapWikiMsg(
"<div class=\"error\">\n$1\n</div>",
1944 [
'virus-badscanner', $antivirus ] );
1946 return wfMessage(
'virus-unknownscanner' )->text() .
" {$antivirus}";
1949 # look up scanner configuration
1950 $command = $antivirusSetup[$antivirus][
'command'];
1951 $exitCodeMap = $antivirusSetup[$antivirus][
'codemap'];
1952 $msgPattern = $antivirusSetup[$antivirus][
'messagepattern'] ??
null;
1954 if ( !str_contains( $command,
"%f" ) ) {
1955 # simple pattern: append file to scan
1956 $command .=
" " . Shell::escape( $file );
1958 # complex pattern: replace "%f" with file to scan
1959 $command = str_replace(
"%f", Shell::escape( $file ), $command );
1962 wfDebug( __METHOD__ .
": running virus scan: $command " );
1964 # execute virus scanner
1967 # NOTE: there's a 50-line workaround to make stderr redirection work on windows, too.
1968 # that does not seem to be worth the pain.
1969 # Ask me (Duesentrieb) about it if it's ever needed.
1972 # map exit code to AV_xxx constants.
1973 $mappedCode = $exitCode;
1974 if ( $exitCodeMap ) {
1975 if ( isset( $exitCodeMap[$exitCode] ) ) {
1976 $mappedCode = $exitCodeMap[$exitCode];
1977 } elseif ( isset( $exitCodeMap[
"*"] ) ) {
1978 $mappedCode = $exitCodeMap[
"*"];
1982 # NB: AV_NO_VIRUS is 0, but AV_SCAN_FAILED is false,
1983 # so we need the strict equalities === and thus can't use a switch here
1985 # scan failed (code was mapped to false by $exitCodeMap)
1986 wfDebug( __METHOD__ .
": failed to scan $file (code $exitCode)." );
1988 $output = $antivirusRequired
1989 ?
wfMessage(
'virus-scanfailed', [ $exitCode ] )->text()
1992 # scan failed because filetype is unknown (probably immune)
1993 wfDebug( __METHOD__ .
": unsupported file type $file (code $exitCode)." );
1997 wfDebug( __METHOD__ .
": file passed virus scan." );
2000 $output = trim( $output );
2003 $output =
true; #
if there
's no output, return true
2004 } elseif ( $msgPattern ) {
2006 if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
2007 $output = $groups[1];
2011 wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
2025 private function checkOverwrite( Authority $performer ) {
2026 // First check whether the local file can be overwritten
2027 $file = $this->getLocalFile();
2028 $file->load( IDBAccessObject::READ_LATEST );
2029 if ( $file->exists() ) {
2030 if ( !self::userCanReUpload( $performer, $file ) ) {
2031 return [ 'fileexists-forbidden
', $file->getName() ];
2037 $services = MediaWikiServices::getInstance();
2039 /* Check shared conflicts: if the local file does not exist, but
2040 * RepoGroup::findFile finds a file, it exists in a shared repository.
2042 $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest
' => true ] );
2043 if ( $file && !$performer->isAllowed( 'reupload-shared
' ) ) {
2044 return [ 'fileexists-shared-forbidden
', $file->getName() ];
2057 public static function userCanReUpload( Authority $performer, File $img ) {
2058 if ( $performer->isAllowed( 'reupload
' ) ) {
2059 return true; // non-conditional
2062 if ( !$performer->isAllowed( 'reupload-own
' ) ) {
2066 if ( !( $img instanceof LocalFile ) ) {
2070 return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2084 public static function getExistsWarning( $file ) {
2085 if ( $file->exists() ) {
2086 return [ 'warning
' => 'exists
', 'file
' => $file ];
2089 if ( $file->getTitle()->getArticleID() ) {
2090 return [ 'warning
' => 'page-exists
', 'file
' => $file ];
2093 $n = strrpos( $file->getName(), '.
' );
2095 $partname = substr( $file->getName(), 0, $n );
2096 $extension = substr( $file->getName(), $n + 1 );
2098 $partname = $file->getName();
2101 $normalizedExtension = File::normalizeExtension( $extension );
2102 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2104 if ( $normalizedExtension != $extension ) {
2105 // We're not
using the normalized form of the extension.
2110 $nt_lc = Title::makeTitle(
NS_FILE,
"{$partname}.{$normalizedExtension}" );
2111 $file_lc = $localRepo->newFile( $nt_lc );
2113 if ( $file_lc->exists() ) {
2115 'warning' =>
'exists-normalized',
2117 'normalizedFile' => $file_lc
2123 $similarFiles = $localRepo->findFilesByPrefix(
"{$partname}.", 1 );
2124 if ( count( $similarFiles ) ) {
2126 'warning' =>
'exists-normalized',
2128 'normalizedFile' => $similarFiles[0],
2132 if ( self::isThumbName( $file->getName() ) ) {
2134 $nt_thb = Title::newFromText(
2135 substr( $partname, strpos( $partname,
'-' ) + 1 ) .
'.' . $extension,
2138 $file_thb = $localRepo->newFile( $nt_thb );
2139 if ( $file_thb->exists() ) {
2141 'warning' =>
'thumb',
2143 'thumbFile' => $file_thb
2149 'warning' =>
'thumb-name',
2151 'thumbFile' => $file_thb
2155 foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2156 if ( str_starts_with( $partname, $prefix ) ) {
2158 'warning' =>
'bad-prefix',
2174 $n = strrpos( $filename,
'.' );
2175 $partname = $n ? substr( $filename, 0, $n ) : $filename;
2178 substr( $partname, 3, 3 ) ===
'px-' ||
2179 substr( $partname, 2, 3 ) ===
'px-'
2180 ) && preg_match(
"/[0-9]{2}/", substr( $partname, 0, 2 ) );
2190 $message =
wfMessage(
'filename-prefix-blacklist' )->inContentLanguage();
2191 if ( !$message->isDisabled() ) {
2192 $lines = explode(
"\n", $message->plain() );
2193 foreach (
$lines as $line ) {
2195 $comment = substr( trim( $line ), 0, 1 );
2196 if ( $comment ===
'#' || $comment ==
'' ) {
2200 $comment = strpos( $line,
'#' );
2201 if ( $comment > 0 ) {
2202 $line = substr( $line, 0, $comment - 1 );
2204 $list[] = trim( $line );
2222 return $apiUpload->getUploadImageInfo( $this );
2230 $code = $error[
'status'];
2231 unset( $code[
'status'] );
2244 $maxUploadSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxUploadSize );
2246 if ( is_array( $maxUploadSize ) ) {
2247 if ( $forType !==
null && isset( $maxUploadSize[$forType] ) ) {
2248 return $maxUploadSize[$forType];
2250 return $maxUploadSize[
'*'];
2252 return intval( $maxUploadSize );
2264 ini_get(
'upload_max_filesize' ),
2268 ini_get(
'post_max_size' ),
2271 return min( $phpMaxFileSize, $phpMaxPostSize );
2286 $store = self::getUploadSessionStore();
2287 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2289 return $store->
get( $key );
2305 $store = self::getUploadSessionStore();
2306 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2307 $logger = LoggerFactory::getInstance(
'upload' );
2309 if ( is_array( $value ) && ( $value[
'result'] ??
'' ) ===
'Failure' ) {
2310 $logger->info(
'Upload session {key} for {user} set to failure {status} at {stage}',
2312 'result' => $value[
'result'] ??
'',
2313 'stage' => $value[
'stage'] ??
'unknown',
2315 'status' => (
string)( $value[
'status'] ??
'-' ),
2316 'filekey' => $value[
'filekey'] ??
'',
2320 } elseif ( is_array( $value ) ) {
2321 $logger->debug(
'Upload session {key} for {user} changed {status} at {stage}',
2323 'result' => $value[
'result'] ??
'',
2324 'stage' => $value[
'stage'] ??
'unknown',
2326 'status' => (
string)( $value[
'status'] ??
'-' ),
2327 'filekey' => $value[
'filekey'] ??
'',
2332 $logger->debug(
"Upload session {key} deleted for {user}",
2335 'key' => $statusKey,
2341 if ( $value ===
false ) {
2344 $store->
set( $key, $value, $store::TTL_DAY );
2365 private static function getUploadSessionStore() {
2366 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.
Class representing a cache/ephemeral data store.
get( $key, $flags=0)
Get an item.
delete( $key, $flags=0)
Delete an item if it exists.
set( $key, $value, $exptime=0, $flags=0)
Set an item.
makeKey( $keygroup,... $components)
Make a cache key from the given components, in the default keyspace.
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
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