MediaWiki  master
UploadBase.php
Go to the documentation of this file.
1 <?php
27 
42 abstract class UploadBase {
44  protected $mTempPath;
46  protected $tempFileObj;
48  protected $mDesiredDestName;
50  protected $mDestName;
52  protected $mRemoveTempFile;
54  protected $mSourceType;
56  protected $mTitle = false;
58  protected $mTitleError = 0;
60  protected $mFilteredName;
62  protected $mFinalExtension;
64  protected $mLocalFile;
66  protected $mStashFile;
68  protected $mFileSize;
70  protected $mFileProps;
74  protected $mJavaDetected;
76  protected $mSVGNSError;
77 
78  protected static $safeXmlEncodings = [
79  'UTF-8',
80  'ISO-8859-1',
81  'ISO-8859-2',
82  'UTF-16',
83  'UTF-32',
84  'WINDOWS-1250',
85  'WINDOWS-1251',
86  'WINDOWS-1252',
87  'WINDOWS-1253',
88  'WINDOWS-1254',
89  'WINDOWS-1255',
90  'WINDOWS-1256',
91  'WINDOWS-1257',
92  'WINDOWS-1258',
93  ];
94 
95  const SUCCESS = 0;
96  const OK = 0;
97  const EMPTY_FILE = 3;
99  const ILLEGAL_FILENAME = 5;
100  const OVERWRITE_EXISTING_FILE = 7; # Not used anymore; handled by verifyTitlePermissions()
101  const FILETYPE_MISSING = 8;
102  const FILETYPE_BADTYPE = 9;
103  const VERIFICATION_ERROR = 10;
104  const HOOK_ABORTED = 11;
105  const FILE_TOO_LARGE = 12;
107  const FILENAME_TOO_LONG = 14;
108 
113  public function getVerificationErrorCode( $error ) {
114  $code_to_status = [
115  self::EMPTY_FILE => 'empty-file',
116  self::FILE_TOO_LARGE => 'file-too-large',
117  self::FILETYPE_MISSING => 'filetype-missing',
118  self::FILETYPE_BADTYPE => 'filetype-banned',
119  self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
120  self::ILLEGAL_FILENAME => 'illegal-filename',
121  self::OVERWRITE_EXISTING_FILE => 'overwrite',
122  self::VERIFICATION_ERROR => 'verification-error',
123  self::HOOK_ABORTED => 'hookaborted',
124  self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
125  self::FILENAME_TOO_LONG => 'filename-toolong',
126  ];
127  return $code_to_status[$error] ?? 'unknown-error';
128  }
129 
135  public static function isEnabled() {
136  global $wgEnableUploads;
137 
138  return $wgEnableUploads && wfIniGetBool( 'file_uploads' );
139  }
140 
149  public static function isAllowed( UserIdentity $user ) {
150  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
151  foreach ( [ 'upload', 'edit' ] as $permission ) {
152  if ( !$permissionManager->userHasRight( $user, $permission ) ) {
153  return $permission;
154  }
155  }
156 
157  return true;
158  }
159 
166  public static function isThrottled( $user ) {
167  return $user->pingLimiter( 'upload' );
168  }
169 
170  // Upload handlers. Should probably just be a global.
171  private static $uploadHandlers = [ 'Stash', 'File', 'Url' ];
172 
180  public static function createFromRequest( &$request, $type = null ) {
181  $type = $type ?: $request->getVal( 'wpSourceType', 'File' );
182 
183  if ( !$type ) {
184  return null;
185  }
186 
187  // Get the upload class
188  $type = ucfirst( $type );
189 
190  // Give hooks the chance to handle this request
191  $className = null;
192  Hooks::run( 'UploadCreateFromRequest', [ $type, &$className ] );
193  if ( is_null( $className ) ) {
194  $className = 'UploadFrom' . $type;
195  wfDebug( __METHOD__ . ": class name: $className\n" );
196  if ( !in_array( $type, self::$uploadHandlers ) ) {
197  return null;
198  }
199  }
200 
201  // Check whether this upload class is enabled
202  if ( !call_user_func( [ $className, 'isEnabled' ] ) ) {
203  return null;
204  }
205 
206  // Check whether the request is valid
207  if ( !call_user_func( [ $className, 'isValidRequest' ], $request ) ) {
208  return null;
209  }
210 
212  $handler = new $className;
213 
214  $handler->initializeFromRequest( $request );
215 
216  return $handler;
217  }
218 
224  public static function isValidRequest( $request ) {
225  return false;
226  }
227 
228  public function __construct() {
229  }
230 
237  public function getSourceType() {
238  return null;
239  }
240 
249  public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
250  $this->mDesiredDestName = $name;
251  if ( FileBackend::isStoragePath( $tempPath ) ) {
252  throw new MWException( __METHOD__ . " given storage path `$tempPath`." );
253  }
254 
255  $this->setTempFile( $tempPath, $fileSize );
256  $this->mRemoveTempFile = $removeTempFile;
257  }
258 
264  abstract public function initializeFromRequest( &$request );
265 
270  protected function setTempFile( $tempPath, $fileSize = null ) {
271  $this->mTempPath = $tempPath;
272  $this->mFileSize = $fileSize ?: null;
273  if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
274  $this->tempFileObj = new TempFSFile( $this->mTempPath );
275  if ( !$fileSize ) {
276  $this->mFileSize = filesize( $this->mTempPath );
277  }
278  } else {
279  $this->tempFileObj = null;
280  }
281  }
282 
287  public function fetchFile() {
288  return Status::newGood();
289  }
290 
295  public function isEmptyFile() {
296  return empty( $this->mFileSize );
297  }
298 
303  public function getFileSize() {
304  return $this->mFileSize;
305  }
306 
311  public function getTempFileSha1Base36() {
312  return FSFile::getSha1Base36FromPath( $this->mTempPath );
313  }
314 
319  public function getRealPath( $srcPath ) {
320  $repo = RepoGroup::singleton()->getLocalRepo();
321  if ( FileRepo::isVirtualUrl( $srcPath ) ) {
325  $tmpFile = $repo->getLocalCopy( $srcPath );
326  if ( $tmpFile ) {
327  $tmpFile->bind( $this ); // keep alive with $this
328  }
329  $path = $tmpFile ? $tmpFile->getPath() : false;
330  } else {
331  $path = $srcPath;
332  }
333 
334  return $path;
335  }
336 
341  public function verifyUpload() {
345  if ( $this->isEmptyFile() ) {
346  return [ 'status' => self::EMPTY_FILE ];
347  }
348 
352  $maxSize = self::getMaxUploadSize( $this->getSourceType() );
353  if ( $this->mFileSize > $maxSize ) {
354  return [
355  'status' => self::FILE_TOO_LARGE,
356  'max' => $maxSize,
357  ];
358  }
359 
365  $verification = $this->verifyFile();
366  if ( $verification !== true ) {
367  return [
368  'status' => self::VERIFICATION_ERROR,
369  'details' => $verification
370  ];
371  }
372 
376  $result = $this->validateName();
377  if ( $result !== true ) {
378  return $result;
379  }
380 
381  return [ 'status' => self::OK ];
382  }
383 
390  public function validateName() {
391  $nt = $this->getTitle();
392  if ( is_null( $nt ) ) {
393  $result = [ 'status' => $this->mTitleError ];
394  if ( $this->mTitleError == self::ILLEGAL_FILENAME ) {
395  $result['filtered'] = $this->mFilteredName;
396  }
397  if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
398  $result['finalExt'] = $this->mFinalExtension;
399  if ( count( $this->mBlackListedExtensions ) ) {
400  $result['blacklistedExt'] = $this->mBlackListedExtensions;
401  }
402  }
403 
404  return $result;
405  }
406  $this->mDestName = $this->getLocalFile()->getName();
407 
408  return true;
409  }
410 
420  protected function verifyMimeType( $mime ) {
422  if ( $wgVerifyMimeType ) {
423  wfDebug( "mime: <$mime> extension: <{$this->mFinalExtension}>\n" );
424  global $wgMimeTypeBlacklist;
425  if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
426  return [ 'filetype-badmime', $mime ];
427  }
428 
429  if ( $wgVerifyMimeTypeIE ) {
430  # Check what Internet Explorer would detect
431  $fp = fopen( $this->mTempPath, 'rb' );
432  $chunk = fread( $fp, 256 );
433  fclose( $fp );
434 
435  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
436  $extMime = $magic->guessTypesForExtension( $this->mFinalExtension );
437  $ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
438  foreach ( $ieTypes as $ieType ) {
439  if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
440  return [ 'filetype-bad-ie-mime', $ieType ];
441  }
442  }
443  }
444  }
445 
446  return true;
447  }
448 
454  protected function verifyFile() {
456 
457  $status = $this->verifyPartialFile();
458  if ( $status !== true ) {
459  return $status;
460  }
461 
462  $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
463  $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
464  $mime = $this->mFileProps['mime'];
465 
466  if ( $wgVerifyMimeType ) {
467  # XXX: Missing extension will be caught by validateName() via getTitle()
468  if ( $this->mFinalExtension != '' && !$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
469  return [ 'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
470  }
471  }
472 
473  # check for htmlish code and javascript
474  if ( !$wgDisableUploadScriptChecks ) {
475  if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
476  $svgStatus = $this->detectScriptInSvg( $this->mTempPath, false );
477  if ( $svgStatus !== false ) {
478  return $svgStatus;
479  }
480  }
481  }
482 
483  $handler = MediaHandler::getHandler( $mime );
484  if ( $handler ) {
485  $handlerStatus = $handler->verifyUpload( $this->mTempPath );
486  if ( !$handlerStatus->isOK() ) {
487  $errors = $handlerStatus->getErrorsArray();
488 
489  return reset( $errors );
490  }
491  }
492 
493  $error = true;
494  Hooks::run( 'UploadVerifyFile', [ $this, $mime, &$error ] );
495  if ( $error !== true ) {
496  if ( !is_array( $error ) ) {
497  $error = [ $error ];
498  }
499  return $error;
500  }
501 
502  wfDebug( __METHOD__ . ": all clear; passing.\n" );
503 
504  return true;
505  }
506 
515  protected function verifyPartialFile() {
517 
518  # getTitle() sets some internal parameters like $this->mFinalExtension
519  $this->getTitle();
520 
521  $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
522  $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
523 
524  # check MIME type, if desired
525  $mime = $this->mFileProps['file-mime'];
526  $status = $this->verifyMimeType( $mime );
527  if ( $status !== true ) {
528  return $status;
529  }
530 
531  # check for htmlish code and javascript
532  if ( !$wgDisableUploadScriptChecks ) {
533  if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
534  return [ 'uploadscripted' ];
535  }
536  if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
537  $svgStatus = $this->detectScriptInSvg( $this->mTempPath, true );
538  if ( $svgStatus !== false ) {
539  return $svgStatus;
540  }
541  }
542  }
543 
544  # Check for Java applets, which if uploaded can bypass cross-site
545  # restrictions.
546  if ( !$wgAllowJavaUploads ) {
547  $this->mJavaDetected = false;
548  $zipStatus = ZipDirectoryReader::read( $this->mTempPath,
549  [ $this, 'zipEntryCallback' ] );
550  if ( !$zipStatus->isOK() ) {
551  $errors = $zipStatus->getErrorsArray();
552  $error = reset( $errors );
553  if ( $error[0] !== 'zip-wrong-format' ) {
554  return $error;
555  }
556  }
557  if ( $this->mJavaDetected ) {
558  return [ 'uploadjava' ];
559  }
560  }
561 
562  # Scan the uploaded file for viruses
563  $virus = $this->detectVirus( $this->mTempPath );
564  if ( $virus ) {
565  return [ 'uploadvirus', $virus ];
566  }
567 
568  return true;
569  }
570 
576  public function zipEntryCallback( $entry ) {
577  $names = [ $entry['name'] ];
578 
579  // If there is a null character, cut off the name at it, because JDK's
580  // ZIP_GetEntry() uses strcmp() if the name hashes match. If a file name
581  // were constructed which had ".class\0" followed by a string chosen to
582  // make the hash collide with the truncated name, that file could be
583  // returned in response to a request for the .class file.
584  $nullPos = strpos( $entry['name'], "\000" );
585  if ( $nullPos !== false ) {
586  $names[] = substr( $entry['name'], 0, $nullPos );
587  }
588 
589  // If there is a trailing slash in the file name, we have to strip it,
590  // because that's what ZIP_GetEntry() does.
591  if ( preg_grep( '!\.class/?$!', $names ) ) {
592  $this->mJavaDetected = true;
593  }
594  }
595 
605  public function verifyPermissions( $user ) {
606  return $this->verifyTitlePermissions( $user );
607  }
608 
620  public function verifyTitlePermissions( $user ) {
625  $nt = $this->getTitle();
626  if ( is_null( $nt ) ) {
627  return true;
628  }
629  $permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
630  $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user );
631  if ( !$nt->exists() ) {
632  $permErrorsCreate = $nt->getUserPermissionsErrors( 'create', $user );
633  } else {
634  $permErrorsCreate = [];
635  }
636  if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
637  $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
638  $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
639 
640  return $permErrors;
641  }
642 
643  $overwriteError = $this->checkOverwrite( $user );
644  if ( $overwriteError !== true ) {
645  return [ $overwriteError ];
646  }
647 
648  return true;
649  }
650 
658  public function checkWarnings() {
659  $warnings = [];
660 
661  $localFile = $this->getLocalFile();
662  $localFile->load( File::READ_LATEST );
663  $filename = $localFile->getName();
664  $hash = $this->getTempFileSha1Base36();
665 
666  $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
667  if ( $badFileName !== null ) {
668  $warnings['badfilename'] = $badFileName;
669  }
670 
671  $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( $this->mFinalExtension );
672  if ( $unwantedFileExtensionDetails !== null ) {
673  $warnings['filetype-unwanted-type'] = $unwantedFileExtensionDetails;
674  }
675 
676  $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
677  if ( $fileSizeWarnings ) {
678  $warnings = array_merge( $warnings, $fileSizeWarnings );
679  }
680 
681  $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
682  if ( $localFileExistsWarnings ) {
683  $warnings = array_merge( $warnings, $localFileExistsWarnings );
684  }
685 
686  if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
687  $warnings['was-deleted'] = $filename;
688  }
689 
690  // If a file with the same name exists locally then the local file has already been tested
691  // for duplication of content
692  $ignoreLocalDupes = isset( $warnings[ 'exists '] );
693  $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
694  if ( $dupes ) {
695  $warnings['duplicate'] = $dupes;
696  }
697 
698  $archivedDupes = $this->checkAgainstArchiveDupes( $hash );
699  if ( $archivedDupes !== null ) {
700  $warnings['duplicate-archive'] = $archivedDupes;
701  }
702 
703  return $warnings;
704  }
705 
717  public static function makeWarningsSerializable( $warnings ) {
718  array_walk_recursive( $warnings, function ( &$param, $key ) {
719  if ( $param instanceof File ) {
720  $param = [
721  'fileName' => $param->getName(),
722  'timestamp' => $param->getTimestamp()
723  ];
724  } elseif ( is_object( $param ) ) {
725  throw new InvalidArgumentException(
726  'UploadBase::makeWarningsSerializable: ' .
727  'Unexpected object of class ' . get_class( $param ) );
728  }
729  } );
730  return $warnings;
731  }
732 
742  private function checkBadFileName( $filename, $desiredFileName ) {
743  $comparableName = str_replace( ' ', '_', $desiredFileName );
744  $comparableName = Title::capitalize( $comparableName, NS_FILE );
745 
746  if ( $desiredFileName != $filename && $comparableName != $filename ) {
747  return $filename;
748  }
749 
750  return null;
751  }
752 
761  private function checkUnwantedFileExtensions( $fileExtension ) {
763 
764  if ( $wgCheckFileExtensions ) {
765  $extensions = array_unique( $wgFileExtensions );
766  if ( !$this->checkFileExtension( $fileExtension, $extensions ) ) {
767  return [
768  $fileExtension,
769  $wgLang->commaList( $extensions ),
770  count( $extensions )
771  ];
772  }
773  }
774 
775  return null;
776  }
777 
783  private function checkFileSize( $fileSize ) {
784  global $wgUploadSizeWarning;
785 
786  $warnings = [];
787 
788  if ( $wgUploadSizeWarning && ( $fileSize > $wgUploadSizeWarning ) ) {
789  $warnings['large-file'] = [ $wgUploadSizeWarning, $fileSize ];
790  }
791 
792  if ( $fileSize == 0 ) {
793  $warnings['empty-file'] = true;
794  }
795 
796  return $warnings;
797  }
798 
805  private function checkLocalFileExists( LocalFile $localFile, $hash ) {
806  $warnings = [];
807 
808  $exists = self::getExistsWarning( $localFile );
809  if ( $exists !== false ) {
810  $warnings['exists'] = $exists;
811 
812  // check if file is an exact duplicate of current file version
813  if ( $hash === $localFile->getSha1() ) {
814  $warnings['no-change'] = $localFile;
815  }
816 
817  // check if file is an exact duplicate of older versions of this file
818  $history = $localFile->getHistory();
819  foreach ( $history as $oldFile ) {
820  if ( $hash === $oldFile->getSha1() ) {
821  $warnings['duplicate-version'][] = $oldFile;
822  }
823  }
824  }
825 
826  return $warnings;
827  }
828 
829  private function checkLocalFileWasDeleted( LocalFile $localFile ) {
830  return $localFile->wasDeleted() && !$localFile->exists();
831  }
832 
839  private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
840  $dupes = RepoGroup::singleton()->findBySha1( $hash );
841  $title = $this->getTitle();
842  foreach ( $dupes as $key => $dupe ) {
843  if (
844  ( $dupe instanceof LocalFile ) &&
845  $ignoreLocalDupes &&
846  $title->equals( $dupe->getTitle() )
847  ) {
848  unset( $dupes[$key] );
849  }
850  }
851 
852  return $dupes;
853  }
854 
861  private function checkAgainstArchiveDupes( $hash ) {
862  $archivedFile = new ArchivedFile( null, 0, '', $hash );
863  if ( $archivedFile->getID() > 0 ) {
864  if ( $archivedFile->userCan( File::DELETED_FILE ) ) {
865  return $archivedFile->getName();
866  } else {
867  return '';
868  }
869  }
870 
871  return null;
872  }
873 
887  public function performUpload( $comment, $pageText, $watch, $user, $tags = [] ) {
888  $this->getLocalFile()->load( File::READ_LATEST );
889  $props = $this->mFileProps;
890 
891  $error = null;
892  Hooks::run( 'UploadVerifyUpload', [ $this, $user, $props, $comment, $pageText, &$error ] );
893  if ( $error ) {
894  if ( !is_array( $error ) ) {
895  $error = [ $error ];
896  }
897  return Status::newFatal( ...$error );
898  }
899 
900  $status = $this->getLocalFile()->upload(
901  $this->mTempPath,
902  $comment,
903  $pageText,
905  $props,
906  false,
907  $user,
908  $tags
909  );
910 
911  if ( $status->isGood() ) {
912  if ( $watch ) {
914  $this->getLocalFile()->getTitle(),
915  $user,
917  );
918  }
919  // Avoid PHP 7.1 warning of passing $this by reference
920  $uploadBase = $this;
921  Hooks::run( 'UploadComplete', [ &$uploadBase ] );
922 
923  $this->postProcessUpload();
924  }
925 
926  return $status;
927  }
928 
934  public function postProcessUpload() {
935  }
936 
943  public function getTitle() {
944  if ( $this->mTitle !== false ) {
945  return $this->mTitle;
946  }
947  if ( !is_string( $this->mDesiredDestName ) ) {
948  $this->mTitleError = self::ILLEGAL_FILENAME;
949  $this->mTitle = null;
950 
951  return $this->mTitle;
952  }
953  /* Assume that if a user specified File:Something.jpg, this is an error
954  * and that the namespace prefix needs to be stripped of.
955  */
956  $title = Title::newFromText( $this->mDesiredDestName );
957  if ( $title && $title->getNamespace() == NS_FILE ) {
958  $this->mFilteredName = $title->getDBkey();
959  } else {
960  $this->mFilteredName = $this->mDesiredDestName;
961  }
962 
963  # oi_archive_name is max 255 bytes, which include a timestamp and an
964  # exclamation mark, so restrict file name to 240 bytes.
965  if ( strlen( $this->mFilteredName ) > 240 ) {
966  $this->mTitleError = self::FILENAME_TOO_LONG;
967  $this->mTitle = null;
968 
969  return $this->mTitle;
970  }
971 
977  $this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName );
978  /* Normalize to title form before we do any further processing */
979  $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
980  if ( is_null( $nt ) ) {
981  $this->mTitleError = self::ILLEGAL_FILENAME;
982  $this->mTitle = null;
983 
984  return $this->mTitle;
985  }
986  $this->mFilteredName = $nt->getDBkey();
987 
992  list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName );
993 
994  if ( $ext !== [] ) {
995  $this->mFinalExtension = trim( end( $ext ) );
996  } else {
997  $this->mFinalExtension = '';
998 
999  # No extension, try guessing one
1000  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
1001  $mime = $magic->guessMimeType( $this->mTempPath );
1002  if ( $mime !== 'unknown/unknown' ) {
1003  # Get a space separated list of extensions
1004  $extList = $magic->getExtensionsForType( $mime );
1005  if ( $extList ) {
1006  # Set the extension to the canonical extension
1007  $this->mFinalExtension = strtok( $extList, ' ' );
1008 
1009  # Fix up the other variables
1010  $this->mFilteredName .= ".{$this->mFinalExtension}";
1011  $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
1013  }
1014  }
1015  }
1016 
1017  /* Don't allow users to override the blacklist (check file extension) */
1020 
1021  $blackListedExtensions = $this->checkFileExtensionList( $ext, $wgFileBlacklist );
1022 
1023  if ( $this->mFinalExtension == '' ) {
1024  $this->mTitleError = self::FILETYPE_MISSING;
1025  $this->mTitle = null;
1026 
1027  return $this->mTitle;
1028  } elseif ( $blackListedExtensions ||
1029  ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
1030  !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) )
1031  ) {
1032  $this->mBlackListedExtensions = $blackListedExtensions;
1033  $this->mTitleError = self::FILETYPE_BADTYPE;
1034  $this->mTitle = null;
1035 
1036  return $this->mTitle;
1037  }
1038 
1039  // Windows may be broken with special characters, see T3780
1040  if ( !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() )
1041  && !RepoGroup::singleton()->getLocalRepo()->backendSupportsUnicodePaths()
1042  ) {
1043  $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1044  $this->mTitle = null;
1045 
1046  return $this->mTitle;
1047  }
1048 
1049  # If there was more than one "extension", reassemble the base
1050  # filename to prevent bogus complaints about length
1051  if ( count( $ext ) > 1 ) {
1052  $iterations = count( $ext ) - 1;
1053  for ( $i = 0; $i < $iterations; $i++ ) {
1054  $partname .= '.' . $ext[$i];
1055  }
1056  }
1057 
1058  if ( strlen( $partname ) < 1 ) {
1059  $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1060  $this->mTitle = null;
1061 
1062  return $this->mTitle;
1063  }
1064 
1065  $this->mTitle = $nt;
1066 
1067  return $this->mTitle;
1068  }
1069 
1075  public function getLocalFile() {
1076  if ( is_null( $this->mLocalFile ) ) {
1077  $nt = $this->getTitle();
1078  $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt );
1079  }
1080 
1081  return $this->mLocalFile;
1082  }
1083 
1087  public function getStashFile() {
1088  return $this->mStashFile;
1089  }
1090 
1102  public function tryStashFile( User $user, $isPartial = false ) {
1103  if ( !$isPartial ) {
1104  $error = $this->runUploadStashFileHook( $user );
1105  if ( $error ) {
1106  return Status::newFatal( ...$error );
1107  }
1108  }
1109  try {
1110  $file = $this->doStashFile( $user );
1111  return Status::newGood( $file );
1112  } catch ( UploadStashException $e ) {
1113  return Status::newFatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() );
1114  }
1115  }
1116 
1121  protected function runUploadStashFileHook( User $user ) {
1122  $props = $this->mFileProps;
1123  $error = null;
1124  Hooks::run( 'UploadStashFile', [ $this, $user, $props, &$error ] );
1125  if ( $error && !is_array( $error ) ) {
1126  $error = [ $error ];
1127  }
1128  return $error;
1129  }
1130 
1150  public function stashFile( User $user = null ) {
1151  wfDeprecated( __METHOD__, '1.28' );
1152 
1153  return $this->doStashFile( $user );
1154  }
1155 
1162  protected function doStashFile( User $user = null ) {
1163  $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $user );
1164  $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
1165  $this->mStashFile = $file;
1166 
1167  return $file;
1168  }
1169 
1174  public function cleanupTempFile() {
1175  if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1176  // Delete when all relevant TempFSFile handles go out of scope
1177  wfDebug( __METHOD__ . ": Marked temporary file '{$this->mTempPath}' for removal\n" );
1178  $this->tempFileObj->autocollect();
1179  }
1180  }
1181 
1182  public function getTempPath() {
1183  return $this->mTempPath;
1184  }
1185 
1195  public static function splitExtensions( $filename ) {
1196  $bits = explode( '.', $filename );
1197  $basename = array_shift( $bits );
1198 
1199  return [ $basename, $bits ];
1200  }
1201 
1210  public static function checkFileExtension( $ext, $list ) {
1211  return in_array( strtolower( $ext ), $list );
1212  }
1213 
1222  public static function checkFileExtensionList( $ext, $list ) {
1223  return array_intersect( array_map( 'strtolower', $ext ), $list );
1224  }
1225 
1233  public static function verifyExtension( $mime, $extension ) {
1234  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
1235 
1236  if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) {
1237  if ( !$magic->isRecognizableExtension( $extension ) ) {
1238  wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
1239  "unrecognized extension '$extension', can't verify\n" );
1240 
1241  return true;
1242  } else {
1243  wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
1244  "recognized extension '$extension', so probably invalid file\n" );
1245 
1246  return false;
1247  }
1248  }
1249 
1250  $match = $magic->isMatchingExtension( $extension, $mime );
1251 
1252  if ( $match === null ) {
1253  if ( $magic->getTypesForExtension( $extension ) !== null ) {
1254  wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension\n" );
1255 
1256  return false;
1257  } else {
1258  wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
1259 
1260  return true;
1261  }
1262  } elseif ( $match === true ) {
1263  wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file\n" );
1264 
1266  return true;
1267  } else {
1268  wfDebug( __METHOD__
1269  . ": mime type $mime mismatches file extension $extension, rejecting file\n" );
1270 
1271  return false;
1272  }
1273  }
1274 
1286  public static function detectScript( $file, $mime, $extension ) {
1287  # ugly hack: for text files, always look at the entire file.
1288  # For binary field, just check the first K.
1289 
1290  $isText = strpos( $mime, 'text/' ) === 0;
1291  if ( $isText ) {
1292  $chunk = file_get_contents( $file );
1293  } else {
1294  $fp = fopen( $file, 'rb' );
1295  $chunk = fread( $fp, 1024 );
1296  fclose( $fp );
1297  }
1298 
1299  $chunk = strtolower( $chunk );
1300 
1301  if ( !$chunk ) {
1302  return false;
1303  }
1304 
1305  # decode from UTF-16 if needed (could be used for obfuscation).
1306  if ( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
1307  $enc = 'UTF-16BE';
1308  } elseif ( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
1309  $enc = 'UTF-16LE';
1310  } else {
1311  $enc = null;
1312  }
1313 
1314  if ( $enc !== null ) {
1315  $chunk = iconv( $enc, "ASCII//IGNORE", $chunk );
1316  }
1317 
1318  $chunk = trim( $chunk );
1319 
1321  wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff\n" );
1322 
1323  # check for HTML doctype
1324  if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1325  return true;
1326  }
1327 
1328  // Some browsers will interpret obscure xml encodings as UTF-8, while
1329  // PHP/expat will interpret the given encoding in the xml declaration (T49304)
1330  if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) {
1331  if ( self::checkXMLEncodingMissmatch( $file ) ) {
1332  return true;
1333  }
1334  }
1335 
1336  // Quick check for HTML heuristics in old IE and Safari.
1337  //
1338  // The exact heuristics IE uses are checked separately via verifyMimeType(), so we
1339  // don't need them all here as it can cause many false positives.
1340  //
1341  // Check for `<script` and such still to forbid script tags and embedded HTML in SVG:
1342  $tags = [
1343  '<body',
1344  '<head',
1345  '<html', # also in safari
1346  '<script', # also in safari
1347  ];
1348 
1349  foreach ( $tags as $tag ) {
1350  if ( strpos( $chunk, $tag ) !== false ) {
1351  wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
1352 
1353  return true;
1354  }
1355  }
1356 
1357  /*
1358  * look for JavaScript
1359  */
1360 
1361  # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1362  $chunk = Sanitizer::decodeCharReferences( $chunk );
1363 
1364  # look for script-types
1365  if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
1366  wfDebug( __METHOD__ . ": found script types\n" );
1367 
1368  return true;
1369  }
1370 
1371  # look for html-style script-urls
1372  if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1373  wfDebug( __METHOD__ . ": found html-style script urls\n" );
1374 
1375  return true;
1376  }
1377 
1378  # look for css-style script-urls
1379  if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1380  wfDebug( __METHOD__ . ": found css-style script urls\n" );
1381 
1382  return true;
1383  }
1384 
1385  wfDebug( __METHOD__ . ": no scripts found\n" );
1386 
1387  return false;
1388  }
1389 
1397  public static function checkXMLEncodingMissmatch( $file ) {
1398  global $wgSVGMetadataCutoff;
1399  $contents = file_get_contents( $file, false, null, 0, $wgSVGMetadataCutoff );
1400  $encodingRegex = '!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1401 
1402  if ( preg_match( "!<\?xml\b(.*?)\?>!si", $contents, $matches ) ) {
1403  if ( preg_match( $encodingRegex, $matches[1], $encMatch )
1404  && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1405  ) {
1406  wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'\n" );
1407 
1408  return true;
1409  }
1410  } elseif ( preg_match( "!<\?xml\b!si", $contents ) ) {
1411  // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
1412  // bytes. There shouldn't be a legitimate reason for this to happen.
1413  wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
1414 
1415  return true;
1416  } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
1417  // EBCDIC encoded XML
1418  wfDebug( __METHOD__ . ": EBCDIC Encoded XML\n" );
1419 
1420  return true;
1421  }
1422 
1423  // It's possible the file is encoded with multi-byte encoding, so re-encode attempt to
1424  // detect the encoding in case is specifies an encoding not whitelisted in self::$safeXmlEncodings
1425  $attemptEncodings = [ 'UTF-16', 'UTF-16BE', 'UTF-32', 'UTF-32BE' ];
1426  foreach ( $attemptEncodings as $encoding ) {
1427  Wikimedia\suppressWarnings();
1428  $str = iconv( $encoding, 'UTF-8', $contents );
1429  Wikimedia\restoreWarnings();
1430  if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
1431  if ( preg_match( $encodingRegex, $matches[1], $encMatch )
1432  && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1433  ) {
1434  wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'\n" );
1435 
1436  return true;
1437  }
1438  } elseif ( $str != '' && preg_match( "!<\?xml\b!si", $str ) ) {
1439  // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
1440  // bytes. There shouldn't be a legitimate reason for this to happen.
1441  wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
1442 
1443  return true;
1444  }
1445  }
1446 
1447  return false;
1448  }
1449 
1455  protected function detectScriptInSvg( $filename, $partial ) {
1456  $this->mSVGNSError = false;
1457  $check = new XmlTypeCheck(
1458  $filename,
1459  [ $this, 'checkSvgScriptCallback' ],
1460  true,
1461  [
1462  'processing_instruction_handler' => 'UploadBase::checkSvgPICallback',
1463  'external_dtd_handler' => 'UploadBase::checkSvgExternalDTD',
1464  ]
1465  );
1466  if ( $check->wellFormed !== true ) {
1467  // Invalid xml (T60553)
1468  // But only when non-partial (T67724)
1469  return $partial ? false : [ 'uploadinvalidxml' ];
1470  } elseif ( $check->filterMatch ) {
1471  if ( $this->mSVGNSError ) {
1472  return [ 'uploadscriptednamespace', $this->mSVGNSError ];
1473  }
1474 
1475  return $check->filterMatchType;
1476  }
1477 
1478  return false;
1479  }
1480 
1487  public static function checkSvgPICallback( $target, $data ) {
1488  // Don't allow external stylesheets (T59550)
1489  if ( preg_match( '/xml-stylesheet/i', $target ) ) {
1490  return [ 'upload-scripted-pi-callback' ];
1491  }
1492 
1493  return false;
1494  }
1495 
1507  public static function checkSvgExternalDTD( $type, $publicId, $systemId ) {
1508  // This doesn't include the XHTML+MathML+SVG doctype since we don't
1509  // allow XHTML anyways.
1510  $allowedDTDs = [
1511  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1512  'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1513  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1514  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1515  // https://phabricator.wikimedia.org/T168856
1516  'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1517  ];
1518  if ( $type !== 'PUBLIC'
1519  || !in_array( $systemId, $allowedDTDs )
1520  || strpos( $publicId, "-//W3C//" ) !== 0
1521  ) {
1522  return [ 'upload-scripted-dtd' ];
1523  }
1524  return false;
1525  }
1526 
1534  public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
1535  list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
1536 
1537  // We specifically don't include:
1538  // http://www.w3.org/1999/xhtml (T62771)
1539  static $validNamespaces = [
1540  '',
1541  'adobe:ns:meta/',
1542  'http://creativecommons.org/ns#',
1543  'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1544  'http://ns.adobe.com/adobeillustrator/10.0/',
1545  'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1546  'http://ns.adobe.com/extensibility/1.0/',
1547  'http://ns.adobe.com/flows/1.0/',
1548  'http://ns.adobe.com/illustrator/1.0/',
1549  'http://ns.adobe.com/imagereplacement/1.0/',
1550  'http://ns.adobe.com/pdf/1.3/',
1551  'http://ns.adobe.com/photoshop/1.0/',
1552  'http://ns.adobe.com/saveforweb/1.0/',
1553  'http://ns.adobe.com/variables/1.0/',
1554  'http://ns.adobe.com/xap/1.0/',
1555  'http://ns.adobe.com/xap/1.0/g/',
1556  'http://ns.adobe.com/xap/1.0/g/img/',
1557  'http://ns.adobe.com/xap/1.0/mm/',
1558  'http://ns.adobe.com/xap/1.0/rights/',
1559  'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1560  'http://ns.adobe.com/xap/1.0/stype/font#',
1561  'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1562  'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1563  'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1564  'http://ns.adobe.com/xap/1.0/t/pg/',
1565  'http://purl.org/dc/elements/1.1/',
1566  'http://purl.org/dc/elements/1.1',
1567  'http://schemas.microsoft.com/visio/2003/svgextensions/',
1568  'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1569  'http://taptrix.com/inkpad/svg_extensions',
1570  'http://web.resource.org/cc/',
1571  'http://www.freesoftware.fsf.org/bkchem/cdml',
1572  'http://www.inkscape.org/namespaces/inkscape',
1573  'http://www.opengis.net/gml',
1574  'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1575  'http://www.w3.org/2000/svg',
1576  'http://www.w3.org/tr/rec-rdf-syntax/',
1577  'http://www.w3.org/2000/01/rdf-schema#',
1578  ];
1579 
1580  // Inkscape mangles namespace definitions created by Adobe Illustrator.
1581  // This is nasty but harmless. (T144827)
1582  $isBuggyInkscape = preg_match( '/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1583 
1584  if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1585  wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file.\n" );
1587  $this->mSVGNSError = $namespace;
1588 
1589  return true;
1590  }
1591 
1592  /*
1593  * check for elements that can contain javascript
1594  */
1595  if ( $strippedElement == 'script' ) {
1596  wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
1597 
1598  return [ 'uploaded-script-svg', $strippedElement ];
1599  }
1600 
1601  # e.g., <svg xmlns="http://www.w3.org/2000/svg">
1602  # <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
1603  if ( $strippedElement == 'handler' ) {
1604  wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
1605 
1606  return [ 'uploaded-script-svg', $strippedElement ];
1607  }
1608 
1609  # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
1610  if ( $strippedElement == 'stylesheet' ) {
1611  wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
1612 
1613  return [ 'uploaded-script-svg', $strippedElement ];
1614  }
1615 
1616  # Block iframes, in case they pass the namespace check
1617  if ( $strippedElement == 'iframe' ) {
1618  wfDebug( __METHOD__ . ": iframe in uploaded file.\n" );
1619 
1620  return [ 'uploaded-script-svg', $strippedElement ];
1621  }
1622 
1623  # Check <style> css
1624  if ( $strippedElement == 'style'
1625  && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1626  ) {
1627  wfDebug( __METHOD__ . ": hostile css in style element.\n" );
1628  return [ 'uploaded-hostile-svg' ];
1629  }
1630 
1631  foreach ( $attribs as $attrib => $value ) {
1632  $stripped = $this->stripXmlNamespace( $attrib );
1633  $value = strtolower( $value );
1634 
1635  if ( substr( $stripped, 0, 2 ) == 'on' ) {
1636  wfDebug( __METHOD__
1637  . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
1638 
1639  return [ 'uploaded-event-handler-on-svg', $attrib, $value ];
1640  }
1641 
1642  # Do not allow relative links, or unsafe url schemas.
1643  # For <a> tags, only data:, http: and https: and same-document
1644  # fragment links are allowed. For all other tags, only data:
1645  # and fragment are allowed.
1646  if ( $stripped == 'href'
1647  && $value !== ''
1648  && strpos( $value, 'data:' ) !== 0
1649  && strpos( $value, '#' ) !== 0
1650  ) {
1651  if ( !( $strippedElement === 'a'
1652  && preg_match( '!^https?://!i', $value ) )
1653  ) {
1654  wfDebug( __METHOD__ . ": Found href attribute <$strippedElement "
1655  . "'$attrib'='$value' in uploaded file.\n" );
1656 
1657  return [ 'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1658  }
1659  }
1660 
1661  # only allow data: targets that should be safe. This prevents vectors like,
1662  # image/svg, text/xml, application/xml, and text/html, which can contain scripts
1663  if ( $stripped == 'href' && strncasecmp( 'data:', $value, 5 ) === 0 ) {
1664  // rfc2397 parameters. This is only slightly slower than (;[\w;]+)*.
1665  // phpcs:ignore Generic.Files.LineLength
1666  $parameters = '(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1667 
1668  if ( !preg_match( "!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1669  wfDebug( __METHOD__ . ": Found href to unwhitelisted data: uri "
1670  . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
1671  return [ 'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1672  }
1673  }
1674 
1675  # Change href with animate from (http://html5sec.org/#137).
1676  if ( $stripped === 'attributename'
1677  && $strippedElement === 'animate'
1678  && $this->stripXmlNamespace( $value ) == 'href'
1679  ) {
1680  wfDebug( __METHOD__ . ": Found animate that might be changing href using from "
1681  . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
1682 
1683  return [ 'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1684  }
1685 
1686  # use set/animate to add event-handler attribute to parent
1687  if ( ( $strippedElement == 'set' || $strippedElement == 'animate' )
1688  && $stripped == 'attributename'
1689  && substr( $value, 0, 2 ) == 'on'
1690  ) {
1691  wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with "
1692  . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
1693 
1694  return [ 'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1695  }
1696 
1697  # use set to add href attribute to parent element
1698  if ( $strippedElement == 'set'
1699  && $stripped == 'attributename'
1700  && strpos( $value, 'href' ) !== false
1701  ) {
1702  wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" );
1703 
1704  return [ 'uploaded-setting-href-svg' ];
1705  }
1706 
1707  # use set to add a remote / data / script target to an element
1708  if ( $strippedElement == 'set'
1709  && $stripped == 'to'
1710  && preg_match( '!(http|https|data|script):!sim', $value )
1711  ) {
1712  wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" );
1713 
1714  return [ 'uploaded-wrong-setting-svg', $value ];
1715  }
1716 
1717  # use handler attribute with remote / data / script
1718  if ( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
1719  wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script "
1720  . "'$attrib'='$value' in uploaded file.\n" );
1721 
1722  return [ 'uploaded-setting-handler-svg', $attrib, $value ];
1723  }
1724 
1725  # use CSS styles to bring in remote code
1726  if ( $stripped == 'style'
1727  && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1728  ) {
1729  wfDebug( __METHOD__ . ": Found svg setting a style with "
1730  . "remote url '$attrib'='$value' in uploaded file.\n" );
1731  return [ 'uploaded-remote-url-svg', $attrib, $value ];
1732  }
1733 
1734  # Several attributes can include css, css character escaping isn't allowed
1735  $cssAttrs = [ 'font', 'clip-path', 'fill', 'filter', 'marker',
1736  'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke' ];
1737  if ( in_array( $stripped, $cssAttrs )
1738  && self::checkCssFragment( $value )
1739  ) {
1740  wfDebug( __METHOD__ . ": Found svg setting a style with "
1741  . "remote url '$attrib'='$value' in uploaded file.\n" );
1742  return [ 'uploaded-remote-url-svg', $attrib, $value ];
1743  }
1744 
1745  # image filters can pull in url, which could be svg that executes scripts
1746  # Only allow url( "#foo" ). Do not allow url( http://example.com )
1747  if ( $strippedElement == 'image'
1748  && $stripped == 'filter'
1749  && preg_match( '!url\s*\(\s*["\']?[^#]!sim', $value )
1750  ) {
1751  wfDebug( __METHOD__ . ": Found image filter with url: "
1752  . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
1753 
1754  return [ 'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1755  }
1756  }
1757 
1758  return false; // No scripts detected
1759  }
1760 
1767  private static function checkCssFragment( $value ) {
1768  # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1769  if ( stripos( $value, '@import' ) !== false ) {
1770  return true;
1771  }
1772 
1773  # We allow @font-face to embed fonts with data: urls, so we snip the string
1774  # 'url' out so this case won't match when we check for urls below
1775  $pattern = '!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1776  $value = preg_replace( $pattern, '$1$2', $value );
1777 
1778  # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1779  # properties filter and accelerator don't seem to be useful for xss in SVG files.
1780  # Expression and -o-link don't seem to work either, but filtering them here in case.
1781  # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1782  # but not local ones such as url("#..., url('#..., url(#....
1783  if ( preg_match( '!expression
1784  | -o-link\s*:
1785  | -o-link-source\s*:
1786  | -o-replace\s*:!imx', $value ) ) {
1787  return true;
1788  }
1789 
1790  if ( preg_match_all(
1791  "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1792  $value,
1793  $matches
1794  ) !== 0
1795  ) {
1796  # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1797  foreach ( $matches[1] as $match ) {
1798  if ( !preg_match( "!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1799  return true;
1800  }
1801  }
1802  }
1803 
1804  if ( preg_match( '/[\000-\010\013\016-\037\177]/', $value ) ) {
1805  return true;
1806  }
1807 
1808  return false;
1809  }
1810 
1816  private static function splitXmlNamespace( $element ) {
1817  // 'http://www.w3.org/2000/svg:script' -> [ 'http://www.w3.org/2000/svg', 'script' ]
1818  $parts = explode( ':', strtolower( $element ) );
1819  $name = array_pop( $parts );
1820  $ns = implode( ':', $parts );
1821 
1822  return [ $ns, $name ];
1823  }
1824 
1829  private function stripXmlNamespace( $name ) {
1830  // 'http://www.w3.org/2000/svg:script' -> 'script'
1831  $parts = explode( ':', strtolower( $name ) );
1832 
1833  return array_pop( $parts );
1834  }
1835 
1846  public static function detectVirus( $file ) {
1848 
1849  if ( !$wgAntivirus ) {
1850  wfDebug( __METHOD__ . ": virus scanner disabled\n" );
1851 
1852  return null;
1853  }
1854 
1855  if ( !$wgAntivirusSetup[$wgAntivirus] ) {
1856  wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" );
1857  $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
1858  [ 'virus-badscanner', $wgAntivirus ] );
1859 
1860  return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
1861  }
1862 
1863  # look up scanner configuration
1864  $command = $wgAntivirusSetup[$wgAntivirus]['command'];
1865  $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]['codemap'];
1866  $msgPattern = $wgAntivirusSetup[$wgAntivirus]['messagepattern'] ?? null;
1867 
1868  if ( strpos( $command, "%f" ) === false ) {
1869  # simple pattern: append file to scan
1870  $command .= " " . Shell::escape( $file );
1871  } else {
1872  # complex pattern: replace "%f" with file to scan
1873  $command = str_replace( "%f", Shell::escape( $file ), $command );
1874  }
1875 
1876  wfDebug( __METHOD__ . ": running virus scan: $command \n" );
1877 
1878  # execute virus scanner
1879  $exitCode = false;
1880 
1881  # NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
1882  # that does not seem to be worth the pain.
1883  # Ask me (Duesentrieb) about it if it's ever needed.
1884  $output = wfShellExecWithStderr( $command, $exitCode );
1885 
1886  # map exit code to AV_xxx constants.
1887  $mappedCode = $exitCode;
1888  if ( $exitCodeMap ) {
1889  if ( isset( $exitCodeMap[$exitCode] ) ) {
1890  $mappedCode = $exitCodeMap[$exitCode];
1891  } elseif ( isset( $exitCodeMap["*"] ) ) {
1892  $mappedCode = $exitCodeMap["*"];
1893  }
1894  }
1895 
1896  /* NB: AV_NO_VIRUS is 0 but AV_SCAN_FAILED is false,
1897  * so we need the strict equalities === and thus can't use a switch here
1898  */
1899  if ( $mappedCode === AV_SCAN_FAILED ) {
1900  # scan failed (code was mapped to false by $exitCodeMap)
1901  wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
1902 
1903  $output = $wgAntivirusRequired
1904  ? wfMessage( 'virus-scanfailed', [ $exitCode ] )->text()
1905  : null;
1906  } elseif ( $mappedCode === AV_SCAN_ABORTED ) {
1907  # scan failed because filetype is unknown (probably imune)
1908  wfDebug( __METHOD__ . ": unsupported file type $file (code $exitCode).\n" );
1909  $output = null;
1910  } elseif ( $mappedCode === AV_NO_VIRUS ) {
1911  # no virus found
1912  wfDebug( __METHOD__ . ": file passed virus scan.\n" );
1913  $output = false;
1914  } else {
1915  $output = trim( $output );
1916 
1917  if ( !$output ) {
1918  $output = true; # if there's no output, return true
1919  } elseif ( $msgPattern ) {
1920  $groups = [];
1921  if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
1922  $output = $groups[1];
1923  }
1924  }
1925 
1926  wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" );
1927  }
1928 
1929  return $output;
1930  }
1931 
1940  private function checkOverwrite( $user ) {
1941  // First check whether the local file can be overwritten
1942  $file = $this->getLocalFile();
1943  $file->load( File::READ_LATEST );
1944  if ( $file->exists() ) {
1945  if ( !self::userCanReUpload( $user, $file ) ) {
1946  return [ 'fileexists-forbidden', $file->getName() ];
1947  } else {
1948  return true;
1949  }
1950  }
1951 
1952  /* Check shared conflicts: if the local file does not exist, but
1953  * wfFindFile finds a file, it exists in a shared repository.
1954  */
1955  $file = wfFindFile( $this->getTitle(), [ 'latest' => true ] );
1956  if ( $file && !MediaWikiServices::getInstance()
1957  ->getPermissionManager()
1958  ->userHasRight( $user, 'reupload-shared' )
1959  ) {
1960  return [ 'fileexists-shared-forbidden', $file->getName() ];
1961  }
1962 
1963  return true;
1964  }
1965 
1973  public static function userCanReUpload( User $user, File $img ) {
1974  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1975  if ( $permissionManager->userHasRight( $user, 'reupload' ) ) {
1976  return true; // non-conditional
1977  } elseif ( !$permissionManager->userHasRight( $user, 'reupload-own' ) ) {
1978  return false;
1979  }
1980 
1981  if ( !( $img instanceof LocalFile ) ) {
1982  return false;
1983  }
1984 
1985  $img->load();
1986 
1987  return $user->getId() == $img->getUser( 'id' );
1988  }
1989 
2001  public static function getExistsWarning( $file ) {
2002  if ( $file->exists() ) {
2003  return [ 'warning' => 'exists', 'file' => $file ];
2004  }
2005 
2006  if ( $file->getTitle()->getArticleID() ) {
2007  return [ 'warning' => 'page-exists', 'file' => $file ];
2008  }
2009 
2010  if ( strpos( $file->getName(), '.' ) == false ) {
2011  $partname = $file->getName();
2012  $extension = '';
2013  } else {
2014  $n = strrpos( $file->getName(), '.' );
2015  $extension = substr( $file->getName(), $n + 1 );
2016  $partname = substr( $file->getName(), 0, $n );
2017  }
2018  $normalizedExtension = File::normalizeExtension( $extension );
2019 
2020  if ( $normalizedExtension != $extension ) {
2021  // We're not using the normalized form of the extension.
2022  // Normal form is lowercase, using most common of alternate
2023  // extensions (eg 'jpg' rather than 'JPEG').
2024 
2025  // Check for another file using the normalized form...
2026  $nt_lc = Title::makeTitle( NS_FILE, "{$partname}.{$normalizedExtension}" );
2027  $file_lc = wfLocalFile( $nt_lc );
2028 
2029  if ( $file_lc->exists() ) {
2030  return [
2031  'warning' => 'exists-normalized',
2032  'file' => $file,
2033  'normalizedFile' => $file_lc
2034  ];
2035  }
2036  }
2037 
2038  // Check for files with the same name but a different extension
2039  $similarFiles = RepoGroup::singleton()->getLocalRepo()->findFilesByPrefix(
2040  "{$partname}.", 1 );
2041  if ( count( $similarFiles ) ) {
2042  return [
2043  'warning' => 'exists-normalized',
2044  'file' => $file,
2045  'normalizedFile' => $similarFiles[0],
2046  ];
2047  }
2048 
2049  if ( self::isThumbName( $file->getName() ) ) {
2050  # Check for filenames like 50px- or 180px-, these are mostly thumbnails
2051  $nt_thb = Title::newFromText(
2052  substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension,
2053  NS_FILE
2054  );
2055  $file_thb = wfLocalFile( $nt_thb );
2056  if ( $file_thb->exists() ) {
2057  return [
2058  'warning' => 'thumb',
2059  'file' => $file,
2060  'thumbFile' => $file_thb
2061  ];
2062  } else {
2063  // File does not exist, but we just don't like the name
2064  return [
2065  'warning' => 'thumb-name',
2066  'file' => $file,
2067  'thumbFile' => $file_thb
2068  ];
2069  }
2070  }
2071 
2072  foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2073  if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
2074  return [
2075  'warning' => 'bad-prefix',
2076  'file' => $file,
2077  'prefix' => $prefix
2078  ];
2079  }
2080  }
2081 
2082  return false;
2083  }
2084 
2090  public static function isThumbName( $filename ) {
2091  $n = strrpos( $filename, '.' );
2092  $partname = $n ? substr( $filename, 0, $n ) : $filename;
2093 
2094  return (
2095  substr( $partname, 3, 3 ) == 'px-' ||
2096  substr( $partname, 2, 3 ) == 'px-'
2097  ) &&
2098  preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
2099  }
2100 
2106  public static function getFilenamePrefixBlacklist() {
2107  $blacklist = [];
2108  $message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage();
2109  if ( !$message->isDisabled() ) {
2110  $lines = explode( "\n", $message->plain() );
2111  foreach ( $lines as $line ) {
2112  // Remove comment lines
2113  $comment = substr( trim( $line ), 0, 1 );
2114  if ( $comment == '#' || $comment == '' ) {
2115  continue;
2116  }
2117  // Remove additional comments after a prefix
2118  $comment = strpos( $line, '#' );
2119  if ( $comment > 0 ) {
2120  $line = substr( $line, 0, $comment - 1 );
2121  }
2122  $blacklist[] = trim( $line );
2123  }
2124  }
2125 
2126  return $blacklist;
2127  }
2128 
2140  public function getImageInfo( $result ) {
2141  $localFile = $this->getLocalFile();
2142  $stashFile = $this->getStashFile();
2143  // Calling a different API module depending on whether the file was stashed is less than optimal.
2144  // In fact, calling API modules here at all is less than optimal. Maybe it should be refactored.
2145  if ( $stashFile ) {
2147  $info = ApiQueryStashImageInfo::getInfo( $stashFile, array_flip( $imParam ), $result );
2148  } else {
2150  $info = ApiQueryImageInfo::getInfo( $localFile, array_flip( $imParam ), $result );
2151  }
2152 
2153  return $info;
2154  }
2155 
2160  public function convertVerifyErrorToStatus( $error ) {
2161  $code = $error['status'];
2162  unset( $code['status'] );
2163 
2164  return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
2165  }
2166 
2174  public static function getMaxUploadSize( $forType = null ) {
2175  global $wgMaxUploadSize;
2176 
2177  if ( is_array( $wgMaxUploadSize ) ) {
2178  if ( !is_null( $forType ) && isset( $wgMaxUploadSize[$forType] ) ) {
2179  return $wgMaxUploadSize[$forType];
2180  } else {
2181  return $wgMaxUploadSize['*'];
2182  }
2183  } else {
2184  return intval( $wgMaxUploadSize );
2185  }
2186  }
2187 
2195  public static function getMaxPhpUploadSize() {
2196  $phpMaxFileSize = wfShorthandToInteger(
2197  ini_get( 'upload_max_filesize' ),
2198  PHP_INT_MAX
2199  );
2200  $phpMaxPostSize = wfShorthandToInteger(
2201  ini_get( 'post_max_size' ),
2202  PHP_INT_MAX
2203  ) ?: PHP_INT_MAX;
2204  return min( $phpMaxFileSize, $phpMaxPostSize );
2205  }
2206 
2216  public static function getSessionStatus( User $user, $statusKey ) {
2217  $store = self::getUploadSessionStore();
2218  $key = self::getUploadSessionKey( $store, $user, $statusKey );
2219 
2220  return $store->get( $key );
2221  }
2222 
2235  public static function setSessionStatus( User $user, $statusKey, $value ) {
2236  $store = self::getUploadSessionStore();
2237  $key = self::getUploadSessionKey( $store, $user, $statusKey );
2238 
2239  if ( $value === false ) {
2240  $store->delete( $key );
2241  } else {
2242  $store->set( $key, $value, $store::TTL_DAY );
2243  }
2244  }
2245 
2252  private static function getUploadSessionKey( BagOStuff $store, User $user, $statusKey ) {
2253  return $store->makeKey(
2254  'uploadstatus',
2255  $user->getId() ?: md5( $user->getName() ),
2256  $statusKey
2257  );
2258  }
2259 
2263  private static function getUploadSessionStore() {
2264  return ObjectCache::getInstance( 'db-replicated' );
2265  }
2266 }
$wgStrictFileExtensions
If this is turned off, users may override the warning for files not covered by $wgFileExtensions.
checkOverwrite( $user)
Check if there&#39;s an overwrite conflict and, if so, if restrictions forbid this user from performing t...
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
detectScriptInSvg( $filename, $partial)
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
exists()
canRender inherited
Definition: LocalFile.php:969
const FILENAME_TOO_LONG
Definition: UploadBase.php:107
static getUploadSessionKey(BagOStuff $store, User $user, $statusKey)
$wgSVGMetadataCutoff
Don&#39;t read SVG metadata beyond this point.
$wgDisableUploadScriptChecks
Setting this to true will disable the upload system&#39;s checks for HTML/JavaScript. ...
static splitXmlNamespace( $element)
Divide the element name passed by the xml parser to the callback into URI and prifix.
static getPropertyNames( $filter=[])
Returns all possible parameters to iiprop.
tryStashFile(User $user, $isPartial=false)
Like stashFile(), but respects extensions&#39; wishes to prevent the stashing.
int $mTitleError
Definition: UploadBase.php:58
const SUCCESS
Definition: UploadBase.php:95
static createFromRequest(&$request, $type=null)
Create a form of UploadBase depending on wpSourceType and initializes it.
Definition: UploadBase.php:180
convertVerifyErrorToStatus( $error)
$command
Definition: cdb.php:65
const DELETE_SOURCE
Definition: File.php:76
string null $mSVGNSError
Definition: UploadBase.php:76
static getSessionStatus(User $user, $statusKey)
Get the current status of a chunked upload (used for polling)
const OVERWRITE_EXISTING_FILE
Definition: UploadBase.php:100
static $safeXmlEncodings
Definition: UploadBase.php:78
initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile=false)
Initialize the path information.
Definition: UploadBase.php:249
static getUploadSessionStore()
makeKey( $class,... $components)
Make a cache key, scoped to this instance&#39;s keyspace.
static normalizeCss( $value)
Normalize CSS into a format we can easily search for hostile input.
Definition: Sanitizer.php:958
wfStripIllegalFilenameChars( $name)
Replace all invalid characters with &#39;-&#39;.
const AV_NO_VIRUS
Definition: Defines.php:91
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:78
checkLocalFileExists(LocalFile $localFile, $hash)
Definition: UploadBase.php:805
string null $mSourceType
Definition: UploadBase.php:54
static splitExtensions( $filename)
Split a file into a base name and all dot-delimited &#39;extensions&#39; on the end.
static detectScript( $file, $mime, $extension)
Heuristic for detecting files that could contain JavaScript instructions or things that may look like...
string null $mDestName
Definition: UploadBase.php:50
checkUnwantedFileExtensions( $fileExtension)
Definition: UploadBase.php:761
static getInstance()
Returns the global default instance of the top level service locator.
A helper class for throttling authentication attempts.
const ILLEGAL_FILENAME
Definition: UploadBase.php:99
isEmptyFile()
Return true if the file is empty.
Definition: UploadBase.php:295
const AV_SCAN_FAILED
Definition: Defines.php:94
string $mTempPath
Local file system path to the file to upload (or a local copy)
Definition: UploadBase.php:44
static getMaxUploadSize( $forType=null)
Get the MediaWiki maximum uploaded file size for given type of upload, based on $wgMaxUploadSize.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
wfLocalFile( $title)
Get an object referring to a locally registered file.
$wgAllowJavaUploads
Allow Java archive uploads.
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2229
const AV_SCAN_ABORTED
Definition: Defines.php:93
const DELETED_FILE
Definition: File.php:63
checkAgainstArchiveDupes( $hash)
Definition: UploadBase.php:861
verifyPartialFile()
A verification routine suitable for partial files.
Definition: UploadBase.php:515
$wgCheckFileExtensions
This is a flag to determine whether or not to check file extensions on upload.
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
$wgEnableUploads
Allow users to upload files.
static checkCssFragment( $value)
Check a block of CSS or CSS fragment for anything that looks like it is bringing in remote code...
Class representing a row of the &#39;filearchive&#39; table.
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1167
wasDeleted()
Was this file ever deleted from the wiki?
Definition: File.php:1918
$wgLang
Definition: Setup.php:857
$wgAntivirusRequired
Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected.
static getPropertyNames( $filter=null)
Returns all possible parameters to siiprop.
getTempFileSha1Base36()
Get the base 36 SHA1 of the file.
Definition: UploadBase.php:311
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:149
UploadBase and subclasses are the backend of MediaWiki&#39;s file uploads.
Definition: UploadBase.php:42
static getInfo( $file, $prop, $result, $thumbParams=null, $opts=false)
Get result information for an image revision.
getDBkey()
Get the main part with underscores.
Definition: Title.php:1016
static checkSvgPICallback( $target, $data)
Callback to filter SVG Processing Instructions.
static singleton()
Definition: RepoGroup.php:60
fetchFile()
Fetch the file.
Definition: UploadBase.php:287
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
int null $mFileSize
Definition: UploadBase.php:68
string null $mRemoveTempFile
Definition: UploadBase.php:52
postProcessUpload()
Perform extra steps after a successful upload.
Definition: UploadBase.php:934
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:264
const IGNORE_USER_RIGHTS
Definition: User.php:83
static doWatch(Title $title, User $user, $checkRights=User::CHECK_USER_RIGHTS)
Watch a page.
getTitle()
Returns the title of the file to be uploaded.
Definition: UploadBase.php:943
static makeWarningsSerializable( $warnings)
Convert the warnings array returned by checkWarnings() to something that can be serialized.
Definition: UploadBase.php:717
cleanupTempFile()
If we&#39;ve modified the upload file we need to manually remove it on exit to clean up.
getSourceType()
Returns the upload type.
Definition: UploadBase.php:237
LocalFile $mLocalFile
Definition: UploadBase.php:64
static checkSvgExternalDTD( $type, $publicId, $systemId)
Verify that DTD urls referenced are only the standard dtds.
const FILE_TOO_LARGE
Definition: UploadBase.php:105
checkBadFileName( $filename, $desiredFileName)
Check whether the resulting filename is different from the desired one, but ignore things like ucfirs...
Definition: UploadBase.php:742
string null $mFinalExtension
Definition: UploadBase.php:62
checkWarnings()
Check for non fatal problems with the file.
Definition: UploadBase.php:658
initializeFromRequest(&$request)
Initialize from a WebRequest.
verifyUpload()
Verify whether the upload is sane.
Definition: UploadBase.php:341
const MIN_LENGTH_PARTNAME
Definition: UploadBase.php:98
getFileSize()
Return the file size.
Definition: UploadBase.php:303
$wgVerifyMimeTypeIE
Determines whether extra checks for IE type detection should be applied.
const NS_FILE
Definition: Defines.php:66
const VERIFICATION_ERROR
Definition: UploadBase.php:103
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding, zero padded to 31 digits.
Definition: FSFile.php:225
string null $mDesiredDestName
Definition: UploadBase.php:48
array null $mFileProps
Definition: UploadBase.php:70
static isEnabled()
Returns true if uploads are enabled.
Definition: UploadBase.php:135
doStashFile(User $user=null)
Implementation for stashFile() and tryStashFile().
validateName()
Verify that the name is valid and, if necessary, that we can overwrite.
Definition: UploadBase.php:390
static isThrottled( $user)
Returns true if the user has surpassed the upload rate limit, false otherwise.
Definition: UploadBase.php:166
static checkXMLEncodingMissmatch( $file)
Check a whitelist of xml encodings that are known not to be interpreted differently by the server&#39;s x...
const FILETYPE_BADTYPE
Definition: UploadBase.php:102
$wgMaxUploadSize
Max size for uploads, in bytes.
getLocalFile()
Return the local file and initializes if necessary.
$wgAntivirusSetup
Configuration for different virus scanners.
const FILETYPE_MISSING
Definition: UploadBase.php:101
Title bool $mTitle
Definition: UploadBase.php:56
getRealPath( $srcPath)
Definition: UploadBase.php:319
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:612
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
verifyPermissions( $user)
Alias for verifyTitlePermissions.
Definition: UploadBase.php:605
$wgFileExtensions
This is the list of preferred extensions for uploading files.
verifyTitlePermissions( $user)
Check whether the user can edit, upload and create the image.
Definition: UploadBase.php:620
static getFilenamePrefixBlacklist()
Get a list of blacklisted filename prefixes from [[MediaWiki:Filename-prefix-blacklist]].
$wgUploadSizeWarning
Warn if uploaded files are larger than this (in bytes), or false to disable.
$lines
Definition: router.php:61
const HOOK_ABORTED
Definition: UploadBase.php:104
string [] $mBlackListedExtensions
Definition: UploadBase.php:72
stashFile(User $user=null)
If the user does not supply all necessary information in the first upload form submission (either by ...
bool null $mJavaDetected
Definition: UploadBase.php:74
getId()
Get the user&#39;s ID.
Definition: User.php:2200
static isThumbName( $filename)
Helper function that checks whether the filename looks like a thumbnail.
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition: Title.php:3316
performUpload( $comment, $pageText, $watch, $user, $tags=[])
Really perform the upload.
Definition: UploadBase.php:887
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$line
Definition: cdb.php:59
static $uploadHandlers
Definition: UploadBase.php:171
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1669
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
checkFileSize( $fileSize)
Definition: UploadBase.php:783
const WINDOWS_NONASCII_FILENAME
Definition: UploadBase.php:106
if(!is_readable( $file)) $ext
Definition: router.php:48
checkAgainstExistingDupes( $hash, $ignoreLocalDupes)
Definition: UploadBase.php:839
Class to represent a local file in the wiki&#39;s own database.
Definition: LocalFile.php:56
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
string null $mFilteredName
Definition: UploadBase.php:60
stripXmlNamespace( $name)
static checkFileExtensionList( $ext, $list)
Perform case-insensitive match against a list of file extensions.
static getMaxPhpUploadSize()
Get the PHP maximum uploaded file size, based on ini settings.
$wgOut
Definition: Setup.php:862
static detectVirus( $file)
Generic wrapper function for a virus scanner program.
static verifyExtension( $mime, $extension)
Checks if the MIME type of the uploaded file matches the file extension.
static setSessionStatus(User $user, $statusKey, $value)
Set the current status of a chunked upload (used for polling)
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
setTempFile( $tempPath, $fileSize=null)
Definition: UploadBase.php:270
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
verifyMimeType( $mime)
Verify the MIME type.
Definition: UploadBase.php:420
TempFSFile null $tempFileObj
Wrapper to handle deleting the temp file.
Definition: UploadBase.php:46
$wgMimeTypeBlacklist
Files with these MIME types will never be allowed as uploads if $wgVerifyMimeType is enabled...
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
getVerificationErrorCode( $error)
Definition: UploadBase.php:113
getImageInfo( $result)
Gets image info about the file just uploaded.
verifyFile()
Verifies that it&#39;s ok to include the uploaded file.
Definition: UploadBase.php:454
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
const OK
Definition: UploadBase.php:96
$wgVerifyMimeType
Determines if the MIME type of uploaded files should be checked.
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
static checkFileExtension( $ext, $list)
Perform case-insensitive match against a list of file extensions.
$wgFileBlacklist
Files with these extensions will never be allowed as uploads.
static read( $fileName, $callback, $options=[])
Read a ZIP file and call a function for each file discovered in it.
static isValidRequest( $request)
Check whether a request if valid for this handler.
Definition: UploadBase.php:224
const EMPTY_FILE
Definition: UploadBase.php:97
zipEntryCallback( $entry)
Callback for ZipDirectoryReader to detect Java class files.
Definition: UploadBase.php:576
wfShorthandToInteger( $string='', $default=-1)
Converts shorthand byte notation to integer form.
UploadStashFile $mStashFile
Definition: UploadBase.php:66
checkLocalFileWasDeleted(LocalFile $localFile)
Definition: UploadBase.php:829
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
$wgAntivirus
Internal name of virus scanner.
$matches
runUploadStashFileHook(User $user)
checkSvgScriptCallback( $element, $attribs, $data=null)
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319