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