MediaWiki  master
UploadBase.php
Go to the documentation of this file.
1 <?php
24 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
28 
43 abstract class UploadBase {
44  use ProtectedHookAccessorTrait;
45 
47  protected $mTempPath;
49  protected $tempFileObj;
51  protected $mDesiredDestName;
53  protected $mDestName;
55  protected $mRemoveTempFile;
57  protected $mSourceType;
59  protected $mTitle = false;
61  protected $mTitleError = 0;
63  protected $mFilteredName;
65  protected $mFinalExtension;
67  protected $mLocalFile;
69  protected $mStashFile;
71  protected $mFileSize;
73  protected $mFileProps;
77  protected $mJavaDetected;
79  protected $mSVGNSError;
80 
81  protected static $safeXmlEncodings = [
82  'UTF-8',
83  'ISO-8859-1',
84  'ISO-8859-2',
85  'UTF-16',
86  'UTF-32',
87  'WINDOWS-1250',
88  'WINDOWS-1251',
89  'WINDOWS-1252',
90  'WINDOWS-1253',
91  'WINDOWS-1254',
92  'WINDOWS-1255',
93  'WINDOWS-1256',
94  'WINDOWS-1257',
95  'WINDOWS-1258',
96  ];
97 
98  public const SUCCESS = 0;
99  public const OK = 0;
100  public const EMPTY_FILE = 3;
101  public const MIN_LENGTH_PARTNAME = 4;
102  public const ILLEGAL_FILENAME = 5;
103  public const OVERWRITE_EXISTING_FILE = 7; # Not used anymore; handled by verifyTitlePermissions()
104  public const FILETYPE_MISSING = 8;
105  public const FILETYPE_BADTYPE = 9;
106  public const VERIFICATION_ERROR = 10;
107  public const HOOK_ABORTED = 11;
108  public const FILE_TOO_LARGE = 12;
109  public const WINDOWS_NONASCII_FILENAME = 13;
110  public const FILENAME_TOO_LONG = 14;
111 
116  public function getVerificationErrorCode( $error ) {
117  $code_to_status = [
118  self::EMPTY_FILE => 'empty-file',
119  self::FILE_TOO_LARGE => 'file-too-large',
120  self::FILETYPE_MISSING => 'filetype-missing',
121  self::FILETYPE_BADTYPE => 'filetype-banned',
122  self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
123  self::ILLEGAL_FILENAME => 'illegal-filename',
124  self::OVERWRITE_EXISTING_FILE => 'overwrite',
125  self::VERIFICATION_ERROR => 'verification-error',
126  self::HOOK_ABORTED => 'hookaborted',
127  self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
128  self::FILENAME_TOO_LONG => 'filename-toolong',
129  ];
130  return $code_to_status[$error] ?? 'unknown-error';
131  }
132 
138  public static function isEnabled() {
139  global $wgEnableUploads;
140 
141  return $wgEnableUploads && wfIniGetBool( 'file_uploads' );
142  }
143 
152  public static function isAllowed( UserIdentity $user ) {
153  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
154  foreach ( [ 'upload', 'edit' ] as $permission ) {
155  if ( !$permissionManager->userHasRight( $user, $permission ) ) {
156  return $permission;
157  }
158  }
159 
160  return true;
161  }
162 
169  public static function isThrottled( $user ) {
170  return $user->pingLimiter( 'upload' );
171  }
172 
173  // Upload handlers. Should probably just be a global.
174  private static $uploadHandlers = [ 'Stash', 'File', 'Url' ];
175 
183  public static function createFromRequest( &$request, $type = null ) {
184  $type = $type ?: $request->getVal( 'wpSourceType', 'File' );
185 
186  if ( !$type ) {
187  return null;
188  }
189 
190  // Get the upload class
191  $type = ucfirst( $type );
192 
193  // Give hooks the chance to handle this request
194  $className = null;
195  Hooks::runner()->onUploadCreateFromRequest( $type, $className );
196  if ( $className === null ) {
197  $className = 'UploadFrom' . $type;
198  wfDebug( __METHOD__ . ": class name: $className" );
199  if ( !in_array( $type, self::$uploadHandlers ) ) {
200  return null;
201  }
202  }
203 
204  // Check whether this upload class is enabled
205  if ( !call_user_func( [ $className, 'isEnabled' ] ) ) {
206  return null;
207  }
208 
209  // Check whether the request is valid
210  if ( !call_user_func( [ $className, 'isValidRequest' ], $request ) ) {
211  return null;
212  }
213 
215  $handler = new $className;
216 
217  $handler->initializeFromRequest( $request );
218 
219  return $handler;
220  }
221 
227  public static function isValidRequest( $request ) {
228  return false;
229  }
230 
231  public function __construct() {
232  }
233 
240  public function getSourceType() {
241  return null;
242  }
243 
251  public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
252  $this->mDesiredDestName = $name;
253  if ( FileBackend::isStoragePath( $tempPath ) ) {
254  throw new MWException( __METHOD__ . " given storage path `$tempPath`." );
255  }
256 
257  $this->setTempFile( $tempPath, $fileSize );
258  $this->mRemoveTempFile = $removeTempFile;
259  }
260 
266  abstract public function initializeFromRequest( &$request );
267 
272  protected function setTempFile( $tempPath, $fileSize = null ) {
273  $this->mTempPath = $tempPath;
274  $this->mFileSize = $fileSize ?: null;
275  if ( strlen( $this->mTempPath ) && file_exists( $this->mTempPath ) ) {
276  $this->tempFileObj = new TempFSFile( $this->mTempPath );
277  if ( !$fileSize ) {
278  $this->mFileSize = filesize( $this->mTempPath );
279  }
280  } else {
281  $this->tempFileObj = null;
282  }
283  }
284 
289  public function fetchFile() {
290  return Status::newGood();
291  }
292 
297  public function isEmptyFile() {
298  return empty( $this->mFileSize );
299  }
300 
305  public function getFileSize() {
306  return $this->mFileSize;
307  }
308 
313  public function getTempFileSha1Base36() {
314  return FSFile::getSha1Base36FromPath( $this->mTempPath );
315  }
316 
321  public function getRealPath( $srcPath ) {
322  $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
323  if ( FileRepo::isVirtualUrl( $srcPath ) ) {
327  $tmpFile = $repo->getLocalCopy( $srcPath );
328  if ( $tmpFile ) {
329  $tmpFile->bind( $this ); // keep alive with $this
330  }
331  $path = $tmpFile ? $tmpFile->getPath() : false;
332  } else {
333  $path = $srcPath;
334  }
335 
336  return $path;
337  }
338 
355  public function verifyUpload() {
359  if ( $this->isEmptyFile() ) {
360  return [ 'status' => self::EMPTY_FILE ];
361  }
362 
366  $maxSize = self::getMaxUploadSize( $this->getSourceType() );
367  if ( $this->mFileSize > $maxSize ) {
368  return [
369  'status' => self::FILE_TOO_LARGE,
370  'max' => $maxSize,
371  ];
372  }
373 
379  $verification = $this->verifyFile();
380  if ( $verification !== true ) {
381  return [
382  'status' => self::VERIFICATION_ERROR,
383  'details' => $verification
384  ];
385  }
386 
390  $result = $this->validateName();
391  if ( $result !== true ) {
392  return $result;
393  }
394 
395  return [ 'status' => self::OK ];
396  }
397 
404  public function validateName() {
405  $nt = $this->getTitle();
406  if ( $nt === null ) {
407  $result = [ 'status' => $this->mTitleError ];
408  if ( $this->mTitleError == self::ILLEGAL_FILENAME ) {
409  $result['filtered'] = $this->mFilteredName;
410  }
411  if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
412  $result['finalExt'] = $this->mFinalExtension;
413  if ( count( $this->mBlackListedExtensions ) ) {
414  $result['blacklistedExt'] = $this->mBlackListedExtensions;
415  }
416  }
417 
418  return $result;
419  }
420  $this->mDestName = $this->getLocalFile()->getName();
421 
422  return true;
423  }
424 
434  protected function verifyMimeType( $mime ) {
436  if ( $wgVerifyMimeType ) {
437  wfDebug( "mime: <$mime> extension: <{$this->mFinalExtension}>" );
438  global $wgMimeTypeBlacklist;
439  if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
440  return [ 'filetype-badmime', $mime ];
441  }
442 
443  if ( $wgVerifyMimeTypeIE ) {
444  # Check what Internet Explorer would detect
445  $fp = fopen( $this->mTempPath, 'rb' );
446  $chunk = fread( $fp, 256 );
447  fclose( $fp );
448 
449  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
450  $extMime = $magic->getMimeTypeFromExtensionOrNull( $this->mFinalExtension );
451  $ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
452  foreach ( $ieTypes as $ieType ) {
453  if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
454  return [ 'filetype-bad-ie-mime', $ieType ];
455  }
456  }
457  }
458  }
459 
460  return true;
461  }
462 
468  protected function verifyFile() {
470 
471  $status = $this->verifyPartialFile();
472  if ( $status !== true ) {
473  return $status;
474  }
475 
476  $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
477  $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
478  $mime = $this->mFileProps['mime'];
479 
480  if ( $wgVerifyMimeType ) {
481  # XXX: Missing extension will be caught by validateName() via getTitle()
482  if ( $this->mFinalExtension != '' && !$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
483  return [ 'filetype-mime-mismatch', $this->mFinalExtension, $mime ];
484  }
485  }
486 
487  # check for htmlish code and javascript
489  if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
490  $svgStatus = $this->detectScriptInSvg( $this->mTempPath, false );
491  if ( $svgStatus !== false ) {
492  return $svgStatus;
493  }
494  }
495  }
496 
497  $handler = MediaHandler::getHandler( $mime );
498  if ( $handler ) {
499  $handlerStatus = $handler->verifyUpload( $this->mTempPath );
500  if ( !$handlerStatus->isOK() ) {
501  $errors = $handlerStatus->getErrorsArray();
502 
503  return reset( $errors );
504  }
505  }
506 
507  $error = true;
508  $this->getHookRunner()->onUploadVerifyFile( $this, $mime, $error );
509  if ( $error !== true ) {
510  if ( !is_array( $error ) ) {
511  $error = [ $error ];
512  }
513  return $error;
514  }
515 
516  wfDebug( __METHOD__ . ": all clear; passing." );
517 
518  return true;
519  }
520 
529  protected function verifyPartialFile() {
531 
532  # getTitle() sets some internal parameters like $this->mFinalExtension
533  $this->getTitle();
534 
535  $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
536  $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
537 
538  # check MIME type, if desired
539  $mime = $this->mFileProps['file-mime'];
540  $status = $this->verifyMimeType( $mime );
541  if ( $status !== true ) {
542  return $status;
543  }
544 
545  # check for htmlish code and javascript
547  if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
548  return [ 'uploadscripted' ];
549  }
550  if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
551  $svgStatus = $this->detectScriptInSvg( $this->mTempPath, true );
552  if ( $svgStatus !== false ) {
553  return $svgStatus;
554  }
555  }
556  }
557 
558  # Check for Java applets, which if uploaded can bypass cross-site
559  # restrictions.
560  if ( !$wgAllowJavaUploads ) {
561  $this->mJavaDetected = false;
562  $zipStatus = ZipDirectoryReader::read( $this->mTempPath,
563  [ $this, 'zipEntryCallback' ] );
564  if ( !$zipStatus->isOK() ) {
565  $errors = $zipStatus->getErrorsArray();
566  $error = reset( $errors );
567  if ( $error[0] !== 'zip-wrong-format' ) {
568  return $error;
569  }
570  }
571  if ( $this->mJavaDetected ) {
572  return [ 'uploadjava' ];
573  }
574  }
575 
576  # Scan the uploaded file for viruses
577  $virus = $this->detectVirus( $this->mTempPath );
578  if ( $virus ) {
579  return [ 'uploadvirus', $virus ];
580  }
581 
582  return true;
583  }
584 
590  public function zipEntryCallback( $entry ) {
591  $names = [ $entry['name'] ];
592 
593  // If there is a null character, cut off the name at it, because JDK's
594  // ZIP_GetEntry() uses strcmp() if the name hashes match. If a file name
595  // were constructed which had ".class\0" followed by a string chosen to
596  // make the hash collide with the truncated name, that file could be
597  // returned in response to a request for the .class file.
598  $nullPos = strpos( $entry['name'], "\000" );
599  if ( $nullPos !== false ) {
600  $names[] = substr( $entry['name'], 0, $nullPos );
601  }
602 
603  // If there is a trailing slash in the file name, we have to strip it,
604  // because that's what ZIP_GetEntry() does.
605  if ( preg_grep( '!\.class/?$!', $names ) ) {
606  $this->mJavaDetected = true;
607  }
608  }
609 
619  public function verifyPermissions( $user ) {
620  return $this->verifyTitlePermissions( $user );
621  }
622 
634  public function verifyTitlePermissions( $user ) {
639  $nt = $this->getTitle();
640  if ( $nt === null ) {
641  return true;
642  }
643  $permManager = MediaWikiServices::getInstance()->getPermissionManager();
644  $permErrors = $permManager->getPermissionErrors( 'edit', $user, $nt );
645  $permErrorsUpload = $permManager->getPermissionErrors( 'upload', $user, $nt );
646  if ( !$nt->exists() ) {
647  $permErrorsCreate = $permManager->getPermissionErrors( 'create', $user, $nt );
648  } else {
649  $permErrorsCreate = [];
650  }
651  if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
652  $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
653  $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
654 
655  return $permErrors;
656  }
657 
658  $overwriteError = $this->checkOverwrite( $user );
659  if ( $overwriteError !== true ) {
660  return [ $overwriteError ];
661  }
662 
663  return true;
664  }
665 
675  public function checkWarnings( $user = null ) {
676  if ( $user === null ) {
677  // TODO check uses and hard deprecate
678  $user = RequestContext::getMain()->getUser();
679  }
680 
681  $warnings = [];
682 
683  $localFile = $this->getLocalFile();
684  $localFile->load( File::READ_LATEST );
685  $filename = $localFile->getName();
686  $hash = $this->getTempFileSha1Base36();
687 
688  $badFileName = $this->checkBadFileName( $filename, $this->mDesiredDestName );
689  if ( $badFileName !== null ) {
690  $warnings['badfilename'] = $badFileName;
691  }
692 
693  $unwantedFileExtensionDetails = $this->checkUnwantedFileExtensions( $this->mFinalExtension );
694  if ( $unwantedFileExtensionDetails !== null ) {
695  $warnings['filetype-unwanted-type'] = $unwantedFileExtensionDetails;
696  }
697 
698  $fileSizeWarnings = $this->checkFileSize( $this->mFileSize );
699  if ( $fileSizeWarnings ) {
700  $warnings = array_merge( $warnings, $fileSizeWarnings );
701  }
702 
703  $localFileExistsWarnings = $this->checkLocalFileExists( $localFile, $hash );
704  if ( $localFileExistsWarnings ) {
705  $warnings = array_merge( $warnings, $localFileExistsWarnings );
706  }
707 
708  if ( $this->checkLocalFileWasDeleted( $localFile ) ) {
709  $warnings['was-deleted'] = $filename;
710  }
711 
712  // If a file with the same name exists locally then the local file has already been tested
713  // for duplication of content
714  $ignoreLocalDupes = isset( $warnings['exists'] );
715  $dupes = $this->checkAgainstExistingDupes( $hash, $ignoreLocalDupes );
716  if ( $dupes ) {
717  $warnings['duplicate'] = $dupes;
718  }
719 
720  $archivedDupes = $this->checkAgainstArchiveDupes( $hash, $user );
721  if ( $archivedDupes !== null ) {
722  $warnings['duplicate-archive'] = $archivedDupes;
723  }
724 
725  return $warnings;
726  }
727 
739  public static function makeWarningsSerializable( $warnings ) {
740  array_walk_recursive( $warnings, function ( &$param, $key ) {
741  if ( $param instanceof File ) {
742  $param = [
743  'fileName' => $param->getName(),
744  'timestamp' => $param->getTimestamp()
745  ];
746  } elseif ( is_object( $param ) ) {
747  throw new InvalidArgumentException(
748  'UploadBase::makeWarningsSerializable: ' .
749  'Unexpected object of class ' . get_class( $param ) );
750  }
751  } );
752  return $warnings;
753  }
754 
764  private function checkBadFileName( $filename, $desiredFileName ) {
765  $comparableName = str_replace( ' ', '_', $desiredFileName );
766  $comparableName = Title::capitalize( $comparableName, NS_FILE );
767 
768  if ( $desiredFileName != $filename && $comparableName != $filename ) {
769  return $filename;
770  }
771 
772  return null;
773  }
774 
783  private function checkUnwantedFileExtensions( $fileExtension ) {
785 
786  if ( $wgCheckFileExtensions ) {
787  $extensions = array_unique( $wgFileExtensions );
788  if ( !$this->checkFileExtension( $fileExtension, $extensions ) ) {
789  return [
790  $fileExtension,
791  $wgLang->commaList( $extensions ),
792  count( $extensions )
793  ];
794  }
795  }
796 
797  return null;
798  }
799 
805  private function checkFileSize( $fileSize ) {
806  global $wgUploadSizeWarning;
807 
808  $warnings = [];
809 
810  if ( $wgUploadSizeWarning && ( $fileSize > $wgUploadSizeWarning ) ) {
811  $warnings['large-file'] = [ $wgUploadSizeWarning, $fileSize ];
812  }
813 
814  if ( $fileSize == 0 ) {
815  $warnings['empty-file'] = true;
816  }
817 
818  return $warnings;
819  }
820 
827  private function checkLocalFileExists( LocalFile $localFile, $hash ) {
828  $warnings = [];
829 
830  $exists = self::getExistsWarning( $localFile );
831  if ( $exists !== false ) {
832  $warnings['exists'] = $exists;
833 
834  // check if file is an exact duplicate of current file version
835  if ( $hash === $localFile->getSha1() ) {
836  $warnings['no-change'] = $localFile;
837  }
838 
839  // check if file is an exact duplicate of older versions of this file
840  $history = $localFile->getHistory();
841  foreach ( $history as $oldFile ) {
842  if ( $hash === $oldFile->getSha1() ) {
843  $warnings['duplicate-version'][] = $oldFile;
844  }
845  }
846  }
847 
848  return $warnings;
849  }
850 
851  private function checkLocalFileWasDeleted( LocalFile $localFile ) {
852  return $localFile->wasDeleted() && !$localFile->exists();
853  }
854 
861  private function checkAgainstExistingDupes( $hash, $ignoreLocalDupes ) {
862  $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
863  $title = $this->getTitle();
864  foreach ( $dupes as $key => $dupe ) {
865  if (
866  ( $dupe instanceof LocalFile ) &&
867  $ignoreLocalDupes &&
868  $title->equals( $dupe->getTitle() )
869  ) {
870  unset( $dupes[$key] );
871  }
872  }
873 
874  return $dupes;
875  }
876 
884  private function checkAgainstArchiveDupes( $hash, User $user ) {
885  $archivedFile = new ArchivedFile( null, 0, '', $hash );
886  if ( $archivedFile->getID() > 0 ) {
887  if ( $archivedFile->userCan( File::DELETED_FILE, $user ) ) {
888  return $archivedFile->getName();
889  } else {
890  return '';
891  }
892  }
893 
894  return null;
895  }
896 
910  public function performUpload( $comment, $pageText, $watch, $user, $tags = [] ) {
911  $this->getLocalFile()->load( File::READ_LATEST );
912  $props = $this->mFileProps;
913 
914  $error = null;
915  $this->getHookRunner()->onUploadVerifyUpload( $this, $user, $props, $comment, $pageText, $error );
916  if ( $error ) {
917  if ( !is_array( $error ) ) {
918  $error = [ $error ];
919  }
920  return Status::newFatal( ...$error );
921  }
922 
923  $status = $this->getLocalFile()->upload(
924  $this->mTempPath,
925  $comment,
926  $pageText,
928  $props,
929  false,
930  $user,
931  $tags
932  );
933 
934  if ( $status->isGood() ) {
935  if ( $watch ) {
937  $this->getLocalFile()->getTitle(),
938  $user,
940  );
941  }
942  $this->getHookRunner()->onUploadComplete( $this );
943 
944  $this->postProcessUpload();
945  }
946 
947  return $status;
948  }
949 
955  public function postProcessUpload() {
956  }
957 
964  public function getTitle() {
965  if ( $this->mTitle !== false ) {
966  return $this->mTitle;
967  }
968  if ( !is_string( $this->mDesiredDestName ) ) {
969  $this->mTitleError = self::ILLEGAL_FILENAME;
970  $this->mTitle = null;
971 
972  return $this->mTitle;
973  }
974  /* Assume that if a user specified File:Something.jpg, this is an error
975  * and that the namespace prefix needs to be stripped of.
976  */
977  $title = Title::newFromText( $this->mDesiredDestName );
978  if ( $title && $title->getNamespace() == NS_FILE ) {
979  $this->mFilteredName = $title->getDBkey();
980  } else {
981  $this->mFilteredName = $this->mDesiredDestName;
982  }
983 
984  # oi_archive_name is max 255 bytes, which include a timestamp and an
985  # exclamation mark, so restrict file name to 240 bytes.
986  if ( strlen( $this->mFilteredName ) > 240 ) {
987  $this->mTitleError = self::FILENAME_TOO_LONG;
988  $this->mTitle = null;
989 
990  return $this->mTitle;
991  }
992 
998  $this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName );
999  /* Normalize to title form before we do any further processing */
1000  $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
1001  if ( $nt === null ) {
1002  $this->mTitleError = self::ILLEGAL_FILENAME;
1003  $this->mTitle = null;
1004 
1005  return $this->mTitle;
1006  }
1007  $this->mFilteredName = $nt->getDBkey();
1008 
1013  list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName );
1014 
1015  if ( $ext !== [] ) {
1016  $this->mFinalExtension = trim( end( $ext ) );
1017  } else {
1018  $this->mFinalExtension = '';
1019 
1020  # No extension, try guessing one
1021  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
1022  $mime = $magic->guessMimeType( $this->mTempPath );
1023  if ( $mime !== 'unknown/unknown' ) {
1024  # Get a space separated list of extensions
1025  $mimeExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
1026  if ( $mimeExt !== null ) {
1027  # Set the extension to the canonical extension
1028  $this->mFinalExtension = $mimeExt;
1029 
1030  # Fix up the other variables
1031  $this->mFilteredName .= ".{$this->mFinalExtension}";
1032  $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
1034  }
1035  }
1036  }
1037 
1038  /* Don't allow users to override the blacklist (check file extension) */
1041 
1042  $blackListedExtensions = $this->checkFileExtensionList( $ext, $wgFileBlacklist );
1043 
1044  if ( $this->mFinalExtension == '' ) {
1045  $this->mTitleError = self::FILETYPE_MISSING;
1046  $this->mTitle = null;
1047 
1048  return $this->mTitle;
1049  } elseif ( $blackListedExtensions ||
1051  !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) )
1052  ) {
1053  $this->mBlackListedExtensions = $blackListedExtensions;
1054  $this->mTitleError = self::FILETYPE_BADTYPE;
1055  $this->mTitle = null;
1056 
1057  return $this->mTitle;
1058  }
1059 
1060  // Windows may be broken with special characters, see T3780
1061  if ( !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() )
1062  && !MediaWikiServices::getInstance()->getRepoGroup()
1063  ->getLocalRepo()->backendSupportsUnicodePaths()
1064  ) {
1065  $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
1066  $this->mTitle = null;
1067 
1068  return $this->mTitle;
1069  }
1070 
1071  # If there was more than one "extension", reassemble the base
1072  # filename to prevent bogus complaints about length
1073  if ( count( $ext ) > 1 ) {
1074  $iterations = count( $ext ) - 1;
1075  for ( $i = 0; $i < $iterations; $i++ ) {
1076  $partname .= '.' . $ext[$i];
1077  }
1078  }
1079 
1080  if ( strlen( $partname ) < 1 ) {
1081  $this->mTitleError = self::MIN_LENGTH_PARTNAME;
1082  $this->mTitle = null;
1083 
1084  return $this->mTitle;
1085  }
1086 
1087  $this->mTitle = $nt;
1088 
1089  return $this->mTitle;
1090  }
1091 
1097  public function getLocalFile() {
1098  if ( $this->mLocalFile === null ) {
1099  $nt = $this->getTitle();
1100  $this->mLocalFile = $nt === null
1101  ? null
1102  : MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $nt );
1103  }
1104 
1105  return $this->mLocalFile;
1106  }
1107 
1111  public function getStashFile() {
1112  return $this->mStashFile;
1113  }
1114 
1126  public function tryStashFile( User $user, $isPartial = false ) {
1127  if ( !$isPartial ) {
1128  $error = $this->runUploadStashFileHook( $user );
1129  if ( $error ) {
1130  return Status::newFatal( ...$error );
1131  }
1132  }
1133  try {
1134  $file = $this->doStashFile( $user );
1135  return Status::newGood( $file );
1136  } catch ( UploadStashException $e ) {
1137  return Status::newFatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() );
1138  }
1139  }
1140 
1145  protected function runUploadStashFileHook( User $user ) {
1146  $props = $this->mFileProps;
1147  $error = null;
1148  $this->getHookRunner()->onUploadStashFile( $this, $user, $props, $error );
1149  if ( $error && !is_array( $error ) ) {
1150  $error = [ $error ];
1151  }
1152  return $error;
1153  }
1154 
1174  public function stashFile( User $user = null ) {
1175  wfDeprecated( __METHOD__, '1.28' );
1176 
1177  return $this->doStashFile( $user );
1178  }
1179 
1186  protected function doStashFile( User $user = null ) {
1187  $stash = MediaWikiServices::getInstance()->getRepoGroup()
1188  ->getLocalRepo()->getUploadStash( $user );
1189  $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
1190  $this->mStashFile = $file;
1191 
1192  return $file;
1193  }
1194 
1199  public function cleanupTempFile() {
1200  if ( $this->mRemoveTempFile && $this->tempFileObj ) {
1201  // Delete when all relevant TempFSFile handles go out of scope
1202  wfDebug( __METHOD__ . ": Marked temporary file '{$this->mTempPath}' for removal" );
1203  $this->tempFileObj->autocollect();
1204  }
1205  }
1206 
1207  public function getTempPath() {
1208  return $this->mTempPath;
1209  }
1210 
1220  public static function splitExtensions( $filename ) {
1221  $bits = explode( '.', $filename );
1222  $basename = array_shift( $bits );
1223 
1224  return [ $basename, $bits ];
1225  }
1226 
1235  public static function checkFileExtension( $ext, $list ) {
1236  return in_array( strtolower( $ext ), $list );
1237  }
1238 
1247  public static function checkFileExtensionList( $ext, $list ) {
1248  return array_intersect( array_map( 'strtolower', $ext ), $list );
1249  }
1250 
1258  public static function verifyExtension( $mime, $extension ) {
1259  $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
1260 
1261  if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) {
1262  if ( !$magic->isRecognizableExtension( $extension ) ) {
1263  wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
1264  "unrecognized extension '$extension', can't verify" );
1265 
1266  return true;
1267  } else {
1268  wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
1269  "recognized extension '$extension', so probably invalid file" );
1270 
1271  return false;
1272  }
1273  }
1274 
1275  $match = $magic->isMatchingExtension( $extension, $mime );
1276 
1277  if ( $match === null ) {
1278  if ( $magic->getTypesForExtension( $extension ) !== null ) {
1279  wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension" );
1280 
1281  return false;
1282  } else {
1283  wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file" );
1284 
1285  return true;
1286  }
1287  } elseif ( $match === true ) {
1288  wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file" );
1289 
1291  return true;
1292  } else {
1293  wfDebug( __METHOD__
1294  . ": mime type $mime mismatches file extension $extension, rejecting file" );
1295 
1296  return false;
1297  }
1298  }
1299 
1311  public static function detectScript( $file, $mime, $extension ) {
1312  # ugly hack: for text files, always look at the entire file.
1313  # For binary field, just check the first K.
1314 
1315  $isText = strpos( $mime, 'text/' ) === 0;
1316  if ( $isText ) {
1317  $chunk = file_get_contents( $file );
1318  } else {
1319  $fp = fopen( $file, 'rb' );
1320  $chunk = fread( $fp, 1024 );
1321  fclose( $fp );
1322  }
1323 
1324  $chunk = strtolower( $chunk );
1325 
1326  if ( !$chunk ) {
1327  return false;
1328  }
1329 
1330  # decode from UTF-16 if needed (could be used for obfuscation).
1331  if ( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
1332  $enc = 'UTF-16BE';
1333  } elseif ( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
1334  $enc = 'UTF-16LE';
1335  } else {
1336  $enc = null;
1337  }
1338 
1339  if ( $enc !== null ) {
1340  $chunk = iconv( $enc, "ASCII//IGNORE", $chunk );
1341  }
1342 
1343  $chunk = trim( $chunk );
1344 
1346  wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff" );
1347 
1348  # check for HTML doctype
1349  if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
1350  return true;
1351  }
1352 
1353  // Some browsers will interpret obscure xml encodings as UTF-8, while
1354  // PHP/expat will interpret the given encoding in the xml declaration (T49304)
1355  if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) {
1356  if ( self::checkXMLEncodingMissmatch( $file ) ) {
1357  return true;
1358  }
1359  }
1360 
1361  // Quick check for HTML heuristics in old IE and Safari.
1362  //
1363  // The exact heuristics IE uses are checked separately via verifyMimeType(), so we
1364  // don't need them all here as it can cause many false positives.
1365  //
1366  // Check for `<script` and such still to forbid script tags and embedded HTML in SVG:
1367  $tags = [
1368  '<body',
1369  '<head',
1370  '<html', # also in safari
1371  '<script', # also in safari
1372  ];
1373 
1374  foreach ( $tags as $tag ) {
1375  if ( strpos( $chunk, $tag ) !== false ) {
1376  wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag" );
1377 
1378  return true;
1379  }
1380  }
1381 
1382  /*
1383  * look for JavaScript
1384  */
1385 
1386  # resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1387  $chunk = Sanitizer::decodeCharReferences( $chunk );
1388 
1389  # look for script-types
1390  if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
1391  wfDebug( __METHOD__ . ": found script types" );
1392 
1393  return true;
1394  }
1395 
1396  # look for html-style script-urls
1397  if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1398  wfDebug( __METHOD__ . ": found html-style script urls" );
1399 
1400  return true;
1401  }
1402 
1403  # look for css-style script-urls
1404  if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
1405  wfDebug( __METHOD__ . ": found css-style script urls" );
1406 
1407  return true;
1408  }
1409 
1410  wfDebug( __METHOD__ . ": no scripts found" );
1411 
1412  return false;
1413  }
1414 
1422  public static function checkXMLEncodingMissmatch( $file ) {
1423  global $wgSVGMetadataCutoff;
1424  $contents = file_get_contents( $file, false, null, 0, $wgSVGMetadataCutoff );
1425  $encodingRegex = '!encoding[ \t\n\r]*=[ \t\n\r]*[\'"](.*?)[\'"]!si';
1426 
1427  if ( preg_match( "!<\?xml\b(.*?)\?>!si", $contents, $matches ) ) {
1428  if ( preg_match( $encodingRegex, $matches[1], $encMatch )
1429  && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1430  ) {
1431  wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'" );
1432 
1433  return true;
1434  }
1435  } elseif ( preg_match( "!<\?xml\b!si", $contents ) ) {
1436  // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
1437  // bytes. There shouldn't be a legitimate reason for this to happen.
1438  wfDebug( __METHOD__ . ": Unmatched XML declaration start" );
1439 
1440  return true;
1441  } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
1442  // EBCDIC encoded XML
1443  wfDebug( __METHOD__ . ": EBCDIC Encoded XML" );
1444 
1445  return true;
1446  }
1447 
1448  // It's possible the file is encoded with multi-byte encoding, so re-encode attempt to
1449  // detect the encoding in case is specifies an encoding not whitelisted in self::$safeXmlEncodings
1450  $attemptEncodings = [ 'UTF-16', 'UTF-16BE', 'UTF-32', 'UTF-32BE' ];
1451  foreach ( $attemptEncodings as $encoding ) {
1452  Wikimedia\suppressWarnings();
1453  $str = iconv( $encoding, 'UTF-8', $contents );
1454  Wikimedia\restoreWarnings();
1455  if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
1456  if ( preg_match( $encodingRegex, $matches[1], $encMatch )
1457  && !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
1458  ) {
1459  wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'" );
1460 
1461  return true;
1462  }
1463  } elseif ( $str != '' && preg_match( "!<\?xml\b!si", $str ) ) {
1464  // Start of XML declaration without an end in the first $wgSVGMetadataCutoff
1465  // bytes. There shouldn't be a legitimate reason for this to happen.
1466  wfDebug( __METHOD__ . ": Unmatched XML declaration start" );
1467 
1468  return true;
1469  }
1470  }
1471 
1472  return false;
1473  }
1474 
1480  protected function detectScriptInSvg( $filename, $partial ) {
1481  $this->mSVGNSError = false;
1482  $check = new XmlTypeCheck(
1483  $filename,
1484  [ $this, 'checkSvgScriptCallback' ],
1485  true,
1486  [
1487  'processing_instruction_handler' => 'UploadBase::checkSvgPICallback',
1488  'external_dtd_handler' => 'UploadBase::checkSvgExternalDTD',
1489  ]
1490  );
1491  if ( $check->wellFormed !== true ) {
1492  // Invalid xml (T60553)
1493  // But only when non-partial (T67724)
1494  return $partial ? false : [ 'uploadinvalidxml' ];
1495  } elseif ( $check->filterMatch ) {
1496  if ( $this->mSVGNSError ) {
1497  return [ 'uploadscriptednamespace', $this->mSVGNSError ];
1498  }
1499 
1500  return $check->filterMatchType;
1501  }
1502 
1503  return false;
1504  }
1505 
1512  public static function checkSvgPICallback( $target, $data ) {
1513  // Don't allow external stylesheets (T59550)
1514  if ( preg_match( '/xml-stylesheet/i', $target ) ) {
1515  return [ 'upload-scripted-pi-callback' ];
1516  }
1517 
1518  return false;
1519  }
1520 
1532  public static function checkSvgExternalDTD( $type, $publicId, $systemId ) {
1533  // This doesn't include the XHTML+MathML+SVG doctype since we don't
1534  // allow XHTML anyways.
1535  $allowedDTDs = [
1536  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd',
1537  'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd',
1538  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd',
1539  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd',
1540  // https://phabricator.wikimedia.org/T168856
1541  'http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd',
1542  ];
1543  if ( $type !== 'PUBLIC'
1544  || !in_array( $systemId, $allowedDTDs )
1545  || strpos( $publicId, "-//W3C//" ) !== 0
1546  ) {
1547  return [ 'upload-scripted-dtd' ];
1548  }
1549  return false;
1550  }
1551 
1559  public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
1560  list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
1561 
1562  // We specifically don't include:
1563  // http://www.w3.org/1999/xhtml (T62771)
1564  static $validNamespaces = [
1565  '',
1566  'adobe:ns:meta/',
1567  'http://creativecommons.org/ns#',
1568  'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
1569  'http://ns.adobe.com/adobeillustrator/10.0/',
1570  'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
1571  'http://ns.adobe.com/extensibility/1.0/',
1572  'http://ns.adobe.com/flows/1.0/',
1573  'http://ns.adobe.com/illustrator/1.0/',
1574  'http://ns.adobe.com/imagereplacement/1.0/',
1575  'http://ns.adobe.com/pdf/1.3/',
1576  'http://ns.adobe.com/photoshop/1.0/',
1577  'http://ns.adobe.com/saveforweb/1.0/',
1578  'http://ns.adobe.com/variables/1.0/',
1579  'http://ns.adobe.com/xap/1.0/',
1580  'http://ns.adobe.com/xap/1.0/g/',
1581  'http://ns.adobe.com/xap/1.0/g/img/',
1582  'http://ns.adobe.com/xap/1.0/mm/',
1583  'http://ns.adobe.com/xap/1.0/rights/',
1584  'http://ns.adobe.com/xap/1.0/stype/dimensions#',
1585  'http://ns.adobe.com/xap/1.0/stype/font#',
1586  'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
1587  'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
1588  'http://ns.adobe.com/xap/1.0/stype/resourceref#',
1589  'http://ns.adobe.com/xap/1.0/t/pg/',
1590  'http://purl.org/dc/elements/1.1/',
1591  'http://purl.org/dc/elements/1.1',
1592  'http://schemas.microsoft.com/visio/2003/svgextensions/',
1593  'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
1594  'http://taptrix.com/inkpad/svg_extensions',
1595  'http://web.resource.org/cc/',
1596  'http://www.freesoftware.fsf.org/bkchem/cdml',
1597  'http://www.inkscape.org/namespaces/inkscape',
1598  'http://www.opengis.net/gml',
1599  'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
1600  'http://www.w3.org/2000/svg',
1601  'http://www.w3.org/tr/rec-rdf-syntax/',
1602  'http://www.w3.org/2000/01/rdf-schema#',
1603  ];
1604 
1605  // Inkscape mangles namespace definitions created by Adobe Illustrator.
1606  // This is nasty but harmless. (T144827)
1607  $isBuggyInkscape = preg_match( '/^&(#38;)*ns_[a-z_]+;$/', $namespace );
1608 
1609  if ( !( $isBuggyInkscape || in_array( $namespace, $validNamespaces ) ) ) {
1610  wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file." );
1612  $this->mSVGNSError = $namespace;
1613 
1614  return true;
1615  }
1616 
1617  /*
1618  * check for elements that can contain javascript
1619  */
1620  if ( $strippedElement == 'script' ) {
1621  wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file." );
1622 
1623  return [ 'uploaded-script-svg', $strippedElement ];
1624  }
1625 
1626  # e.g., <svg xmlns="http://www.w3.org/2000/svg">
1627  # <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
1628  if ( $strippedElement == 'handler' ) {
1629  wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file." );
1630 
1631  return [ 'uploaded-script-svg', $strippedElement ];
1632  }
1633 
1634  # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
1635  if ( $strippedElement == 'stylesheet' ) {
1636  wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file." );
1637 
1638  return [ 'uploaded-script-svg', $strippedElement ];
1639  }
1640 
1641  # Block iframes, in case they pass the namespace check
1642  if ( $strippedElement == 'iframe' ) {
1643  wfDebug( __METHOD__ . ": iframe in uploaded file." );
1644 
1645  return [ 'uploaded-script-svg', $strippedElement ];
1646  }
1647 
1648  # Check <style> css
1649  if ( $strippedElement == 'style'
1650  && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
1651  ) {
1652  wfDebug( __METHOD__ . ": hostile css in style element." );
1653  return [ 'uploaded-hostile-svg' ];
1654  }
1655 
1656  foreach ( $attribs as $attrib => $value ) {
1657  $stripped = $this->stripXmlNamespace( $attrib );
1658  $value = strtolower( $value );
1659 
1660  if ( substr( $stripped, 0, 2 ) == 'on' ) {
1661  wfDebug( __METHOD__
1662  . ": Found event-handler attribute '$attrib'='$value' in uploaded file." );
1663 
1664  return [ 'uploaded-event-handler-on-svg', $attrib, $value ];
1665  }
1666 
1667  # Do not allow relative links, or unsafe url schemas.
1668  # For <a> tags, only data:, http: and https: and same-document
1669  # fragment links are allowed. For all other tags, only data:
1670  # and fragment are allowed.
1671  if ( $stripped == 'href'
1672  && $value !== ''
1673  && strpos( $value, 'data:' ) !== 0
1674  && strpos( $value, '#' ) !== 0
1675  ) {
1676  if ( !( $strippedElement === 'a'
1677  && preg_match( '!^https?://!i', $value ) )
1678  ) {
1679  wfDebug( __METHOD__ . ": Found href attribute <$strippedElement "
1680  . "'$attrib'='$value' in uploaded file." );
1681 
1682  return [ 'uploaded-href-attribute-svg', $strippedElement, $attrib, $value ];
1683  }
1684  }
1685 
1686  # only allow data: targets that should be safe. This prevents vectors like,
1687  # image/svg, text/xml, application/xml, and text/html, which can contain scripts
1688  if ( $stripped == 'href' && strncasecmp( 'data:', $value, 5 ) === 0 ) {
1689  // rfc2397 parameters. This is only slightly slower than (;[\w;]+)*.
1690  // phpcs:ignore Generic.Files.LineLength
1691  $parameters = '(?>;[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+=(?>[a-zA-Z0-9\!#$&\'*+.^_`{|}~-]+|"(?>[\0-\x0c\x0e-\x21\x23-\x5b\x5d-\x7f]+|\\\\[\0-\x7f])*"))*(?:;base64)?';
1692 
1693  if ( !preg_match( "!^data:\s*image/(gif|jpeg|jpg|png)$parameters,!i", $value ) ) {
1694  wfDebug( __METHOD__ . ": Found href to unwhitelisted data: uri "
1695  . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1696  return [ 'uploaded-href-unsafe-target-svg', $strippedElement, $attrib, $value ];
1697  }
1698  }
1699 
1700  # Change href with animate from (http://html5sec.org/#137).
1701  if ( $stripped === 'attributename'
1702  && $strippedElement === 'animate'
1703  && $this->stripXmlNamespace( $value ) == 'href'
1704  ) {
1705  wfDebug( __METHOD__ . ": Found animate that might be changing href using from "
1706  . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file." );
1707 
1708  return [ 'uploaded-animate-svg', $strippedElement, $attrib, $value ];
1709  }
1710 
1711  # use set/animate to add event-handler attribute to parent
1712  if ( ( $strippedElement == 'set' || $strippedElement == 'animate' )
1713  && $stripped == 'attributename'
1714  && substr( $value, 0, 2 ) == 'on'
1715  ) {
1716  wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with "
1717  . "\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1718 
1719  return [ 'uploaded-setting-event-handler-svg', $strippedElement, $stripped, $value ];
1720  }
1721 
1722  # use set to add href attribute to parent element
1723  if ( $strippedElement == 'set'
1724  && $stripped == 'attributename'
1725  && strpos( $value, 'href' ) !== false
1726  ) {
1727  wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file." );
1728 
1729  return [ 'uploaded-setting-href-svg' ];
1730  }
1731 
1732  # use set to add a remote / data / script target to an element
1733  if ( $strippedElement == 'set'
1734  && $stripped == 'to'
1735  && preg_match( '!(http|https|data|script):!sim', $value )
1736  ) {
1737  wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file." );
1738 
1739  return [ 'uploaded-wrong-setting-svg', $value ];
1740  }
1741 
1742  # use handler attribute with remote / data / script
1743  if ( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
1744  wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script "
1745  . "'$attrib'='$value' in uploaded file." );
1746 
1747  return [ 'uploaded-setting-handler-svg', $attrib, $value ];
1748  }
1749 
1750  # use CSS styles to bring in remote code
1751  if ( $stripped == 'style'
1752  && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
1753  ) {
1754  wfDebug( __METHOD__ . ": Found svg setting a style with "
1755  . "remote url '$attrib'='$value' in uploaded file." );
1756  return [ 'uploaded-remote-url-svg', $attrib, $value ];
1757  }
1758 
1759  # Several attributes can include css, css character escaping isn't allowed
1760  $cssAttrs = [ 'font', 'clip-path', 'fill', 'filter', 'marker',
1761  'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke' ];
1762  if ( in_array( $stripped, $cssAttrs )
1763  && self::checkCssFragment( $value )
1764  ) {
1765  wfDebug( __METHOD__ . ": Found svg setting a style with "
1766  . "remote url '$attrib'='$value' in uploaded file." );
1767  return [ 'uploaded-remote-url-svg', $attrib, $value ];
1768  }
1769 
1770  # image filters can pull in url, which could be svg that executes scripts
1771  # Only allow url( "#foo" ). Do not allow url( http://example.com )
1772  if ( $strippedElement == 'image'
1773  && $stripped == 'filter'
1774  && preg_match( '!url\s*\(\s*["\']?[^#]!sim', $value )
1775  ) {
1776  wfDebug( __METHOD__ . ": Found image filter with url: "
1777  . "\"<$strippedElement $stripped='$value'...\" in uploaded file." );
1778 
1779  return [ 'uploaded-image-filter-svg', $strippedElement, $stripped, $value ];
1780  }
1781  }
1782 
1783  return false; // No scripts detected
1784  }
1785 
1792  private static function checkCssFragment( $value ) {
1793  # Forbid external stylesheets, for both reliability and to protect viewer's privacy
1794  if ( stripos( $value, '@import' ) !== false ) {
1795  return true;
1796  }
1797 
1798  # We allow @font-face to embed fonts with data: urls, so we snip the string
1799  # 'url' out so this case won't match when we check for urls below
1800  $pattern = '!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
1801  $value = preg_replace( $pattern, '$1$2', $value );
1802 
1803  # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
1804  # properties filter and accelerator don't seem to be useful for xss in SVG files.
1805  # Expression and -o-link don't seem to work either, but filtering them here in case.
1806  # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
1807  # but not local ones such as url("#..., url('#..., url(#....
1808  if ( preg_match( '!expression
1809  | -o-link\s*:
1810  | -o-link-source\s*:
1811  | -o-replace\s*:!imx', $value ) ) {
1812  return true;
1813  }
1814 
1815  if ( preg_match_all(
1816  "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
1817  $value,
1818  $matches
1819  ) !== 0
1820  ) {
1821  # TODO: redo this in one regex. Until then, url("#whatever") matches the first
1822  foreach ( $matches[1] as $match ) {
1823  if ( !preg_match( "!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
1824  return true;
1825  }
1826  }
1827  }
1828 
1829  if ( preg_match( '/[\000-\010\013\016-\037\177]/', $value ) ) {
1830  return true;
1831  }
1832 
1833  return false;
1834  }
1835 
1841  private static function splitXmlNamespace( $element ) {
1842  // 'http://www.w3.org/2000/svg:script' -> [ 'http://www.w3.org/2000/svg', 'script' ]
1843  $parts = explode( ':', strtolower( $element ) );
1844  $name = array_pop( $parts );
1845  $ns = implode( ':', $parts );
1846 
1847  return [ $ns, $name ];
1848  }
1849 
1854  private function stripXmlNamespace( $name ) {
1855  // 'http://www.w3.org/2000/svg:script' -> 'script'
1856  $parts = explode( ':', strtolower( $name ) );
1857 
1858  return array_pop( $parts );
1859  }
1860 
1871  public static function detectVirus( $file ) {
1873 
1874  if ( !$wgAntivirus ) {
1875  wfDebug( __METHOD__ . ": virus scanner disabled" );
1876 
1877  return null;
1878  }
1879 
1880  if ( !$wgAntivirusSetup[$wgAntivirus] ) {
1881  wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus" );
1882  $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
1883  [ 'virus-badscanner', $wgAntivirus ] );
1884 
1885  return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
1886  }
1887 
1888  # look up scanner configuration
1889  $command = $wgAntivirusSetup[$wgAntivirus]['command'];
1890  $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]['codemap'];
1891  $msgPattern = $wgAntivirusSetup[$wgAntivirus]['messagepattern'] ?? null;
1892 
1893  if ( strpos( $command, "%f" ) === false ) {
1894  # simple pattern: append file to scan
1895  $command .= " " . Shell::escape( $file );
1896  } else {
1897  # complex pattern: replace "%f" with file to scan
1898  $command = str_replace( "%f", Shell::escape( $file ), $command );
1899  }
1900 
1901  wfDebug( __METHOD__ . ": running virus scan: $command " );
1902 
1903  # execute virus scanner
1904  $exitCode = false;
1905 
1906  # NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
1907  # that does not seem to be worth the pain.
1908  # Ask me (Duesentrieb) about it if it's ever needed.
1909  $output = wfShellExecWithStderr( $command, $exitCode );
1910 
1911  # map exit code to AV_xxx constants.
1912  $mappedCode = $exitCode;
1913  if ( $exitCodeMap ) {
1914  if ( isset( $exitCodeMap[$exitCode] ) ) {
1915  $mappedCode = $exitCodeMap[$exitCode];
1916  } elseif ( isset( $exitCodeMap["*"] ) ) {
1917  $mappedCode = $exitCodeMap["*"];
1918  }
1919  }
1920 
1921  /* NB: AV_NO_VIRUS is 0 but AV_SCAN_FAILED is false,
1922  * so we need the strict equalities === and thus can't use a switch here
1923  */
1924  if ( $mappedCode === AV_SCAN_FAILED ) {
1925  # scan failed (code was mapped to false by $exitCodeMap)
1926  wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode)." );
1927 
1928  $output = $wgAntivirusRequired
1929  ? wfMessage( 'virus-scanfailed', [ $exitCode ] )->text()
1930  : null;
1931  } elseif ( $mappedCode === AV_SCAN_ABORTED ) {
1932  # scan failed because filetype is unknown (probably imune)
1933  wfDebug( __METHOD__ . ": unsupported file type $file (code $exitCode)." );
1934  $output = null;
1935  } elseif ( $mappedCode === AV_NO_VIRUS ) {
1936  # no virus found
1937  wfDebug( __METHOD__ . ": file passed virus scan." );
1938  $output = false;
1939  } else {
1940  $output = trim( $output );
1941 
1942  if ( !$output ) {
1943  $output = true; # if there's no output, return true
1944  } elseif ( $msgPattern ) {
1945  $groups = [];
1946  if ( preg_match( $msgPattern, $output, $groups ) && $groups[1] ) {
1947  $output = $groups[1];
1948  }
1949  }
1950 
1951  wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output" );
1952  }
1953 
1954  return $output;
1955  }
1956 
1965  private function checkOverwrite( $user ) {
1966  // First check whether the local file can be overwritten
1967  $file = $this->getLocalFile();
1968  $file->load( File::READ_LATEST );
1969  if ( $file->exists() ) {
1970  if ( !self::userCanReUpload( $user, $file ) ) {
1971  return [ 'fileexists-forbidden', $file->getName() ];
1972  } else {
1973  return true;
1974  }
1975  }
1976 
1977  $services = MediaWikiServices::getInstance();
1978 
1979  /* Check shared conflicts: if the local file does not exist, but
1980  * RepoGroup::findFile finds a file, it exists in a shared repository.
1981  */
1982  $file = $services->getRepoGroup()->findFile( $this->getTitle(), [ 'latest' => true ] );
1983  if ( $file && !$services->getPermissionManager()
1984  ->userHasRight( $user, 'reupload-shared' )
1985  ) {
1986  return [ 'fileexists-shared-forbidden', $file->getName() ];
1987  }
1988 
1989  return true;
1990  }
1991 
1999  public static function userCanReUpload( User $user, File $img ) {
2000  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
2001  if ( $permissionManager->userHasRight( $user, 'reupload' ) ) {
2002  return true; // non-conditional
2003  } elseif ( !$permissionManager->userHasRight( $user, 'reupload-own' ) ) {
2004  return false;
2005  }
2006 
2007  if ( !( $img instanceof LocalFile ) ) {
2008  return false;
2009  }
2010 
2011  $img->load();
2012 
2013  return $user->getId() == $img->getUser( 'id' );
2014  }
2015 
2027  public static function getExistsWarning( $file ) {
2028  if ( $file->exists() ) {
2029  return [ 'warning' => 'exists', 'file' => $file ];
2030  }
2031 
2032  if ( $file->getTitle()->getArticleID() ) {
2033  return [ 'warning' => 'page-exists', 'file' => $file ];
2034  }
2035 
2036  if ( strpos( $file->getName(), '.' ) == false ) {
2037  $partname = $file->getName();
2038  $extension = '';
2039  } else {
2040  $n = strrpos( $file->getName(), '.' );
2041  $extension = substr( $file->getName(), $n + 1 );
2042  $partname = substr( $file->getName(), 0, $n );
2043  }
2044  $normalizedExtension = File::normalizeExtension( $extension );
2045  $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2046 
2047  if ( $normalizedExtension != $extension ) {
2048  // We're not using the normalized form of the extension.
2049  // Normal form is lowercase, using most common of alternate
2050  // extensions (eg 'jpg' rather than 'JPEG').
2051 
2052  // Check for another file using the normalized form...
2053  $nt_lc = Title::makeTitle( NS_FILE, "{$partname}.{$normalizedExtension}" );
2054  $file_lc = $localRepo->newFile( $nt_lc );
2055 
2056  if ( $file_lc->exists() ) {
2057  return [
2058  'warning' => 'exists-normalized',
2059  'file' => $file,
2060  'normalizedFile' => $file_lc
2061  ];
2062  }
2063  }
2064 
2065  // Check for files with the same name but a different extension
2066  $similarFiles = $localRepo->findFilesByPrefix( "{$partname}.", 1 );
2067  if ( count( $similarFiles ) ) {
2068  return [
2069  'warning' => 'exists-normalized',
2070  'file' => $file,
2071  'normalizedFile' => $similarFiles[0],
2072  ];
2073  }
2074 
2075  if ( self::isThumbName( $file->getName() ) ) {
2076  # Check for filenames like 50px- or 180px-, these are mostly thumbnails
2077  $nt_thb = Title::newFromText(
2078  substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension,
2079  NS_FILE
2080  );
2081  $file_thb = $localRepo->newFile( $nt_thb );
2082  if ( $file_thb->exists() ) {
2083  return [
2084  'warning' => 'thumb',
2085  'file' => $file,
2086  'thumbFile' => $file_thb
2087  ];
2088  } else {
2089  // File does not exist, but we just don't like the name
2090  return [
2091  'warning' => 'thumb-name',
2092  'file' => $file,
2093  'thumbFile' => $file_thb
2094  ];
2095  }
2096  }
2097 
2098  foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
2099  if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
2100  return [
2101  'warning' => 'bad-prefix',
2102  'file' => $file,
2103  'prefix' => $prefix
2104  ];
2105  }
2106  }
2107 
2108  return false;
2109  }
2110 
2116  public static function isThumbName( $filename ) {
2117  $n = strrpos( $filename, '.' );
2118  $partname = $n ? substr( $filename, 0, $n ) : $filename;
2119 
2120  return (
2121  substr( $partname, 3, 3 ) == 'px-' ||
2122  substr( $partname, 2, 3 ) == 'px-'
2123  ) &&
2124  preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
2125  }
2126 
2132  public static function getFilenamePrefixBlacklist() {
2133  $blacklist = [];
2134  $message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage();
2135  if ( !$message->isDisabled() ) {
2136  $lines = explode( "\n", $message->plain() );
2137  foreach ( $lines as $line ) {
2138  // Remove comment lines
2139  $comment = substr( trim( $line ), 0, 1 );
2140  if ( $comment == '#' || $comment == '' ) {
2141  continue;
2142  }
2143  // Remove additional comments after a prefix
2144  $comment = strpos( $line, '#' );
2145  if ( $comment > 0 ) {
2146  $line = substr( $line, 0, $comment - 1 );
2147  }
2148  $blacklist[] = trim( $line );
2149  }
2150  }
2151 
2152  return $blacklist;
2153  }
2154 
2166  public function getImageInfo( $result ) {
2167  $localFile = $this->getLocalFile();
2168  $stashFile = $this->getStashFile();
2169  // Calling a different API module depending on whether the file was stashed is less than optimal.
2170  // In fact, calling API modules here at all is less than optimal. Maybe it should be refactored.
2171  if ( $stashFile ) {
2173  $info = ApiQueryStashImageInfo::getInfo( $stashFile, array_flip( $imParam ), $result );
2174  } else {
2176  $info = ApiQueryImageInfo::getInfo( $localFile, array_flip( $imParam ), $result );
2177  }
2178 
2179  return $info;
2180  }
2181 
2186  public function convertVerifyErrorToStatus( $error ) {
2187  $code = $error['status'];
2188  unset( $code['status'] );
2189 
2190  return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
2191  }
2192 
2200  public static function getMaxUploadSize( $forType = null ) {
2201  global $wgMaxUploadSize;
2202 
2203  if ( is_array( $wgMaxUploadSize ) ) {
2204  if ( $forType !== null && isset( $wgMaxUploadSize[$forType] ) ) {
2205  return $wgMaxUploadSize[$forType];
2206  } else {
2207  return $wgMaxUploadSize['*'];
2208  }
2209  } else {
2210  return intval( $wgMaxUploadSize );
2211  }
2212  }
2213 
2221  public static function getMaxPhpUploadSize() {
2222  $phpMaxFileSize = wfShorthandToInteger(
2223  ini_get( 'upload_max_filesize' ),
2224  PHP_INT_MAX
2225  );
2226  $phpMaxPostSize = wfShorthandToInteger(
2227  ini_get( 'post_max_size' ),
2228  PHP_INT_MAX
2229  ) ?: PHP_INT_MAX;
2230  return min( $phpMaxFileSize, $phpMaxPostSize );
2231  }
2232 
2242  public static function getSessionStatus( User $user, $statusKey ) {
2243  $store = self::getUploadSessionStore();
2244  $key = self::getUploadSessionKey( $store, $user, $statusKey );
2245 
2246  return $store->get( $key );
2247  }
2248 
2261  public static function setSessionStatus( User $user, $statusKey, $value ) {
2262  $store = self::getUploadSessionStore();
2263  $key = self::getUploadSessionKey( $store, $user, $statusKey );
2264 
2265  if ( $value === false ) {
2266  $store->delete( $key );
2267  } else {
2268  $store->set( $key, $value, $store::TTL_DAY );
2269  }
2270  }
2271 
2278  private static function getUploadSessionKey( BagOStuff $store, User $user, $statusKey ) {
2279  return $store->makeKey(
2280  'uploadstatus',
2281  $user->getId() ?: md5( $user->getName() ),
2282  $statusKey
2283  );
2284  }
2285 
2289  private static function getUploadSessionStore() {
2290  return ObjectCache::getInstance( 'db-replicated' );
2291  }
2292 }
UploadBase
Definition: UploadBase.php:43
UploadBase\checkSvgExternalDTD
static checkSvgExternalDTD( $type, $publicId, $systemId)
Verify that DTD urls referenced are only the standard dtds.
Definition: UploadBase.php:1532
AV_NO_VIRUS
const AV_NO_VIRUS
Definition: Defines.php:100
LocalFile\getSha1
getSha1()
Definition: LocalFile.php:2245
UploadBase\getRealPath
getRealPath( $srcPath)
Definition: UploadBase.php:321
File\wasDeleted
wasDeleted()
Was this file ever deleted from the wiki?
Definition: File.php:1960
$wgAllowJavaUploads
$wgAllowJavaUploads
Allow Java archive uploads.
Definition: DefaultSettings.php:1040
MediaWiki\Shell\Shell
Executes shell commands.
Definition: Shell.php:44
ApiQueryImageInfo\getPropertyNames
static getPropertyNames( $filter=[])
Returns all possible parameters to iiprop.
Definition: ApiQueryImageInfo.php:734
UploadBase\checkWarnings
checkWarnings( $user=null)
Check for non fatal problems with the file.
Definition: UploadBase.php:675
UploadBase\verifyTitlePermissions
verifyTitlePermissions( $user)
Check whether the user can edit, upload and create the image.
Definition: UploadBase.php:634
UploadBase\$mFileSize
int null $mFileSize
Definition: UploadBase.php:71
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:332
UploadBase\VERIFICATION_ERROR
const VERIFICATION_ERROR
Definition: UploadBase.php:106
ApiQueryStashImageInfo\getPropertyNames
static getPropertyNames( $filter=null)
Returns all possible parameters to siiprop.
Definition: ApiQueryStashImageInfo.php:87
UploadBase\$mBlackListedExtensions
string[] $mBlackListedExtensions
Definition: UploadBase.php:75
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:152
UploadBase\$mJavaDetected
bool null $mJavaDetected
Definition: UploadBase.php:77
$wgFileBlacklist
$wgFileBlacklist
Files with these extensions will never be allowed as uploads.
Definition: DefaultSettings.php:1008
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:1174
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
User\getId
getId()
Get the user's ID.
Definition: User.php:2108
UploadBase\isThrottled
static isThrottled( $user)
Returns true if the user has surpassed the upload rate limit, false otherwise.
Definition: UploadBase.php:169
WatchAction\doWatch
static doWatch(Title $title, User $user, $checkRights=User::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch a page.
Definition: WatchAction.php:201
UploadBase\makeWarningsSerializable
static makeWarningsSerializable( $warnings)
Convert the warnings array returned by checkWarnings() to something that can be serialized.
Definition: UploadBase.php:739
UploadBase\isThumbName
static isThumbName( $filename)
Helper function that checks whether the filename looks like a thumbnail.
Definition: UploadBase.php:2116
UploadBase\getSourceType
getSourceType()
Returns the upload type.
Definition: UploadBase.php:240
AV_SCAN_FAILED
const AV_SCAN_FAILED
Definition: Defines.php:103
UploadBase\FILE_TOO_LARGE
const FILE_TOO_LARGE
Definition: UploadBase.php:108
UploadBase\checkAgainstArchiveDupes
checkAgainstArchiveDupes( $hash, User $user)
Definition: UploadBase.php:884
UploadBase\MIN_LENGTH_PARTNAME
const MIN_LENGTH_PARTNAME
Definition: UploadBase.php:101
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:146
UploadBase\verifyUpload
verifyUpload()
Verify whether the upload is sane.
Definition: UploadBase.php:355
UploadBase\postProcessUpload
postProcessUpload()
Perform extra steps after a successful upload.
Definition: UploadBase.php:955
UploadBase\$mRemoveTempFile
string null $mRemoveTempFile
Definition: UploadBase.php:55
UploadBase\isEnabled
static isEnabled()
Returns true if uploads are enabled.
Definition: UploadBase.php:138
UploadBase\checkFileExtensionList
static checkFileExtensionList( $ext, $list)
Perform case-insensitive match against a list of file extensions.
Definition: UploadBase.php:1247
UploadBase\getTitle
getTitle()
Returns the title of the file to be uploaded.
Definition: UploadBase.php:964
$wgMaxUploadSize
$wgMaxUploadSize
Max size for uploads, in bytes.
Definition: DefaultSettings.php:926
UploadBase\$mTitle
Title bool $mTitle
Definition: UploadBase.php:59
UploadBase\getMaxPhpUploadSize
static getMaxPhpUploadSize()
Get the PHP maximum uploaded file size, based on ini settings.
Definition: UploadBase.php:2221
UploadBase\cleanupTempFile
cleanupTempFile()
If we've modified the upload file we need to manually remove it on exit to clean up.
Definition: UploadBase.php:1199
UploadBase\initializeFromRequest
initializeFromRequest(&$request)
Initialize from a WebRequest.
UploadBase\getLocalFile
getLocalFile()
Return the local file and initializes if necessary.
Definition: UploadBase.php:1097
UploadBase\checkBadFileName
checkBadFileName( $filename, $desiredFileName)
Check whether the resulting filename is different from the desired one, but ignore things like ucfirs...
Definition: UploadBase.php:764
$wgFileExtensions
$wgFileExtensions
This is the list of preferred extensions for uploading files.
Definition: DefaultSettings.php:1001
UploadBase\$mFinalExtension
string null $mFinalExtension
Definition: UploadBase.php:65
LocalFile\getHistory
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1186
NS_FILE
const NS_FILE
Definition: Defines.php:75
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
UploadBase\$uploadHandlers
static $uploadHandlers
Definition: UploadBase.php:174
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:2261
UploadBase\getFilenamePrefixBlacklist
static getFilenamePrefixBlacklist()
Get a list of blacklisted filename prefixes from [[MediaWiki:Filename-prefix-blacklist]].
Definition: UploadBase.php:2132
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:65
$wgMimeTypeBlacklist
$wgMimeTypeBlacklist
Files with these MIME types will never be allowed as uploads if $wgVerifyMimeType is enabled.
Definition: DefaultSettings.php:1022
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1198
$wgVerifyMimeTypeIE
$wgVerifyMimeTypeIE
Determines whether extra checks for IE type detection should be applied.
Definition: DefaultSettings.php:1470
UploadBase\$tempFileObj
TempFSFile null $tempFileObj
Wrapper to handle deleting the temp file.
Definition: UploadBase.php:49
UploadBase\HOOK_ABORTED
const HOOK_ABORTED
Definition: UploadBase.php:107
UploadBase\checkAgainstExistingDupes
checkAgainstExistingDupes( $hash, $ignoreLocalDupes)
Definition: UploadBase.php:861
UploadBase\OK
const OK
Definition: UploadBase.php:99
UploadBase\stripXmlNamespace
stripXmlNamespace( $name)
Definition: UploadBase.php:1854
UploadBase\$mFilteredName
string null $mFilteredName
Definition: UploadBase.php:63
UploadBase\validateName
validateName()
Verify that the name is valid and, if necessary, that we can overwrite.
Definition: UploadBase.php:404
$wgStrictFileExtensions
$wgStrictFileExtensions
If this is turned off, users may override the warning for files not covered by $wgFileExtensions.
Definition: DefaultSettings.php:1055
ApiQueryImageInfo\getInfo
static getInfo( $file, $prop, $result, $thumbParams=null, $opts=false)
Get result information for an image revision.
Definition: ApiQueryImageInfo.php:375
UploadBase\isValidRequest
static isValidRequest( $request)
Check whether a request if valid for this handler.
Definition: UploadBase.php:227
UploadBase\performUpload
performUpload( $comment, $pageText, $watch, $user, $tags=[])
Really perform the upload.
Definition: UploadBase.php:910
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
UploadBase\EMPTY_FILE
const EMPTY_FILE
Definition: UploadBase.php:100
UploadBase\verifyPermissions
verifyPermissions( $user)
Alias for verifyTitlePermissions.
Definition: UploadBase.php:619
UploadBase\checkFileSize
checkFileSize( $fileSize)
Definition: UploadBase.php:805
AV_SCAN_ABORTED
const AV_SCAN_ABORTED
Definition: Defines.php:102
UploadBase\runUploadStashFileHook
runUploadStashFileHook(User $user)
Definition: UploadBase.php:1145
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:177
UploadBase\checkSvgScriptCallback
checkSvgScriptCallback( $element, $attribs, $data=null)
Definition: UploadBase.php:1559
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:62
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1025
MWException
MediaWiki exception.
Definition: MWException.php:26
wfStripIllegalFilenameChars
wfStripIllegalFilenameChars( $name)
Replace all invalid characters with '-'.
Definition: GlobalFunctions.php:2643
$wgUploadSizeWarning
$wgUploadSizeWarning
Warn if uploaded files are larger than this (in bytes), or false to disable.
Definition: DefaultSettings.php:1068
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1030
$wgAntivirusRequired
$wgAntivirusRequired
Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected.
Definition: DefaultSettings.php:1455
UploadBase\verifyExtension
static verifyExtension( $mime, $extension)
Checks if the MIME type of the uploaded file matches the file extension.
Definition: UploadBase.php:1258
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:851
FileBackend\isStoragePath
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
Definition: FileBackend.php:1524
UploadBase\WINDOWS_NONASCII_FILENAME
const WINDOWS_NONASCII_FILENAME
Definition: UploadBase.php:109
UploadBase\verifyMimeType
verifyMimeType( $mime)
Verify the MIME type.
Definition: UploadBase.php:434
$matches
$matches
Definition: NoLocalSettings.php:24
UploadBase\verifyFile
verifyFile()
Verifies that it's ok to include the uploaded file.
Definition: UploadBase.php:468
UploadBase\tryStashFile
tryStashFile(User $user, $isPartial=false)
Like stashFile(), but respects extensions' wishes to prevent the stashing.
Definition: UploadBase.php:1126
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:116
UploadBase\getImageInfo
getImageInfo( $result)
Gets image info about the file just uploaded.
Definition: UploadBase.php:2166
$wgLang
$wgLang
Definition: Setup.php:781
$wgAntivirusSetup
$wgAntivirusSetup
Configuration for different virus scanners.
Definition: DefaultSettings.php:1437
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
UploadBase\getStashFile
getStashFile()
Definition: UploadBase.php:1111
UploadBase\zipEntryCallback
zipEntryCallback( $entry)
Callback for ZipDirectoryReader to detect Java class files.
Definition: UploadBase.php:590
UploadBase\detectVirus
static detectVirus( $file)
Generic wrapper function for a virus scanner program.
Definition: UploadBase.php:1871
UploadBase\getUploadSessionKey
static getUploadSessionKey(BagOStuff $store, User $user, $statusKey)
Definition: UploadBase.php:2278
$title
$title
Definition: testCompression.php:38
MWFileProps
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
UploadBase\setTempFile
setTempFile( $tempPath, $fileSize=null)
Definition: UploadBase.php:272
UploadBase\getTempPath
getTempPath()
Definition: UploadBase.php:1207
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:595
UploadBase\$safeXmlEncodings
static $safeXmlEncodings
Definition: UploadBase.php:81
UploadBase\$mTitleError
int $mTitleError
Definition: UploadBase.php:61
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:913
UploadBase\SUCCESS
const SUCCESS
Definition: UploadBase.php:98
LocalFile
Class to represent a local file in the wiki's own database.
Definition: LocalFile.php:56
UploadBase\checkFileExtension
static checkFileExtension( $ext, $list)
Perform case-insensitive match against a list of file extensions.
Definition: UploadBase.php:1235
$wgSVGMetadataCutoff
$wgSVGMetadataCutoff
Don't read SVG metadata beyond this point.
Definition: DefaultSettings.php:1283
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:1311
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:621
UploadBase\splitXmlNamespace
static splitXmlNamespace( $element)
Divide the element name passed by the xml parser to the callback into URI and prifix.
Definition: UploadBase.php:1841
UploadBase\__construct
__construct()
Definition: UploadBase.php:231
UploadBase\isEmptyFile
isEmptyFile()
Return true if the file is empty.
Definition: UploadBase.php:297
ArchivedFile
Class representing a row of the 'filearchive' table.
Definition: ArchivedFile.php:32
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:171
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:1965
UploadBase\detectScriptInSvg
detectScriptInSvg( $filename, $partial)
Definition: UploadBase.php:1480
UploadBase\$mLocalFile
LocalFile null $mLocalFile
Definition: UploadBase.php:67
$line
$line
Definition: mcc.php:119
UploadBase\getUploadSessionStore
static getUploadSessionStore()
Definition: UploadBase.php:2289
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:827
UploadBase\getExistsWarning
static getExistsWarning( $file)
Helper function that does various existence checks for a file.
Definition: UploadBase.php:2027
XmlTypeCheck
Definition: XmlTypeCheck.php:28
UploadBase\FILENAME_TOO_LONG
const FILENAME_TOO_LONG
Definition: UploadBase.php:110
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:529
UploadBase\$mSVGNSError
string null $mSVGNSError
Definition: UploadBase.php:79
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:451
wfIniGetBool
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
Definition: GlobalFunctions.php:1989
File\DELETE_SOURCE
const DELETE_SOURCE
Definition: File.php:79
UploadBase\getSessionStatus
static getSessionStatus(User $user, $statusKey)
Get the current status of a chunked upload (used for polling)
Definition: UploadBase.php:2242
$lines
if(!file_exists( $CREDITS)) $lines
Definition: updateCredits.php:49
UploadBase\OVERWRITE_EXISTING_FILE
const OVERWRITE_EXISTING_FILE
Definition: UploadBase.php:103
UploadBase\ILLEGAL_FILENAME
const ILLEGAL_FILENAME
Definition: UploadBase.php:102
UploadBase\getMaxUploadSize
static getMaxUploadSize( $forType=null)
Get MediaWiki's maximum uploaded file size for given type of upload, based on $wgMaxUploadSize.
Definition: UploadBase.php:2200
BagOStuff\makeKey
makeKey( $class,... $components)
Make a cache key, scoped to this instance's keyspace.
UploadBase\createFromRequest
static createFromRequest(&$request, $type=null)
Create a form of UploadBase depending on wpSourceType and initializes it.
Definition: UploadBase.php:183
UploadBase\convertVerifyErrorToStatus
convertVerifyErrorToStatus( $error)
Definition: UploadBase.php:2186
UploadBase\initializePathInfo
initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile=false)
Definition: UploadBase.php:251
wfShorthandToInteger
wfShorthandToInteger( $string='', $default=-1)
Converts shorthand byte notation to integer form.
Definition: GlobalFunctions.php:2708
Title
Represents a title within MediaWiki.
Definition: Title.php:42
$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:815
UploadBase\splitExtensions
static splitExtensions( $filename)
Split a file into a base name and all dot-delimited 'extensions' on the end.
Definition: UploadBase.php:1220
UploadBase\$mSourceType
string null $mSourceType
Definition: UploadBase.php:57
$wgDisableUploadScriptChecks
$wgDisableUploadScriptChecks
Setting this to true will disable the upload system's checks for HTML/JavaScript.
Definition: DefaultSettings.php:1063
UploadBase\getTempFileSha1Base36
getTempFileSha1Base36()
Get the base 36 SHA1 of the file.
Definition: UploadBase.php:313
UploadBase\$mDestName
string null $mDestName
Definition: UploadBase.php:53
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:3316
UploadBase\checkUnwantedFileExtensions
checkUnwantedFileExtensions( $fileExtension)
Definition: UploadBase.php:783
UploadBase\checkSvgPICallback
static checkSvgPICallback( $target, $data)
Callback to filter SVG Processing Instructions.
Definition: UploadBase.php:1512
$path
$path
Definition: NoLocalSettings.php:25
MediaHandler\getHandler
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
Definition: MediaHandler.php:51
$wgVerifyMimeType
$wgVerifyMimeType
Determines if the MIME type of uploaded files should be checked.
Definition: DefaultSettings.php:1460
UploadBase\$mTempPath
string $mTempPath
Local file system path to the file to upload (or a local copy)
Definition: UploadBase.php:47
FileRepo\isVirtualUrl
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:282
UploadBase\fetchFile
fetchFile()
Fetch the file.
Definition: UploadBase.php:289
$wgEnableUploads
$wgEnableUploads
Allow users to upload files.
Definition: DefaultSettings.php:442
$ext
if(!is_readable( $file)) $ext
Definition: router.php:48
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:66
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:89
UploadBase\$mStashFile
UploadStashFile null $mStashFile
Definition: UploadBase.php:69
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:1511
$mime
$mime
Definition: router.php:60
$wgAntivirus
$wgAntivirus
Internal name of virus scanner.
Definition: DefaultSettings.php:1401
$wgOut
$wgOut
Definition: Setup.php:786
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:55
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2137
UploadBase\getFileSize
getFileSize()
Return the file size.
Definition: UploadBase.php:305
LocalFile\exists
exists()
canRender inherited
Definition: LocalFile.php:975
$wgCheckFileExtensions
$wgCheckFileExtensions
This is a flag to determine whether or not to check file extensions on upload.
Definition: DefaultSettings.php:1047
UploadBase\$mDesiredDestName
string null $mDesiredDestName
Definition: UploadBase.php:51
UploadBase\$mFileProps
array null $mFileProps
Definition: UploadBase.php:73
UploadStashException
Definition: UploadStashException.php:26
wfShellExecWithStderr
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
Definition: GlobalFunctions.php:2107
UploadBase\doStashFile
doStashFile(User $user=null)
Implementation for stashFile() and tryStashFile().
Definition: UploadBase.php:1186
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:1422
UploadBase\FILETYPE_MISSING
const FILETYPE_MISSING
Definition: UploadBase.php:104
$type
$type
Definition: testCompression.php:52
UploadBase\FILETYPE_BADTYPE
const FILETYPE_BADTYPE
Definition: UploadBase.php:105
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:1792