24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
31use Wikimedia\AtEase\AtEase;
50 use ProtectedHookAccessorTrait;
67 protected $mTitleError = 0;
111 public const FILETYPE_MISSING = 8;
112 public const FILETYPE_BADTYPE = 9;
113 public const VERIFICATION_ERROR = 10;
114 public const HOOK_ABORTED = 11;
115 public const FILE_TOO_LARGE = 12;
116 public const WINDOWS_NONASCII_FILENAME = 13;
117 public const FILENAME_TOO_LONG = 14;
123 public function getVerificationErrorCode( $error ) {
125 self::EMPTY_FILE =>
'empty-file',
126 self::FILE_TOO_LARGE =>
'file-too-large',
127 self::FILETYPE_MISSING =>
'filetype-missing',
128 self::FILETYPE_BADTYPE =>
'filetype-banned',
129 self::MIN_LENGTH_PARTNAME =>
'filename-tooshort',
130 self::ILLEGAL_FILENAME =>
'illegal-filename',
131 self::OVERWRITE_EXISTING_FILE =>
'overwrite',
132 self::VERIFICATION_ERROR =>
'verification-error',
133 self::HOOK_ABORTED =>
'hookaborted',
134 self::WINDOWS_NONASCII_FILENAME =>
'windows-nonascii-filename',
135 self::FILENAME_TOO_LONG =>
'filename-toolong',
137 return $code_to_status[$error] ??
'unknown-error';
147 $enableUploads = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnableUploads );
149 return $enableUploads &&
wfIniGetBool(
'file_uploads' );
161 foreach ( [
'upload',
'edit' ] as $permission ) {
162 if ( !$performer->
isAllowed( $permission ) ) {
177 return $user->pingLimiter(
'upload' );
181 private static $uploadHandlers = [
'Stash',
'File',
'Url' ];
191 $type =
$type ?: $request->getVal(
'wpSourceType',
'File' );
204 Hooks::runner()->onUploadCreateFromRequest(
$type, $className );
205 if ( $className ===
null ) {
206 $className =
'UploadFrom' .
$type;
207 wfDebug( __METHOD__ .
": class name: $className" );
208 if ( !in_array(
$type, self::$uploadHandlers ) ) {
214 if ( !$className::isEnabled() ) {
219 if ( !$className::isValidRequest( $request ) ) {
224 $handler =
new $className;
226 $handler->initializeFromRequest( $request );
265 $this->mDesiredDestName = $name;
267 throw new MWException( __METHOD__ .
" given storage path `$tempPath`." );
271 $this->mRemoveTempFile = $removeTempFile;
286 $this->mTempPath = $tempPath ??
'';
287 $this->mFileSize = $fileSize ?:
null;
288 if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
289 $this->tempFileObj =
new TempFSFile( $this->mTempPath );
291 $this->mFileSize = filesize( $this->mTempPath );
294 $this->tempFileObj =
null;
304 return Status::newGood();
312 return empty( $this->mFileSize );
320 return $this->mFileSize;
337 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
342 $tmpFile = $repo->getLocalCopy( $srcPath );
344 $tmpFile->bind( $this );
346 $path = $tmpFile ? $tmpFile->getPath() :
false;
376 return [
'status' => self::EMPTY_FILE ];
383 if ( $this->mFileSize > $maxSize ) {
385 'status' => self::FILE_TOO_LARGE,
396 if ( $verification !==
true ) {
398 'status' => self::VERIFICATION_ERROR,
399 'details' => $verification
407 if ( $result !==
true ) {
411 return [
'status' => self::OK ];
422 if ( $nt ===
null ) {
423 $result = [
'status' => $this->mTitleError ];
424 if ( $this->mTitleError == self::ILLEGAL_FILENAME ) {
425 $result[
'filtered'] = $this->mFilteredName;
427 if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
428 $result[
'finalExt'] = $this->mFinalExtension;
429 if ( count( $this->mBlackListedExtensions ) ) {
430 $result[
'blacklistedExt'] = $this->mBlackListedExtensions;
451 $verifyMimeType = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeType );
452 $verifyMimeTypeIE = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::VerifyMimeTypeIE );
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 ];
461 if ( $verifyMimeTypeIE ) {
462 # Check what Internet Explorer would detect
463 $fp = fopen( $this->mTempPath,
'rb' );
465 $chunk = fread( $fp, 256 );
468 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
469 $extMime = $magic->getMimeTypeFromExtensionOrNull( (
string)$this->mFinalExtension ) ??
'';
470 $ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
471 foreach ( $ieTypes as $ieType ) {
472 if ( self::checkFileExtension( $ieType, $mimeTypeExclusions ) ) {
473 return [
'filetype-bad-ie-mime', $ieType ];
489 $config = MediaWikiServices::getInstance()->getMainConfig();
490 $verifyMimeType = $config->get( MainConfigNames::VerifyMimeType );
491 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
493 if ( $status !==
true ) {
497 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
498 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
499 $mime = $this->mFileProps[
'mime'];
501 if ( $verifyMimeType ) {
502 # XXX: Missing extension will be caught by validateName() via getTitle()
503 if ( (
string)$this->mFinalExtension !==
'' &&
506 return [
'filetype-mime-mismatch', $this->mFinalExtension,
$mime ];
510 # check for htmlish code and javascript
511 if ( !$disableUploadScriptChecks ) {
512 if ( $this->mFinalExtension ==
'svg' ||
$mime ==
'image/svg+xml' ) {
514 if ( $svgStatus !==
false ) {
522 $handlerStatus = $handler->verifyUpload( $this->mTempPath );
523 if ( !$handlerStatus->isOK() ) {
524 $errors = $handlerStatus->getErrorsArray();
526 return reset( $errors );
531 $this->getHookRunner()->onUploadVerifyFile( $this,
$mime, $error );
532 if ( $error !==
true ) {
533 if ( !is_array( $error ) ) {
539 wfDebug( __METHOD__ .
": all clear; passing." );
554 $config = MediaWikiServices::getInstance()->getMainConfig();
555 $disableUploadScriptChecks = $config->get( MainConfigNames::DisableUploadScriptChecks );
556 # getTitle() sets some internal parameters like $this->mFinalExtension
559 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
560 $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
562 # check MIME type, if desired
563 $mime = $this->mFileProps[
'file-mime'];
565 if ( $status !==
true ) {
569 # check for htmlish code and javascript
570 if ( !$disableUploadScriptChecks ) {
571 if ( self::detectScript( $this->mTempPath,
$mime, $this->mFinalExtension ) ) {
572 return [
'uploadscripted' ];
574 if ( $this->mFinalExtension ==
'svg' ||
$mime ==
'image/svg+xml' ) {
576 if ( $svgStatus !==
false ) {
582 # Scan the uploaded file for viruses
585 return [
'uploadvirus', $virus ];
597 $names = [ $entry[
'name'] ];
604 $nullPos = strpos( $entry[
'name'],
"\000" );
605 if ( $nullPos !==
false ) {
606 $names[] = substr( $entry[
'name'], 0, $nullPos );
611 if ( preg_grep(
'!\.class/?$!', $names ) ) {
612 $this->mJavaDetected =
true;
646 if ( $nt ===
null ) {
650 $status = PermissionStatus::newEmpty();
653 if ( !$status->isGood() ) {
654 return $status->toLegacyErrorArray();
657 $overwriteError = $this->checkOverwrite( $performer );
658 if ( $overwriteError !==
true ) {
659 return [ $overwriteError ];
675 if ( $user ===
null ) {
677 $user = RequestContext::getMain()->getUser();
683 $localFile->
load( File::READ_LATEST );
684 $filename = $localFile->
getName();
687 $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
688 if ( $badFileName !==
null ) {
689 $warnings[
'badfilename'] = $badFileName;
692 $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (
string)$this->mFinalExtension );
693 if ( $unwantedFileExtensionDetails !==
null ) {
694 $warnings[
'filetype-unwanted-type'] = $unwantedFileExtensionDetails;
697 $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
698 if ( $fileSizeWarnings ) {
699 $warnings = array_merge( $warnings, $fileSizeWarnings );
702 $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
703 if ( $localFileExistsWarnings ) {
704 $warnings = array_merge( $warnings, $localFileExistsWarnings );
707 if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
708 $warnings[
'was-deleted'] = $filename;
713 $ignoreLocalDupes = isset( $warnings[
'exists'] );
714 $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
716 $warnings[
'duplicate'] = $dupes;
719 $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
720 if ( $archivedDupes !==
null ) {
721 $warnings[
'duplicate-archive'] = $archivedDupes;
739 array_walk_recursive( $warnings,
static function ( &$param, $key ) {
740 if ( $param instanceof
File ) {
742 'fileName' => $param->getName(),
743 'timestamp' => $param->getTimestamp()
745 } elseif ( is_object( $param ) ) {
746 throw new InvalidArgumentException(
747 'UploadBase::makeWarningsSerializable: ' .
748 'Unexpected object of class ' . get_class( $param ) );
763 private function checkBadFileName( $filename, $desiredFileName ) {
764 $comparableName = str_replace(
' ',
'_', $desiredFileName );
765 $comparableName = Title::capitalize( $comparableName,
NS_FILE );
767 if ( $desiredFileName != $filename && $comparableName != $filename ) {
782 private function checkUnwantedFileExtensions( $fileExtension ) {
784 $checkFileExtensions = MediaWikiServices::getInstance()->getMainConfig()
785 ->get( MainConfigNames::CheckFileExtensions );
786 $fileExtensions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FileExtensions );
787 if ( $checkFileExtensions ) {
788 $extensions = array_unique( $fileExtensions );
792 $wgLang->commaList( $extensions ),
806 private function checkFileSize( $fileSize ) {
807 $uploadSizeWarning = MediaWikiServices::getInstance()->getMainConfig()
808 ->get( MainConfigNames::UploadSizeWarning );
812 if ( $uploadSizeWarning && ( $fileSize > $uploadSizeWarning ) ) {
813 $warnings[
'large-file'] = [
819 if ( $fileSize == 0 ) {
820 $warnings[
'empty-file'] =
true;
832 private function checkLocalFileExists(
LocalFile $localFile, $hash ) {
835 $exists = self::getExistsWarning( $localFile );
836 if ( $exists !==
false ) {
837 $warnings[
'exists'] = $exists;
840 if ( $hash !==
false && $hash === $localFile->
getSha1() ) {
841 $warnings[
'no-change'] = $localFile;
846 foreach ( $history as $oldFile ) {
847 if ( $hash === $oldFile->getSha1() ) {
848 $warnings[
'duplicate-version'][] = $oldFile;
856 private function checkLocalFileWasDeleted(
LocalFile $localFile ) {
866 private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
867 if ( $hash ===
false ) {
870 $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
872 foreach ( $dupes as $key => $dupe ) {
876 $title->equals( $dupe->getTitle() )
878 unset( $dupes[$key] );
892 private function checkAgainstArchiveDupes( $hash,
Authority $performer ) {
893 if ( $hash ===
false ) {
897 if ( $archivedFile->getID() > 0 ) {
898 if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
899 return $archivedFile->getName();
926 $comment, $pageText, $watch, $user, $tags = [], ?
string $watchlistExpiry =
null
929 $props = $this->mFileProps;
933 $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
935 if ( !is_array( $error ) ) {
938 return Status::newFatal( ...$error );
952 if ( $status->isGood() ) {
954 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
960 $this->getHookRunner()->onUploadComplete( $this );
984 if ( $this->mTitle !==
false ) {
987 if ( !is_string( $this->mDesiredDestName ) ) {
988 $this->mTitleError = self::ILLEGAL_FILENAME;
989 $this->mTitle =
null;
996 $title = Title::newFromText( $this->mDesiredDestName );
998 $this->mFilteredName =
$title->getDBkey();
1000 $this->mFilteredName = $this->mDesiredDestName;
1003 # oi_archive_name is max 255 bytes, which include a timestamp and an
1004 # exclamation mark, so restrict file name to 240 bytes.
1005 if ( strlen( $this->mFilteredName ) > 240 ) {
1006 $this->mTitleError = self::FILENAME_TOO_LONG;
1007 $this->mTitle =
null;
1019 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1020 if ( $nt ===
null ) {
1021 $this->mTitleError = self::ILLEGAL_FILENAME;
1022 $this->mTitle =
null;
1026 $this->mFilteredName = $nt->
getDBkey();
1034 if (
$ext !== [] ) {
1035 $this->mFinalExtension = trim( end(
$ext ) );
1037 $this->mFinalExtension =
'';
1042 if ( $this->mTempPath !==
null ) {
1043 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1044 $mime = $magic->guessMimeType( $this->mTempPath );
1045 if (
$mime !==
'unknown/unknown' ) {
1046 # Get a space separated list of extensions
1047 $mimeExt = $magic->getExtensionFromMimeTypeOrNull(
$mime );
1048 if ( $mimeExt !==
null ) {
1049 # Set the extension to the canonical extension
1050 $this->mFinalExtension = $mimeExt;
1052 # Fix up the other variables
1053 $this->mFilteredName .=
".{$this->mFinalExtension}";
1054 $nt = Title::makeTitleSafe(
NS_FILE, $this->mFilteredName );
1055 $ext = [ $this->mFinalExtension ];
1062 $config = MediaWikiServices::getInstance()->getMainConfig();
1063 $checkFileExtensions = $config->get( MainConfigNames::CheckFileExtensions );
1064 $strictFileExtensions = $config->get( MainConfigNames::StrictFileExtensions );
1065 $fileExtensions = $config->get( MainConfigNames::FileExtensions );
1066 $prohibitedFileExtensions = $config->get( MainConfigNames::ProhibitedFileExtensions );
1068 $blackListedExtensions = self::checkFileExtensionList(
$ext, $prohibitedFileExtensions );
1070 if ( $this->mFinalExtension ==
'' ) {
1071 $this->mTitleError = self::FILETYPE_MISSING;
1072 $this->mTitle =
null;
1075 } elseif ( $blackListedExtensions ||
1076 ( $checkFileExtensions && $strictFileExtensions &&
1079 $this->mBlackListedExtensions = $blackListedExtensions;
1080 $this->mTitleError = self::FILETYPE_BADTYPE;
1081 $this->mTitle =
null;
1087 if ( !preg_match(
'/^[\x0-\x7f]*$/', $nt->getText() )
1088 && !MediaWikiServices::getInstance()->getRepoGroup()
1089 ->getLocalRepo()->backendSupportsUnicodePaths()
1091 $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1092 $this->mTitle =
null;
1097 # If there was more than one "extension", reassemble the base
1098 # filename to prevent bogus complaints about length
1099 if ( count(
$ext ) > 1 ) {
1100 $iterations = count(
$ext ) - 1;
1101 for ( $i = 0; $i < $iterations; $i++ ) {
1102 $partname .=
'.' .
$ext[$i];
1106 if ( strlen( $partname ) < 1 ) {
1107 $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1108 $this->mTitle =
null;
1113 $this->mTitle = $nt;
1125 if ( $this->mLocalFile ===
null ) {
1127 $this->mLocalFile = $nt ===
null
1129 : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1132 return $this->mLocalFile;
1139 return $this->mStashFile;
1155 if ( !$isPartial ) {
1158 return Status::newFatal( ...$error );
1163 return Status::newGood(
$file );
1165 return Status::newFatal(
'uploadstash-exception', get_class( $e ), $e->getMessage() );
1174 $props = $this->mFileProps;
1176 $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1177 if ( $error && !is_array( $error ) ) {
1178 $error = [ $error ];
1191 $stash = MediaWikiServices::getInstance()->getRepoGroup()
1192 ->getLocalRepo()->getUploadStash( $user );
1194 $this->mStashFile =
$file;
1204 if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1206 wfDebug( __METHOD__ .
": Marked temporary file '{$this->mTempPath}' for removal" );
1207 $this->tempFileObj->autocollect();
1212 return $this->mTempPath;
1225 $bits = explode(
'.', $filename );
1226 $basename = array_shift( $bits );
1228 return [ $basename, $bits ];
1240 return in_array( strtolower(
$ext ), $list );
1252 return array_intersect( array_map(
'strtolower',
$ext ), $list );
1263 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
1266 if ( !$magic->isRecognizableExtension( $extension ) ) {
1267 wfDebug( __METHOD__ .
": passing file with unknown detected mime type; " .
1268 "unrecognized extension '$extension', can't verify" );
1272 wfDebug( __METHOD__ .
": rejecting file with unknown detected mime type; " .
1273 "recognized extension '$extension', so probably invalid file" );
1279 $match = $magic->isMatchingExtension( $extension,
$mime );
1281 if ( $match ===
null ) {
1282 if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1283 wfDebug( __METHOD__ .
": No extension known for $mime, but we know a mime for $extension" );
1287 wfDebug( __METHOD__ .
": no file extension known for mime type $mime, passing file" );
1291 } elseif ( $match ) {
1292 wfDebug( __METHOD__ .
": mime type $mime matches extension $extension, passing file" );
1298 .
": mime type $mime mismatches file extension $extension, rejecting file" );
1316 # ugly hack: for text files, always look at the entire file.
1317 # For binary field, just check the first K.
1319 $isText = strpos(
$mime,
'text/' ) === 0;
1321 $chunk = file_get_contents(
$file );
1323 $fp = fopen(
$file,
'rb' );
1327 $chunk = fread( $fp, 1024 );
1331 $chunk = strtolower( $chunk );
1337 # decode from UTF-16 if needed (could be used for obfuscation).
1338 if ( substr( $chunk, 0, 2 ) ==
"\xfe\xff" ) {
1340 } elseif ( substr( $chunk, 0, 2 ) ==
"\xff\xfe" ) {
1346 if ( $enc !==
null ) {
1347 $chunk = iconv( $enc,
"ASCII//IGNORE", $chunk );
1350 $chunk = trim( $chunk );
1353 wfDebug( __METHOD__ .
": checking for embedded scripts and HTML stuff" );
1355 # check for HTML doctype
1356 if ( preg_match(
"/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1362 if ( $extension ==
'svg' || strpos(
$mime,
'image/svg' ) === 0 ) {
1363 if ( self::checkXMLEncodingMissmatch(
$file ) ) {
1377 '<html', # also in safari
1378 '<script', # also in safari
1381 foreach ( $tags as $tag ) {
1382 if ( strpos( $chunk, $tag ) !==
false ) {
1383 wfDebug( __METHOD__ .
": found something that may make it be mistaken for html: $tag" );
1393 # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1394 $chunk = Sanitizer::decodeCharReferences( $chunk );
1396 # look for script-types
1397 if ( preg_match(
'!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
1398 wfDebug( __METHOD__ .
": found script types" );
1403 # look for html-style script-urls
1404 if ( preg_match(
'!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1405 wfDebug( __METHOD__ .
": found html-style script urls" );
1410 # look for css-style script-urls
1411 if ( preg_match(
'!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1412 wfDebug( __METHOD__ .
": found css-style script urls" );
1417 wfDebug( __METHOD__ .
": no scripts found" );
1430 $svgMetadataCutoff = MediaWikiServices::getInstance()->getMainConfig()
1431 ->get( MainConfigNames::SVGMetadataCutoff );
1432 $contents = file_get_contents(
$file,
false,
null, 0, $svgMetadataCutoff );
1433 $encodingRegex =
'!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1435 if ( preg_match(
"!<\?xml\b(.*?)\?>!si", $contents,
$matches ) ) {
1436 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1437 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1439 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1443 } elseif ( preg_match(
"!<\?xml\b!si", $contents ) ) {
1446 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1449 } elseif ( substr( $contents, 0, 4 ) ==
"\x4C\x6F\xA7\x94" ) {
1451 wfDebug( __METHOD__ .
": EBCDIC Encoded XML" );
1458 $attemptEncodings = [
'UTF-16',
'UTF-16BE',
'UTF-32',
'UTF-32BE' ];
1459 foreach ( $attemptEncodings as $encoding ) {
1460 AtEase::suppressWarnings();
1461 $str = iconv( $encoding,
'UTF-8', $contents );
1462 AtEase::restoreWarnings();
1463 if ( $str !=
'' && preg_match(
"!<\?xml\b(.*?)\?>!si", $str,
$matches ) ) {
1464 if ( preg_match( $encodingRegex,
$matches[1], $encMatch )
1465 && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1467 wfDebug( __METHOD__ .
": Found unsafe XML encoding '{$encMatch[1]}'" );
1471 } elseif ( $str !=
'' && preg_match(
"!<\?xml\b!si", $str ) ) {
1474 wfDebug( __METHOD__ .
": Unmatched XML declaration start" );
1489 $this->mSVGNSError =
false;
1492 [ $this,
'checkSvgScriptCallback' ],
1495 'processing_instruction_handler' => [ __CLASS__,
'checkSvgPICallback' ],
1496 'external_dtd_handler' => [ __CLASS__,
'checkSvgExternalDTD' ],
1499 if ( $check->wellFormed !==
true ) {
1502 return $partial ? false : [
'uploadinvalidxml' ];
1503 } elseif ( $check->filterMatch ) {
1504 if ( $this->mSVGNSError ) {
1505 return [
'uploadscriptednamespace', $this->mSVGNSError ];
1508 return $check->filterMatchType;
1522 if ( preg_match(
'/xml-stylesheet/i', $target ) ) {
1523 return [
'upload-scripted-pi-callback' ];
1544 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1545 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1546 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1547 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1549 'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1551 if (
$type !==
'PUBLIC'
1552 || !in_array( $systemId, $allowedDTDs )
1553 || strpos( $publicId,
"-//W3C//" ) !== 0
1555 return [
'upload-scripted-dtd' ];
1568 list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
1572 static $validNamespaces = [
1575 'http://creativecommons.org/ns#',
1576 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1577 'http://ns.adobe.com/adobeillustrator/10.0/',
1578 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1579 'http://ns.adobe.com/extensibility/1.0/',
1580 'http://ns.adobe.com/flows/1.0/',
1581 'http://ns.adobe.com/illustrator/1.0/',
1582 'http://ns.adobe.com/imagereplacement/1.0/',
1583 'http://ns.adobe.com/pdf/1.3/',
1584 'http://ns.adobe.com/photoshop/1.0/',
1585 'http://ns.adobe.com/saveforweb/1.0/',
1586 'http://ns.adobe.com/variables/1.0/',
1587 'http://ns.adobe.com/xap/1.0/',
1588 'http://ns.adobe.com/xap/1.0/g/',
1589 'http://ns.adobe.com/xap/1.0/g/img/',
1590 'http://ns.adobe.com/xap/1.0/mm/',
1591 'http://ns.adobe.com/xap/1.0/rights/',
1592 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1593 'http://ns.adobe.com/xap/1.0/stype/font#',
1594 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1595 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1596 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1597 'http://ns.adobe.com/xap/1.0/t/pg/',
1598 'http://purl.org/dc/elements/1.1/',
1599 'http://purl.org/dc/elements/1.1',
1600 'http://schemas.microsoft.com/visio/2003/svgextensions/',
1601 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1602 'http://taptrix.com/inkpad/svg_extensions',
1603 'http://web.resource.org/cc/',
1604 'http://www.freesoftware.fsf.org/bkchem/cdml',
1605 'http://www.inkscape.org/namespaces/inkscape',
1606 'http://www.opengis.net/gml',
1607 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1608 'http://www.w3.org/2000/svg',
1609 'http://www.w3.org/tr/rec-rdf-syntax/',
1610 'http://www.w3.org/2000/01/rdf-schema#',
1615 $isBuggyInkscape = preg_match(
'/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1617 if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1618 wfDebug( __METHOD__ .
": Non-svg namespace '$namespace' in uploaded file." );
1620 $this->mSVGNSError = $namespace;
1628 if ( $strippedElement ==
'script' ) {
1629 wfDebug( __METHOD__ .
": Found script element '$element' in uploaded file." );
1631 return [
'uploaded-script-svg', $strippedElement ];
1634 # e.g., <svg xmlns="http://www.w3.org/2000/svg">
1635 # <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
1636 if ( $strippedElement ==
'handler' ) {
1637 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1639 return [
'uploaded-script-svg', $strippedElement ];
1642 # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
1643 if ( $strippedElement ==
'stylesheet' ) {
1644 wfDebug( __METHOD__ .
": Found scriptable element '$element' in uploaded file." );
1646 return [
'uploaded-script-svg', $strippedElement ];
1649 # Block iframes, in case they pass the namespace check
1650 if ( $strippedElement ==
'iframe' ) {
1651 wfDebug( __METHOD__ .
": iframe in uploaded file." );
1653 return [
'uploaded-script-svg', $strippedElement ];
1657 if ( $strippedElement ==
'style'
1658 && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1660 wfDebug( __METHOD__ .
": hostile css in style element." );
1661 return [
'uploaded-hostile-svg' ];
1664 foreach ( $attribs as $attrib => $value ) {
1665 $stripped = $this->stripXmlNamespace( $attrib );
1666 $value = strtolower( $value );
1668 if ( substr( $stripped, 0, 2 ) ==
'on' ) {
1670 .
": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1672 return [
'uploaded-event-handler-on-svg', $attrib, $value ];
1675 # Do not allow relative links, or unsafe url schemas.
1676 # For <a> tags, only data:, http: and https: and same-document
1677 # fragment links are allowed. For all other tags, only data:
1678 # and fragment are allowed.
1679 if ( $stripped ==
'href'
1681 && strpos( $value,
'data:' ) !== 0
1682 && strpos( $value,
'#' ) !== 0
1684 if ( !( $strippedElement ===
'a'
1685 && preg_match(
'!^https?://!i', $value ) )
1687 wfDebug( __METHOD__ .
": Found href attribute <$strippedElement "
1688 .
"'$attrib'='$value' in uploaded file." );
1690 return [
'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1694 # only allow data: targets that should be safe. This prevents vectors like,
1695 # image/svg, text/xml, application/xml, and text/html, which can contain scripts
1696 if ( $stripped ==
'href' && strncasecmp(
'data:', $value, 5 ) === 0 ) {
1699 $parameters =
'(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1701 if ( !preg_match(
"!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1702 wfDebug( __METHOD__ .
": Found href to unwhitelisted data: uri "
1703 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1704 return [
'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1708 # Change href with animate from (http:
1709 if ( $stripped ===
'attributename'
1710 && $strippedElement ===
'animate'
1711 && $this->stripXmlNamespace( $value ) ==
'href'
1713 wfDebug( __METHOD__ .
": Found animate that might be changing href using from "
1714 .
"\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1716 return [
'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1719 # use set/animate to add event-handler attribute to parent
1720 if ( ( $strippedElement ==
'set' || $strippedElement ==
'animate' )
1721 && $stripped ==
'attributename'
1722 && substr( $value, 0, 2 ) ==
'on'
1724 wfDebug( __METHOD__ .
": Found svg setting event-handler attribute with "
1725 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1727 return [
'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1730 # use set to add href attribute to parent element
1731 if ( $strippedElement ==
'set'
1732 && $stripped ==
'attributename'
1733 && strpos( $value,
'href' ) !==
false
1735 wfDebug( __METHOD__ .
": Found svg setting href attribute '$value' in uploaded file." );
1737 return [
'uploaded-setting-href-svg' ];
1740 # use set to add a remote / data / script target to an element
1741 if ( $strippedElement ==
'set'
1742 && $stripped ==
'to'
1743 && preg_match(
'!(http|https|data|script):!sim', $value )
1745 wfDebug( __METHOD__ .
": Found svg setting attribute to '$value' in uploaded file." );
1747 return [
'uploaded-wrong-setting-svg', $value ];
1750 # use handler attribute with remote / data / script
1751 if ( $stripped ==
'handler' && preg_match(
'!(http|https|data|script):!sim', $value ) ) {
1752 wfDebug( __METHOD__ .
": Found svg setting handler with remote/data/script "
1753 .
"'$attrib'='$value' in uploaded file." );
1755 return [
'uploaded-setting-handler-svg', $attrib, $value ];
1758 # use CSS styles to bring in remote code
1759 if ( $stripped ==
'style'
1760 && self::checkCssFragment( Sanitizer::normalizeCss( $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 # Several attributes can include css, css character escaping isn't allowed
1768 $cssAttrs = [
'font',
'clip-path',
'fill',
'filter',
'marker',
1769 'marker-end',
'marker-mid',
'marker-start',
'mask',
'stroke' ];
1770 if ( in_array( $stripped, $cssAttrs )
1771 && self::checkCssFragment( $value )
1773 wfDebug( __METHOD__ .
": Found svg setting a style with "
1774 .
"remote url '$attrib'='$value' in uploaded file." );
1775 return [
'uploaded-remote-url-svg', $attrib, $value ];
1778 # image filters can pull in url, which could be svg that executes scripts
1779 # Only allow url( "#foo" ). Do not allow url( http:
1780 if ( $strippedElement ==
'image'
1781 && $stripped ==
'filter'
1782 && preg_match(
'!url\s*\(\s*["\']?[^#]!sim', $value )
1784 wfDebug( __METHOD__ .
": Found image filter with url: "
1785 .
"\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1787 return [
'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1800 private static function checkCssFragment( $value ) {
1801 # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1802 if ( stripos( $value,
'@import' ) !==
false ) {
1806 # We allow @font-face to embed fonts with data: urls, so we snip the string
1807 # 'url' out so this case won't match when we check for urls below
1808 $pattern =
'!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1809 $value = preg_replace( $pattern,
'$1$2', $value );
1811 # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1812 # properties filter and accelerator don't seem to be useful for xss in SVG files.
1813 # Expression and -o-link don't seem to work either, but filtering them here in case.
1814 # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1815 # but not local ones such as url("#..., url('#..., url(#....
1816 if ( preg_match(
'!expression
1818 | -o-link-source\s*:
1819 | -o-replace\s*:!imx', $value ) ) {
1823 if ( preg_match_all(
1824 "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1829 # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1830 foreach (
$matches[1] as $match ) {
1831 if ( !preg_match(
"!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1837 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
1849 private static function splitXmlNamespace( $element ) {
1851 $parts = explode(
':', strtolower( $element ) );
1852 $name = array_pop( $parts );
1853 $ns = implode(
':', $parts );
1855 return [ $ns, $name ];
1862 private function stripXmlNamespace( $name ) {
1864 $parts = explode(
':', strtolower( $name ) );
1866 return array_pop( $parts );
1881 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1882 $antivirus = $mainConfig->get( MainConfigNames::Antivirus );
1883 $antivirusSetup = $mainConfig->get( MainConfigNames::AntivirusSetup );
1884 $antivirusRequired = $mainConfig->get( MainConfigNames::AntivirusRequired );
1885 if ( !$antivirus ) {
1886 wfDebug( __METHOD__ .
": virus scanner disabled" );
1891 if ( !$antivirusSetup[$antivirus] ) {
1892 wfDebug( __METHOD__ .
": unknown virus scanner: {$antivirus}" );
1893 $wgOut->wrapWikiMsg(
"<div class=\"error\">\n$1\n</div>",
1894 [
'virus-badscanner', $antivirus ] );
1896 return wfMessage(
'virus-unknownscanner' )->text() .
" {$antivirus}";
1899 # look up scanner configuration
1900 $command = $antivirusSetup[$antivirus][
'command'];
1901 $exitCodeMap = $antivirusSetup[$antivirus][
'codemap'];
1902 $msgPattern = $antivirusSetup[$antivirus][
'messagepattern'] ??
null;
1904 if ( strpos(
$command,
"%f" ) ===
false ) {
1905 # simple pattern: append file to scan
1908 # complex pattern: replace "%f" with file to scan
1912 wfDebug( __METHOD__ .
": running virus scan: $command " );
1914 # execute virus scanner
1917 # NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
1918 # that does not seem to be worth the pain.
1919 # Ask me (Duesentrieb) about it if it's ever needed.
1922 # map exit code to AV_xxx constants.
1923 $mappedCode = $exitCode;
1924 if ( $exitCodeMap ) {
1925 if ( isset( $exitCodeMap[$exitCode] ) ) {
1926 $mappedCode = $exitCodeMap[$exitCode];
1927 } elseif ( isset( $exitCodeMap[
"*"] ) ) {
1928 $mappedCode = $exitCodeMap[
"*"];
1936 # scan failed (code was mapped to false by $exitCodeMap)
1937 wfDebug( __METHOD__ .
": failed to scan $file (code $exitCode)." );
1939 $output = $antivirusRequired
1940 ?
wfMessage(
'virus-scanfailed', [ $exitCode ] )->text()
1943 # scan failed because filetype is unknown (probably immune)
1944 wfDebug( __METHOD__ .
": unsupported file type $file (code $exitCode)." );
1948 wfDebug( __METHOD__ .
": file passed virus scan." );
1951 $output = trim( $output );
1954 $output =
true; #
if there
's no output, return true
1955 } elseif ( $msgPattern ) {
1957 if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
1958 $output = $groups[1];
1962 wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
1976 private function checkOverwrite( Authority $performer ) {
1977 // First check whether the local file can be overwritten
1978 $file = $this->getLocalFile();
1979 $file->load( File::READ_LATEST );
1980 if ( $file->exists() ) {
1981 if ( !self::userCanReUpload( $performer, $file ) ) {
1982 return [ 'fileexists-forbidden
', $file->getName() ];
1988 $services = MediaWikiServices::getInstance();
1990 /* Check shared conflicts: if the local file does not exist, but
1991 * RepoGroup::findFile finds a file, it exists in a shared repository.
1993 $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest
' => true ] );
1994 if ( $file && !$performer->isAllowed( 'reupload-shared
' )
1996 return [ 'fileexists-shared-forbidden
', $file->getName() ];
2009 public static function userCanReUpload( Authority $performer, File $img ) {
2010 if ( $performer->isAllowed( 'reupload
' ) ) {
2011 return true; // non-conditional
2012 } elseif ( !$performer->isAllowed( 'reupload-own
' ) ) {
2016 if ( !( $img instanceof LocalFile ) ) {
2020 return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2034 public static function getExistsWarning( $file ) {
2035 if ( $file->exists() ) {
2036 return [ 'warning
' => 'exists
', 'file
' => $file ];
2039 if ( $file->getTitle()->getArticleID() ) {
2040 return [ 'warning
' => 'page-exists
', 'file
' => $file ];
2043 if ( !strpos( $file->getName(), '.
' ) ) {
2044 $partname = $file->getName();
2047 $n = strrpos( $file->getName(), '.
' );
2048 $extension = substr( $file->getName(), $n + 1 );
2049 $partname = substr( $file->getName(), 0, $n );
2051 $normalizedExtension = File::normalizeExtension( $extension );
2052 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2054 if ( $normalizedExtension != $extension ) {
2055 // We're not
using the normalized form of the extension.
2060 $nt_lc = Title::makeTitle(
NS_FILE,
"{$partname}.{$normalizedExtension}" );
2061 $file_lc = $localRepo->newFile( $nt_lc );
2063 if ( $file_lc->exists() ) {
2065 'warning' =>
'exists-normalized',
2067 'normalizedFile' => $file_lc
2073 $similarFiles = $localRepo->findFilesByPrefix(
"{$partname}.", 1 );
2074 if ( count( $similarFiles ) ) {
2076 'warning' =>
'exists-normalized',
2078 'normalizedFile' => $similarFiles[0],
2082 if ( self::isThumbName(
$file->getName() ) ) {
2083 # Check for filenames like 50px- or 180px-, these are mostly thumbnails
2085 substr( $partname, strpos( $partname,
'-' ) + 1 ) .
'.' . $extension,
2088 $file_thb = $localRepo->newFile( $nt_thb );
2089 if ( $file_thb->exists() ) {
2091 'warning' =>
'thumb',
2093 'thumbFile' => $file_thb
2098 'warning' =>
'thumb-name',
2100 'thumbFile' => $file_thb
2105 foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2106 if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
2108 'warning' =>
'bad-prefix',
2124 $n = strrpos( $filename,
'.' );
2125 $partname = $n ? substr( $filename, 0, $n ) : $filename;
2128 substr( $partname, 3, 3 ) ==
'px-' ||
2129 substr( $partname, 2, 3 ) ==
'px-'
2131 preg_match(
"/[0-9]{2}/", substr( $partname, 0, 2 ) );
2141 $message =
wfMessage(
'filename-prefix-blacklist' )->inContentLanguage();
2142 if ( !$message->isDisabled() ) {
2143 $lines = explode(
"\n", $message->plain() );
2146 $comment = substr( trim(
$line ), 0, 1 );
2147 if ( $comment ==
'#' || $comment ==
'' ) {
2151 $comment = strpos(
$line,
'#' );
2152 if ( $comment > 0 ) {
2155 $list[] = trim(
$line );
2194 $code = $error[
'status'];
2195 unset( $code[
'status'] );
2208 $maxUploadSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxUploadSize );
2210 if ( is_array( $maxUploadSize ) ) {
2211 if ( $forType !==
null && isset( $maxUploadSize[$forType] ) ) {
2212 return $maxUploadSize[$forType];
2214 return $maxUploadSize[
'*'];
2217 return intval( $maxUploadSize );
2230 ini_get(
'upload_max_filesize' ),
2234 ini_get(
'post_max_size' ),
2237 return min( $phpMaxFileSize, $phpMaxPostSize );
2252 $store = self::getUploadSessionStore();
2253 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2255 return $store->
get( $key );
2271 $store = self::getUploadSessionStore();
2272 $key = self::getUploadSessionKey( $store, $user, $statusKey );
2274 if ( $value ===
false ) {
2277 $store->
set( $key, $value, $store::TTL_DAY );
2298 private static function getUploadSessionStore() {
2299 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
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgLang
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.
This class is used to hold the location and do limited manipulation of files stored temporarily (this...
Represents a title within MediaWiki.
getDBkey()
Get the main part with underscores.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
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