MediaWiki  master
UploadBase.php
Go to the documentation of this file.
1 <?php
24 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
30 
47 abstract class UploadBase {
48  use ProtectedHookAccessorTrait;
49 
51  protected $mTempPath;
53  protected $tempFileObj;
55  protected $mDesiredDestName;
57  protected $mDestName;
59  protected $mRemoveTempFile;
61  protected $mSourceType;
63  protected $mTitle = false;
65  protected $mTitleError = 0;
67  protected $mFilteredName;
69  protected $mFinalExtension;
71  protected $mLocalFile;
73  protected $mStashFile;
75  protected $mFileSize;
77  protected $mFileProps;
81  protected $mJavaDetected;
83  protected $mSVGNSError;
84 
85  protected static $safeXmlEncodings = [
86  'UTF-8',
87  'ISO-8859-1',
88  'ISO-8859-2',
89  'UTF-16',
90  'UTF-32',
91  'WINDOWS-1250',
92  'WINDOWS-1251',
93  'WINDOWS-1252',
94  'WINDOWS-1253',
95  'WINDOWS-1254',
96  'WINDOWS-1255',
97  'WINDOWS-1256',
98  'WINDOWS-1257',
99  'WINDOWS-1258',
100  ];
101 
102  public const SUCCESS = 0;
103  public const OK = 0;
104  public const EMPTY_FILE = 3;
105  public const MIN_LENGTH_PARTNAME = 4;
106  public const ILLEGAL_FILENAME = 5;
107  public const OVERWRITE_EXISTING_FILE = 7; # Not used anymore; handled by verifyTitlePermissions()
108  public const FILETYPE_MISSING = 8;
109  public const FILETYPE_BADTYPE = 9;
110  public const VERIFICATION_ERROR = 10;
111  public const HOOK_ABORTED = 11;
112  public const FILE_TOO_LARGE = 12;
113  public const WINDOWS_NONASCII_FILENAME = 13;
114  public const FILENAME_TOO_LONG = 14;
115 
120  public function getVerificationErrorCode( $error ) {
121  $code_to_status = [
122  self::EMPTY_FILE => 'empty-file',
123  self::FILE_TOO_LARGE => 'file-too-large',
124  self::FILETYPE_MISSING => 'filetype-missing',
125  self::FILETYPE_BADTYPE => 'filetype-banned',
126  self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
127  self::ILLEGAL_FILENAME => 'illegal-filename',
128  self::OVERWRITE_EXISTING_FILE => 'overwrite',
129  self::VERIFICATION_ERROR => 'verification-error',
130  self::HOOK_ABORTED => 'hookaborted',
131  self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
132  self::FILENAME_TOO_LONG => 'filename-toolong',
133  ];
134  return $code_to_status[$error] ?? 'unknown-error';
135  }
136 
143  public static function isEnabled() {
144  global $wgEnableUploads;
145 
146  return $wgEnableUploads && wfIniGetBool( 'file_uploads' );
147  }
148 
157  public static function isAllowed( Authority $performer ) {
158  foreach ( [ 'upload', 'edit' ] as $permission ) {
159  if ( !$performer->isAllowed( $permission ) ) {
160  return $permission;
161  }
162  }
163 
164  return true;
165  }
166 
173  public static function isThrottled( $user ) {
174  return $user->pingLimiter( 'upload' );
175  }
176 
178  private static $uploadHandlers = [ 'Stash', 'File', 'Url' ];
179 
187  public static function createFromRequest( &$request, $type = null ) {
188  $type = $type ?: $request->getVal( 'wpSourceType', 'File' );
189 
190  if ( !$type ) {
191  return null;
192  }
193 
194  // Get the upload class
195  $type = ucfirst( $type );
196 
197  // Give hooks the chance to handle this request
199  $className = null;
200  Hooks::runner()->onUploadCreateFromRequest( $type, $className );
201  if ( $className === null ) {
202  $className = 'UploadFrom' . $type;
203  wfDebug( __METHOD__ . ": class name: $className" );
204  if ( !in_array( $type, self::$uploadHandlers ) ) {
205  return null;
206  }
207  }
208 
209  // Check whether this upload class is enabled
210  if ( !$className::isEnabled() ) {
211  return null;
212  }
213 
214  // Check whether the request is valid
215  if ( !$className::isValidRequest( $request ) ) {
216  return null;
217  }
218 
220  $handler = new $className;
221 
222  $handler->initializeFromRequest( $request );
223 
224  return $handler;
225  }
226 
232  public static function isValidRequest( $request ) {
233  return false;
234  }
235 
239  public function __construct() {
240  }
241 
249  public function getSourceType() {
250  return null;
251  }
252 
260  public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
261  $this->mDesiredDestName = $name;
262  if ( FileBackend::isStoragePath( $tempPath ) ) {
263  throw new MWException( __METHOD__ . " given storage path `$tempPath`." );
264  }
265 
266  $this->setTempFile( $tempPath, $fileSize );
267  $this->mRemoveTempFile = $removeTempFile;
268  }
269 
275  abstract public function initializeFromRequest( &$request );
276 
281  protected function setTempFile( $tempPath, $fileSize = null ) {
282  $this->mTempPath = $tempPath;
283  $this->mFileSize = $fileSize ?: null;
284  if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
285  $this->tempFileObj = new TempFSFile( $this->mTempPath );
286  if ( !$fileSize ) {
287  $this->mFileSize = filesize( $this->mTempPath );
288  }
289  } else {
290  $this->tempFileObj = null;
291  }
292  }
293 
299  public function fetchFile() {
300  return Status::newGood();
301  }
302 
307  public function isEmptyFile() {
308  return empty( $this->mFileSize );
309  }
310 
315  public function getFileSize() {
316  return $this->mFileSize;
317  }
318 
324  public function getTempFileSha1Base36() {
325  return FSFile::getSha1Base36FromPath( $this->mTempPath );
326  }
327 
332  public function getRealPath( $srcPath ) {
333  $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
334  if ( FileRepo::isVirtualUrl( $srcPath ) ) {
338  $tmpFile = $repo->getLocalCopy( $srcPath );
339  if ( $tmpFile ) {
340  $tmpFile->bind( $this ); // keep alive with $this
341  }
342  $path = $tmpFile ? $tmpFile->getPath() : false;
343  } else {
344  $path = $srcPath;
345  }
346 
347  return $path;
348  }
349 
367  public function verifyUpload() {
371  if ( $this->isEmptyFile() ) {
372  return [ 'status' => self::EMPTY_FILE ];
373  }
374 
378  $maxSize = self::getMaxUploadSize( $this->getSourceType() );
379  if ( $this->mFileSize > $maxSize ) {
380  return [
381  'status' => self::FILE_TOO_LARGE,
382  'max' => $maxSize,
383  ];
384  }
385 
391  $verification = $this->verifyFile();
392  if ( $verification !== true ) {
393  return [
394  'status' => self::VERIFICATION_ERROR,
395  'details' => $verification
396  ];
397  }
398 
402  $result = $this->validateName();
403  if ( $result !== true ) {
404  return $result;
405  }
406 
407  return [ 'status' => self::OK ];
408  }
409 
416  public function validateName() {
417  $nt = $this->getTitle();
418  if ( $nt === null ) {
419  $result = [ 'status' => $this->mTitleError ];
420  if ( $this->mTitleError == self::ILLEGAL_FILENAME ) {
421  $result['filtered'] = $this->mFilteredName;
422  }
423  if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
424  $result['finalExt'] = $this->mFinalExtension;
425  if ( count( $this->mBlackListedExtensions ) ) {
426  $result['blacklistedExt'] = $this->mBlackListedExtensions;
427  }
428  }
429 
430  return $result;
431  }
432  $this->mDestName = $this->getLocalFile()->getName();
433 
434  return true;
435  }
436 
446  protected function verifyMimeType( $mime ) {
448  if ( $wgVerifyMimeType ) {
449  wfDebug( "mime: <$mime> extension: <{$this->mFinalExtension}>" );
450  global $wgMimeTypeBlacklist;
451  if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
452  return [ 'filetype-badmime', $mime ];
453  }
454 
455  if ( $wgVerifyMimeTypeIE ) {
456  # Check what Internet Explorer would detect
457  $fp = fopen( $this->mTempPath, 'rb' );
458  if ( $fp ) {
459  $chunk = fread( $fp, 256 );
460  fclose( $fp );
461 
462  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
463  $extMime = $magic->getMimeTypeFromExtensionOrNull( (string)$this->mFinalExtension ) ?? '';
464  $ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
465  foreach ( $ieTypes as $ieType ) {
466  if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
467  return [ 'filetype-bad-ie-mime', $ieType ];
468  }
469  }
470  }
471  }
472  }
473 
474  return true;
475  }
476 
482  protected function verifyFile() {
484 
485  $status = $this->verifyPartialFile();
486  if ( $status !== true ) {
487  return $status;
488  }
489 
490  $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
491  $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
492  $mime = $this->mFileProps['mime'];
493 
494  if ( $wgVerifyMimeType ) {
495  # XXX: Missing extension will be caught by validateName() via getTitle()
496  if ( (string)$this->mFinalExtension !== '' &&
497  !$this->verifyExtension( $mime, $this->mFinalExtension )
498  ) {
499  return [ 'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
500  }
501  }
502 
503  # check for htmlish code and javascript
505  if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
506  $svgStatus = $this->detectScriptInSvg( $this->mTempPath, false );
507  if ( $svgStatus !== false ) {
508  return $svgStatus;
509  }
510  }
511  }
512 
513  $handler = MediaHandler::getHandler( $mime );
514  if ( $handler ) {
515  $handlerStatus = $handler->verifyUpload( $this->mTempPath );
516  if ( !$handlerStatus->isOK() ) {
517  $errors = $handlerStatus->getErrorsArray();
518 
519  return reset( $errors );
520  }
521  }
522 
523  $error = true;
524  $this->getHookRunner()->onUploadVerifyFile( $this, $mime, $error );
525  if ( $error !== true ) {
526  if ( !is_array( $error ) ) {
527  $error = [ $error ];
528  }
529  return $error;
530  }
531 
532  wfDebug( __METHOD__ . ": all clear; passing." );
533 
534  return true;
535  }
536 
545  protected function verifyPartialFile() {
547 
548  # getTitle() sets some internal parameters like $this->mFinalExtension
549  $this->getTitle();
550 
551  $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
552  $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
553 
554  # check MIME type, if desired
555  $mime = $this->mFileProps['file-mime'];
556  $status = $this->verifyMimeType( $mime );
557  if ( $status !== true ) {
558  return $status;
559  }
560 
561  # check for htmlish code and javascript
563  if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
564  return [ 'uploadscripted' ];
565  }
566  if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
567  $svgStatus = $this->detectScriptInSvg( $this->mTempPath, true );
568  if ( $svgStatus !== false ) {
569  return $svgStatus;
570  }
571  }
572  }
573 
574  # Check for Java applets, which if uploaded can bypass cross-site
575  # restrictions.
576  if ( !$wgAllowJavaUploads ) {
577  $this->mJavaDetected = false;
578  $zipStatus = ZipDirectoryReader::read( $this->mTempPath,
579  [ $this, 'zipEntryCallback' ] );
580  if ( !$zipStatus->isOK() ) {
581  $errors = $zipStatus->getErrorsArray();
582  $error = reset( $errors );
583  if ( $error[0] !== 'zip-wrong-format' ) {
584  return $error;
585  }
586  }
587  if ( $this->mJavaDetected ) {
588  return [ 'uploadjava' ];
589  }
590  }
591 
592  # Scan the uploaded file for viruses
593  $virus = $this->detectVirus( $this->mTempPath );
594  if ( $virus ) {
595  return [ 'uploadvirus', $virus ];
596  }
597 
598  return true;
599  }
600 
606  public function zipEntryCallback( $entry ) {
607  $names = [ $entry['name'] ];
608 
609  // If there is a null character, cut off the name at it, because JDK's
610  // ZIP_GetEntry() uses strcmp() if the name hashes match. If a file name
611  // were constructed which had ".class\0" followed by a string chosen to
612  // make the hash collide with the truncated name, that file could be
613  // returned in response to a request for the .class file.
614  $nullPos = strpos( $entry['name'], "\000" );
615  if ( $nullPos !== false ) {
616  $names[] = substr( $entry['name'], 0, $nullPos );
617  }
618 
619  // If there is a trailing slash in the file name, we have to strip it,
620  // because that's what ZIP_GetEntry() does.
621  if ( preg_grep( '!\.class/?$!', $names ) ) {
622  $this->mJavaDetected = true;
623  }
624  }
625 
635  public function verifyPermissions( Authority $performer ) {
636  return $this->verifyTitlePermissions( $performer );
637  }
638 
650  public function verifyTitlePermissions( Authority $performer ) {
655  $nt = $this->getTitle();
656  if ( $nt === null ) {
657  return true;
658  }
659 
660  $status = PermissionStatus::newEmpty();
661  $performer->authorizeWrite( 'edit', $nt, $status );
662  $performer->authorizeWrite( 'upload', $nt, $status );
663  if ( !$status->isGood() ) {
664  return $status->toLegacyErrorArray();
665  }
666 
667  $overwriteError = $this->checkOverwrite( $performer );
668  if ( $overwriteError !== true ) {
669  return [ $overwriteError ];
670  }
671 
672  return true;
673  }
674 
684  public function checkWarnings( $user = null ) {
685  if ( $user === null ) {
686  // TODO check uses and hard deprecate
687  $user = RequestContext::getMain()->getUser();
688  }
689 
690  $warnings = [];
691 
692  $localFile = $this->getLocalFile();
693  $localFile->load( File::READ_LATEST );
694  $filename = $localFile->getName();
695  $hash = $this->getTempFileSha1Base36();
696 
697  $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
698  if ( $badFileName !== null ) {
699  $warnings['badfilename'] = $badFileName;
700  }
701 
702  $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( (string)$this->mFinalExtension );
703  if ( $unwantedFileExtensionDetails !== null ) {
704  $warnings['filetype-unwanted-type'] = $unwantedFileExtensionDetails;
705  }
706 
707  $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
708  if ( $fileSizeWarnings ) {
709  $warnings = array_merge( $warnings, $fileSizeWarnings );
710  }
711 
712  $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
713  if ( $localFileExistsWarnings ) {
714  $warnings = array_merge( $warnings, $localFileExistsWarnings );
715  }
716 
717  if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
718  $warnings['was-deleted'] = $filename;
719  }
720 
721  // If a file with the same name exists locally then the local file has already been tested
722  // for duplication of content
723  $ignoreLocalDupes = isset( $warnings['exists'] );
724  $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
725  if ( $dupes ) {
726  $warnings['duplicate'] = $dupes;
727  }
728 
729  $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
730  if ( $archivedDupes !== null ) {
731  $warnings['duplicate-archive'] = $archivedDupes;
732  }
733 
734  return $warnings;
735  }
736 
748  public static function makeWarningsSerializable( $warnings ) {
749  array_walk_recursive( $warnings, static function ( &$param, $key ) {
750  if ( $param instanceof File ) {
751  $param = [
752  'fileName' => $param->getName(),
753  'timestamp' => $param->getTimestamp()
754  ];
755  } elseif ( is_object( $param ) ) {
756  throw new InvalidArgumentException(
757  'UploadBase::makeWarningsSerializable: ' .
758  'Unexpected object of class ' . get_class( $param ) );
759  }
760  } );
761  return $warnings;
762  }
763 
773  private function checkBadFileName( $filename, $desiredFileName ) {
774  $comparableName = str_replace( ' ', '_', $desiredFileName );
775  $comparableName = Title::capitalize( $comparableName, NS_FILE );
776 
777  if ( $desiredFileName != $filename && $comparableName != $filename ) {
778  return $filename;
779  }
780 
781  return null;
782  }
783 
792  private function checkUnwantedFileExtensions( $fileExtension ) {
794 
795  if ( $wgCheckFileExtensions ) {
796  $extensions = array_unique( $wgFileExtensions );
797  if ( !$this->checkFileExtension( $fileExtension, $extensions ) ) {
798  return [
799  $fileExtension,
800  $wgLang->commaList( $extensions ),
801  count( $extensions )
802  ];
803  }
804  }
805 
806  return null;
807  }
808 
814  private function checkFileSize( $fileSize ) {
815  global $wgUploadSizeWarning;
816 
817  $warnings = [];
818 
819  if ( $wgUploadSizeWarning && ( $fileSize > $wgUploadSizeWarning ) ) {
820  $warnings['large-file'] = [
822  Message::sizeParam( $fileSize ),
823  ];
824  }
825 
826  if ( $fileSize == 0 ) {
827  $warnings['empty-file'] = true;
828  }
829 
830  return $warnings;
831  }
832 
839  private function checkLocalFileExists( LocalFile $localFile, $hash ) {
840  $warnings = [];
841 
842  $exists = self::getExistsWarning( $localFile );
843  if ( $exists !== false ) {
844  $warnings['exists'] = $exists;
845 
846  // check if file is an exact duplicate of current file version
847  if ( $hash !== false && $hash === $localFile->getSha1() ) {
848  $warnings['no-change'] = $localFile;
849  }
850 
851  // check if file is an exact duplicate of older versions of this file
852  $history = $localFile->getHistory();
853  foreach ( $history as $oldFile ) {
854  if ( $hash === $oldFile->getSha1() ) {
855  $warnings['duplicate-version'][] = $oldFile;
856  }
857  }
858  }
859 
860  return $warnings;
861  }
862 
863  private function checkLocalFileWasDeleted( LocalFile $localFile ) {
864  return $localFile->wasDeleted() && !$localFile->exists();
865  }
866 
873  private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
874  if ( $hash === false ) {
875  return [];
876  }
877  $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
878  $title = $this->getTitle();
879  foreach ( $dupes as $key => $dupe ) {
880  if (
881  ( $dupe instanceof LocalFile ) &&
882  $ignoreLocalDupes &&
883  $title->equals( $dupe->getTitle() )
884  ) {
885  unset( $dupes[$key] );
886  }
887  }
888 
889  return $dupes;
890  }
891 
899  private function checkAgainstArchiveDupes( $hash, Authority $performer ) {
900  if ( $hash === false ) {
901  return null;
902  }
903  $archivedFile = new ArchivedFile( null, 0, '', $hash );
904  if ( $archivedFile->getID() > 0 ) {
905  if ( $archivedFile->userCan( File::DELETED_FILE, $performer ) ) {
906  return $archivedFile->getName();
907  } else {
908  return '';
909  }
910  }
911 
912  return null;
913  }
914 
932  public function performUpload(
933  $comment, $pageText, $watch, $user, $tags = [], ?string $watchlistExpiry = null
934  ) {
935  $this->getLocalFile()->load( File::READ_LATEST );
936  $props = $this->mFileProps;
937 
938  $error = null;
939  $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
940  if ( $error ) {
941  if ( !is_array( $error ) ) {
942  $error = [ $error ];
943  }
944  return Status::newFatal( ...$error );
945  }
946 
947  $status = $this->getLocalFile()->upload(
948  $this->mTempPath,
949  $comment,
950  $pageText,
952  $props,
953  false,
954  $user,
955  $tags
956  );
957 
958  if ( $status->isGood() ) {
959  if ( $watch ) {
960  MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
961  $user,
962  $this->getLocalFile()->getTitle(),
963  $watchlistExpiry
964  );
965  }
966  $this->getHookRunner()->onUploadComplete( $this );
967 
968  $this->postProcessUpload();
969  }
970 
971  return $status;
972  }
973 
980  public function postProcessUpload() {
981  }
982 
989  public function getTitle() {
990  if ( $this->mTitle !== false ) {
991  return $this->mTitle;
992  }
993  if ( !is_string( $this->mDesiredDestName ) ) {
994  $this->mTitleError = self::ILLEGAL_FILENAME;
995  $this->mTitle = null;
996 
997  return $this->mTitle;
998  }
999  /* Assume that if a user specified File:Something.jpg, this is an error
1000  * and that the namespace prefix needs to be stripped of.
1001  */
1002  $title = Title::newFromText( $this->mDesiredDestName );
1003  if ( $title && $title->getNamespace() === NS_FILE ) {
1004  $this->mFilteredName = $title->getDBkey();
1005  } else {
1006  $this->mFilteredName = $this->mDesiredDestName;
1007  }
1008 
1009  # oi_archive_name is max 255 bytes, which include a timestamp and an
1010  # exclamation mark, so restrict file name to 240 bytes.
1011  if ( strlen( $this->mFilteredName ) > 240 ) {
1012  $this->mTitleError = self::FILENAME_TOO_LONG;
1013  $this->mTitle = null;
1014 
1015  return $this->mTitle;
1016  }
1017 
1023  $this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName );
1024  /* Normalize to title form before we do any further processing */
1025  $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
1026  if ( $nt === null ) {
1027  $this->mTitleError = self::ILLEGAL_FILENAME;
1028  $this->mTitle = null;
1029 
1030  return $this->mTitle;
1031  }
1032  $this->mFilteredName = $nt->getDBkey();
1033 
1038  list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName );
1039 
1040  if ( $ext !== [] ) {
1041  $this->mFinalExtension = trim( end( $ext ) );
1042  } else {
1043  $this->mFinalExtension = '';
1044 
1045  // No extension, try guessing one from the temporary file
1046  // FIXME: Sometimes we mTempPath isn't set yet here, possibly due to an unrealistic
1047  // or incomplete test case in UploadBaseTest (T272328)
1048  if ( $this->mTempPath !== null ) {
1049  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
1050  $mime = $magic->guessMimeType( $this->mTempPath );
1051  if ( $mime !== 'unknown/unknown' ) {
1052  # Get a space separated list of extensions
1053  $mimeExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
1054  if ( $mimeExt !== null ) {
1055  # Set the extension to the canonical extension
1056  $this->mFinalExtension = $mimeExt;
1057 
1058  # Fix up the other variables
1059  $this->mFilteredName .= ".{$this->mFinalExtension}";
1060  $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
1062  }
1063  }
1064  }
1065  }
1066 
1067  /* Don't allow users to override the blacklist (check file extension) */
1070 
1071  $blackListedExtensions = $this->checkFileExtensionList( $ext, $wgFileBlacklist );
1072 
1073  if ( $this->mFinalExtension == '' ) {
1074  $this->mTitleError = self::FILETYPE_MISSING;
1075  $this->mTitle = null;
1076 
1077  return $this->mTitle;
1078  } elseif ( $blackListedExtensions ||
1080  !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) )
1081  ) {
1082  $this->mBlackListedExtensions = $blackListedExtensions;
1083  $this->mTitleError = self::FILETYPE_BADTYPE;
1084  $this->mTitle = null;
1085 
1086  return $this->mTitle;
1087  }
1088 
1089  // Windows may be broken with special characters, see T3780
1090  if ( !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() )
1091  && !MediaWikiServices::getInstance()->getRepoGroup()
1092  ->getLocalRepo()->backendSupportsUnicodePaths()
1093  ) {
1094  $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1095  $this->mTitle = null;
1096 
1097  return $this->mTitle;
1098  }
1099 
1100  # If there was more than one "extension", reassemble the base
1101  # filename to prevent bogus complaints about length
1102  if ( count( $ext ) > 1 ) {
1103  $iterations = count( $ext ) - 1;
1104  for ( $i = 0; $i < $iterations; $i++ ) {
1105  $partname .= '.' . $ext[$i];
1106  }
1107  }
1108 
1109  if ( strlen( $partname ) < 1 ) {
1110  $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1111  $this->mTitle = null;
1112 
1113  return $this->mTitle;
1114  }
1115 
1116  $this->mTitle = $nt;
1117 
1118  return $this->mTitle;
1119  }
1120 
1127  public function getLocalFile() {
1128  if ( $this->mLocalFile === null ) {
1129  $nt = $this->getTitle();
1130  $this->mLocalFile = $nt === null
1131  ? null
1132  : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1133  }
1134 
1135  return $this->mLocalFile;
1136  }
1137 
1141  public function getStashFile() {
1142  return $this->mStashFile;
1143  }
1144 
1157  public function tryStashFile( User $user, $isPartial = false ) {
1158  if ( !$isPartial ) {
1159  $error = $this->runUploadStashFileHook( $user );
1160  if ( $error ) {
1161  return Status::newFatal( ...$error );
1162  }
1163  }
1164  try {
1165  $file = $this->doStashFile( $user );
1166  return Status::newGood( $file );
1167  } catch ( UploadStashException $e ) {
1168  return Status::newFatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() );
1169  }
1170  }
1171 
1176  protected function runUploadStashFileHook( User $user ) {
1177  $props = $this->mFileProps;
1178  $error = null;
1179  $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1180  if ( $error && !is_array( $error ) ) {
1181  $error = [ $error ];
1182  }
1183  return $error;
1184  }
1185 
1193  protected function doStashFile( User $user = null ) {
1194  $stash = MediaWikiServices::getInstance()->getRepoGroup()
1195  ->getLocalRepo()->getUploadStash( $user );
1196  $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
1197  $this->mStashFile = $file;
1198 
1199  return $file;
1200  }
1201 
1206  public function cleanupTempFile() {
1207  if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1208  // Delete when all relevant TempFSFile handles go out of scope
1209  wfDebug( __METHOD__ . ": Marked temporary file '{$this->mTempPath}' for removal" );
1210  $this->tempFileObj->autocollect();
1211  }
1212  }
1213 
1214  public function getTempPath() {
1215  return $this->mTempPath;
1216  }
1217 
1227  public static function splitExtensions( $filename ) {
1228  $bits = explode( '.', $filename );
1229  $basename = array_shift( $bits );
1230 
1231  return [ $basename, $bits ];
1232  }
1233 
1242  public static function checkFileExtension( $ext, $list ) {
1243  return in_array( strtolower( $ext ), $list );
1244  }
1245 
1254  public static function checkFileExtensionList( $ext, $list ) {
1255  return array_intersect( array_map( 'strtolower', $ext ), $list );
1256  }
1257 
1265  public static function verifyExtension( $mime, $extension ) {
1266  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
1267 
1268  if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) {
1269  if ( !$magic->isRecognizableExtension( $extension ) ) {
1270  wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
1271  "unrecognized extension '$extension', can't verify" );
1272 
1273  return true;
1274  } else {
1275  wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
1276  "recognized extension '$extension', so probably invalid file" );
1277 
1278  return false;
1279  }
1280  }
1281 
1282  $match = $magic->isMatchingExtension( $extension, $mime );
1283 
1284  if ( $match === null ) {
1285  if ( $magic->getMimeTypesFromExtension( $extension ) !== [] ) {
1286  wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension" );
1287 
1288  return false;
1289  } else {
1290  wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file" );
1291 
1292  return true;
1293  }
1294  } elseif ( $match === true ) {
1295  wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file" );
1296 
1298  return true;
1299  } else {
1300  wfDebug( __METHOD__
1301  . ": mime type $mime mismatches file extension $extension, rejecting file" );
1302 
1303  return false;
1304  }
1305  }
1306 
1318  public static function detectScript( $file, $mime, $extension ) {
1319  # ugly hack: for text files, always look at the entire file.
1320  # For binary field, just check the first K.
1321 
1322  $isText = strpos( $mime, 'text/' ) === 0;
1323  if ( $isText ) {
1324  $chunk = file_get_contents( $file );
1325  } else {
1326  $fp = fopen( $file, 'rb' );
1327  if ( !$fp ) {
1328  return false;
1329  }
1330  $chunk = fread( $fp, 1024 );
1331  fclose( $fp );
1332  }
1333 
1334  $chunk = strtolower( $chunk );
1335 
1336  if ( !$chunk ) {
1337  return false;
1338  }
1339 
1340  # decode from UTF-16 if needed (could be used for obfuscation).
1341  if ( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
1342  $enc = 'UTF-16BE';
1343  } elseif ( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
1344  $enc = 'UTF-16LE';
1345  } else {
1346  $enc = null;
1347  }
1348 
1349  if ( $enc !== null ) {
1350  $chunk = iconv( $enc, "ASCII//IGNORE", $chunk );
1351  }
1352 
1353  $chunk = trim( $chunk );
1354 
1356  wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff" );
1357 
1358  # check for HTML doctype
1359  if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1360  return true;
1361  }
1362 
1363  // Some browsers will interpret obscure xml encodings as UTF-8, while
1364  // PHP/expat will interpret the given encoding in the xml declaration (T49304)
1365  if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) {
1366  if ( self::checkXMLEncodingMissmatch( $file ) ) {
1367  return true;
1368  }
1369  }
1370 
1371  // Quick check for HTML heuristics in old IE and Safari.
1372  //
1373  // The exact heuristics IE uses are checked separately via verifyMimeType(), so we
1374  // don't need them all here as it can cause many false positives.
1375  //
1376  // Check for `<script` and such still to forbid script tags and embedded HTML in SVG:
1377  $tags = [
1378  '<body',
1379  '<head',
1380  '<html', # also in safari
1381  '<script', # also in safari
1382  ];
1383 
1384  foreach ( $tags as $tag ) {
1385  if ( strpos( $chunk, $tag ) !== false ) {
1386  wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag" );
1387 
1388  return true;
1389  }
1390  }
1391 
1392  /*
1393  * look for JavaScript
1394  */
1395 
1396  # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1397  $chunk = Sanitizer::decodeCharReferences( $chunk );
1398 
1399  # look for script-types
1400  if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
1401  wfDebug( __METHOD__ . ": found script types" );
1402 
1403  return true;
1404  }
1405 
1406  # look for html-style script-urls
1407  if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1408  wfDebug( __METHOD__ . ": found html-style script urls" );
1409 
1410  return true;
1411  }
1412 
1413  # look for css-style script-urls
1414  if ( preg_match( '!url\s*\‍(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1415  wfDebug( __METHOD__ . ": found css-style script urls" );
1416 
1417  return true;
1418  }
1419 
1420  wfDebug( __METHOD__ . ": no scripts found" );
1421 
1422  return false;
1423  }
1424 
1432  public static function checkXMLEncodingMissmatch( $file ) {
1433  global $wgSVGMetadataCutoff;
1434  $contents = file_get_contents( $file, false, null, 0, $wgSVGMetadataCutoff );
1435  $encodingRegex = '!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1436 
1437  if ( preg_match( "!<\?xml\b(.*?)\?>!si", $contents, $matches ) ) {
1438  if ( preg_match( $encodingRegex, $matches[1], $encMatch )
1439  && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1440  ) {
1441  wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'" );
1442 
1443  return true;
1444  }
1445  } elseif ( preg_match( "!<\?xml\b!si", $contents ) ) {
1446  // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
1447  // bytes. There shouldn't be a legitimate reason for this to happen.
1448  wfDebug( __METHOD__ . ": Unmatched XML declaration start" );
1449 
1450  return true;
1451  } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
1452  // EBCDIC encoded XML
1453  wfDebug( __METHOD__ . ": EBCDIC Encoded XML" );
1454 
1455  return true;
1456  }
1457 
1458  // It's possible the file is encoded with multi-byte encoding, so re-encode attempt to
1459  // detect the encoding in case is specifies an encoding not whitelisted in self::$safeXmlEncodings
1460  $attemptEncodings = [ 'UTF-16', 'UTF-16BE', 'UTF-32', 'UTF-32BE' ];
1461  foreach ( $attemptEncodings as $encoding ) {
1462  Wikimedia\suppressWarnings();
1463  $str = iconv( $encoding, 'UTF-8', $contents );
1464  Wikimedia\restoreWarnings();
1465  if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
1466  if ( preg_match( $encodingRegex, $matches[1], $encMatch )
1467  && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1468  ) {
1469  wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'" );
1470 
1471  return true;
1472  }
1473  } elseif ( $str != '' && preg_match( "!<\?xml\b!si", $str ) ) {
1474  // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
1475  // bytes. There shouldn't be a legitimate reason for this to happen.
1476  wfDebug( __METHOD__ . ": Unmatched XML declaration start" );
1477 
1478  return true;
1479  }
1480  }
1481 
1482  return false;
1483  }
1484 
1490  protected function detectScriptInSvg( $filename, $partial ) {
1491  $this->mSVGNSError = false;
1492  $check = new XmlTypeCheck(
1493  $filename,
1494  [ $this, 'checkSvgScriptCallback' ],
1495  true,
1496  [
1497  'processing_instruction_handler' => [ __CLASS__, 'checkSvgPICallback' ],
1498  'external_dtd_handler' => [ __CLASS__, 'checkSvgExternalDTD' ],
1499  ]
1500  );
1501  if ( $check->wellFormed !== true ) {
1502  // Invalid xml (T60553)
1503  // But only when non-partial (T67724)
1504  return $partial ? false : [ 'uploadinvalidxml' ];
1505  } elseif ( $check->filterMatch ) {
1506  if ( $this->mSVGNSError ) {
1507  return [ 'uploadscriptednamespace', $this->mSVGNSError ];
1508  }
1509 
1510  return $check->filterMatchType;
1511  }
1512 
1513  return false;
1514  }
1515 
1522  public static function checkSvgPICallback( $target, $data ) {
1523  // Don't allow external stylesheets (T59550)
1524  if ( preg_match( '/xml-stylesheet/i', $target ) ) {
1525  return [ 'upload-scripted-pi-callback' ];
1526  }
1527 
1528  return false;
1529  }
1530 
1542  public static function checkSvgExternalDTD( $type, $publicId, $systemId ) {
1543  // This doesn't include the XHTML+MathML+SVG doctype since we don't
1544  // allow XHTML anyways.
1545  $allowedDTDs = [
1546  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1547  'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1548  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1549  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1550  // https://phabricator.wikimedia.org/T168856
1551  'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1552  ];
1553  if ( $type !== 'PUBLIC'
1554  || !in_array( $systemId, $allowedDTDs )
1555  || strpos( $publicId, "-//W3C//" ) !== 0
1556  ) {
1557  return [ 'upload-scripted-dtd' ];
1558  }
1559  return false;
1560  }
1561 
1569  public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
1570  list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
1571 
1572  // We specifically don't include:
1573  // http://www.w3.org/1999/xhtml (T62771)
1574  static $validNamespaces = [
1575  '',
1576  'adobe:ns:meta/',
1577  'http://creativecommons.org/ns#',
1578  'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1579  'http://ns.adobe.com/adobeillustrator/10.0/',
1580  'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1581  'http://ns.adobe.com/extensibility/1.0/',
1582  'http://ns.adobe.com/flows/1.0/',
1583  'http://ns.adobe.com/illustrator/1.0/',
1584  'http://ns.adobe.com/imagereplacement/1.0/',
1585  'http://ns.adobe.com/pdf/1.3/',
1586  'http://ns.adobe.com/photoshop/1.0/',
1587  'http://ns.adobe.com/saveforweb/1.0/',
1588  'http://ns.adobe.com/variables/1.0/',
1589  'http://ns.adobe.com/xap/1.0/',
1590  'http://ns.adobe.com/xap/1.0/g/',
1591  'http://ns.adobe.com/xap/1.0/g/img/',
1592  'http://ns.adobe.com/xap/1.0/mm/',
1593  'http://ns.adobe.com/xap/1.0/rights/',
1594  'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1595  'http://ns.adobe.com/xap/1.0/stype/font#',
1596  'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1597  'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1598  'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1599  'http://ns.adobe.com/xap/1.0/t/pg/',
1600  'http://purl.org/dc/elements/1.1/',
1601  'http://purl.org/dc/elements/1.1',
1602  'http://schemas.microsoft.com/visio/2003/svgextensions/',
1603  'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1604  'http://taptrix.com/inkpad/svg_extensions',
1605  'http://web.resource.org/cc/',
1606  'http://www.freesoftware.fsf.org/bkchem/cdml',
1607  'http://www.inkscape.org/namespaces/inkscape',
1608  'http://www.opengis.net/gml',
1609  'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1610  'http://www.w3.org/2000/svg',
1611  'http://www.w3.org/tr/rec-rdf-syntax/',
1612  'http://www.w3.org/2000/01/rdf-schema#',
1613  ];
1614 
1615  // Inkscape mangles namespace definitions created by Adobe Illustrator.
1616  // This is nasty but harmless. (T144827)
1617  $isBuggyInkscape = preg_match( '/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1618 
1619  if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1620  wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file." );
1622  $this->mSVGNSError = $namespace;
1623 
1624  return true;
1625  }
1626 
1627  /*
1628  * check for elements that can contain javascript
1629  */
1630  if ( $strippedElement == 'script' ) {
1631  wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file." );
1632 
1633  return [ 'uploaded-script-svg', $strippedElement ];
1634  }
1635 
1636  # e.g., <svg xmlns="http://www.w3.org/2000/svg">
1637  # <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
1638  if ( $strippedElement == 'handler' ) {
1639  wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file." );
1640 
1641  return [ 'uploaded-script-svg', $strippedElement ];
1642  }
1643 
1644  # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
1645  if ( $strippedElement == 'stylesheet' ) {
1646  wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file." );
1647 
1648  return [ 'uploaded-script-svg', $strippedElement ];
1649  }
1650 
1651  # Block iframes, in case they pass the namespace check
1652  if ( $strippedElement == 'iframe' ) {
1653  wfDebug( __METHOD__ . ": iframe in uploaded file." );
1654 
1655  return [ 'uploaded-script-svg', $strippedElement ];
1656  }
1657 
1658  # Check <style> css
1659  if ( $strippedElement == 'style'
1660  && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1661  ) {
1662  wfDebug( __METHOD__ . ": hostile css in style element." );
1663  return [ 'uploaded-hostile-svg' ];
1664  }
1665 
1666  foreach ( $attribs as $attrib => $value ) {
1667  $stripped = $this->stripXmlNamespace( $attrib );
1668  $value = strtolower( $value );
1669 
1670  if ( substr( $stripped, 0, 2 ) == 'on' ) {
1671  wfDebug( __METHOD__
1672  . ": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1673 
1674  return [ 'uploaded-event-handler-on-svg', $attrib, $value ];
1675  }
1676 
1677  # Do not allow relative links, or unsafe url schemas.
1678  # For <a> tags, only data:, http: and https: and same-document
1679  # fragment links are allowed. For all other tags, only data:
1680  # and fragment are allowed.
1681  if ( $stripped == 'href'
1682  && $value !== ''
1683  && strpos( $value, 'data:' ) !== 0
1684  && strpos( $value, '#' ) !== 0
1685  ) {
1686  if ( !( $strippedElement === 'a'
1687  && preg_match( '!^https?://!i', $value ) )
1688  ) {
1689  wfDebug( __METHOD__ . ": Found href attribute <$strippedElement "
1690  . "'$attrib'='$value' in uploaded file." );
1691 
1692  return [ 'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1693  }
1694  }
1695 
1696  # only allow data: targets that should be safe. This prevents vectors like,
1697  # image/svg, text/xml, application/xml, and text/html, which can contain scripts
1698  if ( $stripped == 'href' && strncasecmp( 'data:', $value, 5 ) === 0 ) {
1699  // rfc2397 parameters. This is only slightly slower than (;[\w;]+)*.
1700  // phpcs:ignore Generic.Files.LineLength
1701  $parameters = '(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1702 
1703  if ( !preg_match( "!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1704  wfDebug( __METHOD__ . ": Found href to unwhitelisted data: uri "
1705  . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1706  return [ 'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1707  }
1708  }
1709 
1710  # Change href with animate from (http://html5sec.org/#137).
1711  if ( $stripped === 'attributename'
1712  && $strippedElement === 'animate'
1713  && $this->stripXmlNamespace( $value ) == 'href'
1714  ) {
1715  wfDebug( __METHOD__ . ": Found animate that might be changing href using from "
1716  . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1717 
1718  return [ 'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1719  }
1720 
1721  # use set/animate to add event-handler attribute to parent
1722  if ( ( $strippedElement == 'set' || $strippedElement == 'animate' )
1723  && $stripped == 'attributename'
1724  && substr( $value, 0, 2 ) == 'on'
1725  ) {
1726  wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with "
1727  . "\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1728 
1729  return [ 'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1730  }
1731 
1732  # use set to add href attribute to parent element
1733  if ( $strippedElement == 'set'
1734  && $stripped == 'attributename'
1735  && strpos( $value, 'href' ) !== false
1736  ) {
1737  wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file." );
1738 
1739  return [ 'uploaded-setting-href-svg' ];
1740  }
1741 
1742  # use set to add a remote / data / script target to an element
1743  if ( $strippedElement == 'set'
1744  && $stripped == 'to'
1745  && preg_match( '!(http|https|data|script):!sim', $value )
1746  ) {
1747  wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file." );
1748 
1749  return [ 'uploaded-wrong-setting-svg', $value ];
1750  }
1751 
1752  # use handler attribute with remote / data / script
1753  if ( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
1754  wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script "
1755  . "'$attrib'='$value' in uploaded file." );
1756 
1757  return [ 'uploaded-setting-handler-svg', $attrib, $value ];
1758  }
1759 
1760  # use CSS styles to bring in remote code
1761  if ( $stripped == 'style'
1762  && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1763  ) {
1764  wfDebug( __METHOD__ . ": Found svg setting a style with "
1765  . "remote url '$attrib'='$value' in uploaded file." );
1766  return [ 'uploaded-remote-url-svg', $attrib, $value ];
1767  }
1768 
1769  # Several attributes can include css, css character escaping isn't allowed
1770  $cssAttrs = [ 'font', 'clip-path', 'fill', 'filter', 'marker',
1771  'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke' ];
1772  if ( in_array( $stripped, $cssAttrs )
1773  && self::checkCssFragment( $value )
1774  ) {
1775  wfDebug( __METHOD__ . ": Found svg setting a style with "
1776  . "remote url '$attrib'='$value' in uploaded file." );
1777  return [ 'uploaded-remote-url-svg', $attrib, $value ];
1778  }
1779 
1780  # image filters can pull in url, which could be svg that executes scripts
1781  # Only allow url( "#foo" ). Do not allow url( http://example.com )
1782  if ( $strippedElement == 'image'
1783  && $stripped == 'filter'
1784  && preg_match( '!url\s*\‍(\s*["\']?[^#]!sim', $value )
1785  ) {
1786  wfDebug( __METHOD__ . ": Found image filter with url: "
1787  . "\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1788 
1789  return [ 'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1790  }
1791  }
1792 
1793  return false; // No scripts detected
1794  }
1795 
1802  private static function checkCssFragment( $value ) {
1803  # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1804  if ( stripos( $value, '@import' ) !== false ) {
1805  return true;
1806  }
1807 
1808  # We allow @font-face to embed fonts with data: urls, so we snip the string
1809  # 'url' out so this case won't match when we check for urls below
1810  $pattern = '!(@font-face\s*{[^}]*src:)url(\‍("data:;base64,)!im';
1811  $value = preg_replace( $pattern, '$1$2', $value );
1812 
1813  # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1814  # properties filter and accelerator don't seem to be useful for xss in SVG files.
1815  # Expression and -o-link don't seem to work either, but filtering them here in case.
1816  # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1817  # but not local ones such as url("#..., url('#..., url(#....
1818  if ( preg_match( '!expression
1819  | -o-link\s*:
1820  | -o-link-source\s*:
1821  | -o-replace\s*:!imx', $value ) ) {
1822  return true;
1823  }
1824 
1825  if ( preg_match_all(
1826  "!(\s*(url|image|image-set)\s*\‍(\s*[\"']?\s*[^#]+.*?\‍))!sim",
1827  $value,
1828  $matches
1829  ) !== 0
1830  ) {
1831  # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1832  foreach ( $matches[1] as $match ) {
1833  if ( !preg_match( "!\s*(url|image|image-set)\s*\‍(\s*(#|'#|\"#)!im", $match ) ) {
1834  return true;
1835  }
1836  }
1837  }
1838 
1839  if ( preg_match( '/[\000-\010\013\016-\037\177]/', $value ) ) {
1840  return true;
1841  }
1842 
1843  return false;
1844  }
1845 
1851  private static function splitXmlNamespace( $element ) {
1852  // 'http://www.w3.org/2000/svg:script' -> [ 'http://www.w3.org/2000/svg', 'script' ]
1853  $parts = explode( ':', strtolower( $element ) );
1854  $name = array_pop( $parts );
1855  $ns = implode( ':', $parts );
1856 
1857  return [ $ns, $name ];
1858  }
1859 
1864  private function stripXmlNamespace( $name ) {
1865  // 'http://www.w3.org/2000/svg:script' -> 'script'
1866  $parts = explode( ':', strtolower( $name ) );
1867 
1868  return array_pop( $parts );
1869  }
1870 
1881  public static function detectVirus( $file ) {
1883 
1884  if ( !$wgAntivirus ) {
1885  wfDebug( __METHOD__ . ": virus scanner disabled" );
1886 
1887  return null;
1888  }
1889 
1890  if ( !$wgAntivirusSetup[$wgAntivirus] ) {
1891  wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus" );
1892  $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
1893  [ 'virus-badscanner', $wgAntivirus ] );
1894 
1895  return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
1896  }
1897 
1898  # look up scanner configuration
1899  $command = $wgAntivirusSetup[$wgAntivirus]['command'];
1900  $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]['codemap'];
1901  $msgPattern = $wgAntivirusSetup[$wgAntivirus]['messagepattern'] ?? null;
1902 
1903  if ( strpos( $command, "%f" ) === false ) {
1904  # simple pattern: append file to scan
1905  $command .= " " . Shell::escape( $file );
1906  } else {
1907  # complex pattern: replace "%f" with file to scan
1908  $command = str_replace( "%f", Shell::escape( $file ), $command );
1909  }
1910 
1911  wfDebug( __METHOD__ . ": running virus scan: $command " );
1912 
1913  # execute virus scanner
1914  $exitCode = false;
1915 
1916  # NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
1917  # that does not seem to be worth the pain.
1918  # Ask me (Duesentrieb) about it if it's ever needed.
1919  $output = wfShellExecWithStderr( $command, $exitCode );
1920 
1921  # map exit code to AV_xxx constants.
1922  $mappedCode = $exitCode;
1923  if ( $exitCodeMap ) {
1924  if ( isset( $exitCodeMap[$exitCode] ) ) {
1925  $mappedCode = $exitCodeMap[$exitCode];
1926  } elseif ( isset( $exitCodeMap["*"] ) ) {
1927  $mappedCode = $exitCodeMap["*"];
1928  }
1929  }
1930 
1931  /* NB: AV_NO_VIRUS is 0 but AV_SCAN_FAILED is false,
1932  * so we need the strict equalities === and thus can't use a switch here
1933  */
1934  if ( $mappedCode === AV_SCAN_FAILED ) {
1935  # scan failed (code was mapped to false by $exitCodeMap)
1936  wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode)." );
1937 
1938  $output = $wgAntivirusRequired
1939  ? wfMessage( 'virus-scanfailed', [ $exitCode ] )->text()
1940  : null;
1941  } elseif ( $mappedCode === AV_SCAN_ABORTED ) {
1942  # scan failed because filetype is unknown (probably imune)
1943  wfDebug( __METHOD__ . ": unsupported file type $file (code $exitCode)." );
1944  $output = null;
1945  } elseif ( $mappedCode === AV_NO_VIRUS ) {
1946  # no virus found
1947  wfDebug( __METHOD__ . ": file passed virus scan." );
1948  $output = false;
1949  } else {
1950  $output = trim( $output );
1951 
1952  if ( !$output ) {
1953  $output = true; # if there's no output, return true
1954  } elseif ( $msgPattern ) {
1955  $groups = [];
1956  if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
1957  $output = $groups[1];
1958  }
1959  }
1960 
1961  wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
1962  }
1963 
1964  return $output;
1965  }
1966 
1975  private function checkOverwrite( Authority $performer ) {
1976  // First check whether the local file can be overwritten
1977  $file = $this->getLocalFile();
1978  $file->load( File::READ_LATEST );
1979  if ( $file->exists() ) {
1980  if ( !self::userCanReUpload( $performer, $file ) ) {
1981  return [ 'fileexists-forbidden', $file->getName() ];
1982  } else {
1983  return true;
1984  }
1985  }
1986 
1987  $services = MediaWikiServices::getInstance();
1988 
1989  /* Check shared conflicts: if the local file does not exist, but
1990  * RepoGroup::findFile finds a file, it exists in a shared repository.
1991  */
1992  $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest' => true ] );
1993  if ( $file && !$performer->isAllowed( 'reupload-shared' )
1994  ) {
1995  return [ 'fileexists-shared-forbidden', $file->getName() ];
1996  }
1997 
1998  return true;
1999  }
2000 
2008  public static function userCanReUpload( Authority $performer, File $img ) {
2009  if ( $performer->isAllowed( 'reupload' ) ) {
2010  return true; // non-conditional
2011  } elseif ( !$performer->isAllowed( 'reupload-own' ) ) {
2012  return false;
2013  }
2014 
2015  if ( !( $img instanceof LocalFile ) ) {
2016  return false;
2017  }
2018 
2019  return $performer->getUser()->equals( $img->getUploader( File::RAW ) );
2020  }
2021 
2033  public static function getExistsWarning( $file ) {
2034  if ( $file->exists() ) {
2035  return [ 'warning' => 'exists', 'file' => $file ];
2036  }
2037 
2038  if ( $file->getTitle()->getArticleID() ) {
2039  return [ 'warning' => 'page-exists', 'file' => $file ];
2040  }
2041 
2042  if ( strpos( $file->getName(), '.' ) == false ) {
2043  $partname = $file->getName();
2044  $extension = '';
2045  } else {
2046  $n = strrpos( $file->getName(), '.' );
2047  $extension = substr( $file->getName(), $n + 1 );
2048  $partname = substr( $file->getName(), 0, $n );
2049  }
2050  $normalizedExtension = File::normalizeExtension( $extension );
2051  $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2052 
2053  if ( $normalizedExtension != $extension ) {
2054  // We're not using the normalized form of the extension.
2055  // Normal form is lowercase, using most common of alternate
2056  // extensions (eg 'jpg' rather than 'JPEG').
2057 
2058  // Check for another file using the normalized form...
2059  $nt_lc = Title::makeTitle( NS_FILE, "{$partname}.{$normalizedExtension}" );
2060  $file_lc = $localRepo->newFile( $nt_lc );
2061 
2062  if ( $file_lc->exists() ) {
2063  return [
2064  'warning' => 'exists-normalized',
2065  'file' => $file,
2066  'normalizedFile' => $file_lc
2067  ];
2068  }
2069  }
2070 
2071  // Check for files with the same name but a different extension
2072  $similarFiles = $localRepo->findFilesByPrefix( "{$partname}.", 1 );
2073  if ( count( $similarFiles ) ) {
2074  return [
2075  'warning' => 'exists-normalized',
2076  'file' => $file,
2077  'normalizedFile' => $similarFiles[0],
2078  ];
2079  }
2080 
2081  if ( self::isThumbName( $file->getName() ) ) {
2082  # Check for filenames like 50px- or 180px-, these are mostly thumbnails
2083  $nt_thb = Title::newFromText(
2084  substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension,
2085  NS_FILE
2086  );
2087  $file_thb = $localRepo->newFile( $nt_thb );
2088  if ( $file_thb->exists() ) {
2089  return [
2090  'warning' => 'thumb',
2091  'file' => $file,
2092  'thumbFile' => $file_thb
2093  ];
2094  } else {
2095  // File does not exist, but we just don't like the name
2096  return [
2097  'warning' => 'thumb-name',
2098  'file' => $file,
2099  'thumbFile' => $file_thb
2100  ];
2101  }
2102  }
2103 
2104  foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2105  if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
2106  return [
2107  'warning' => 'bad-prefix',
2108  'file' => $file,
2109  'prefix' => $prefix
2110  ];
2111  }
2112  }
2113 
2114  return false;
2115  }
2116 
2122  public static function isThumbName( $filename ) {
2123  $n = strrpos( $filename, '.' );
2124  $partname = $n ? substr( $filename, 0, $n ) : $filename;
2125 
2126  return (
2127  substr( $partname, 3, 3 ) == 'px-' ||
2128  substr( $partname, 2, 3 ) == 'px-'
2129  ) &&
2130  preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
2131  }
2132 
2138  public static function getFilenamePrefixBlacklist() {
2139  $list = [];
2140  $message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage();
2141  if ( !$message->isDisabled() ) {
2142  $lines = explode( "\n", $message->plain() );
2143  foreach ( $lines as $line ) {
2144  // Remove comment lines
2145  $comment = substr( trim( $line ), 0, 1 );
2146  if ( $comment == '#' || $comment == '' ) {
2147  continue;
2148  }
2149  // Remove additional comments after a prefix
2150  $comment = strpos( $line, '#' );
2151  if ( $comment > 0 ) {
2152  $line = substr( $line, 0, $comment - 1 );
2153  }
2154  $list[] = trim( $line );
2155  }
2156  }
2157 
2158  return $list;
2159  }
2160 
2172  public function getImageInfo( $result ) {
2173  $localFile = $this->getLocalFile();
2174  $stashFile = $this->getStashFile();
2175  // Calling a different API module depending on whether the file was stashed is less than optimal.
2176  // In fact, calling API modules here at all is less than optimal. Maybe it should be refactored.
2177  if ( $stashFile ) {
2179  $info = ApiQueryStashImageInfo::getInfo( $stashFile, array_fill_keys( $imParam, true ), $result );
2180  } else {
2182  $info = ApiQueryImageInfo::getInfo( $localFile, array_fill_keys( $imParam, true ), $result );
2183  }
2184 
2185  return $info;
2186  }
2187 
2192  public function convertVerifyErrorToStatus( $error ) {
2193  $code = $error['status'];
2194  unset( $code['status'] );
2195 
2196  return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
2197  }
2198 
2206  public static function getMaxUploadSize( $forType = null ) {
2207  global $wgMaxUploadSize;
2208 
2209  if ( is_array( $wgMaxUploadSize ) ) {
2210  if ( $forType !== null && isset( $wgMaxUploadSize[$forType] ) ) {
2211  return $wgMaxUploadSize[$forType];
2212  } else {
2213  return $wgMaxUploadSize['*'];
2214  }
2215  } else {
2216  return intval( $wgMaxUploadSize );
2217  }
2218  }
2219 
2227  public static function getMaxPhpUploadSize() {
2228  $phpMaxFileSize = wfShorthandToInteger(
2229  ini_get( 'upload_max_filesize' ),
2230  PHP_INT_MAX
2231  );
2232  $phpMaxPostSize = wfShorthandToInteger(
2233  ini_get( 'post_max_size' ),
2234  PHP_INT_MAX
2235  ) ?: PHP_INT_MAX;
2236  return min( $phpMaxFileSize, $phpMaxPostSize );
2237  }
2238 
2248  public static function getSessionStatus( UserIdentity $user, $statusKey ) {
2249  $store = self::getUploadSessionStore();
2250  $key = self::getUploadSessionKey( $store, $user, $statusKey );
2251 
2252  return $store->get( $key );
2253  }
2254 
2267  public static function setSessionStatus( UserIdentity $user, $statusKey, $value ) {
2268  $store = self::getUploadSessionStore();
2269  $key = self::getUploadSessionKey( $store, $user, $statusKey );
2270 
2271  if ( $value === false ) {
2272  $store->delete( $key );
2273  } else {
2274  $store->set( $key, $value, $store::TTL_DAY );
2275  }
2276  }
2277 
2284  private static function getUploadSessionKey( BagOStuff $store, UserIdentity $user, $statusKey ) {
2285  return $store->makeKey(
2286  'uploadstatus',
2287  $user->getId() ?: md5( $user->getName() ),
2288  $statusKey
2289  );
2290  }
2291 
2295  private static function getUploadSessionStore() {
2296  return ObjectCache::getInstance( 'db-replicated' );
2297  }
2298 }
UploadBase
UploadBase and subclasses are the backend of MediaWiki's file uploads.
Definition: UploadBase.php:47
UploadBase\getUploadSessionKey
static getUploadSessionKey(BagOStuff $store, UserIdentity $user, $statusKey)
Definition: UploadBase.php:2284
UploadBase\checkSvgExternalDTD
static checkSvgExternalDTD( $type, $publicId, $systemId)
Verify that DTD urls referenced are only the standard dtds.
Definition: UploadBase.php:1542
LocalFile\getSha1
getSha1()
Definition: LocalFile.php:2637
UploadBase\getRealPath
getRealPath( $srcPath)
Definition: UploadBase.php:332
File\wasDeleted
wasDeleted()
Was this file ever deleted from the wiki?
Definition: File.php:2074
$wgAllowJavaUploads
$wgAllowJavaUploads
Allow Java archive uploads.
Definition: DefaultSettings.php:1143
MediaWiki\Shell\Shell
Executes shell commands.
Definition: Shell.php:45
ApiQueryImageInfo\getPropertyNames
static getPropertyNames( $filter=[])
Returns all possible parameters to iiprop.
Definition: ApiQueryImageInfo.php:774
UploadBase\checkWarnings
checkWarnings( $user=null)
Check for non fatal problems with the file.
Definition: UploadBase.php:684
UploadBase\$mFileSize
int null $mFileSize
Definition: UploadBase.php:75
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:415
UploadBase\VERIFICATION_ERROR
const VERIFICATION_ERROR
Definition: UploadBase.php:110
ApiQueryStashImageInfo\getPropertyNames
static getPropertyNames( $filter=null)
Returns all possible parameters to siiprop.
Definition: ApiQueryStashImageInfo.php:110
UploadBase\$mBlackListedExtensions
string[] $mBlackListedExtensions
Definition: UploadBase.php:79
UploadBase\$mJavaDetected
bool null $mJavaDetected
Definition: UploadBase.php:81
$wgFileBlacklist
$wgFileBlacklist
Files with these extensions will never be allowed as uploads.
Definition: DefaultSettings.php:1110
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
UploadBase\isThrottled
static isThrottled( $user)
Returns true if the user has surpassed the upload rate limit, false otherwise.
Definition: UploadBase.php:173
UploadBase\makeWarningsSerializable
static makeWarningsSerializable( $warnings)
Convert the warnings array returned by checkWarnings() to something that can be serialized.
Definition: UploadBase.php:748
UploadBase\isThumbName
static isThumbName( $filename)
Helper function that checks whether the filename looks like a thumbnail.
Definition: UploadBase.php:2122
UploadBase\getSourceType
getSourceType()
Returns the upload type.
Definition: UploadBase.php:249
UploadBase\FILE_TOO_LARGE
const FILE_TOO_LARGE
Definition: UploadBase.php:112
UploadBase\MIN_LENGTH_PARTNAME
const MIN_LENGTH_PARTNAME
Definition: UploadBase.php:105
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:184
UploadBase\verifyUpload
verifyUpload()
Verify whether the upload is sane.
Definition: UploadBase.php:367
UploadBase\getSessionStatus
static getSessionStatus(UserIdentity $user, $statusKey)
Get the current status of a chunked upload (used for polling)
Definition: UploadBase.php:2248
UploadBase\postProcessUpload
postProcessUpload()
Perform extra steps after a successful upload.
Definition: UploadBase.php:980
UploadBase\isEnabled
static isEnabled()
Returns true if uploads are enabled.
Definition: UploadBase.php:143
UploadBase\checkFileExtensionList
static checkFileExtensionList( $ext, $list)
Perform case-insensitive match against a list of file extensions.
Definition: UploadBase.php:1254
UploadBase\getTitle
getTitle()
Returns the title of the file to be uploaded.
Definition: UploadBase.php:989
$wgMaxUploadSize
$wgMaxUploadSize
Max size for uploads, in bytes.
Definition: DefaultSettings.php:1028
UploadBase\$mTitle
Title bool $mTitle
Definition: UploadBase.php:63
UploadBase\getMaxPhpUploadSize
static getMaxPhpUploadSize()
Get the PHP maximum uploaded file size, based on ini settings.
Definition: UploadBase.php:2227
UploadBase\cleanupTempFile
cleanupTempFile()
If we've modified the upload file we need to manually remove it on exit to clean up.
Definition: UploadBase.php:1206
UploadBase\initializeFromRequest
initializeFromRequest(&$request)
Initialize from a WebRequest.
UploadBase\getLocalFile
getLocalFile()
Return the local file and initializes if necessary.
Definition: UploadBase.php:1127
UploadBase\checkBadFileName
checkBadFileName( $filename, $desiredFileName)
Check whether the resulting filename is different from the desired one, but ignore things like ucfirs...
Definition: UploadBase.php:773
$wgFileExtensions
$wgFileExtensions
This is the list of preferred extensions for uploading files.
Definition: DefaultSettings.php:1103
UploadBase\$mFinalExtension
string null $mFinalExtension
Definition: UploadBase.php:69
LocalFile\getHistory
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1608
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
UploadStashFile
Definition: UploadStashFile.php:24
UploadBase\getFilenamePrefixBlacklist
static getFilenamePrefixBlacklist()
Get a list of blacklisted filename prefixes from [[MediaWiki:Filename-prefix-blacklist]].
Definition: UploadBase.php:2138
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:86
$wgMimeTypeBlacklist
$wgMimeTypeBlacklist
Files with these MIME types will never be allowed as uploads if $wgVerifyMimeType is enabled.
Definition: DefaultSettings.php:1125
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1182
$wgVerifyMimeTypeIE
$wgVerifyMimeTypeIE
Determines whether extra checks for IE type detection should be applied.
Definition: DefaultSettings.php:1563
UploadBase\$uploadHandlers
static string[] $uploadHandlers
Upload handlers.
Definition: UploadBase.php:178
UploadBase\$tempFileObj
TempFSFile null $tempFileObj
Wrapper to handle deleting the temp file.
Definition: UploadBase.php:53
UploadBase\HOOK_ABORTED
const HOOK_ABORTED
Definition: UploadBase.php:111
UploadBase\checkAgainstExistingDupes
checkAgainstExistingDupes( $hash, $ignoreLocalDupes)
Definition: UploadBase.php:873
MediaWiki\User\UserIdentity\getId
getId( $wikiId=self::LOCAL)
UploadBase\OK
const OK
Definition: UploadBase.php:103
UploadBase\stripXmlNamespace
stripXmlNamespace( $name)
Definition: UploadBase.php:1864
UploadBase\$mFilteredName
string null $mFilteredName
Definition: UploadBase.php:67
UploadBase\validateName
validateName()
Verify that the name is valid and, if necessary, that we can overwrite.
Definition: UploadBase.php:416
$wgStrictFileExtensions
$wgStrictFileExtensions
If this is turned off, users may override the warning for files not covered by $wgFileExtensions.
Definition: DefaultSettings.php:1158
ApiQueryImageInfo\getInfo
static getInfo( $file, $prop, $result, $thumbParams=null, $opts=false)
Get result information for an image revision.
Definition: ApiQueryImageInfo.php:410
AV_SCAN_ABORTED
const AV_SCAN_ABORTED
Definition: Defines.php:97
UploadBase\isValidRequest
static isValidRequest( $request)
Check whether a request if valid for this handler.
Definition: UploadBase.php:232
$wgLang
$wgLang
Definition: Setup.php:798
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
UploadBase\EMPTY_FILE
const EMPTY_FILE
Definition: UploadBase.php:104
UploadBase\checkFileSize
checkFileSize( $fileSize)
Definition: UploadBase.php:814
UploadBase\runUploadStashFileHook
runUploadStashFileHook(User $user)
Definition: UploadBase.php:1176
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:245
UploadBase\checkSvgScriptCallback
checkSvgScriptCallback( $element, $attribs, $data=null)
Definition: UploadBase.php:1569
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:66
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1088
MWException
MediaWiki exception.
Definition: MWException.php:29
UploadBase\performUpload
performUpload( $comment, $pageText, $watch, $user, $tags=[], ?string $watchlistExpiry=null)
Really perform the upload.
Definition: UploadBase.php:932
wfStripIllegalFilenameChars
wfStripIllegalFilenameChars( $name)
Replace all invalid characters with '-'.
Definition: GlobalFunctions.php:2370
$wgUploadSizeWarning
$wgUploadSizeWarning
Warn if uploaded files are larger than this (in bytes), or false to disable.
Definition: DefaultSettings.php:1171
$wgAntivirusRequired
$wgAntivirusRequired
Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected.
Definition: DefaultSettings.php:1548
UploadBase\verifyExtension
static verifyExtension( $mime, $extension)
Checks if the MIME type of the uploaded file matches the file extension.
Definition: UploadBase.php:1265
UploadBase\$mTempPath
string null $mTempPath
Local file system path to the file to upload (or a local copy)
Definition: UploadBase.php:51
UploadBase\checkLocalFileWasDeleted
checkLocalFileWasDeleted(LocalFile $localFile)
Definition: UploadBase.php:863
FileBackend\isStoragePath
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
Definition: FileBackend.php:1525
UploadBase\WINDOWS_NONASCII_FILENAME
const WINDOWS_NONASCII_FILENAME
Definition: UploadBase.php:113
UploadBase\verifyMimeType
verifyMimeType( $mime)
Verify the MIME type.
Definition: UploadBase.php:446
$matches
$matches
Definition: NoLocalSettings.php:24
UploadBase\verifyFile
verifyFile()
Verifies that it's ok to include the uploaded file.
Definition: UploadBase.php:482
UploadBase\tryStashFile
tryStashFile(User $user, $isPartial=false)
Like stashFile(), but respects extensions' wishes to prevent the stashing.
Definition: UploadBase.php:1157
FSFile\getSha1Base36FromPath
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
Definition: FSFile.php:225
UploadBase\getVerificationErrorCode
getVerificationErrorCode( $error)
Definition: UploadBase.php:120
UploadBase\getImageInfo
getImageInfo( $result)
Gets image info about the file just uploaded.
Definition: UploadBase.php:2172
$wgAntivirusSetup
$wgAntivirusSetup
Configuration for different virus scanners.
Definition: DefaultSettings.php:1530
MediaWiki
A helper class for throttling authentication attempts.
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:74
AV_NO_VIRUS
const AV_NO_VIRUS
Definition: Defines.php:95
UploadBase\getStashFile
getStashFile()
Definition: UploadBase.php:1141
UploadBase\zipEntryCallback
zipEntryCallback( $entry)
Callback for ZipDirectoryReader to detect Java class files.
Definition: UploadBase.php:606
UploadBase\detectVirus
static detectVirus( $file)
Generic wrapper function for a virus scanner program.
Definition: UploadBase.php:1881
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:38
MWFileProps
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
UploadBase\setTempFile
setTempFile( $tempPath, $fileSize=null)
Definition: UploadBase.php:281
UploadBase\getTempPath
getTempPath()
Definition: UploadBase.php:1214
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:680
UploadBase\$safeXmlEncodings
static $safeXmlEncodings
Definition: UploadBase.php:85
MediaWiki\Permissions\Authority\authorizeWrite
authorizeWrite(string $action, PageIdentity $target, PermissionStatus $status=null)
Authorize write access.
UploadBase\$mTitleError
int $mTitleError
Definition: UploadBase.php:65
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
UploadBase\SUCCESS
const SUCCESS
Definition: UploadBase.php:102
LocalFile
Class to represent a local file in the wiki's own database.
Definition: LocalFile.php:63
UploadBase\checkFileExtension
static checkFileExtension( $ext, $list)
Perform case-insensitive match against a list of file extensions.
Definition: UploadBase.php:1242
UploadBase\setSessionStatus
static setSessionStatus(UserIdentity $user, $statusKey, $value)
Set the current status of a chunked upload (used for polling)
Definition: UploadBase.php:2267
$wgSVGMetadataCutoff
$wgSVGMetadataCutoff
Don't read SVG metadata beyond this point.
Definition: DefaultSettings.php:1376
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
UploadBase\detectScript
static detectScript( $file, $mime, $extension)
Heuristic for detecting files that could contain JavaScript instructions or things that may look like...
Definition: UploadBase.php:1318
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:706
UploadBase\verifyTitlePermissions
verifyTitlePermissions(Authority $performer)
Check whether the user can edit, upload and create the image.
Definition: UploadBase.php:650
Message\sizeParam
static sizeParam( $size)
Definition: Message.php:1180
UploadBase\splitXmlNamespace
static splitXmlNamespace( $element)
Divide the element name passed by the xml parser to the callback into URI and prifix.
Definition: UploadBase.php:1851
UploadBase\__construct
__construct()
Definition: UploadBase.php:239
UploadBase\isEmptyFile
isEmptyFile()
Return true if the file is empty.
Definition: UploadBase.php:307
ArchivedFile
Class representing a row of the 'filearchive' table.
Definition: ArchivedFile.php:35
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
UploadBase\detectScriptInSvg
detectScriptInSvg( $filename, $partial)
Definition: UploadBase.php:1490
BagOStuff\makeKey
makeKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
UploadBase\$mLocalFile
LocalFile null $mLocalFile
Definition: UploadBase.php:71
$line
$line
Definition: mcc.php:119
UploadBase\getUploadSessionStore
static getUploadSessionStore()
Definition: UploadBase.php:2295
TempFSFile
This class is used to hold the location and do limited manipulation of files stored temporarily (this...
Definition: TempFSFile.php:35
UploadBase\checkLocalFileExists
checkLocalFileExists(LocalFile $localFile, $hash)
Definition: UploadBase.php:839
UploadBase\getExistsWarning
static getExistsWarning( $file)
Helper function that does various existence checks for a file.
Definition: UploadBase.php:2033
XmlTypeCheck
Definition: XmlTypeCheck.php:28
AV_SCAN_FAILED
const AV_SCAN_FAILED
Definition: Defines.php:98
UploadBase\isAllowed
static isAllowed(Authority $performer)
Returns true if the user can use this upload module or else a string identifying the missing permissi...
Definition: UploadBase.php:157
UploadBase\FILENAME_TOO_LONG
const FILENAME_TOO_LONG
Definition: UploadBase.php:114
ZipDirectoryReader\read
static read( $fileName, $callback, $options=[])
Read a ZIP file and call a function for each file discovered in it.
Definition: ZipDirectoryReader.php:88
UploadBase\verifyPartialFile
verifyPartialFile()
A verification routine suitable for partial files.
Definition: UploadBase.php:545
UploadBase\$mSVGNSError
string null $mSVGNSError
Definition: UploadBase.php:83
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
wfIniGetBool
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
Definition: GlobalFunctions.php:1871
File\DELETE_SOURCE
const DELETE_SOURCE
Definition: File.php:83
$lines
if(!file_exists( $CREDITS)) $lines
Definition: updateCredits.php:45
UploadBase\OVERWRITE_EXISTING_FILE
const OVERWRITE_EXISTING_FILE
Definition: UploadBase.php:107
UploadBase\ILLEGAL_FILENAME
const ILLEGAL_FILENAME
Definition: UploadBase.php:106
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
UploadBase\getMaxUploadSize
static getMaxUploadSize( $forType=null)
Get MediaWiki's maximum uploaded file size for given type of upload, based on $wgMaxUploadSize.
Definition: UploadBase.php:2206
UploadBase\createFromRequest
static createFromRequest(&$request, $type=null)
Create a form of UploadBase depending on wpSourceType and initializes it.
Definition: UploadBase.php:187
UploadBase\convertVerifyErrorToStatus
convertVerifyErrorToStatus( $error)
Definition: UploadBase.php:2192
UploadBase\initializePathInfo
initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile=false)
Definition: UploadBase.php:260
wfShorthandToInteger
wfShorthandToInteger( $string='', $default=-1)
Converts shorthand byte notation to integer form.
Definition: GlobalFunctions.php:2444
Title
Represents a title within MediaWiki.
Definition: Title.php:49
$command
$command
Definition: mcc.php:125
Sanitizer\normalizeCss
static normalizeCss( $value)
Normalize CSS into a format we can easily search for hostile input.
Definition: Sanitizer.php:562
UploadBase\splitExtensions
static splitExtensions( $filename)
Split a file into a base name and all dot-delimited 'extensions' on the end.
Definition: UploadBase.php:1227
MediaWiki\Permissions\Authority\isAllowed
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
UploadBase\$mSourceType
string null $mSourceType
Definition: UploadBase.php:61
$wgDisableUploadScriptChecks
$wgDisableUploadScriptChecks
Setting this to true will disable the upload system's checks for HTML/JavaScript.
Definition: DefaultSettings.php:1166
UploadBase\getTempFileSha1Base36
getTempFileSha1Base36()
Get the base 36 SHA1 of the file.
Definition: UploadBase.php:324
UploadBase\$mDestName
string null $mDestName
Definition: UploadBase.php:57
Title\capitalize
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition: Title.php:3376
UploadBase\checkUnwantedFileExtensions
checkUnwantedFileExtensions( $fileExtension)
Definition: UploadBase.php:792
UploadBase\checkAgainstArchiveDupes
checkAgainstArchiveDupes( $hash, Authority $performer)
Definition: UploadBase.php:899
UploadBase\checkSvgPICallback
static checkSvgPICallback( $target, $data)
Callback to filter SVG Processing Instructions.
Definition: UploadBase.php:1522
$path
$path
Definition: NoLocalSettings.php:25
UploadBase\$mRemoveTempFile
bool null $mRemoveTempFile
Definition: UploadBase.php:59
MediaHandler\getHandler
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
Definition: MediaHandler.php:53
$wgVerifyMimeType
$wgVerifyMimeType
Determines if the MIME type of uploaded files should be checked.
Definition: DefaultSettings.php:1553
UploadBase\checkOverwrite
checkOverwrite(Authority $performer)
Check if there's an overwrite conflict and, if so, if restrictions forbid this user from performing t...
Definition: UploadBase.php:1975
FileRepo\isVirtualUrl
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:290
UploadBase\fetchFile
fetchFile()
Fetch the file.
Definition: UploadBase.php:299
$wgEnableUploads
$wgEnableUploads
Allow users to upload files.
Definition: DefaultSettings.php:515
$ext
if(!is_readable( $file)) $ext
Definition: router.php:48
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:70
UploadBase\$mStashFile
UploadStashFile null $mStashFile
Definition: UploadBase.php:73
Sanitizer\decodeCharReferences
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
Definition: Sanitizer.php:1228
$mime
$mime
Definition: router.php:60
NS_FILE
const NS_FILE
Definition: Defines.php:70
$wgAntivirus
$wgAntivirus
Internal name of virus scanner.
Definition: DefaultSettings.php:1494
UploadBase\verifyPermissions
verifyPermissions(Authority $performer)
Alias for verifyTitlePermissions.
Definition: UploadBase.php:635
$wgOut
$wgOut
Definition: Setup.php:803
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
UploadBase\getFileSize
getFileSize()
Return the file size.
Definition: UploadBase.php:315
LocalFile\exists
exists()
canRender inherited
Definition: LocalFile.php:1380
$wgCheckFileExtensions
$wgCheckFileExtensions
This is a flag to determine whether or not to check file extensions on upload.
Definition: DefaultSettings.php:1150
UploadBase\$mDesiredDestName
string null $mDesiredDestName
Definition: UploadBase.php:55
UploadBase\$mFileProps
array null $mFileProps
Definition: UploadBase.php:77
UploadStashException
@newable
Definition: UploadStashException.php:27
wfShellExecWithStderr
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
Definition: GlobalFunctions.php:1989
UploadBase\doStashFile
doStashFile(User $user=null)
Implementation for stashFile() and tryStashFile().
Definition: UploadBase.php:1193
UploadBase\checkXMLEncodingMissmatch
static checkXMLEncodingMissmatch( $file)
Check a whitelist of xml encodings that are known not to be interpreted differently by the server's x...
Definition: UploadBase.php:1432
UploadBase\FILETYPE_MISSING
const FILETYPE_MISSING
Definition: UploadBase.php:108
$type
$type
Definition: testCompression.php:52
UploadBase\FILETYPE_BADTYPE
const FILETYPE_BADTYPE
Definition: UploadBase.php:109
UploadBase\checkCssFragment
static checkCssFragment( $value)
Check a block of CSS or CSS fragment for anything that looks like it is bringing in remote code.
Definition: UploadBase.php:1802