24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
32use Wikimedia\AtEase\AtEase;
51 use ProtectedHookAccessorTrait;
68 protected $mTitleError = 0;
112 public const FILETYPE_MISSING = 8;
113 public const FILETYPE_BADTYPE = 9;
114 public const VERIFICATION_ERROR = 10;
115 public const HOOK_ABORTED = 11;
116 public const FILE_TOO_LARGE = 12;
117 public const WINDOWS_NONASCII_FILENAME = 13;
118 public const FILENAME_TOO_LONG = 14;
124 public function getVerificationErrorCode( $error ) {
126 self::EMPTY_FILE =>
'empty-file',
127 self::FILE_TOO_LARGE =>
'file-too-large',
128 self::FILETYPE_MISSING =>
'filetype-missing',
129 self::FILETYPE_BADTYPE =>
'filetype-banned',
130 self::MIN_LENGTH_PARTNAME =>
'filename-tooshort',
131 self::ILLEGAL_FILENAME =>
'illegal-filename',
132 self::OVERWRITE_EXISTING_FILE =>
'overwrite',
133 self::VERIFICATION_ERROR =>
'verification-error',
134 self::HOOK_ABORTED =>
'hookaborted',
135 self::WINDOWS_NONASCII_FILENAME =>
'windows-nonascii-filename',
136 self::FILENAME_TOO_LONG =>
'filename-toolong',
138 return $code_to_status[$error] ??
'unknown-error';
148 $enableUploads = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnableUploads );
150 return $enableUploads &&
wfIniGetBool(
'file_uploads' );
162 foreach ( [
'upload',
'edit' ] as $permission ) {
163 if ( !$performer->
isAllowed( $permission ) ) {
178 return $user->pingLimiter(
'upload' );
182 private static $uploadHandlers = [
'Stash',
'File',
'Url' ];
192 $type =
$type ?: $request->getVal(
'wpSourceType',
'File' );
205 Hooks::runner()->onUploadCreateFromRequest(
$type, $className );
206 if ( $className ===
null ) {
207 $className =
'UploadFrom' .
$type;
208 wfDebug( __METHOD__ .
": class name: $className" );
209 if ( !in_array(
$type, self::$uploadHandlers ) ) {
215 if ( !$className::isEnabled() ) {
220 if ( !$className::isValidRequest( $request ) ) {
225 $handler =
new $className;
227 $handler->initializeFromRequest( $request );
266 $this->mDesiredDestName = $name;
268 throw new MWException( __METHOD__ .
" given storage path `$tempPath`." );
272 $this->mRemoveTempFile = $removeTempFile;
287 $this->mTempPath = $tempPath ??
'';
288 $this->mFileSize = $fileSize ?:
null;
289 if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
290 $this->tempFileObj =
new TempFSFile( $this->mTempPath );
292 $this->mFileSize = filesize( $this->mTempPath );
295 $this->tempFileObj =
null;
305 return Status::newGood();
313 return empty( $this->mFileSize );
321 return $this->mFileSize;
338 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
343 $tmpFile = $repo->getLocalCopy( $srcPath );
345 $tmpFile->bind( $this );
347 $path = $tmpFile ? $tmpFile->getPath() :
false;
377 return [
'status' => self::EMPTY_FILE ];
384 if ( $this->mFileSize > $maxSize ) {
386 'status' => self::FILE_TOO_LARGE,
397 if ( $verification !==
true ) {
399 'status' => self::VERIFICATION_ERROR,
400 'details' => $verification
408 if ( $result !==
true ) {
412 return [
'status' => self::OK ];
423 if ( $nt ===
null ) {
424 $result = [
'status' => $this->mTitleError ];
425 if ( $this->mTitleError === self::ILLEGAL_FILENAME ) {
426 $result[
'filtered'] = $this->mFilteredName;
428 if ( $this->mTitleError === self::FILETYPE_BADTYPE ) {
429 $result[
'finalExt'] = $this->mFinalExtension;
430 if ( count( $this->mBlackListedExtensions ) ) {
431 $result[
'blacklistedExt'] = $this->mBlackListedExtensions;
452 $verifyMimeType = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeType );
453 if ( $verifyMimeType ) {
454 wfDebug(
"mime: <$mime> extension: <{$this->mFinalExtension}>" );
455 $mimeTypeExclusions = MediaWikiServices::getInstance()->getMainConfig()
456 ->get( MainConfigNames::MimeTypeExclusions );
457 if ( self::checkFileExtension(
$mime, $mimeTypeExclusions ) ) {
458 return [
'filetype-badmime',
$mime ];
471 $config = MediaWikiServices::getInstance()->getMainConfig();
472 $verifyMimeType = $config->get( MainConfigNames::VerifyMimeType );
473 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
475 if ( $status !==
true ) {
479 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
480 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
481 $mime = $this->mFileProps[
'mime'];
483 if ( $verifyMimeType ) {
484 # XXX: Missing extension will be caught by validateName() via getTitle()
485 if ( (
string)$this->mFinalExtension !==
'' &&
486 !self::verifyExtension(
$mime, $this->mFinalExtension )
488 return [
'filetype-mime-mismatch', $this->mFinalExtension,
$mime ];
492 # check for htmlish code and javascript
493 if ( !$disableUploadScriptChecks ) {
494 if ( $this->mFinalExtension ===
'svg' ||
$mime ===
'image/svg+xml' ) {
496 if ( $svgStatus !==
false ) {
504 $handlerStatus = $handler->verifyUpload( $this->mTempPath );
505 if ( !$handlerStatus->isOK() ) {
506 $errors = $handlerStatus->getErrorsArray();
508 return reset( $errors );
513 $this->getHookRunner()->onUploadVerifyFile( $this,
$mime, $error );
514 if ( $error !==
true ) {
515 if ( !is_array( $error ) ) {
521 wfDebug( __METHOD__ .
": all clear; passing." );
536 $config = MediaWikiServices::getInstance()->getMainConfig();
537 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
538 # getTitle() sets some internal parameters like $this->mFinalExtension
541 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
542 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
544 # check MIME type, if desired
545 $mime = $this->mFileProps[
'file-mime'];
547 if ( $status !==
true ) {
551 # check for htmlish code and javascript
552 if ( !$disableUploadScriptChecks ) {
553 if ( self::detectScript( $this->mTempPath,
$mime, $this->mFinalExtension ) ) {
554 return [
'uploadscripted' ];
556 if ( $this->mFinalExtension ===
'svg' ||
$mime ===
'image/svg+xml' ) {
558 if ( $svgStatus !==
false ) {
564 # Scan the uploaded file for viruses
565 $virus = self::detectVirus( $this->mTempPath );
567 return [
'uploadvirus', $virus ];
579 $names = [ $entry[
'name'] ];
586 $nullPos = strpos( $entry[
'name'],
"\000" );
587 if ( $nullPos !==
false ) {
588 $names[] = substr( $entry[
'name'], 0, $nullPos );
593 if ( preg_grep(
'!\.class/?$!', $names ) ) {
594 $this->mJavaDetected =
true;
628 if ( $nt ===
null ) {
632 $status = PermissionStatus::newEmpty();
635 if ( !$status->isGood() ) {
636 return $status->toLegacyErrorArray();
639 $overwriteError = $this->checkOverwrite( $performer );
640 if ( $overwriteError !==
true ) {
641 return [ $overwriteError ];
657 if ( $user ===
null ) {
659 $user = RequestContext::getMain()->getUser();
665 $localFile->
load( File::READ_LATEST );
666 $filename = $localFile->
getName();
669 $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
670 if ( $badFileName !==
null ) {
671 $warnings[
'badfilename'] = $badFileName;
674 $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (
string)$this->mFinalExtension );
675 if ( $unwantedFileExtensionDetails !==
null ) {
676 $warnings[
'filetype-unwanted-type'] = $unwantedFileExtensionDetails;
679 $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
680 if ( $fileSizeWarnings ) {
681 $warnings = array_merge( $warnings, $fileSizeWarnings );
684 $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
685 if ( $localFileExistsWarnings ) {
686 $warnings = array_merge( $warnings, $localFileExistsWarnings );
689 if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
690 $warnings[
'was-deleted'] = $filename;
695 $ignoreLocalDupes = isset( $warnings[
'exists'] );
696 $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
698 $warnings[
'duplicate'] = $dupes;
701 $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
702 if ( $archivedDupes !==
null ) {
703 $warnings[
'duplicate-archive'] = $archivedDupes;
721 array_walk_recursive( $warnings,
static function ( &$param, $key ) {
722 if ( $param instanceof
File ) {
724 'fileName' => $param->getName(),
725 'timestamp' => $param->getTimestamp()
727 } elseif ( is_object( $param ) ) {
728 throw new InvalidArgumentException(
729 'UploadBase::makeWarningsSerializable: ' .
730 'Unexpected object of class ' . get_class( $param ) );
745 private function checkBadFileName( $filename, $desiredFileName ) {
746 $comparableName = str_replace(
' ',
'_', $desiredFileName );
747 $comparableName = Title::capitalize( $comparableName,
NS_FILE );
749 if ( $desiredFileName != $filename && $comparableName != $filename ) {
764 private function checkUnwantedFileExtensions( $fileExtension ) {
765 $checkFileExtensions = MediaWikiServices::getInstance()->getMainConfig()
766 ->get( MainConfigNames::CheckFileExtensions );
767 $fileExtensions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FileExtensions );
768 if ( $checkFileExtensions ) {
769 $extensions = array_unique( $fileExtensions );
770 if ( !self::checkFileExtension( $fileExtension, $extensions ) ) {
787 private function checkFileSize( $fileSize ) {
788 $uploadSizeWarning = MediaWikiServices::getInstance()->getMainConfig()
789 ->get( MainConfigNames::UploadSizeWarning );
793 if ( $uploadSizeWarning && ( $fileSize > $uploadSizeWarning ) ) {
794 $warnings[
'large-file'] = [
800 if ( $fileSize == 0 ) {
801 $warnings[
'empty-file'] =
true;
813 private function checkLocalFileExists(
LocalFile $localFile, $hash ) {
816 $exists = self::getExistsWarning( $localFile );
817 if ( $exists !==
false ) {
818 $warnings[
'exists'] = $exists;
821 if ( $hash !==
false && $hash === $localFile->
getSha1() ) {
822 $warnings[
'no-change'] = $localFile;
827 foreach ( $history as $oldFile ) {
828 if ( $hash === $oldFile->getSha1() ) {
829 $warnings[
'duplicate-version'][] = $oldFile;
837 private function checkLocalFileWasDeleted(
LocalFile $localFile ) {
847 private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
848 if ( $hash ===
false ) {
851 $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
853 foreach ( $dupes as $key => $dupe ) {
857 $title->equals( $dupe->getTitle() )
859 unset( $dupes[$key] );
873 private function checkAgainstArchiveDupes( $hash,
Authority $performer ) {
874 if ( $hash ===
false ) {
878 if ( $archivedFile->getID() > 0 ) {
879 if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
880 return $archivedFile->getName();
906 $comment, $pageText, $watch, $user, $tags = [], ?
string $watchlistExpiry =
null
909 $props = $this->mFileProps;
913 $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
915 if ( !is_array( $error ) ) {
918 return Status::newFatal( ...$error );
932 if ( $status->isGood() ) {
934 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
940 $this->getHookRunner()->onUploadComplete( $this );
964 if ( $this->mTitle !==
false ) {
967 if ( !is_string( $this->mDesiredDestName ) ) {
968 $this->mTitleError = self::ILLEGAL_FILENAME;
969 $this->mTitle =
null;
976 $title = Title::newFromText( $this->mDesiredDestName );
978 $this->mFilteredName =
$title->getDBkey();
980 $this->mFilteredName = $this->mDesiredDestName;
983 # oi_archive_name is max 255 bytes, which include a timestamp and an
984 # exclamation mark, so restrict file name to 240 bytes.
985 if ( strlen( $this->mFilteredName ) > 240 ) {
986 $this->mTitleError = self::FILENAME_TOO_LONG;
987 $this->mTitle =
null;
999 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1000 if ( $nt ===
null ) {
1001 $this->mTitleError = self::ILLEGAL_FILENAME;
1002 $this->mTitle =
null;
1006 $this->mFilteredName = $nt->
getDBkey();
1012 [ $partname,
$ext ] = self::splitExtensions( $this->mFilteredName );
1014 if (
$ext !== [] ) {
1015 $this->mFinalExtension = trim( end(
$ext ) );
1017 $this->mFinalExtension =
'';
1022 if ( $this->mTempPath !==
null ) {
1023 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1024 $mime = $magic->guessMimeType( $this->mTempPath );
1025 if (
$mime !==
'unknown/unknown' ) {
1026 # Get a space separated list of extensions
1027 $mimeExt = $magic->getExtensionFromMimeTypeOrNull(
$mime );
1028 if ( $mimeExt !==
null ) {
1029 # Set the extension to the canonical extension
1030 $this->mFinalExtension = $mimeExt;
1032 # Fix up the other variables
1033 $this->mFilteredName .=
".{$this->mFinalExtension}";
1034 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1035 $ext = [ $this->mFinalExtension ];
1042 $config = MediaWikiServices::getInstance()->getMainConfig();
1043 $checkFileExtensions = $config->get( MainConfigNames::CheckFileExtensions );
1044 $strictFileExtensions = $config->get( MainConfigNames::StrictFileExtensions );
1045 $fileExtensions = $config->get( MainConfigNames::FileExtensions );
1046 $prohibitedFileExtensions = $config->get( MainConfigNames::ProhibitedFileExtensions );
1048 $blackListedExtensions = self::checkFileExtensionList(
$ext, $prohibitedFileExtensions );
1050 if ( $this->mFinalExtension ==
'' ) {
1051 $this->mTitleError = self::FILETYPE_MISSING;
1052 $this->mTitle =
null;
1057 if ( $blackListedExtensions ||
1058 ( $checkFileExtensions && $strictFileExtensions &&
1059 !self::checkFileExtension( $this->mFinalExtension, $fileExtensions ) ) ) {
1060 $this->mBlackListedExtensions = $blackListedExtensions;
1061 $this->mTitleError = self::FILETYPE_BADTYPE;
1062 $this->mTitle =
null;
1068 if ( !preg_match(
'/^[\x0-\x7f]*$/', $nt->getText() )
1069 && !MediaWikiServices::getInstance()->getRepoGroup()
1070 ->getLocalRepo()->backendSupportsUnicodePaths()
1072 $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1073 $this->mTitle =
null;
1078 # If there was more than one "extension", reassemble the base
1079 # filename to prevent bogus complaints about length
1080 if ( count(
$ext ) > 1 ) {
1081 $iterations = count(
$ext ) - 1;
1082 for ( $i = 0; $i < $iterations; $i++ ) {
1083 $partname .=
'.' .
$ext[$i];
1087 if ( strlen( $partname ) < 1 ) {
1088 $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1089 $this->mTitle =
null;
1094 $this->mTitle = $nt;
1106 if ( $this->mLocalFile ===
null ) {
1108 $this->mLocalFile = $nt ===
null
1110 : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1113 return $this->mLocalFile;
1120 return $this->mStashFile;
1136 if ( !$isPartial ) {
1139 return Status::newFatal( ...$error );
1144 return Status::newGood(
$file );
1146 return Status::newFatal(
'uploadstash-exception', get_class( $e ), $e->getMessage() );
1155 $props = $this->mFileProps;
1157 $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1158 if ( $error && !is_array( $error ) ) {
1159 $error = [ $error ];
1172 $stash = MediaWikiServices::getInstance()->getRepoGroup()
1173 ->getLocalRepo()->getUploadStash( $user );
1175 $this->mStashFile =
$file;
1185 if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1187 wfDebug( __METHOD__ .
": Marked temporary file '{$this->mTempPath}' for removal" );
1188 $this->tempFileObj->autocollect();
1193 return $this->mTempPath;
1206 $bits = explode(
'.', $filename );
1207 $basename = array_shift( $bits );
1209 return [ $basename, $bits ];
1221 return in_array( strtolower(
$ext ??
'' ), $list,
true );
1233 return array_intersect( array_map(
'strtolower',
$ext ), $list );
1244 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1247 if ( !$magic->isRecognizableExtension( $extension ) ) {
1248 wfDebug( __METHOD__ .
": passing file with unknown detected mime type; " .
1249 "unrecognized extension '$extension', can't verify" );
1254 wfDebug( __METHOD__ .
": rejecting file with unknown detected mime type; " .
1255 "recognized extension '$extension', so probably invalid file" );
1259 $match = $magic->isMatchingExtension( $extension,
$mime );
1261 if ( $match ===
null ) {
1262 if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1263 wfDebug( __METHOD__ .
": No extension known for $mime, but we know a mime for $extension" );
1268 wfDebug( __METHOD__ .
": no file extension known for mime type $mime, passing file" );
1273 wfDebug( __METHOD__ .
": mime type $mime matches extension $extension, passing file" );
1280 .
": mime type $mime mismatches file extension $extension, rejecting file" );
1297 # ugly hack: for text files, always look at the entire file.
1298 # For binary field, just check the first K.
1300 if ( str_starts_with(
$mime ??
'',
'text/' ) ) {
1301 $chunk = file_get_contents(
$file );
1303 $fp = fopen(
$file,
'rb' );
1307 $chunk = fread( $fp, 1024 );
1311 $chunk = strtolower( $chunk );
1317 # decode from UTF-16 if needed (could be used for obfuscation).
1318 if ( str_starts_with( $chunk,
"\xfe\xff" ) ) {
1320 } elseif ( str_starts_with( $chunk,
"\xff\xfe" ) ) {
1326 if ( $enc !==
null ) {
1327 $chunk = iconv( $enc,
"ASCII//IGNORE", $chunk );
1330 $chunk = trim( $chunk );
1333 wfDebug( __METHOD__ .
": checking for embedded scripts and HTML stuff" );
1335 # check for HTML doctype
1336 if ( preg_match(
"/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1342 if ( $extension ===
'svg' || str_starts_with(
$mime ??
'',
'image/svg' ) ) {
1343 if ( self::checkXMLEncodingMissmatch(
$file ) ) {
1357 '<html', # also in safari
1358 '<script', # also in safari
1361 foreach ( $tags as $tag ) {
1362 if ( strpos( $chunk, $tag ) !==
false ) {
1363 wfDebug( __METHOD__ .
": found something that may make it be mistaken for html: $tag" );
1373 # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1374 $chunk = Sanitizer::decodeCharReferences( $chunk );
1376 # look for script-types
1377 if ( preg_match(
'!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
1378 wfDebug( __METHOD__ .
": found script types" );
1383 # look for html-style script-urls
1384 if ( preg_match(
'!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1385 wfDebug( __METHOD__ .
": found html-style script urls" );
1390 # look for css-style script-urls
1391 if ( preg_match(
'!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1392 wfDebug( __METHOD__ .
": found css-style script urls" );
1397 wfDebug( __METHOD__ .
": no scripts found" );
1410 $svgMetadataCutoff = MediaWikiServices::getInstance()->getMainConfig()
1411 ->get( MainConfigNames::SVGMetadataCutoff );
1412 $contents = file_get_contents(
$file,
false,
null, 0, $svgMetadataCutoff );
1413 $encodingRegex =
'!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1415 if ( preg_match(
"!<\?xml\b(.*?)\?>!si", $contents,
$matches ) ) {
1416 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1417 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1419 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1423 } elseif ( preg_match(
"!<\?xml\b!si", $contents ) ) {
1426 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1429 } elseif ( str_starts_with( $contents,
"\x4C\x6F\xA7\x94" ) ) {
1431 wfDebug( __METHOD__ .
": EBCDIC Encoded XML" );
1438 $attemptEncodings = [
'UTF-16',
'UTF-16BE',
'UTF-32',
'UTF-32BE' ];
1439 foreach ( $attemptEncodings as $encoding ) {
1440 AtEase::suppressWarnings();
1441 $str = iconv( $encoding,
'UTF-8', $contents );
1442 AtEase::restoreWarnings();
1443 if ( $str !=
'' && preg_match(
"!<\?xml\b(.*?)\?>!si", $str,
$matches ) ) {
1444 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1445 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1447 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1451 } elseif ( $str !=
'' && preg_match(
"!<\?xml\b!si", $str ) ) {
1454 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1469 $this->mSVGNSError =
false;
1472 [ $this,
'checkSvgScriptCallback' ],
1475 'processing_instruction_handler' => [ __CLASS__,
'checkSvgPICallback' ],
1476 'external_dtd_handler' => [ __CLASS__,
'checkSvgExternalDTD' ],
1479 if ( $check->wellFormed !==
true ) {
1482 return $partial ? false : [
'uploadinvalidxml' ];
1485 if ( $check->filterMatch ) {
1486 if ( $this->mSVGNSError ) {
1487 return [
'uploadscriptednamespace', $this->mSVGNSError ];
1489 return $check->filterMatchType;
1503 if ( preg_match(
'/xml-stylesheet/i', $target ) ) {
1504 return [
'upload-scripted-pi-callback' ];
1525 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1526 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1527 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1528 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1530 'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1532 if (
$type !==
'PUBLIC'
1533 || !in_array( $systemId, $allowedDTDs )
1534 || !str_starts_with( $publicId,
"-//W3C//" )
1536 return [
'upload-scripted-dtd' ];
1549 [ $namespace, $strippedElement ] = self::splitXmlNamespace( $element );
1553 static $validNamespaces = [
1556 'http://creativecommons.org/ns#',
1557 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1558 'http://ns.adobe.com/adobeillustrator/10.0/',
1559 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1560 'http://ns.adobe.com/extensibility/1.0/',
1561 'http://ns.adobe.com/flows/1.0/',
1562 'http://ns.adobe.com/illustrator/1.0/',
1563 'http://ns.adobe.com/imagereplacement/1.0/',
1564 'http://ns.adobe.com/pdf/1.3/',
1565 'http://ns.adobe.com/photoshop/1.0/',
1566 'http://ns.adobe.com/saveforweb/1.0/',
1567 'http://ns.adobe.com/variables/1.0/',
1568 'http://ns.adobe.com/xap/1.0/',
1569 'http://ns.adobe.com/xap/1.0/g/',
1570 'http://ns.adobe.com/xap/1.0/g/img/',
1571 'http://ns.adobe.com/xap/1.0/mm/',
1572 'http://ns.adobe.com/xap/1.0/rights/',
1573 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1574 'http://ns.adobe.com/xap/1.0/stype/font#',
1575 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1576 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1577 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1578 'http://ns.adobe.com/xap/1.0/t/pg/',
1579 'http://purl.org/dc/elements/1.1/',
1580 'http://purl.org/dc/elements/1.1',
1581 'http://schemas.microsoft.com/visio/2003/svgextensions/',
1582 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1583 'http://taptrix.com/inkpad/svg_extensions',
1584 'http://web.resource.org/cc/',
1585 'http://www.freesoftware.fsf.org/bkchem/cdml',
1586 'http://www.inkscape.org/namespaces/inkscape',
1587 'http://www.opengis.net/gml',
1588 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1589 'http://www.w3.org/2000/svg',
1590 'http://www.w3.org/tr/rec-rdf-syntax/',
1591 'http://www.w3.org/2000/01/rdf-schema#',
1592 'http://www.w3.org/2000/02/svg/testsuite/description/',
1597 $isBuggyInkscape = preg_match(
'/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1599 if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1600 wfDebug( __METHOD__ .
": Non-svg namespace '$namespace' in uploaded file." );
1602 $this->mSVGNSError = $namespace;
1610 if ( $strippedElement ===
'script' ) {
1611 wfDebug( __METHOD__ .
": Found script element '$element' in uploaded file." );
1613 return [
'uploaded-script-svg', $strippedElement ];
1616 # e.g., <svg xmlns="http://www.w3.org/2000/svg">
1617 # <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
1618 if ( $strippedElement ===
'handler' ) {
1619 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1621 return [
'uploaded-script-svg', $strippedElement ];
1624 # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
1625 if ( $strippedElement ===
'stylesheet' ) {
1626 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1628 return [
'uploaded-script-svg', $strippedElement ];
1631 # Block iframes, in case they pass the namespace check
1632 if ( $strippedElement ===
'iframe' ) {
1633 wfDebug( __METHOD__ .
": iframe in uploaded file." );
1635 return [
'uploaded-script-svg', $strippedElement ];
1639 if ( $strippedElement ===
'style'
1640 && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1642 wfDebug( __METHOD__ .
": hostile css in style element." );
1644 return [
'uploaded-hostile-svg' ];
1647 foreach ( $attribs as $attrib => $value ) {
1649 [ $attributeNamespace, $stripped ] = self::splitXmlNamespace( $attrib );
1650 $value = strtolower( $value );
1654 $namespace ===
'http://www.inkscape.org/namespaces/inkscape' &&
1655 $attributeNamespace ===
''
1656 ) && str_starts_with( $stripped,
'on' )
1659 .
": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1661 return [
'uploaded-event-handler-on-svg', $attrib, $value ];
1664 # Do not allow relative links, or unsafe url schemas.
1665 # For <a> tags, only data:, http: and https: and same-document
1666 # fragment links are allowed. For all other tags, only data:
1667 # and fragment are allowed.
1668 if ( $stripped ===
'href'
1670 && !str_starts_with( $value,
'data:' )
1671 && !str_starts_with( $value,
'#' )
1673 if ( !( $strippedElement ===
'a'
1674 && preg_match(
'!^https?://!i', $value ) )
1676 wfDebug( __METHOD__ .
": Found href attribute <$strippedElement "
1677 .
"'$attrib'='$value' in uploaded file." );
1679 return [
'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1683 # only allow data: targets that should be safe. This prevents vectors like,
1684 # image/svg, text/xml, application/xml, and text/html, which can contain scripts
1685 if ( $stripped ===
'href' && strncasecmp(
'data:', $value, 5 ) === 0 ) {
1688 $parameters =
'(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1690 if ( !preg_match(
"!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1691 wfDebug( __METHOD__ .
": Found href to unwhitelisted data: uri "
1692 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1693 return [
'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1697 # Change href with animate from (http:
1698 if ( $stripped ===
'attributename'
1699 && $strippedElement ===
'animate'
1700 && $this->stripXmlNamespace( $value ) ===
'href'
1702 wfDebug( __METHOD__ .
": Found animate that might be changing href using from "
1703 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1705 return [
'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1708 # use set/animate to add event-handler attribute to parent
1709 if ( ( $strippedElement ===
'set' || $strippedElement ===
'animate' )
1710 && $stripped ===
'attributename'
1711 && substr( $value, 0, 2 ) ===
'on'
1713 wfDebug( __METHOD__ .
": Found svg setting event-handler attribute with "
1714 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1716 return [
'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1719 # use set to add href attribute to parent element
1720 if ( $strippedElement ===
'set'
1721 && $stripped ===
'attributename'
1722 && str_contains( $value,
'href' )
1724 wfDebug( __METHOD__ .
": Found svg setting href attribute '$value' in uploaded file." );
1726 return [
'uploaded-setting-href-svg' ];
1729 # use set to add a remote / data / script target to an element
1730 if ( $strippedElement ===
'set'
1731 && $stripped ===
'to'
1732 && preg_match(
'!(http|https|data|script):!sim', $value )
1734 wfDebug( __METHOD__ .
": Found svg setting attribute to '$value' in uploaded file." );
1736 return [
'uploaded-wrong-setting-svg', $value ];
1739 # use handler attribute with remote / data / script
1740 if ( $stripped ===
'handler' && preg_match(
'!(http|https|data|script):!sim', $value ) ) {
1741 wfDebug( __METHOD__ .
": Found svg setting handler with remote/data/script "
1742 .
"'$attrib'='$value' in uploaded file." );
1744 return [
'uploaded-setting-handler-svg', $attrib, $value ];
1747 # use CSS styles to bring in remote code
1748 if ( $stripped ===
'style'
1749 && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1751 wfDebug( __METHOD__ .
": Found svg setting a style with "
1752 .
"remote url '$attrib'='$value' in uploaded file." );
1753 return [
'uploaded-remote-url-svg', $attrib, $value ];
1756 # Several attributes can include css, css character escaping isn't allowed
1757 $cssAttrs = [
'font',
'clip-path',
'fill',
'filter',
'marker',
1758 'marker-end',
'marker-mid',
'marker-start',
'mask',
'stroke' ];
1759 if ( in_array( $stripped, $cssAttrs,
true )
1760 && self::checkCssFragment( $value )
1762 wfDebug( __METHOD__ .
": Found svg setting a style with "
1763 .
"remote url '$attrib'='$value' in uploaded file." );
1764 return [
'uploaded-remote-url-svg', $attrib, $value ];
1767 # image filters can pull in url, which could be svg that executes scripts
1768 # Only allow url( "#foo" ). Do not allow url( http:
1769 if ( $strippedElement ===
'image'
1770 && $stripped ===
'filter'
1771 && preg_match(
'!url\s*\(\s*["\']?[^#]!sim', $value )
1773 wfDebug( __METHOD__ .
": Found image filter with url: "
1774 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1776 return [
'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1789 private static function checkCssFragment( $value ) {
1790 # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1791 if ( stripos( $value,
'@import' ) !==
false ) {
1795 # We allow @font-face to embed fonts with data: urls, so we snip the string
1796 # 'url' out so this case won't match when we check for urls below
1797 $pattern =
'!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1798 $value = preg_replace( $pattern,
'$1$2', $value );
1800 # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1801 # properties filter and accelerator don't seem to be useful for xss in SVG files.
1802 # Expression and -o-link don't seem to work either, but filtering them here in case.
1803 # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1804 # but not local ones such as url("#..., url('#..., url(#....
1805 if ( preg_match(
'!expression
1807 | -o-link-source\s*:
1808 | -o-replace\s*:!imx', $value ) ) {
1812 if ( preg_match_all(
1813 "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1818 # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1819 foreach (
$matches[1] as $match ) {
1820 if ( !preg_match(
"!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1826 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
1838 private static function splitXmlNamespace( $element ) {
1840 $parts = explode(
':', strtolower( $element ) );
1841 $name = array_pop( $parts );
1842 $ns = implode(
':', $parts );
1844 return [ $ns, $name ];
1851 private function stripXmlNamespace( $name ) {
1853 $parts = explode(
':', strtolower( $name ) );
1855 return array_pop( $parts );
1870 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1871 $antivirus = $mainConfig->get( MainConfigNames::Antivirus );
1872 $antivirusSetup = $mainConfig->get( MainConfigNames::AntivirusSetup );
1873 $antivirusRequired = $mainConfig->get( MainConfigNames::AntivirusRequired );
1874 if ( !$antivirus ) {
1875 wfDebug( __METHOD__ .
": virus scanner disabled" );
1880 if ( !$antivirusSetup[$antivirus] ) {
1881 wfDebug( __METHOD__ .
": unknown virus scanner: {$antivirus}" );
1882 $wgOut->wrapWikiMsg(
"<div class=\"error\">\n$1\n</div>",
1883 [
'virus-badscanner', $antivirus ] );
1885 return wfMessage(
'virus-unknownscanner' )->text() .
" {$antivirus}";
1888 # look up scanner configuration
1889 $command = $antivirusSetup[$antivirus][
'command'];
1890 $exitCodeMap = $antivirusSetup[$antivirus][
'codemap'];
1891 $msgPattern = $antivirusSetup[$antivirus][
'messagepattern'] ??
null;
1893 if ( !str_contains( $command,
"%f" ) ) {
1894 # simple pattern: append file to scan
1895 $command .=
" " . Shell::escape(
$file );
1897 # complex pattern: replace "%f" with file to scan
1898 $command = str_replace(
"%f", Shell::escape(
$file ), $command );
1901 wfDebug( __METHOD__ .
": running virus scan: $command " );
1903 # execute virus scanner
1906 # NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
1907 # that does not seem to be worth the pain.
1908 # Ask me (Duesentrieb) about it if it's ever needed.
1911 # map exit code to AV_xxx constants.
1912 $mappedCode = $exitCode;
1913 if ( $exitCodeMap ) {
1914 if ( isset( $exitCodeMap[$exitCode] ) ) {
1915 $mappedCode = $exitCodeMap[$exitCode];
1916 } elseif ( isset( $exitCodeMap[
"*"] ) ) {
1917 $mappedCode = $exitCodeMap[
"*"];
1925 # scan failed (code was mapped to false by $exitCodeMap)
1926 wfDebug( __METHOD__ .
": failed to scan $file (code $exitCode)." );
1928 $output = $antivirusRequired
1929 ?
wfMessage(
'virus-scanfailed', [ $exitCode ] )->text()
1932 # scan failed because filetype is unknown (probably immune)
1933 wfDebug( __METHOD__ .
": unsupported file type $file (code $exitCode)." );
1937 wfDebug( __METHOD__ .
": file passed virus scan." );
1940 $output = trim( $output );
1943 $output =
true; #
if there
's no output, return true
1944 } elseif ( $msgPattern ) {
1946 if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
1947 $output = $groups[1];
1951 wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
1965 private function checkOverwrite( Authority $performer ) {
1966 // First check whether the local file can be overwritten
1967 $file = $this->getLocalFile();
1968 $file->load( File::READ_LATEST );
1969 if ( $file->exists() ) {
1970 if ( !self::userCanReUpload( $performer, $file ) ) {
1971 return [ 'fileexists-forbidden
', $file->getName() ];
1977 $services = MediaWikiServices::getInstance();
1979 /* Check shared conflicts: if the local file does not exist, but
1980 * RepoGroup::findFile finds a file, it exists in a shared repository.
1982 $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest
' => true ] );
1983 if ( $file && !$performer->isAllowed( 'reupload-shared
' )
1985 return [ 'fileexists-shared-forbidden
', $file->getName() ];
1998 public static function userCanReUpload( Authority $performer, File $img ) {
1999 if ( $performer->isAllowed( 'reupload
' ) ) {
2000 return true; // non-conditional
2003 if ( !$performer->isAllowed( 'reupload-own
' ) ) {
2007 if ( !( $img instanceof LocalFile ) ) {
2011 return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2025 public static function getExistsWarning( $file ) {
2026 if ( $file->exists() ) {
2027 return [ 'warning
' => 'exists
', 'file
' => $file ];
2030 if ( $file->getTitle()->getArticleID() ) {
2031 return [ 'warning
' => 'page-exists
', 'file
' => $file ];
2034 if ( !strpos( $file->getName(), '.
' ) ) {
2035 $partname = $file->getName();
2038 $n = strrpos( $file->getName(), '.
' );
2039 $extension = substr( $file->getName(), $n + 1 );
2040 $partname = substr( $file->getName(), 0, $n );
2042 $normalizedExtension = File::normalizeExtension( $extension );
2043 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2045 if ( $normalizedExtension != $extension ) {
2046 // We're not
using the normalized form of the extension.
2051 $nt_lc = Title::makeTitle(
NS_FILE,
"{$partname}.{$normalizedExtension}" );
2052 $file_lc = $localRepo->newFile( $nt_lc );
2054 if ( $file_lc->exists() ) {
2056 'warning' =>
'exists-normalized',
2058 'normalizedFile' => $file_lc
2064 $similarFiles = $localRepo->findFilesByPrefix(
"{$partname}.", 1 );
2065 if ( count( $similarFiles ) ) {
2067 'warning' =>
'exists-normalized',
2069 'normalizedFile' => $similarFiles[0],
2073 if ( self::isThumbName(
$file->getName() ) ) {
2074 # Check for filenames like 50px- or 180px-, these are mostly thumbnails
2075 $nt_thb = Title::newFromText(
2076 substr( $partname, strpos( $partname,
'-' ) + 1 ) .
'.' . $extension,
2079 $file_thb = $localRepo->newFile( $nt_thb );
2080 if ( $file_thb->exists() ) {
2082 'warning' =>
'thumb',
2084 'thumbFile' => $file_thb
2090 'warning' =>
'thumb-name',
2092 'thumbFile' => $file_thb
2096 foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2097 if ( str_starts_with( $partname, $prefix ) ) {
2099 'warning' =>
'bad-prefix',
2115 $n = strrpos( $filename,
'.' );
2116 $partname = $n ? substr( $filename, 0, $n ) : $filename;
2119 substr( $partname, 3, 3 ) ===
'px-' ||
2120 substr( $partname, 2, 3 ) ===
'px-'
2122 preg_match(
"/[0-9]{2}/", substr( $partname, 0, 2 ) );
2132 $message =
wfMessage(
'filename-prefix-blacklist' )->inContentLanguage();
2133 if ( !$message->isDisabled() ) {
2134 $lines = explode(
"\n", $message->plain() );
2135 foreach (
$lines as $line ) {
2137 $comment = substr( trim( $line ), 0, 1 );
2138 if ( $comment ===
'#' || $comment ==
'' ) {
2142 $comment = strpos( $line,
'#' );
2143 if ( $comment > 0 ) {
2144 $line = substr( $line, 0, $comment - 1 );
2146 $list[] = trim( $line );
2185 $code = $error[
'status'];
2186 unset( $code[
'status'] );
2199 $maxUploadSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxUploadSize );
2201 if ( is_array( $maxUploadSize ) ) {
2202 if ( $forType !==
null && isset( $maxUploadSize[$forType] ) ) {
2203 return $maxUploadSize[$forType];
2205 return $maxUploadSize[
'*'];
2207 return intval( $maxUploadSize );
2219 ini_get(
'upload_max_filesize' ),
2223 ini_get(
'post_max_size' ),
2226 return min( $phpMaxFileSize, $phpMaxPostSize );
2241 $store = self::getUploadSessionStore();
2242 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2244 return $store->
get( $key );
2260 $store = self::getUploadSessionStore();
2261 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2263 if ( $value ===
false ) {
2266 $store->
set( $key, $value, $store::TTL_DAY );
2287 private static function getUploadSessionStore() {
2288 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.
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgOut
static getPropertyNames( $filter=[])
Returns all possible parameters to iiprop.
static getInfo( $file, $prop, $result, $thumbParams=null, $opts=false)
Get result information for an image revision.
static getPropertyNames( $filter=null)
Returns all possible parameters to siiprop.
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.
makeKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
set( $key, $value, $exptime=0, $flags=0)
Set an item.
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.
A class containing constants representing the names of configuration variables.
static listParam(array $list, $type='text')
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.
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 blacklisted 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 a whitelist of xml encodings that are known not to be interpreted differently by the server's x...
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 we need to manually remove it on exit to clean up.
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 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.
getImageInfo( $result)
Gets image info about the file just uploaded.
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
static getMaxPhpUploadSize()
Get the PHP maximum uploaded file size, based on ini settings.
verifyMimeType( $mime)
Verify the MIME type.
initializeFromRequest(&$request)
Initialize from a WebRequest.
string false $mSVGNSError
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!is_readable( $file)) $ext
if(!file_exists( $CREDITS)) $lines